Skip to content

Commit

Permalink
- Release vobject 0.6.0 with Cyrus' vavailability and unicode line
Browse files Browse the repository at this point in the history
  folding enhancements



git-svn-id: http://svn.osafoundation.org/vobject/trunk@193 72e31ff3-66e1-0310-9094-9c83e53ae81d
  • Loading branch information
jeffrey committed Feb 21, 2008
1 parent 556bdf8 commit bb112d9
Show file tree
Hide file tree
Showing 6 changed files with 188 additions and 48 deletions.
2 changes: 1 addition & 1 deletion README.txt
Expand Up @@ -193,7 +193,7 @@ attributes are required.
serializing will add any required computable attributes (like 'VERSION')

>>> j.serialize()
u'BEGIN:VCARD\r\nVERSION:3.0\r\nEMAIL;TYPE=INTERNET:jeffrey@osafoundation.org\r\nFN:Jeffrey Harris\r\nN:Harris;Jeffrey;;;\r\nEND:VCARD\r\n'
'BEGIN:VCARD\r\nVERSION:3.0\r\nEMAIL;TYPE=INTERNET:jeffrey@osafoundation.org\r\nFN:Jeffrey Harris\r\nN:Harris;Jeffrey;;;\r\nEND:VCARD\r\n'
>>> j.prettyPrint()
VCARD
VERSION: 3.0
Expand Down
9 changes: 3 additions & 6 deletions setup.py
Expand Up @@ -5,11 +5,8 @@
Requires python 2.4 or later, dateutil (http://labix.org/python-dateutil) 1.1 or later.
Recent changes:
- Added ORG behavior for vCard, ORG is now treated as a list of organizations
- Fixed UNTIL values in RRULEs to have the right value when a dateutil rruleset is created
- Fixed a problem causing DATE valued RDATEs and EXDATEs to be ignored when interpreting recurrence rules
- Added an ics_diff module and an ics_diff command line script for comparing the VEVENTs and VTODOs in similar iCalendar files
- Added VAVAILABILITY support
- Improved wrapping of unicode lines, serialize encodes unicode as utf-8 by default
For older changes, see http://vobject.skyhouseconsulting.com/history.html or http://websvn.osafoundation.org/listing.php?repname=vobject&path=/trunk/
"""
Expand All @@ -23,7 +20,7 @@

# Metadata
PACKAGE_NAME = "vobject"
PACKAGE_VERSION = "0.5.0"
PACKAGE_VERSION = "0.6.0"

ALL_EXTS = ['*.py', '*.ics', '*.txt']

Expand Down
56 changes: 41 additions & 15 deletions src/vobject/base.py
Expand Up @@ -855,13 +855,35 @@ def dquoteEscape(param):
return param

def foldOneLine(outbuf, input, lineLength = 75):
if isinstance(input, basestring): input = StringIO.StringIO(input)
input.seek(0)
outbuf.write(input.read(lineLength) + CRLF)
brokenline = input.read(lineLength - 1)
while brokenline:
outbuf.write(' ' + brokenline + CRLF)
brokenline = input.read(lineLength - 1)
# Folding line procedure that ensures multi-byte utf-8 sequences are not broken
# across lines

if len(input) < lineLength:
# Optimize for unfolded line case
outbuf.write(input)
else:
# Look for valid utf8 range and write that out
start = 0
written = 0
while written < len(input):
# Start max length -1 chars on from where we are
offset = start + lineLength - 1
if offset >= len(input):
line = input[start:]
outbuf.write(line)
written = len(input)
else:
# Check whether next char is valid utf8 lead byte
while (input[offset] > 0x7F) and ((ord(input[offset]) & 0xC0) == 0x80):
# Step back until we have a valid char
offset -= 1

line = input[start:offset]
outbuf.write(line)
outbuf.write("\r\n ")
written += offset - start
start = offset
outbuf.write("\r\n")

def defaultSerialize(obj, buf, lineLength):
"""Encode and fold obj and its children, write to buf or return a string."""
Expand All @@ -874,27 +896,31 @@ def defaultSerialize(obj, buf, lineLength):
else:
groupString = obj.group + '.'
if obj.useBegin:
foldOneLine(outbuf, groupString + u"BEGIN:" + obj.name, lineLength)
foldOneLine(outbuf, str(groupString + u"BEGIN:" + obj.name), lineLength)
for child in obj.getSortedChildren():
#validate is recursive, we only need to validate once
child.serialize(outbuf, lineLength, validate=False)
if obj.useBegin:
foldOneLine(outbuf, groupString + u"END:" + obj.name, lineLength)
foldOneLine(outbuf, str(groupString + u"END:" + obj.name), lineLength)
if DEBUG: logger.debug("Finished %s" % obj.name.upper())

elif isinstance(obj, ContentLine):
startedEncoded = obj.encoded
if obj.behavior and not startedEncoded: obj.behavior.encode(obj)
s=StringIO.StringIO() #unfolded buffer
if obj.group is not None:
s.write(obj.group + '.')
s.write(str(obj.group + '.'))
if DEBUG: logger.debug("Serializing line" + str(obj))
s.write(obj.name.upper())
s.write(str(obj.name.upper()))
for key, paramvals in obj.params.iteritems():
s.write(';' + key + '=' + ','.join(map(dquoteEscape, paramvals)))
s.write(':' + obj.value)
s.write(';' + str(key) + '=' + ','.join(map(dquoteEscape, paramvals)).encode("utf-8"))
if isinstance(obj.value, unicode):
strout = obj.value.encode("utf-8")
else:
strout = obj.value
s.write(':' + strout)
if obj.behavior and not startedEncoded: obj.behavior.decode(obj)
foldOneLine(outbuf, s, lineLength)
foldOneLine(outbuf, s.getvalue(), lineLength)
if DEBUG: logger.debug("Finished %s line" % obj.name.upper())

return buf or outbuf.getvalue()
Expand Down Expand Up @@ -1057,7 +1083,7 @@ def newFromBehavior(name, id=None):
else:
obj = ContentLine(name, [], '')
obj.behavior = behavior
obj.isNative = True
obj.isNative = False
return obj


Expand Down
144 changes: 129 additions & 15 deletions src/vobject/icalendar.py
Expand Up @@ -782,20 +782,21 @@ def encode(line):

#------------------------ Registered Behavior subclasses -----------------------
class VCalendar2_0(VCalendarComponentBehavior):
"""vCalendar 2.0 behavior."""
"""vCalendar 2.0 behavior. With added VAVAILABILITY support."""
name = 'VCALENDAR'
description = 'vCalendar 2.0, also known as iCalendar.'
versionString = '2.0'
sortFirst = ('version', 'calscale', 'method', 'prodid', 'vtimezone')
knownChildren = {'CALSCALE': (0, 1, None),#min, max, behaviorRegistry id
'METHOD': (0, 1, None),
'VERSION': (0, 1, None),#required, but auto-generated
'PRODID': (1, 1, None),
'VTIMEZONE': (0, None, None),
'VEVENT': (0, None, None),
'VTODO': (0, None, None),
'VJOURNAL': (0, None, None),
'VFREEBUSY': (0, None, None)
knownChildren = {'CALSCALE': (0, 1, None),#min, max, behaviorRegistry id
'METHOD': (0, 1, None),
'VERSION': (0, 1, None),#required, but auto-generated
'PRODID': (1, 1, None),
'VTIMEZONE': (0, None, None),
'VEVENT': (0, None, None),
'VTODO': (0, None, None),
'VJOURNAL': (0, None, None),
'VFREEBUSY': (0, None, None),
'VAVAILABILITY': (0, None, None),
}

@classmethod
Expand Down Expand Up @@ -941,7 +942,7 @@ class VEvent(RecurringBehavior):

@classmethod
def validate(cls, obj, raiseException, *args):
if obj.contents.has_key('DTEND') and obj.contents.has_key('DURATION'):
if obj.contents.has_key('dtend') and obj.contents.has_key('duration'):
if raiseException:
m = "VEVENT components cannot contain both DTEND and DURATION\
components"
Expand Down Expand Up @@ -996,7 +997,7 @@ class VTodo(RecurringBehavior):

@classmethod
def validate(cls, obj, raiseException, *args):
if obj.contents.has_key('DUE') and obj.contents.has_key('DURATION'):
if obj.contents.has_key('due') and obj.contents.has_key('duration'):
if raiseException:
m = "VTODO components cannot contain both DUE and DURATION\
components"
Expand Down Expand Up @@ -1047,12 +1048,14 @@ class VFreeBusy(VCalendarComponentBehavior):
>>> vfb.add('dtstart').value = datetime.datetime(2006, 2, 16, 1, tzinfo=utc)
>>> vfb.add('dtend').value = vfb.dtstart.value + twoHours
>>> vfb.add('freebusy').value = [(vfb.dtstart.value, twoHours / 2)]
>>> vfb.add('freebusy').value = [(vfb.dtstart.value, vfb.dtend.value)]
>>> print vfb.serialize()
BEGIN:VFREEBUSY
UID:test
DTSTART:20060216T010000Z
DTEND:20060216T030000Z
FREEBUSY:20060216T010000Z/PT1H
FREEBUSY:20060216T010000Z/20060216T030000Z
END:VFREEBUSY
"""
Expand Down Expand Up @@ -1172,7 +1175,7 @@ def validate(cls, obj, raiseException, *args):
; and MUST NOT occur more than once
description /
if obj.contents.has_key('DTEND') and obj.contents.has_key('DURATION'):
if obj.contents.has_key('dtend') and obj.contents.has_key('duration'):
if raiseException:
m = "VEVENT components cannot contain both DTEND and DURATION\
components"
Expand All @@ -1185,6 +1188,117 @@ def validate(cls, obj, raiseException, *args):

registerBehavior(VAlarm)

class VAvailability(VCalendarComponentBehavior):
"""Availability state behavior.
>>> vav = newFromBehavior('VAVAILABILITY')
>>> vav.add('uid').value = 'test'
>>> vav.add('dtstamp').value = datetime.datetime(2006, 2, 15, 0, tzinfo=utc)
>>> vav.add('dtstart').value = datetime.datetime(2006, 2, 16, 0, tzinfo=utc)
>>> vav.add('dtend').value = datetime.datetime(2006, 2, 17, 0, tzinfo=utc)
>>> vav.add('busytype').value = "BUSY"
>>> av = newFromBehavior('AVAILABLE')
>>> av.add('uid').value = 'test1'
>>> av.add('dtstamp').value = datetime.datetime(2006, 2, 15, 0, tzinfo=utc)
>>> av.add('dtstart').value = datetime.datetime(2006, 2, 16, 9, tzinfo=utc)
>>> av.add('dtend').value = datetime.datetime(2006, 2, 16, 12, tzinfo=utc)
>>> av.add('summary').value = "Available in the morning"
>>> ignore = vav.add(av)
>>> print vav.serialize()
BEGIN:VAVAILABILITY
UID:test
DTSTART:20060216T000000Z
DTEND:20060217T000000Z
BEGIN:AVAILABLE
UID:test1
DTSTART:20060216T090000Z
DTEND:20060216T120000Z
DTSTAMP:20060215T000000Z
SUMMARY:Available in the morning
END:AVAILABLE
BUSYTYPE:BUSY
DTSTAMP:20060215T000000Z
END:VAVAILABILITY
"""
name='VAVAILABILITY'
description='A component used to represent a user\'s available time slots.'
sortFirst = ('uid', 'dtstart', 'duration', 'dtend')
knownChildren = {'UID': (1, 1, None),#min, max, behaviorRegistry id
'DTSTAMP': (1, 1, None),
'BUSYTYPE': (0, 1, None),
'CREATED': (0, 1, None),
'DTSTART': (0, 1, None),
'LAST-MODIFIED': (0, 1, None),
'ORGANIZER': (0, 1, None),
'SEQUENCE': (0, 1, None),
'SUMMARY': (0, 1, None),
'URL': (0, 1, None),
'DTEND': (0, 1, None),
'DURATION': (0, 1, None),
'CATEGORIES': (0, None, None),
'COMMENT': (0, None, None),
'CONTACT': (0, None, None),
'AVAILABLE': (0, None, None),
}

@classmethod
def validate(cls, obj, raiseException, *args):
if obj.contents.has_key('dtend') and obj.contents.has_key('duration'):
if raiseException:
m = "VAVAILABILITY components cannot contain both DTEND and DURATION\
components"
raise ValidateError(m)
return False
else:
return super(VAvailability, cls).validate(obj, raiseException, *args)

registerBehavior(VAvailability)

class Available(RecurringBehavior):
"""Event behavior."""
name='AVAILABLE'
sortFirst = ('uid', 'recurrence-id', 'dtstart', 'duration', 'dtend')

description='Defines a period of time in which a user is normally available.'
knownChildren = {'DTSTAMP': (1, 1, None),#min, max, behaviorRegistry id
'DTSTART': (1, 1, None),
'UID': (1, 1, None),
'DTEND': (0, 1, None), #NOTE: One of DtEnd or
'DURATION': (0, 1, None), # Duration must appear, but not both
'CREATED': (0, 1, None),
'LAST-MODIFIED':(0, 1, None),
'RECURRENCE-ID':(0, 1, None),
'RRULE': (0, 1, None),
'SUMMARY': (0, 1, None),
'CATEGORIES': (0, None, None),
'COMMENT': (0, None, None),
'CONTACT': (0, None, None),
'EXDATE': (0, None, None),
'RDATE': (0, None, None),
}

@classmethod
def validate(cls, obj, raiseException, *args):
has_dtend = obj.contents.has_key('dtend')
has_duration = obj.contents.has_key('duration')
if has_dtend and has_duration:
if raiseException:
m = "AVAILABLE components cannot contain both DTEND and DURATION\
properties"
raise ValidateError(m)
return False
elif not (has_dtend or has_duration):
if raiseException:
m = "AVAILABLE components must contain one of DTEND or DURATION\
properties"
raise ValidateError(m)
return False
else:
return super(Available, cls).validate(obj, raiseException, *args)

registerBehavior(Available)

class Duration(behavior.Behavior):
"""Behavior for Duration ContentLines. Transform to datetime.timedelta."""
name = 'DURATION'
Expand Down Expand Up @@ -1344,7 +1458,7 @@ class RRule(behavior.Behavior):

textList = ['CALSCALE', 'METHOD', 'PRODID', 'CLASS', 'COMMENT', 'DESCRIPTION',
'LOCATION', 'STATUS', 'SUMMARY', 'TRANSP', 'CONTACT', 'RELATED-TO',
'UID', 'ACTION', 'REQUEST-STATUS', 'TZID']
'UID', 'ACTION', 'REQUEST-STATUS', 'TZID', 'BUSYTYPE']
map(lambda x: registerBehavior(TextBehavior, x), textList)

multiTextList = ['CATEGORIES', 'RESOURCES']
Expand Down Expand Up @@ -1678,7 +1792,7 @@ def stringToPeriod(s, tzinfo=None):
delta = stringToDurations(valEnd)[0]
return (start, delta)
else:
return (start, stringToDateTime(valEnd, tzinfo) - start)
return (start, stringToDateTime(valEnd, tzinfo))


def getTransition(transitionTo, year, tzinfo):
Expand Down
4 changes: 2 additions & 2 deletions tests/more_tests.txt
Expand Up @@ -8,9 +8,9 @@ Unicode in vCards
>>> card.add('adr').value = vobject.vcard.Address(u'5\u1234 Nowhere, Apt 1', 'Berkeley', 'CA', '94704', 'USA')
>>> card
<VCARD| [<ADR{}5? Nowhere, Apt 1\nBerkeley, CA 94704\nUSA>, <FN{}Hello? World!>, <N{} Hello? World >]>
>>> card.serialize()
>>> card.serialize().decode("utf-8")
u'BEGIN:VCARD\r\nVERSION:3.0\r\nADR:;;5\u1234 Nowhere\\, Apt 1;Berkeley;CA;94704;USA\r\nFN:Hello\u1234 World!\r\nN:World;Hello\u1234;;;\r\nEND:VCARD\r\n'
>>> print card.serialize().encode('ascii', 'replace')
>>> print card.serialize().decode("utf-8").encode('ascii', 'replace')
BEGIN:VCARD
VERSION:3.0
ADR:;;5? Nowhere\, Apt 1;Berkeley;CA;94704;USA
Expand Down

0 comments on commit bb112d9

Please sign in to comment.