Merge remote-tracking branch 'untitaker/preserve_order'

pull/139/head
Johannes Raggam 2014-06-02 11:38:58 +02:00
commit a8e13b21d7
7 zmienionych plików z 93 dodań i 34 usunięć

Wyświetl plik

@ -12,6 +12,9 @@ Changelog
- Remove incorrect use of __all__. We don't encourage using ``from package
import *`` imports. Fixes #129.
[eric-wieser]
- Add optional ``sorted`` parameter to ``Component.to_ical``. Setting it to
false allows the user to preserve the original property and parameter order.
[untitaker]
3.6.2 (2014-04-05)

Wyświetl plik

@ -35,3 +35,4 @@ icalendar contributors
- Wichert Akkerman <wichert@wiggy.net>
- spanktar <spanky@kapanka.com>
- tgecho <tgecho@gmail.com>
- Markus Unterwaditzer <markus@unterwaditzer.net>

Wyświetl plik

@ -11,9 +11,17 @@ longdesc += codecs.open('LICENSE.rst', encoding='utf-8').read()
tests_require = []
install_requires = [
'setuptools',
'python-dateutil',
'pytz'
]
if sys.version_info[:2] == (2, 6):
# Python unittest2 only needed for Python 2.6
tests_require = ['unittest2']
tests_require.append('unittest2')
# OrderedDict was added in 2.7
install_requires.append('ordereddict')
setuptools.setup(
@ -42,11 +50,7 @@ setuptools.setup(
package_dir={'': 'src'},
include_package_data=True,
zip_safe=False,
install_requires=[
'setuptools',
'python-dateutil',
'pytz',
],
install_requires=install_requires,
extras_require={
'test': tests_require
}

Wyświetl plik

@ -267,13 +267,17 @@ class Component(CaselessDict):
#####################
# Generation
def property_items(self, recursive=True):
def property_items(self, recursive=True, sorted=True):
"""Returns properties in this component and subcomponents as:
[(name, value), ...]
"""
vText = types_factory['text']
properties = [('BEGIN', vText(self.name).to_ical())]
property_names = self.sorted_keys()
if sorted:
property_names = self.sorted_keys()
else:
property_names = self.keys()
for name in property_names:
values = self[name]
if isinstance(values, list):
@ -285,7 +289,7 @@ class Component(CaselessDict):
if recursive:
# recursion is fun!
for subcomponent in self.subcomponents:
properties += subcomponent.property_items()
properties += subcomponent.property_items(sorted=sorted)
properties.append(('END', vText(self.name).to_ical()))
return properties
@ -353,24 +357,29 @@ class Component(CaselessDict):
def __repr__(self):
return '%s(%s)' % (self.name, data_encode(self))
def content_line(self, name, value):
def content_line(self, name, value, sorted=True):
"""Returns property as content line.
"""
params = getattr(value, 'params', Parameters())
return Contentline.from_parts(name, params, value)
return Contentline.from_parts(name, params, value, sorted=sorted)
def content_lines(self):
def content_lines(self, sorted=True):
"""Converts the Component and subcomponents into content lines.
"""
contentlines = Contentlines()
for name, value in self.property_items():
cl = self.content_line(name, value)
for name, value in self.property_items(sorted=sorted):
cl = self.content_line(name, value, sorted=sorted)
contentlines.append(cl)
contentlines.append('') # remember the empty string in the end
return contentlines
def to_ical(self):
content_lines = self.content_lines()
def to_ical(self, sorted=True):
'''
:param sorted: Whether parameters and properties should be
lexicographically sorted.
'''
content_lines = self.content_lines(sorted=sorted)
return content_lines.to_ical()

Wyświetl plik

@ -2,6 +2,11 @@
from icalendar.parser_tools import to_unicode
from icalendar.parser_tools import data_encode
try:
from collections import OrderedDict
except ImportError:
from ordereddict import OrderedDict
def canonsort_keys(keys, canonical_order=None):
"""Sorts leading keys according to canonical_order. Keys not specified in
@ -20,7 +25,7 @@ def canonsort_items(dict1, canonical_order=None):
k in canonsort_keys(dict1.keys(), canonical_order)]
class CaselessDict(dict):
class CaselessDict(OrderedDict):
"""A dictionary that isn't case sensitive, and only uses strings as keys.
Values retain their case.
"""
@ -28,47 +33,47 @@ class CaselessDict(dict):
def __init__(self, *args, **kwargs):
"""Set keys to upper for initial dict.
"""
dict.__init__(self, *args, **kwargs)
super(CaselessDict, self).__init__(*args, **kwargs)
for key, value in self.items():
key_upper = to_unicode(key).upper()
if key != key_upper:
dict.__delitem__(self, key)
super(CaselessDict, self).__delitem__(key)
self[key_upper] = value
def __getitem__(self, key):
key = to_unicode(key)
return dict.__getitem__(self, key.upper())
return super(CaselessDict, self).__getitem__(key.upper())
def __setitem__(self, key, value):
key = to_unicode(key)
dict.__setitem__(self, key.upper(), value)
super(CaselessDict, self).__setitem__(key.upper(), value)
def __delitem__(self, key):
key = to_unicode(key)
dict.__delitem__(self, key.upper())
super(CaselessDict, self).__delitem__(key.upper())
def __contains__(self, key):
key = to_unicode(key)
return dict.__contains__(self, key.upper())
return super(CaselessDict, self).__contains__(key.upper())
def get(self, key, default=None):
key = to_unicode(key)
return dict.get(self, key.upper(), default)
return super(CaselessDict, self).get(key.upper(), default)
def setdefault(self, key, value=None):
key = to_unicode(key)
return dict.setdefault(self, key.upper(), value)
return super(CaselessDict, self).setdefault(key.upper(), value)
def pop(self, key, default=None):
key = to_unicode(key)
return dict.pop(self, key.upper(), default)
return super(CaselessDict, self).pop(key.upper(), default)
def popitem(self):
return dict.popitem(self)
return super(CaselessDict, self).popitem()
def has_key(self, key):
key = to_unicode(key)
return dict.__contains__(self, key.upper())
return super(CaselessDict, self).__contains__(key.upper())
def update(self, indict):
# Multiple keys where key1.upper() == key2.upper() will be lost.
@ -76,11 +81,14 @@ class CaselessDict(dict):
self[key] = value
def copy(self):
return CaselessDict(dict.copy(self))
return type(self)(super(CaselessDict, self).copy())
def __repr__(self):
return 'CaselessDict(%s)' % data_encode(self)
def __eq__(self, other):
return self is other or dict(self.items()) == dict(other.items())
# A list of keys that must appear first in sorted_keys and sorted_items;
# must be uppercase.
canonical_order = None

Wyświetl plik

@ -194,10 +194,13 @@ class Parameters(CaselessDict):
def __repr__(self):
return 'Parameters(%s)' % data_encode(self)
def to_ical(self):
def to_ical(self, sorted=True):
result = []
items = self.items()
for key, value in sorted(items):
items = list(self.items())
if sorted:
items.sort()
for key, value in items:
value = param_value(value)
if isinstance(value, compat.unicode_type):
value = value.encode(DEFAULT_ENCODING)
@ -270,7 +273,7 @@ class Contentline(compat.unicode_type):
return self
@classmethod
def from_parts(cls, name, params, values):
def from_parts(cls, name, params, values, sorted=True):
"""Turn a parts into a content line.
"""
assert isinstance(params, Parameters)
@ -286,7 +289,7 @@ class Contentline(compat.unicode_type):
name = to_unicode(name)
values = to_unicode(values)
if params:
params = to_unicode(params.to_ical())
params = to_unicode(params.to_ical(sorted=sorted))
return cls(u'%s;%s:%s' % (name, params, values))
return cls(u'%s:%s' % (name, values))

Wyświetl plik

@ -275,6 +275,37 @@ class TestCalComponent(unittest.TestCase):
self.assertEqual(component[property_name].dt.tzinfo,
None)
def test_cal_Component_to_ical_property_order(self):
Component = icalendar.cal.Component
component_str = [b'BEGIN:VEVENT',
b'DTSTART:19970714T170000Z',
b'DTEND:19970715T035959Z',
b'SUMMARY:Bastille Day Party',
b'END:VEVENT']
component = Component.from_ical(b'\r\n'.join(component_str))
sorted_str = component.to_ical().splitlines()
assert sorted_str != component_str
assert set(sorted_str) == set(component_str)
preserved_str = component.to_ical(sorted=False).splitlines()
assert preserved_str == component_str
def test_cal_Component_to_ical_parameter_order(self):
Component = icalendar.cal.Component
component_str = [b'BEGIN:VEVENT',
b'X-FOOBAR;C=one;A=two;B=three:helloworld.',
b'END:VEVENT']
component = Component.from_ical(b'\r\n'.join(component_str))
sorted_str = component.to_ical().splitlines()
assert sorted_str[0] == component_str[0]
assert sorted_str[1] == b'X-FOOBAR;A=two;B=three;C=one:helloworld.'
assert sorted_str[2] == component_str[2]
preserved_str = component.to_ical(sorted=False).splitlines()
assert preserved_str == component_str
class TestCal(unittest.TestCase):