Code bereinigt und Seitenstruktur verfeinert.
Das Layout der einzelnen Seiten ist jetzt einheitlich: - jumbotron - Naviation der Unterseiten (wenn Vorhanden) - Hauptinhalt - Pager (bei mehrseiteigen Ergebnissen) - Kommentare (wenn Kommentare möglich) - Buttonleiste zum bearbeiten (wen erlaubt) * Viele kleine HTML5 Fehler wurden bereinigt * CSS Dateien wurden angepasst * Nicht mehr benötigte Dateien in STATIC gelöscht * externe Pakete aus utlis/ entfernt, als Vobreitung für virtenv Nutzung.
This commit is contained in:
committed by
Christian Berg
parent
86a0db050d
commit
34f5bdca58
@@ -1,16 +0,0 @@
|
||||
# Components
|
||||
from .cal import Calendar, Event, Todo, Journal
|
||||
from .cal import FreeBusy, Timezone, Alarm, ComponentFactory
|
||||
|
||||
# Property Data Value Types
|
||||
from .prop import vBinary, vBoolean, vCalAddress, vDatetime, vDate, \
|
||||
vDDDTypes, vDuration, vFloat, vInt, vPeriod, \
|
||||
vWeekday, vFrequency, vRecur, vText, vTime, vUri, \
|
||||
vGeo, vUTCOffset, TypesFactory
|
||||
|
||||
# useful tzinfo subclasses
|
||||
from .prop import FixedOffset, UTC, LocalTimezone
|
||||
|
||||
# Parameters and helper methods for splitting and joining string with escaped
|
||||
# chars.
|
||||
from .parser import Parameters, q_split, q_join
|
||||
@@ -1,533 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
|
||||
Calendar is a dictionary like Python object that can render itself as VCAL
|
||||
files according to rfc2445.
|
||||
|
||||
These are the defined components.
|
||||
|
||||
"""
|
||||
|
||||
# from python
|
||||
from types import ListType, TupleType
|
||||
SequenceTypes = (ListType, TupleType)
|
||||
|
||||
# from this package
|
||||
from .caselessdict import CaselessDict
|
||||
from .parser import Contentlines, Contentline, Parameters
|
||||
from .parser import q_split, q_join
|
||||
from .prop import TypesFactory, vText
|
||||
|
||||
|
||||
######################################
|
||||
# The component factory
|
||||
|
||||
class ComponentFactory(CaselessDict):
|
||||
"""
|
||||
All components defined in rfc 2445 are registered in this factory class. To
|
||||
get a component you can use it like this.
|
||||
|
||||
>>> factory = ComponentFactory()
|
||||
>>> component = factory['VEVENT']
|
||||
>>> event = component(dtstart='19700101')
|
||||
>>> event.as_string()
|
||||
'BEGIN:VEVENT\\r\\nDTSTART:19700101\\r\\nEND:VEVENT\\r\\n'
|
||||
|
||||
>>> factory.get('VCALENDAR', Component)
|
||||
<class 'icalendar.cal.Calendar'>
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"Set keys to upper for initial dict"
|
||||
CaselessDict.__init__(self, *args, **kwargs)
|
||||
self['VEVENT'] = Event
|
||||
self['VTODO'] = Todo
|
||||
self['VJOURNAL'] = Journal
|
||||
self['VFREEBUSY'] = FreeBusy
|
||||
self['VTIMEZONE'] = Timezone
|
||||
self['VALARM'] = Alarm
|
||||
self['VCALENDAR'] = Calendar
|
||||
|
||||
|
||||
# These Properties have multiple property values inlined in one propertyline
|
||||
# seperated by comma. Use CaselessDict as simple caseless set.
|
||||
INLINE = CaselessDict(
|
||||
[(cat, 1) for cat in ('CATEGORIES', 'RESOURCES', 'FREEBUSY')]
|
||||
)
|
||||
|
||||
_marker = []
|
||||
|
||||
class Component(CaselessDict):
|
||||
"""
|
||||
Component is the base object for calendar, Event and the other components
|
||||
defined in RFC 2445. normally you will not use this class directy, but
|
||||
rather one of the subclasses.
|
||||
|
||||
A component is like a dictionary with extra methods and attributes.
|
||||
>>> c = Component()
|
||||
>>> c.name = 'VCALENDAR'
|
||||
|
||||
Every key defines a property. A property can consist of either a single
|
||||
item. This can be set with a single value
|
||||
>>> c['prodid'] = '-//max m//icalendar.mxm.dk/'
|
||||
>>> c
|
||||
VCALENDAR({'PRODID': '-//max m//icalendar.mxm.dk/'})
|
||||
|
||||
or with a list
|
||||
>>> c['ATTENDEE'] = ['Max M', 'Rasmussen']
|
||||
|
||||
if you use the add method you don't have to considder if a value is a list
|
||||
or not.
|
||||
>>> c = Component()
|
||||
>>> c.name = 'VEVENT'
|
||||
>>> c.add('attendee', 'maxm@mxm.dk')
|
||||
>>> c.add('attendee', 'test@example.dk')
|
||||
>>> c
|
||||
VEVENT({'ATTENDEE': [vCalAddress('maxm@mxm.dk'), vCalAddress('test@example.dk')]})
|
||||
|
||||
You can get the values back directly
|
||||
>>> c.add('prodid', '-//my product//')
|
||||
>>> c['prodid']
|
||||
vText(u'-//my product//')
|
||||
|
||||
or decoded to a python type
|
||||
>>> c.decoded('prodid')
|
||||
u'-//my product//'
|
||||
|
||||
With default values for non existing properties
|
||||
>>> c.decoded('version', 'No Version')
|
||||
'No Version'
|
||||
|
||||
The component can render itself in the RFC 2445 format.
|
||||
>>> c = Component()
|
||||
>>> c.name = 'VCALENDAR'
|
||||
>>> c.add('attendee', 'Max M')
|
||||
>>> c.as_string()
|
||||
'BEGIN:VCALENDAR\\r\\nATTENDEE:Max M\\r\\nEND:VCALENDAR\\r\\n'
|
||||
|
||||
>>> from icalendar.prop import vDatetime
|
||||
|
||||
Components can be nested, so You can add a subcompont. Eg a calendar holds events.
|
||||
>>> e = Component(summary='A brief history of time')
|
||||
>>> e.name = 'VEVENT'
|
||||
>>> e.add('dtend', '20000102T000000', encode=0)
|
||||
>>> e.add('dtstart', '20000101T000000', encode=0)
|
||||
>>> e.as_string()
|
||||
'BEGIN:VEVENT\\r\\nDTEND:20000102T000000\\r\\nDTSTART:20000101T000000\\r\\nSUMMARY:A brief history of time\\r\\nEND:VEVENT\\r\\n'
|
||||
|
||||
>>> c.add_component(e)
|
||||
>>> c.subcomponents
|
||||
[VEVENT({'DTEND': '20000102T000000', 'DTSTART': '20000101T000000', 'SUMMARY': 'A brief history of time'})]
|
||||
|
||||
We can walk over nested componentes with the walk method.
|
||||
>>> [i.name for i in c.walk()]
|
||||
['VCALENDAR', 'VEVENT']
|
||||
|
||||
We can also just walk over specific component types, by filtering them on
|
||||
their name.
|
||||
>>> [i.name for i in c.walk('VEVENT')]
|
||||
['VEVENT']
|
||||
|
||||
>>> [i['dtstart'] for i in c.walk('VEVENT')]
|
||||
['20000101T000000']
|
||||
|
||||
INLINE properties have their values on one property line. Note the double
|
||||
quoting of the value with a colon in it.
|
||||
>>> c = Calendar()
|
||||
>>> c['resources'] = 'Chair, Table, "Room: 42"'
|
||||
>>> c
|
||||
VCALENDAR({'RESOURCES': 'Chair, Table, "Room: 42"'})
|
||||
|
||||
>>> c.as_string()
|
||||
'BEGIN:VCALENDAR\\r\\nRESOURCES:Chair, Table, "Room: 42"\\r\\nEND:VCALENDAR\\r\\n'
|
||||
|
||||
The inline values must be handled by the get_inline() and set_inline()
|
||||
methods.
|
||||
|
||||
>>> c.get_inline('resources', decode=0)
|
||||
['Chair', 'Table', 'Room: 42']
|
||||
|
||||
These can also be decoded
|
||||
>>> c.get_inline('resources', decode=1)
|
||||
[u'Chair', u'Table', u'Room: 42']
|
||||
|
||||
You can set them directly
|
||||
>>> c.set_inline('resources', ['A', 'List', 'of', 'some, recources'], encode=1)
|
||||
>>> c['resources']
|
||||
'A,List,of,"some, recources"'
|
||||
|
||||
and back again
|
||||
>>> c.get_inline('resources', decode=0)
|
||||
['A', 'List', 'of', 'some, recources']
|
||||
|
||||
>>> c['freebusy'] = '19970308T160000Z/PT3H,19970308T200000Z/PT1H,19970308T230000Z/19970309T000000Z'
|
||||
>>> c.get_inline('freebusy', decode=0)
|
||||
['19970308T160000Z/PT3H', '19970308T200000Z/PT1H', '19970308T230000Z/19970309T000000Z']
|
||||
|
||||
>>> freebusy = c.get_inline('freebusy', decode=1)
|
||||
>>> type(freebusy[0][0]), type(freebusy[0][1])
|
||||
(<type 'datetime.datetime'>, <type 'datetime.timedelta'>)
|
||||
"""
|
||||
|
||||
name = '' # must be defined in each component
|
||||
required = () # These properties are required
|
||||
singletons = () # These properties must only appear once
|
||||
multiple = () # may occur more than once
|
||||
exclusive = () # These properties are mutually exclusive
|
||||
inclusive = () # if any occurs the other(s) MUST occur ('duration', 'repeat')
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"Set keys to upper for initial dict"
|
||||
CaselessDict.__init__(self, *args, **kwargs)
|
||||
# set parameters here for properties that use non-default values
|
||||
self.subcomponents = [] # Components can be nested.
|
||||
|
||||
|
||||
# def non_complience(self, warnings=0):
|
||||
# """
|
||||
# not implemented yet!
|
||||
# Returns a dict describing non compliant properties, if any.
|
||||
# If warnings is true it also returns warnings.
|
||||
#
|
||||
# If the parser is too strict it might prevent parsing erroneous but
|
||||
# otherwise compliant properties. So the parser is pretty lax, but it is
|
||||
# possible to test for non-complience by calling this method.
|
||||
# """
|
||||
# nc = {}
|
||||
# if not getattr(self, 'name', ''):
|
||||
# nc['name'] = {'type':'ERROR', 'description':'Name is not defined'}
|
||||
# return nc
|
||||
|
||||
|
||||
#############################
|
||||
# handling of property values
|
||||
|
||||
def _encode(self, name, value, cond=1):
|
||||
# internal, for conditional convertion of values.
|
||||
if cond:
|
||||
klass = types_factory.for_property(name)
|
||||
return klass(value)
|
||||
return value
|
||||
|
||||
|
||||
def set(self, name, value, encode=1): #@ReservedAssignment
|
||||
if type(value) == ListType:
|
||||
self[name] = [self._encode(name, v, encode) for v in value]
|
||||
else:
|
||||
self[name] = self._encode(name, value, encode)
|
||||
|
||||
|
||||
def add(self, name, value, encode=1):
|
||||
"If property exists append, else create and set it"
|
||||
if name in self:
|
||||
oldval = self[name]
|
||||
value = self._encode(name, value, encode)
|
||||
if type(oldval) == ListType:
|
||||
oldval.append(value)
|
||||
else:
|
||||
self.set(name, [oldval, value], encode=0)
|
||||
else:
|
||||
self.set(name, value, encode)
|
||||
|
||||
|
||||
def _decode(self, name, value):
|
||||
# internal for decoding property values
|
||||
decoded = types_factory.from_ical(name, value)
|
||||
return decoded
|
||||
|
||||
|
||||
def decoded(self, name, default=_marker):
|
||||
"Returns decoded value of property"
|
||||
if name in self:
|
||||
value = self[name]
|
||||
if type(value) == ListType:
|
||||
return [self._decode(name, v) for v in value]
|
||||
return self._decode(name, value)
|
||||
else:
|
||||
if default is _marker:
|
||||
raise KeyError, name
|
||||
else:
|
||||
return default
|
||||
|
||||
|
||||
########################################################################
|
||||
# Inline values. A few properties have multiple values inlined in in one
|
||||
# property line. These methods are used for splitting and joining these.
|
||||
|
||||
def get_inline(self, name, decode=1):
|
||||
"""
|
||||
Returns a list of values (split on comma).
|
||||
"""
|
||||
vals = [v.strip('" ').encode(vText.encoding)
|
||||
for v in q_split(self[name])]
|
||||
if decode:
|
||||
return [self._decode(name, val) for val in vals]
|
||||
return vals
|
||||
|
||||
|
||||
def set_inline(self, name, values, encode=1):
|
||||
"""
|
||||
Converts a list of values into comma seperated string and sets value to
|
||||
that.
|
||||
"""
|
||||
if encode:
|
||||
values = [self._encode(name, value, 1) for value in values]
|
||||
joined = q_join(values).encode(vText.encoding)
|
||||
self[name] = types_factory['inline'](joined)
|
||||
|
||||
|
||||
#########################
|
||||
# Handling of components
|
||||
|
||||
def add_component(self, component):
|
||||
"add a subcomponent to this component"
|
||||
self.subcomponents.append(component)
|
||||
|
||||
|
||||
def _walk(self, name):
|
||||
# private!
|
||||
result = []
|
||||
if name is None or self.name == name:
|
||||
result.append(self)
|
||||
for subcomponent in self.subcomponents:
|
||||
result += subcomponent._walk(name)
|
||||
return result
|
||||
|
||||
|
||||
def walk(self, name=None):
|
||||
"""
|
||||
Recursively traverses component and subcomponents. Returns sequence of
|
||||
same. If name is passed, only components with name will be returned.
|
||||
"""
|
||||
if not name is None:
|
||||
name = name.upper()
|
||||
return self._walk(name)
|
||||
|
||||
#####################
|
||||
# Generation
|
||||
|
||||
def property_items(self):
|
||||
"""
|
||||
Returns properties in this component and subcomponents as:
|
||||
[(name, value), ...]
|
||||
"""
|
||||
vText = types_factory['text']
|
||||
properties = [('BEGIN', vText(self.name).ical())]
|
||||
property_names = self.keys()
|
||||
property_names.sort()
|
||||
for name in property_names:
|
||||
values = self[name]
|
||||
if type(values) == ListType:
|
||||
# normally one property is one line
|
||||
for value in values:
|
||||
properties.append((name, value))
|
||||
else:
|
||||
properties.append((name, values))
|
||||
# recursion is fun!
|
||||
for subcomponent in self.subcomponents:
|
||||
properties += subcomponent.property_items()
|
||||
properties.append(('END', vText(self.name).ical()))
|
||||
return properties
|
||||
|
||||
|
||||
def from_string(st, multiple=False):
|
||||
"""
|
||||
Populates the component recursively from a string
|
||||
"""
|
||||
stack = [] # a stack of components
|
||||
comps = []
|
||||
for line in Contentlines.from_string(st): # raw parsing
|
||||
if not line:
|
||||
continue
|
||||
name, params, vals = line.parts()
|
||||
uname = name.upper()
|
||||
# check for start of component
|
||||
if uname == 'BEGIN':
|
||||
# try and create one of the components defined in the spec,
|
||||
# otherwise get a general Components for robustness.
|
||||
component_name = vals.upper()
|
||||
component_class = component_factory.get(component_name, Component)
|
||||
component = component_class()
|
||||
if not getattr(component, 'name', ''): # for undefined components
|
||||
component.name = component_name
|
||||
stack.append(component)
|
||||
# check for end of event
|
||||
elif uname == 'END':
|
||||
# we are done adding properties to this component
|
||||
# so pop it from the stack and add it to the new top.
|
||||
component = stack.pop()
|
||||
if not stack: # we are at the end
|
||||
comps.append(component)
|
||||
else:
|
||||
stack[-1].add_component(component)
|
||||
# we are adding properties to the current top of the stack
|
||||
else:
|
||||
factory = types_factory.for_property(name)
|
||||
vals = factory(factory.from_ical(vals))
|
||||
vals.params = params
|
||||
stack[-1].add(name, vals, encode=0)
|
||||
if multiple:
|
||||
return comps
|
||||
if not len(comps) == 1:
|
||||
raise ValueError('Found multiple components where '
|
||||
'only one is allowed')
|
||||
return comps[0]
|
||||
from_string = staticmethod(from_string)
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
return '%s(' % self.name + dict.__repr__(self) + ')'
|
||||
|
||||
# def content_line(self, name):
|
||||
# "Returns property as content line"
|
||||
# value = self[name]
|
||||
# params = getattr(value, 'params', Parameters())
|
||||
# return Contentline.from_parts((name, params, value))
|
||||
|
||||
def content_lines(self):
|
||||
"Converts the Component and subcomponents into content lines"
|
||||
contentlines = Contentlines()
|
||||
for name, values in self.property_items():
|
||||
params = getattr(values, 'params', Parameters())
|
||||
contentlines.append(Contentline.from_parts((name, params, values)))
|
||||
contentlines.append('') # remember the empty string in the end
|
||||
return contentlines
|
||||
|
||||
|
||||
def as_string(self):
|
||||
return str(self.content_lines())
|
||||
|
||||
|
||||
def __str__(self):
|
||||
"Returns rendered iCalendar"
|
||||
return self.as_string()
|
||||
|
||||
|
||||
|
||||
#######################################
|
||||
# components defined in RFC 2445
|
||||
|
||||
|
||||
class Event(Component):
|
||||
|
||||
name = 'VEVENT'
|
||||
|
||||
required = ('UID',)
|
||||
singletons = (
|
||||
'CLASS', 'CREATED', 'DESCRIPTION', 'DTSTART', 'GEO',
|
||||
'LAST-MOD', 'LOCATION', 'ORGANIZER', 'PRIORITY', 'DTSTAMP', 'SEQUENCE',
|
||||
'STATUS', 'SUMMARY', 'TRANSP', 'URL', 'RECURID', 'DTEND', 'DURATION',
|
||||
'DTSTART',
|
||||
)
|
||||
exclusive = ('DTEND', 'DURATION',)
|
||||
multiple = (
|
||||
'ATTACH', 'ATTENDEE', 'CATEGORIES', 'COMMENT', 'CONTACT', 'EXDATE',
|
||||
'EXRULE', 'RSTATUS', 'RELATED', 'RESOURCES', 'RDATE', 'RRULE'
|
||||
)
|
||||
|
||||
|
||||
|
||||
class Todo(Component):
|
||||
|
||||
name = 'VTODO'
|
||||
|
||||
required = ('UID',)
|
||||
singletons = (
|
||||
'CLASS', 'COMPLETED', 'CREATED', 'DESCRIPTION', 'DTSTAMP', 'DTSTART',
|
||||
'GEO', 'LAST-MOD', 'LOCATION', 'ORGANIZER', 'PERCENT', 'PRIORITY',
|
||||
'RECURID', 'SEQUENCE', 'STATUS', 'SUMMARY', 'UID', 'URL', 'DUE', 'DURATION',
|
||||
)
|
||||
exclusive = ('DUE', 'DURATION',)
|
||||
multiple = (
|
||||
'ATTACH', 'ATTENDEE', 'CATEGORIES', 'COMMENT', 'CONTACT', 'EXDATE',
|
||||
'EXRULE', 'RSTATUS', 'RELATED', 'RESOURCES', 'RDATE', 'RRULE'
|
||||
)
|
||||
|
||||
|
||||
|
||||
class Journal(Component):
|
||||
|
||||
name = 'VJOURNAL'
|
||||
|
||||
required = ('UID',)
|
||||
singletons = (
|
||||
'CLASS', 'CREATED', 'DESCRIPTION', 'DTSTART', 'DTSTAMP', 'LAST-MOD',
|
||||
'ORGANIZER', 'RECURID', 'SEQUENCE', 'STATUS', 'SUMMARY', 'UID', 'URL',
|
||||
)
|
||||
multiple = (
|
||||
'ATTACH', 'ATTENDEE', 'CATEGORIES', 'COMMENT', 'CONTACT', 'EXDATE',
|
||||
'EXRULE', 'RELATED', 'RDATE', 'RRULE', 'RSTATUS',
|
||||
)
|
||||
|
||||
|
||||
class FreeBusy(Component):
|
||||
|
||||
name = 'VFREEBUSY'
|
||||
|
||||
required = ('UID',)
|
||||
singletons = (
|
||||
'CONTACT', 'DTSTART', 'DTEND', 'DURATION', 'DTSTAMP', 'ORGANIZER',
|
||||
'UID', 'URL',
|
||||
)
|
||||
multiple = ('ATTENDEE', 'COMMENT', 'FREEBUSY', 'RSTATUS',)
|
||||
|
||||
|
||||
class Timezone(Component):
|
||||
|
||||
name = 'VTIMEZONE'
|
||||
|
||||
required = (
|
||||
'TZID', 'STANDARDC', 'DAYLIGHTC', 'DTSTART', 'TZOFFSETTO',
|
||||
'TZOFFSETFROM'
|
||||
)
|
||||
singletons = ('LAST-MOD', 'TZURL', 'TZID',)
|
||||
multiple = ('COMMENT', 'RDATE', 'RRULE', 'TZNAME',)
|
||||
|
||||
|
||||
class Alarm(Component):
|
||||
|
||||
name = 'VALARM'
|
||||
# not quite sure about these ...
|
||||
required = ('ACTION', 'TRIGGER',)
|
||||
singletons = ('ATTACH', 'ACTION', 'TRIGGER', 'DURATION', 'REPEAT',)
|
||||
inclusive = (('DURATION', 'REPEAT',),)
|
||||
multiple = ('STANDARDC', 'DAYLIGHTC')
|
||||
|
||||
|
||||
class Calendar(Component):
|
||||
"""
|
||||
This is the base object for an iCalendar file.
|
||||
|
||||
Setting up a minimal calendar component looks like this
|
||||
>>> cal = Calendar()
|
||||
|
||||
Som properties are required to be compliant
|
||||
>>> cal['prodid'] = '-//My calendar product//mxm.dk//'
|
||||
>>> cal['version'] = '2.0'
|
||||
|
||||
We also need at least one subcomponent for a calendar to be compliant
|
||||
>>> from datetime import datetime
|
||||
>>> event = Event()
|
||||
>>> event['summary'] = 'Python meeting about calendaring'
|
||||
>>> event['uid'] = '42'
|
||||
>>> event.set('dtstart', datetime(2005,4,4,8,0,0))
|
||||
>>> cal.add_component(event)
|
||||
>>> cal.subcomponents[0].as_string()
|
||||
'BEGIN:VEVENT\\r\\nDTSTART:20050404T080000\\r\\nSUMMARY:Python meeting about calendaring\\r\\nUID:42\\r\\nEND:VEVENT\\r\\n'
|
||||
|
||||
Write to disc
|
||||
>>> import tempfile, os
|
||||
>>> directory = tempfile.mkdtemp()
|
||||
>>> open(os.path.join(directory, 'test.ics'), 'wb').write(cal.as_string())
|
||||
"""
|
||||
|
||||
name = 'VCALENDAR'
|
||||
required = ('prodid', 'version',)
|
||||
singletons = ('prodid', 'version',)
|
||||
multiple = ('calscale', 'method',)
|
||||
|
||||
|
||||
# These are read only singleton, so one instance is enough for the module
|
||||
types_factory = TypesFactory()
|
||||
component_factory = ComponentFactory()
|
||||
@@ -1,93 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
class CaselessDict(dict):
|
||||
"""
|
||||
A dictionary that isn't case sensitive, and only use string as keys.
|
||||
|
||||
>>> ncd = CaselessDict(key1='val1', key2='val2')
|
||||
>>> ncd
|
||||
CaselessDict({'KEY2': 'val2', 'KEY1': 'val1'})
|
||||
>>> ncd['key1']
|
||||
'val1'
|
||||
>>> ncd['KEY1']
|
||||
'val1'
|
||||
>>> ncd['KEY3'] = 'val3'
|
||||
>>> ncd['key3']
|
||||
'val3'
|
||||
>>> ncd.setdefault('key3', 'FOUND')
|
||||
'val3'
|
||||
>>> ncd.setdefault('key4', 'NOT FOUND')
|
||||
'NOT FOUND'
|
||||
>>> ncd['key4']
|
||||
'NOT FOUND'
|
||||
>>> ncd.get('key1')
|
||||
'val1'
|
||||
>>> ncd.get('key3', 'NOT FOUND')
|
||||
'val3'
|
||||
>>> ncd.get('key4', 'NOT FOUND')
|
||||
'NOT FOUND'
|
||||
>>> 'key4' in ncd
|
||||
True
|
||||
>>> del ncd['key4']
|
||||
>>> ncd.has_key('key4')
|
||||
False
|
||||
>>> ncd.update({'key5':'val5', 'KEY6':'val6', 'KEY5':'val7'})
|
||||
>>> ncd['key6']
|
||||
'val6'
|
||||
>>> keys = ncd.keys()
|
||||
>>> keys.sort()
|
||||
>>> keys
|
||||
['KEY1', 'KEY2', 'KEY3', 'KEY5', 'KEY6']
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"Set keys to upper for initial dict"
|
||||
dict.__init__(self, *args, **kwargs)
|
||||
for k,v in self.items():
|
||||
k_upper = k.upper()
|
||||
if k != k_upper:
|
||||
dict.__delitem__(self, k)
|
||||
self[k_upper] = v
|
||||
|
||||
def __getitem__(self, key):
|
||||
return dict.__getitem__(self, key.upper())
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
dict.__setitem__(self, key.upper(), value)
|
||||
|
||||
def __delitem__(self, key):
|
||||
dict.__delitem__(self, key.upper())
|
||||
|
||||
def __contains__(self, item):
|
||||
return dict.__contains__(self, item.upper())
|
||||
|
||||
def get(self, key, default=None):
|
||||
return dict.get(self, key.upper(), default)
|
||||
|
||||
def setdefault(self, key, value=None):
|
||||
return dict.setdefault(self, key.upper(), value)
|
||||
|
||||
def pop(self, key, default=None):
|
||||
return dict.pop(self, key.upper(), default)
|
||||
|
||||
def popitem(self):
|
||||
return dict.popitem(self)
|
||||
|
||||
def has_key(self, key):
|
||||
return dict.has_key(self, key.upper())
|
||||
|
||||
def update(self, indict):
|
||||
"""
|
||||
Multiple keys where key1.upper() == key2.upper() will be lost.
|
||||
"""
|
||||
for entry in indict:
|
||||
self[entry] = indict[entry]
|
||||
|
||||
def copy(self):
|
||||
return CaselessDict(dict.copy(self))
|
||||
|
||||
def clear(self):
|
||||
dict.clear(self)
|
||||
|
||||
def __repr__(self):
|
||||
return 'CaselessDict(' + dict.__repr__(self) + ')'
|
||||
@@ -1,256 +0,0 @@
|
||||
try:
|
||||
from zope.interface import Interface, Attribute
|
||||
except ImportError:
|
||||
class Interface:
|
||||
"""A dummy interface base class"""
|
||||
|
||||
class Attribute:
|
||||
"""A dummy attribute implementation"""
|
||||
def __init__(self, doc):
|
||||
self.doc = doc
|
||||
|
||||
_marker = object()
|
||||
|
||||
class IComponent(Interface):
|
||||
"""
|
||||
Component is the base object for calendar, Event and the other
|
||||
components defined in RFC 2445.
|
||||
|
||||
A component is like a dictionary with extra methods and attributes.
|
||||
"""
|
||||
|
||||
# MANIPULATORS
|
||||
def __setitem__(self, name, value):
|
||||
"""Set a property.
|
||||
|
||||
name - case insensitive name
|
||||
value - value of the property to set. This can be either a single
|
||||
item or a list.
|
||||
|
||||
Some iCalendar properties are set INLINE; these properties
|
||||
have multiple values on one property line in the iCalendar
|
||||
representation. The list can be supplied as a comma separated
|
||||
string to __setitem__. If special iCalendar characters exist in
|
||||
an entry, such as the colon (:) and (,), that comma-separated
|
||||
entry needs to be quoted with double quotes. For example:
|
||||
|
||||
'foo, bar, "baz:hoi"'
|
||||
|
||||
See also set_inline() for an easier way to deal with this case.
|
||||
"""
|
||||
def set_inline(self, name, values, encode=1):
|
||||
"""Set list of INLINE values for property.
|
||||
|
||||
Converts a list of values into valid iCalendar comma seperated
|
||||
string and sets value to that.
|
||||
|
||||
name - case insensitive name of property
|
||||
values - list of values to set
|
||||
encode - if True, encode Python values as iCalendar types first.
|
||||
"""
|
||||
def add(self, name, value):
|
||||
"""Add a property. Can be called multiple times to set a list.
|
||||
|
||||
name - case insensitive name
|
||||
value - value of property to set or add to list for this property.
|
||||
"""
|
||||
def add_component(self, component):
|
||||
"""Add a nested subcomponent to this component.
|
||||
"""
|
||||
|
||||
# static method, can be called on class directly
|
||||
def from_string(self, st, multiple=False):
|
||||
"""Populates the component recursively from a iCalendar string.
|
||||
|
||||
Reads the iCalendar string and constructs components and
|
||||
subcomponents out of it.
|
||||
"""
|
||||
|
||||
# ACCESSORS
|
||||
def __getitem__(self, name):
|
||||
"""Get a property
|
||||
|
||||
name - case insensitive name
|
||||
|
||||
Returns an iCalendar property object such as vText.
|
||||
"""
|
||||
def decoded(self, name, default=_marker):
|
||||
"""Get a property as a python object.
|
||||
|
||||
name - case insensitive name
|
||||
default - optional argument. If supplied, will use this if
|
||||
name cannot be found. If not supplied, decoded will raise a
|
||||
KeyError if name cannot be found.
|
||||
|
||||
Returns python object (such as unicode string, datetime, etc).
|
||||
"""
|
||||
def get_inline(self, name, decode=1):
|
||||
"""Get list of INLINE values from property.
|
||||
|
||||
name - case insensitive name
|
||||
decode - decode to Python objects.
|
||||
|
||||
Returns list of python objects.
|
||||
"""
|
||||
|
||||
def as_string(self):
|
||||
"""Render the component in the RFC 2445 (iCalendar) format.
|
||||
|
||||
Returns a string in RFC 2445 format.
|
||||
"""
|
||||
|
||||
subcomponents = Attribute("""
|
||||
A list of all subcomponents of this component,
|
||||
added using add_component()""")
|
||||
|
||||
name = Attribute("""
|
||||
Name of this component (VEVENT, etc)
|
||||
""")
|
||||
|
||||
def walk(self, name=None):
|
||||
"""Recursively traverses component and subcomponents.
|
||||
|
||||
name - optional, if given, only return components with that name
|
||||
|
||||
Returns sequence of components.
|
||||
"""
|
||||
|
||||
def property_items(self):
|
||||
"""Return properties as (name, value) tuples.
|
||||
|
||||
Returns all properties in this comopnent and subcomponents as
|
||||
name, value tuples.
|
||||
"""
|
||||
|
||||
class IEvent(IComponent):
|
||||
"""A component which conforms to an iCalendar VEVENT.
|
||||
"""
|
||||
|
||||
class ITodo(IComponent):
|
||||
"""A component which conforms to an iCalendar VTODO.
|
||||
"""
|
||||
|
||||
class IJournal(IComponent):
|
||||
"""A component which conforms to an iCalendar VJOURNAL.
|
||||
"""
|
||||
|
||||
class IFreeBusy(IComponent):
|
||||
"""A component which conforms to an iCalendar VFREEBUSY.
|
||||
"""
|
||||
|
||||
class ITimezone(IComponent):
|
||||
"""A component which conforms to an iCalendar VTIMEZONE.
|
||||
"""
|
||||
|
||||
class IAlarm(IComponent):
|
||||
"""A component which conforms to an iCalendar VALARM.
|
||||
"""
|
||||
|
||||
class ICalendar(IComponent):
|
||||
"""A component which conforms to an iCalendar VCALENDAR.
|
||||
"""
|
||||
|
||||
class IPropertyValue(Interface):
|
||||
"""An iCalendar property value.
|
||||
iCalendar properties have strongly typed values.
|
||||
|
||||
This invariance should always be true:
|
||||
|
||||
assert x == vDataType.from_ical(vDataType(x).ical())
|
||||
"""
|
||||
|
||||
def ical(self):
|
||||
"""Render property as string, as defined in iCalendar RFC 2445.
|
||||
"""
|
||||
|
||||
# this is a static method
|
||||
def from_ical(self, ical):
|
||||
"""Parse property from iCalendar RFC 2445 text.
|
||||
|
||||
Inverse of ical().
|
||||
"""
|
||||
|
||||
class IBinary(IPropertyValue):
|
||||
"""Binary property values are base 64 encoded
|
||||
"""
|
||||
|
||||
class IBoolean(IPropertyValue):
|
||||
"""Boolean property.
|
||||
|
||||
Also behaves like a python int.
|
||||
"""
|
||||
|
||||
class ICalAddress(IPropertyValue):
|
||||
"""Email address.
|
||||
|
||||
Also behaves like a python str.
|
||||
"""
|
||||
|
||||
class IDateTime(IPropertyValue):
|
||||
"""Render and generates iCalendar datetime format.
|
||||
|
||||
Important: if tzinfo is defined it renders itself as 'date with utc time'
|
||||
Meaning that it has a 'Z' appended, and is in absolute time.
|
||||
"""
|
||||
|
||||
class IDate(IPropertyValue):
|
||||
"""Render and generates iCalendar date format.
|
||||
"""
|
||||
|
||||
class IDuration(IPropertyValue):
|
||||
"""Render and generates timedelta in iCalendar DURATION format.
|
||||
"""
|
||||
|
||||
class IFloat(IPropertyValue):
|
||||
"""Render and generate floats in iCalendar format.
|
||||
|
||||
Also behaves like a python float.
|
||||
"""
|
||||
|
||||
class IInt(IPropertyValue):
|
||||
"""Render and generate ints in iCalendar format.
|
||||
|
||||
Also behaves like a python int.
|
||||
"""
|
||||
|
||||
class IPeriod(IPropertyValue):
|
||||
"""A precise period of time (datetime, datetime).
|
||||
"""
|
||||
|
||||
class IWeekDay(IPropertyValue):
|
||||
"""Render and generate weekday abbreviation.
|
||||
"""
|
||||
|
||||
class IFrequency(IPropertyValue):
|
||||
"""Frequency.
|
||||
"""
|
||||
|
||||
class IRecur(IPropertyValue):
|
||||
"""Render and generate data based on recurrent event representation.
|
||||
|
||||
This acts like a caseless dictionary.
|
||||
"""
|
||||
|
||||
class IText(IPropertyValue):
|
||||
"""Unicode text.
|
||||
"""
|
||||
|
||||
class ITime(IPropertyValue):
|
||||
"""Time.
|
||||
"""
|
||||
|
||||
class IUri(IPropertyValue):
|
||||
"""URI
|
||||
"""
|
||||
|
||||
class IGeo(IPropertyValue):
|
||||
"""Geographical location.
|
||||
"""
|
||||
|
||||
class IUTCOffset(IPropertyValue):
|
||||
"""Offset from UTC.
|
||||
"""
|
||||
|
||||
class IInline(IPropertyValue):
|
||||
"""Inline list.
|
||||
"""
|
||||
@@ -1,522 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
This module parses and generates contentlines as defined in RFC 2445
|
||||
(iCalendar), but will probably work for other MIME types with similar syntax.
|
||||
Eg. RFC 2426 (vCard)
|
||||
|
||||
It is stupid in the sense that it treats the content purely as strings. No type
|
||||
conversion is attempted.
|
||||
|
||||
Copyright, 2005: Max M <maxm@mxm.dk>
|
||||
License: GPL (Just contact med if and why you would like it changed)
|
||||
"""
|
||||
|
||||
# from python
|
||||
from types import TupleType, ListType
|
||||
SequenceTypes = [TupleType, ListType]
|
||||
import re
|
||||
# from this package
|
||||
from .caselessdict import CaselessDict
|
||||
|
||||
|
||||
#################################################################
|
||||
# Property parameter stuff
|
||||
|
||||
def paramVal(val):
|
||||
"Returns a parameter value"
|
||||
if type(val) in SequenceTypes:
|
||||
return q_join(val)
|
||||
return dQuote(val)
|
||||
|
||||
# Could be improved
|
||||
NAME = re.compile('[\w-]+')
|
||||
UNSAFE_CHAR = re.compile('[\x00-\x08\x0a-\x1f\x7F",:;]')
|
||||
QUNSAFE_CHAR = re.compile('[\x00-\x08\x0a-\x1f\x7F"]')
|
||||
FOLD = re.compile('([\r]?\n)+[ \t]{1}')
|
||||
|
||||
def validate_token(name):
|
||||
match = NAME.findall(name)
|
||||
if len(match) == 1 and name == match[0]:
|
||||
return
|
||||
raise ValueError, name
|
||||
|
||||
def validate_param_value(value, quoted=True):
|
||||
validator = UNSAFE_CHAR
|
||||
if quoted:
|
||||
validator = QUNSAFE_CHAR
|
||||
if validator.findall(value):
|
||||
raise ValueError, value
|
||||
|
||||
QUOTABLE = re.compile('[,;:].')
|
||||
def dQuote(val):
|
||||
"""
|
||||
Parameter values containing [,;:] must be double quoted
|
||||
>>> dQuote('Max')
|
||||
'Max'
|
||||
>>> dQuote('Rasmussen, Max')
|
||||
'"Rasmussen, Max"'
|
||||
>>> dQuote('name:value')
|
||||
'"name:value"'
|
||||
"""
|
||||
if QUOTABLE.search(val):
|
||||
return '"%s"' % val
|
||||
return val
|
||||
|
||||
# parsing helper
|
||||
def q_split(st, sep=','):
|
||||
"""
|
||||
Splits a string on char, taking double (q)uotes into considderation
|
||||
>>> q_split('Max,Moller,"Rasmussen, Max"')
|
||||
['Max', 'Moller', '"Rasmussen, Max"']
|
||||
"""
|
||||
result = []
|
||||
cursor = 0
|
||||
length = len(st)
|
||||
inquote = 0
|
||||
for i in range(length):
|
||||
ch = st[i]
|
||||
if ch == '"':
|
||||
inquote = not inquote
|
||||
if not inquote and ch == sep:
|
||||
result.append(st[cursor:i])
|
||||
cursor = i + 1
|
||||
if i + 1 == length:
|
||||
result.append(st[cursor:])
|
||||
return result
|
||||
|
||||
def q_join(lst, sep=','):
|
||||
"""
|
||||
Joins a list on sep, quoting strings with QUOTABLE chars
|
||||
>>> s = ['Max', 'Moller', 'Rasmussen, Max']
|
||||
>>> q_join(s)
|
||||
'Max,Moller,"Rasmussen, Max"'
|
||||
"""
|
||||
return sep.join([dQuote(itm) for itm in lst])
|
||||
|
||||
class Parameters(CaselessDict):
|
||||
"""
|
||||
Parser and generator of Property parameter strings. It knows nothing of
|
||||
datatypes. It's main concern is textual structure.
|
||||
|
||||
|
||||
Simple parameter:value pair
|
||||
>>> p = Parameters(parameter1='Value1')
|
||||
>>> str(p)
|
||||
'PARAMETER1=Value1'
|
||||
|
||||
|
||||
keys are converted to upper
|
||||
>>> p.keys()
|
||||
['PARAMETER1']
|
||||
|
||||
|
||||
Parameters are case insensitive
|
||||
>>> p['parameter1']
|
||||
'Value1'
|
||||
>>> p['PARAMETER1']
|
||||
'Value1'
|
||||
|
||||
|
||||
Parameter with list of values must be seperated by comma
|
||||
>>> p = Parameters({'parameter1':['Value1', 'Value2']})
|
||||
>>> str(p)
|
||||
'PARAMETER1=Value1,Value2'
|
||||
|
||||
|
||||
Multiple parameters must be seperated by a semicolon
|
||||
>>> p = Parameters({'RSVP':'TRUE', 'ROLE':'REQ-PARTICIPANT'})
|
||||
>>> str(p)
|
||||
'ROLE=REQ-PARTICIPANT;RSVP=TRUE'
|
||||
|
||||
|
||||
Parameter values containing ',;:' must be double quoted
|
||||
>>> p = Parameters({'ALTREP':'http://www.wiz.org'})
|
||||
>>> str(p)
|
||||
'ALTREP="http://www.wiz.org"'
|
||||
|
||||
|
||||
list items must be quoted seperately
|
||||
>>> p = Parameters({'MEMBER':['MAILTO:projectA@host.com', 'MAILTO:projectB@host.com', ]})
|
||||
>>> str(p)
|
||||
'MEMBER="MAILTO:projectA@host.com","MAILTO:projectB@host.com"'
|
||||
|
||||
Now the whole sheebang
|
||||
>>> p = Parameters({'parameter1':'Value1', 'parameter2':['Value2', 'Value3'],\
|
||||
'ALTREP':['http://www.wiz.org', 'value4']})
|
||||
>>> str(p)
|
||||
'ALTREP="http://www.wiz.org",value4;PARAMETER1=Value1;PARAMETER2=Value2,Value3'
|
||||
|
||||
We can also parse parameter strings
|
||||
>>> Parameters.from_string('PARAMETER1=Value 1;param2=Value 2')
|
||||
Parameters({'PARAMETER1': 'Value 1', 'PARAM2': 'Value 2'})
|
||||
|
||||
Including empty strings
|
||||
>>> Parameters.from_string('param=')
|
||||
Parameters({'PARAM': ''})
|
||||
|
||||
We can also parse parameter strings
|
||||
>>> Parameters.from_string('MEMBER="MAILTO:projectA@host.com","MAILTO:projectB@host.com"')
|
||||
Parameters({'MEMBER': ['MAILTO:projectA@host.com', 'MAILTO:projectB@host.com']})
|
||||
|
||||
We can also parse parameter strings
|
||||
>>> Parameters.from_string('ALTREP="http://www.wiz.org",value4;PARAMETER1=Value1;PARAMETER2=Value2,Value3')
|
||||
Parameters({'PARAMETER1': 'Value1', 'ALTREP': ['http://www.wiz.org', 'value4'], 'PARAMETER2': ['Value2', 'Value3']})
|
||||
"""
|
||||
|
||||
|
||||
def params(self):
|
||||
"""
|
||||
in rfc2445 keys are called parameters, so this is to be consitent with
|
||||
the naming conventions
|
||||
"""
|
||||
return self.keys()
|
||||
|
||||
### Later, when I get more time... need to finish this off now. The last majot thing missing.
|
||||
### def _encode(self, name, value, cond=1):
|
||||
### # internal, for conditional convertion of values.
|
||||
### if cond:
|
||||
### klass = types_factory.for_property(name)
|
||||
### return klass(value)
|
||||
### return value
|
||||
###
|
||||
### def add(self, name, value, encode=0):
|
||||
### "Add a parameter value and optionally encode it."
|
||||
### if encode:
|
||||
### value = self._encode(name, value, encode)
|
||||
### self[name] = value
|
||||
###
|
||||
### def decoded(self, name):
|
||||
### "returns a decoded value, or list of same"
|
||||
|
||||
def __repr__(self):
|
||||
return 'Parameters(' + dict.__repr__(self) + ')'
|
||||
|
||||
|
||||
def __str__(self):
|
||||
result = []
|
||||
items = self.items()
|
||||
items.sort() # To make doctests work
|
||||
for key, value in items:
|
||||
value = paramVal(value)
|
||||
result.append('%s=%s' % (key.upper(), value))
|
||||
return ';'.join(result)
|
||||
|
||||
|
||||
def from_string(st, strict=False):
|
||||
"Parses the parameter format from ical text format"
|
||||
try:
|
||||
# parse into strings
|
||||
result = Parameters()
|
||||
for param in q_split(st, ';'):
|
||||
key, val = q_split(param, '=')
|
||||
validate_token(key)
|
||||
param_values = [v for v in q_split(val, ',')]
|
||||
# Property parameter values that are not in quoted
|
||||
# strings are case insensitive.
|
||||
vals = []
|
||||
for v in param_values:
|
||||
if v.startswith('"') and v.endswith('"'):
|
||||
v = v.strip('"')
|
||||
validate_param_value(v, quoted=True)
|
||||
vals.append(v)
|
||||
else:
|
||||
validate_param_value(v, quoted=False)
|
||||
if strict:
|
||||
vals.append(v.upper())
|
||||
else:
|
||||
vals.append(v)
|
||||
if not vals:
|
||||
result[key] = val
|
||||
else:
|
||||
if len(vals) == 1:
|
||||
result[key] = vals[0]
|
||||
else:
|
||||
result[key] = vals
|
||||
return result
|
||||
except:
|
||||
raise ValueError, 'Not a valid parameter string'
|
||||
from_string = staticmethod(from_string)
|
||||
|
||||
|
||||
#########################################
|
||||
# parsing and generation of content lines
|
||||
|
||||
class Contentline(str):
|
||||
"""
|
||||
A content line is basically a string that can be folded and parsed into
|
||||
parts.
|
||||
|
||||
>>> c = Contentline('Si meliora dies, ut vina, poemata reddit')
|
||||
>>> str(c)
|
||||
'Si meliora dies, ut vina, poemata reddit'
|
||||
|
||||
A long line gets folded
|
||||
>>> c = Contentline(''.join(['123456789 ']*10))
|
||||
>>> str(c)
|
||||
'123456789 123456789 123456789 123456789 123456789 123456789 123456789 1234\\r\\n 56789 123456789 123456789 '
|
||||
|
||||
A folded line gets unfolded
|
||||
>>> c = Contentline.from_string(str(c))
|
||||
>>> c
|
||||
'123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 '
|
||||
|
||||
We do not fold within a UTF-8 character:
|
||||
>>> c = Contentline('This line has a UTF-8 character where it should be folded. Make sure it g\xc3\xabts folded before that character.')
|
||||
>>> '\xc3\xab' in str(c)
|
||||
True
|
||||
|
||||
Don't fail if we fold a line that is exactly X times 74 characters long:
|
||||
>>> c = str(Contentline(''.join(['x']*148)))
|
||||
|
||||
It can parse itself into parts. Which is a tuple of (name, params, vals)
|
||||
|
||||
>>> c = Contentline('dtstart:20050101T120000')
|
||||
>>> c.parts()
|
||||
('dtstart', Parameters({}), '20050101T120000')
|
||||
|
||||
>>> c = Contentline('dtstart;value=datetime:20050101T120000')
|
||||
>>> c.parts()
|
||||
('dtstart', Parameters({'VALUE': 'datetime'}), '20050101T120000')
|
||||
|
||||
>>> c = Contentline('ATTENDEE;CN=Max Rasmussen;ROLE=REQ-PARTICIPANT:MAILTO:maxm@example.com')
|
||||
>>> c.parts()
|
||||
('ATTENDEE', Parameters({'ROLE': 'REQ-PARTICIPANT', 'CN': 'Max Rasmussen'}), 'MAILTO:maxm@example.com')
|
||||
>>> str(c)
|
||||
'ATTENDEE;CN=Max Rasmussen;ROLE=REQ-PARTICIPANT:MAILTO:maxm@example.com'
|
||||
|
||||
and back again
|
||||
>>> parts = ('ATTENDEE', Parameters({'ROLE': 'REQ-PARTICIPANT', 'CN': 'Max Rasmussen'}), 'MAILTO:maxm@example.com')
|
||||
>>> Contentline.from_parts(parts)
|
||||
'ATTENDEE;CN=Max Rasmussen;ROLE=REQ-PARTICIPANT:MAILTO:maxm@example.com'
|
||||
|
||||
and again
|
||||
>>> parts = ('ATTENDEE', Parameters(), 'MAILTO:maxm@example.com')
|
||||
>>> Contentline.from_parts(parts)
|
||||
'ATTENDEE:MAILTO:maxm@example.com'
|
||||
|
||||
A value can also be any of the types defined in PropertyValues
|
||||
>>> from icalendar.prop import vText
|
||||
>>> parts = ('ATTENDEE', Parameters(), vText('MAILTO:test@example.com'))
|
||||
>>> Contentline.from_parts(parts)
|
||||
'ATTENDEE:MAILTO:test@example.com'
|
||||
|
||||
A value can also be unicode
|
||||
>>> from icalendar.prop import vText
|
||||
>>> parts = ('SUMMARY', Parameters(), vText(u'INternational char <20> <20> <20>'))
|
||||
>>> Contentline.from_parts(parts)
|
||||
'SUMMARY:INternational char \\xc3\\xa6 \\xc3\\xb8 \\xc3\\xa5'
|
||||
|
||||
Traversing could look like this.
|
||||
>>> name, params, vals = c.parts()
|
||||
>>> name
|
||||
'ATTENDEE'
|
||||
>>> vals
|
||||
'MAILTO:maxm@example.com'
|
||||
>>> for key, val in params.items():
|
||||
... (key, val)
|
||||
('ROLE', 'REQ-PARTICIPANT')
|
||||
('CN', 'Max Rasmussen')
|
||||
|
||||
And the traditional failure
|
||||
>>> c = Contentline('ATTENDEE;maxm@example.com')
|
||||
>>> c.parts()
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Content line could not be parsed into parts
|
||||
|
||||
Another failure:
|
||||
>>> c = Contentline(':maxm@example.com')
|
||||
>>> c.parts()
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Content line could not be parsed into parts
|
||||
|
||||
>>> c = Contentline('key;param=:value')
|
||||
>>> c.parts()
|
||||
('key', Parameters({'PARAM': ''}), 'value')
|
||||
|
||||
>>> c = Contentline('key;param="pvalue":value')
|
||||
>>> c.parts()
|
||||
('key', Parameters({'PARAM': 'pvalue'}), 'value')
|
||||
|
||||
Should bomb on missing param:
|
||||
>>> c = Contentline.from_string("k;:no param")
|
||||
>>> c.parts()
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Content line could not be parsed into parts
|
||||
|
||||
>>> c = Contentline('key;param=pvalue:value', strict=False)
|
||||
>>> c.parts()
|
||||
('key', Parameters({'PARAM': 'pvalue'}), 'value')
|
||||
|
||||
If strict is set to True, uppercase param values that are not
|
||||
double-quoted, this is because the spec says non-quoted params are
|
||||
case-insensitive.
|
||||
|
||||
>>> c = Contentline('key;param=pvalue:value', strict=True)
|
||||
>>> c.parts()
|
||||
('key', Parameters({'PARAM': 'PVALUE'}), 'value')
|
||||
|
||||
>>> c = Contentline('key;param="pValue":value', strict=True)
|
||||
>>> c.parts()
|
||||
('key', Parameters({'PARAM': 'pValue'}), 'value')
|
||||
|
||||
"""
|
||||
|
||||
def __new__(cls, st, strict=False):
|
||||
self = str.__new__(cls, st)
|
||||
setattr(self, 'strict', strict)
|
||||
return self
|
||||
|
||||
def from_parts(parts):
|
||||
"Turns a tuple of parts into a content line"
|
||||
(name, params, values) = [str(p) for p in parts]
|
||||
try:
|
||||
if params:
|
||||
return Contentline('%s;%s:%s' % (name, params, values))
|
||||
return Contentline('%s:%s' % (name, values))
|
||||
except:
|
||||
raise ValueError(
|
||||
'Property: %s Wrong values "%s" or "%s"' % (repr(name),
|
||||
repr(params),
|
||||
repr(values)))
|
||||
from_parts = staticmethod(from_parts)
|
||||
|
||||
def parts(self):
|
||||
""" Splits the content line up into (name, parameters, values) parts
|
||||
"""
|
||||
try:
|
||||
name_split = None
|
||||
value_split = None
|
||||
inquotes = 0
|
||||
for i in range(len(self)):
|
||||
ch = self[i]
|
||||
if not inquotes:
|
||||
if ch in ':;' and not name_split:
|
||||
name_split = i
|
||||
if ch == ':' and not value_split:
|
||||
value_split = i
|
||||
if ch == '"':
|
||||
inquotes = not inquotes
|
||||
name = self[:name_split]
|
||||
if not name:
|
||||
raise ValueError, 'Key name is required'
|
||||
validate_token(name)
|
||||
if name_split + 1 == value_split:
|
||||
raise ValueError, 'Invalid content line'
|
||||
params = Parameters.from_string(self[name_split + 1:value_split],
|
||||
strict=self.strict)
|
||||
values = self[value_split + 1:]
|
||||
return (name, params, values)
|
||||
except:
|
||||
raise ValueError, 'Content line could not be parsed into parts'
|
||||
|
||||
def from_string(st, strict=False):
|
||||
"Unfolds the content lines in an iCalendar into long content lines"
|
||||
try:
|
||||
# a fold is carriage return followed by either a space or a tab
|
||||
return Contentline(FOLD.sub('', st), strict=strict)
|
||||
except:
|
||||
raise ValueError, 'Expected StringType with content line'
|
||||
from_string = staticmethod(from_string)
|
||||
|
||||
def __str__(self):
|
||||
"Long content lines are folded so they are less than 75 characters wide"
|
||||
l_line = len(self)
|
||||
new_lines = []
|
||||
start = 0
|
||||
end = 74
|
||||
while True:
|
||||
if end >= l_line:
|
||||
end = l_line
|
||||
else:
|
||||
# Check that we don't fold in the middle of a UTF-8 character:
|
||||
# http://lists.osafoundation.org/pipermail/ietf-calsify/2006-August/001126.html
|
||||
while True:
|
||||
char_value = ord(self[end])
|
||||
if char_value < 128 or char_value >= 192:
|
||||
# This is not in the middle of a UTF-8 character, so we
|
||||
# can fold here:
|
||||
break
|
||||
else:
|
||||
end -= 1
|
||||
|
||||
new_lines.append(self[start:end])
|
||||
if end == l_line:
|
||||
# Done
|
||||
break
|
||||
start = end
|
||||
end = start + 74
|
||||
return '\r\n '.join(new_lines)
|
||||
|
||||
|
||||
|
||||
class Contentlines(list):
|
||||
"""
|
||||
I assume that iCalendar files generally are a few kilobytes in size. Then
|
||||
this should be efficient. for Huge files, an iterator should probably be
|
||||
used instead.
|
||||
|
||||
>>> c = Contentlines([Contentline('BEGIN:VEVENT\\r\\n')])
|
||||
>>> str(c)
|
||||
'BEGIN:VEVENT\\r\\n'
|
||||
|
||||
Lets try appending it with a 100 charater wide string
|
||||
>>> c.append(Contentline(''.join(['123456789 ']*10)+'\\r\\n'))
|
||||
>>> str(c)
|
||||
'BEGIN:VEVENT\\r\\n\\r\\n123456789 123456789 123456789 123456789 123456789 123456789 123456789 1234\\r\\n 56789 123456789 123456789 \\r\\n'
|
||||
|
||||
Notice that there is an extra empty string in the end of the content lines.
|
||||
That is so they can be easily joined with: '\r\n'.join(contentlines)).
|
||||
>>> Contentlines.from_string('A short line\\r\\n')
|
||||
['A short line', '']
|
||||
>>> Contentlines.from_string('A faked\\r\\n long line\\r\\n')
|
||||
['A faked long line', '']
|
||||
>>> Contentlines.from_string('A faked\\r\\n long line\\r\\nAnd another lin\\r\\n\\te that is folded\\r\\n')
|
||||
['A faked long line', 'And another line that is folded', '']
|
||||
"""
|
||||
|
||||
def __str__(self):
|
||||
"Simply join self."
|
||||
return '\r\n'.join(map(str, self))
|
||||
|
||||
def from_string(st):
|
||||
"Parses a string into content lines"
|
||||
try:
|
||||
# a fold is carriage return followed by either a space or a tab
|
||||
unfolded = FOLD.sub('', st)
|
||||
lines = [Contentline(line) for line in unfolded.splitlines() if line]
|
||||
lines.append('') # we need a '\r\n' in the end of every content line
|
||||
return Contentlines(lines)
|
||||
except:
|
||||
raise ValueError, 'Expected StringType with content lines'
|
||||
from_string = staticmethod(from_string)
|
||||
|
||||
|
||||
# ran this:
|
||||
# sample = open('./samples/test.ics', 'rb').read() # binary file in windows!
|
||||
# lines = Contentlines.from_string(sample)
|
||||
# for line in lines[:-1]:
|
||||
# print line.parts()
|
||||
|
||||
# got this:
|
||||
#('BEGIN', Parameters({}), 'VCALENDAR')
|
||||
#('METHOD', Parameters({}), 'Request')
|
||||
#('PRODID', Parameters({}), '-//My product//mxm.dk/')
|
||||
#('VERSION', Parameters({}), '2.0')
|
||||
#('BEGIN', Parameters({}), 'VEVENT')
|
||||
#('DESCRIPTION', Parameters({}), 'This is a very long description that ...')
|
||||
#('PARTICIPANT', Parameters({'CN': 'Max M'}), 'MAILTO:maxm@mxm.dk')
|
||||
#('DTEND', Parameters({}), '20050107T160000')
|
||||
#('DTSTART', Parameters({}), '20050107T120000')
|
||||
#('SUMMARY', Parameters({}), 'A second event')
|
||||
#('END', Parameters({}), 'VEVENT')
|
||||
#('BEGIN', Parameters({}), 'VEVENT')
|
||||
#('DTEND', Parameters({}), '20050108T235900')
|
||||
#('DTSTART', Parameters({}), '20050108T230000')
|
||||
#('SUMMARY', Parameters({}), 'A single event')
|
||||
#('UID', Parameters({}), '42')
|
||||
#('END', Parameters({}), 'VEVENT')
|
||||
#('END', Parameters({}), 'VCALENDAR')
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1 +0,0 @@
|
||||
# this is a package
|
||||
@@ -1,16 +0,0 @@
|
||||
import unittest, doctest, os
|
||||
from utils.icalendar import cal, caselessdict, parser, prop
|
||||
|
||||
def test_suite():
|
||||
suite = unittest.TestSuite()
|
||||
|
||||
suite.addTest(doctest.DocTestSuite(caselessdict))
|
||||
suite.addTest(doctest.DocTestSuite(parser))
|
||||
suite.addTest(doctest.DocTestSuite(prop))
|
||||
suite.addTest(doctest.DocTestSuite(cal))
|
||||
doc_dir = '../../../doc'
|
||||
for docfile in ['example.txt', 'groupscheduled.txt',
|
||||
'small.txt', 'multiple.txt', 'recurrence.txt']:
|
||||
suite.addTest(doctest.DocFileSuite(os.path.join(doc_dir, docfile),
|
||||
optionflags=doctest.ELLIPSIS),)
|
||||
return suite
|
||||
Reference in New Issue
Block a user