mirror of
https://github.com/samsonjs/vdirsyncer.git
synced 2026-04-27 14:57:41 +00:00
Remove icalendar
This commit is contained in:
parent
42662a97c3
commit
8d5fed48bc
6 changed files with 80 additions and 99 deletions
|
|
@ -9,7 +9,7 @@ env:
|
||||||
# Radicale with filesystem storage (default)
|
# Radicale with filesystem storage (default)
|
||||||
|
|
||||||
- BUILD=test DAV_SERVER=radicale RADICALE_BACKEND=filesystem REQUIREMENTS=release
|
- BUILD=test DAV_SERVER=radicale RADICALE_BACKEND=filesystem REQUIREMENTS=release
|
||||||
PKGS='icalendar==3.6 lxml==3.0 requests==2.4.1 requests_toolbelt==0.3.0 click==3.1'
|
PKGS='lxml==3.0 requests==2.4.1 requests_toolbelt==0.3.0 click==3.1'
|
||||||
# Minimal requirements
|
# Minimal requirements
|
||||||
|
|
||||||
#- BUILD=test DAV_SERVER=radicale RADICALE_BACKEND=filesystem REQUIREMENTS=devel
|
#- BUILD=test DAV_SERVER=radicale RADICALE_BACKEND=filesystem REQUIREMENTS=devel
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ Version 0.4.4
|
||||||
|
|
||||||
- Support for client certificates via the new ``auth_cert``
|
- Support for client certificates via the new ``auth_cert``
|
||||||
parameter, see :gh:`182` and :ghpr:`183`.
|
parameter, see :gh:`182` and :ghpr:`183`.
|
||||||
|
- The ``icalendar`` package is now not a dependency anymore.
|
||||||
|
|
||||||
Version 0.4.3
|
Version 0.4.3
|
||||||
=============
|
=============
|
||||||
|
|
|
||||||
1
setup.py
1
setup.py
|
|
@ -36,7 +36,6 @@ setup(
|
||||||
'click>=3.1',
|
'click>=3.1',
|
||||||
'requests',
|
'requests',
|
||||||
'lxml>=3.0',
|
'lxml>=3.0',
|
||||||
'icalendar>=3.6',
|
|
||||||
# https://github.com/sigmavirus24/requests-toolbelt/pull/28
|
# https://github.com/sigmavirus24/requests-toolbelt/pull/28
|
||||||
'requests_toolbelt>=0.3.0',
|
'requests_toolbelt>=0.3.0',
|
||||||
'atomicwrites'
|
'atomicwrites'
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,6 @@
|
||||||
|
|
||||||
from textwrap import dedent
|
from textwrap import dedent
|
||||||
|
|
||||||
import icalendar
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
import vdirsyncer.utils.vobject as vobject
|
import vdirsyncer.utils.vobject as vobject
|
||||||
|
|
@ -30,7 +28,6 @@ def test_split_collection_simple(benchmark):
|
||||||
assert [normalize_item(item) for item in given] == \
|
assert [normalize_item(item) for item in given] == \
|
||||||
[normalize_item(item) for item in _simple_split]
|
[normalize_item(item) for item in _simple_split]
|
||||||
|
|
||||||
if vobject.ICALENDAR_ORIGINAL_ORDER_SUPPORT:
|
|
||||||
assert [x.splitlines() for x in given] == \
|
assert [x.splitlines() for x in given] == \
|
||||||
[x.splitlines() for x in _simple_split]
|
[x.splitlines() for x in _simple_split]
|
||||||
|
|
||||||
|
|
@ -47,7 +44,6 @@ def test_split_collection_multiple_wrappers(benchmark):
|
||||||
assert [normalize_item(item) for item in given] == \
|
assert [normalize_item(item) for item in given] == \
|
||||||
[normalize_item(item) for item in _simple_split]
|
[normalize_item(item) for item in _simple_split]
|
||||||
|
|
||||||
if vobject.ICALENDAR_ORIGINAL_ORDER_SUPPORT:
|
|
||||||
assert [x.splitlines() for x in given] == \
|
assert [x.splitlines() for x in given] == \
|
||||||
[x.splitlines() for x in _simple_split]
|
[x.splitlines() for x in _simple_split]
|
||||||
|
|
||||||
|
|
@ -70,7 +66,6 @@ def test_split_collection_different_wrappers():
|
||||||
def test_join_collection_simple(benchmark):
|
def test_join_collection_simple(benchmark):
|
||||||
given = benchmark(lambda: vobject.join_collection(_simple_split))
|
given = benchmark(lambda: vobject.join_collection(_simple_split))
|
||||||
assert normalize_item(given) == normalize_item(_simple_joined)
|
assert normalize_item(given) == normalize_item(_simple_joined)
|
||||||
if vobject.ICALENDAR_ORIGINAL_ORDER_SUPPORT:
|
|
||||||
assert given.splitlines() == _simple_joined.splitlines()
|
assert given.splitlines() == _simple_joined.splitlines()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -201,46 +196,3 @@ def test_multiline_uid_complex():
|
||||||
assert vobject.Item(a).uid == (u'040000008200E00074C5B7101A82E008000000005'
|
assert vobject.Item(a).uid == (u'040000008200E00074C5B7101A82E008000000005'
|
||||||
u'0AAABEEF50DCF001000000062548482FA830A46B9'
|
u'0AAABEEF50DCF001000000062548482FA830A46B9'
|
||||||
u'EA62114AC9F0EF')
|
u'EA62114AC9F0EF')
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.xfail(icalendar.parser.NAME.findall('FOO.BAR') != ['FOO.BAR'],
|
|
||||||
reason=('version of icalendar doesn\'t support dots in '
|
|
||||||
'property names'))
|
|
||||||
def test_vcard_property_groups():
|
|
||||||
vcard = dedent(u'''
|
|
||||||
BEGIN:VCARD
|
|
||||||
VERSION:3.0
|
|
||||||
MYLABEL123.ADR:;;This is the Address 08; Some City;;12345;Germany
|
|
||||||
MYLABEL123.X-ABLABEL:
|
|
||||||
FN:Some Name
|
|
||||||
N:Name;Some;;;Nickname
|
|
||||||
UID:67c15e43-34d2-4f55-a6c6-4adb7aa7e3b2
|
|
||||||
END:VCARD
|
|
||||||
''').strip()
|
|
||||||
|
|
||||||
book = u'BEGIN:VADDRESSBOOK\n' + vcard + u'\nEND:VADDRESSBOOK'
|
|
||||||
splitted = list(vobject.split_collection(book))
|
|
||||||
assert len(splitted) == 1
|
|
||||||
|
|
||||||
assert vobject.Item(vcard).hash == vobject.Item(splitted[0]).hash
|
|
||||||
assert 'is the Address' in vobject.Item(vcard).parsed['MYLABEL123.ADR']
|
|
||||||
|
|
||||||
|
|
||||||
def test_vcard_semicolons_in_values():
|
|
||||||
# If this test fails because proper vCard support was added to icalendar,
|
|
||||||
# we can remove some ugly postprocessing code in to_unicode_lines.
|
|
||||||
|
|
||||||
vcard = dedent(u'''
|
|
||||||
BEGIN:VCARD
|
|
||||||
VERSION:3.0
|
|
||||||
ADR:;;Address 08;City;;12345;Germany
|
|
||||||
END:VCARD
|
|
||||||
''').strip()
|
|
||||||
|
|
||||||
# Assert that icalendar breaks vcard properties with semicolons in values
|
|
||||||
assert b'ADR:\\;\\;Address 08\\;City\\;\\;12345\\;Germany' in \
|
|
||||||
vobject.Item(vcard).parsed.to_ical().splitlines()
|
|
||||||
|
|
||||||
# Assert that vdirsyncer fixes these properties
|
|
||||||
assert u'ADR:;;Address 08;City;;12345;Germany' in \
|
|
||||||
list(vobject.to_unicode_lines(vobject.Item(vcard).parsed))
|
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ from .utils import CliError, JobFailed, cli_logger, collections_for_pair, \
|
||||||
storage_class_from_config, storage_instance_from_config
|
storage_class_from_config, storage_instance_from_config
|
||||||
|
|
||||||
from ..sync import sync
|
from ..sync import sync
|
||||||
from ..utils.vobject import Item, to_unicode_lines
|
from ..utils.vobject import Item
|
||||||
|
|
||||||
|
|
||||||
def sync_pair(wq, pair_name, collections_to_sync, general, all_pairs,
|
def sync_pair(wq, pair_name, collections_to_sync, general, all_pairs,
|
||||||
|
|
@ -145,7 +145,7 @@ def _repair_collection(storage):
|
||||||
else:
|
else:
|
||||||
stack.extend(component.subcomponents)
|
stack.extend(component.subcomponents)
|
||||||
|
|
||||||
new_item = Item(u'\n'.join(to_unicode_lines(parsed)))
|
new_item = Item(u'\r\n'.join(parsed.dump_lines()))
|
||||||
assert new_item.uid
|
assert new_item.uid
|
||||||
seen_uids.add(new_item.uid)
|
seen_uids.add(new_item.uid)
|
||||||
if changed:
|
if changed:
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,6 @@
|
||||||
import hashlib
|
import hashlib
|
||||||
from itertools import chain, tee
|
from itertools import chain, tee
|
||||||
|
|
||||||
import icalendar.cal
|
|
||||||
import icalendar.caselessdict
|
|
||||||
import icalendar.parser
|
|
||||||
|
|
||||||
from . import cached_property, split_sequence, uniq
|
from . import cached_property, split_sequence, uniq
|
||||||
from .compat import text_type
|
from .compat import text_type
|
||||||
|
|
||||||
|
|
@ -34,20 +30,6 @@ IGNORE_PROPS = _process_properties(
|
||||||
del _process_properties
|
del _process_properties
|
||||||
|
|
||||||
|
|
||||||
# Whether the installed icalendar version has
|
|
||||||
# https://github.com/collective/icalendar/pull/136
|
|
||||||
# (support for keeping the order of properties and parameters)
|
|
||||||
#
|
|
||||||
# This basically checks whether the superclass of all icalendar classes has a
|
|
||||||
# method from OrderedDict.
|
|
||||||
try:
|
|
||||||
reversed(icalendar.caselessdict.CaselessDict())
|
|
||||||
except TypeError:
|
|
||||||
ICALENDAR_ORIGINAL_ORDER_SUPPORT = False
|
|
||||||
else:
|
|
||||||
ICALENDAR_ORIGINAL_ORDER_SUPPORT = True
|
|
||||||
|
|
||||||
|
|
||||||
class Item(object):
|
class Item(object):
|
||||||
|
|
||||||
'''Immutable wrapper class for VCALENDAR (VEVENT, VTODO) and
|
'''Immutable wrapper class for VCALENDAR (VEVENT, VTODO) and
|
||||||
|
|
@ -107,7 +89,7 @@ class Item(object):
|
||||||
@cached_property
|
@cached_property
|
||||||
def parsed(self):
|
def parsed(self):
|
||||||
try:
|
try:
|
||||||
return icalendar.cal.Component.from_ical(self.raw)
|
return _Component.parse(self.raw)
|
||||||
except Exception:
|
except Exception:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
@ -130,7 +112,7 @@ def split_collection(text, inline=(u'VTIMEZONE',),
|
||||||
wrap_items_with=(u'VCALENDAR',)):
|
wrap_items_with=(u'VCALENDAR',)):
|
||||||
'''Emits items in the order they occur in the text.'''
|
'''Emits items in the order they occur in the text.'''
|
||||||
assert isinstance(text, text_type)
|
assert isinstance(text, text_type)
|
||||||
collections = icalendar.cal.Component.from_ical(text, multiple=True)
|
collections = _Component.parse(text, multiple=True)
|
||||||
collection_name = None
|
collection_name = None
|
||||||
|
|
||||||
for collection in collections:
|
for collection in collections:
|
||||||
|
|
@ -150,32 +132,14 @@ def split_collection(text, inline=(u'VTIMEZONE',),
|
||||||
collection.subcomponents,
|
collection.subcomponents,
|
||||||
lambda item: item.name in inline
|
lambda item: item.name in inline
|
||||||
)
|
)
|
||||||
inlined_lines = list(chain(*(to_unicode_lines(inlined_item)
|
inlined_lines = list(chain(*(inlined_item.dump_lines()
|
||||||
for inlined_item in inlined_items)))
|
for inlined_item in inlined_items)))
|
||||||
|
|
||||||
for item in normal_items:
|
for item in normal_items:
|
||||||
lines = chain(start, inlined_lines, to_unicode_lines(item), end)
|
lines = chain(start, inlined_lines, item.dump_lines(), end)
|
||||||
yield u''.join(line + u'\r\n' for line in lines if line)
|
yield u''.join(line + u'\r\n' for line in lines if line)
|
||||||
|
|
||||||
|
|
||||||
def to_unicode_lines(item):
|
|
||||||
'''icalendar doesn't provide an efficient way of getting the ical data as
|
|
||||||
unicode. So let's do it ourselves.'''
|
|
||||||
|
|
||||||
if ICALENDAR_ORIGINAL_ORDER_SUPPORT:
|
|
||||||
content_lines = item.content_lines(sorted=False)
|
|
||||||
else:
|
|
||||||
content_lines = item.content_lines()
|
|
||||||
|
|
||||||
for content_line in content_lines:
|
|
||||||
if content_line:
|
|
||||||
# https://github.com/untitaker/vdirsyncer/issues/70
|
|
||||||
# XXX: icalendar escapes semicolons which are not supposed to get
|
|
||||||
# escaped, because it is not aware of vcard
|
|
||||||
content_line = content_line.replace(u'\\;', u';')
|
|
||||||
yield icalendar.parser.foldline(content_line)
|
|
||||||
|
|
||||||
|
|
||||||
_default_join_wrappers = {
|
_default_join_wrappers = {
|
||||||
u'VCALENDAR': u'VCALENDAR',
|
u'VCALENDAR': u'VCALENDAR',
|
||||||
u'VEVENT': u'VCALENDAR',
|
u'VEVENT': u'VCALENDAR',
|
||||||
|
|
@ -191,7 +155,7 @@ def join_collection(items, wrappers=_default_join_wrappers):
|
||||||
}
|
}
|
||||||
'''
|
'''
|
||||||
|
|
||||||
items1, items2 = tee((icalendar.cal.Component.from_ical(x)
|
items1, items2 = tee((_Component.parse(x)
|
||||||
for x in items), 2)
|
for x in items), 2)
|
||||||
item_type, wrapper_type = _get_item_type(items1, wrappers)
|
item_type, wrapper_type = _get_item_type(items1, wrappers)
|
||||||
|
|
||||||
|
|
@ -199,7 +163,7 @@ def join_collection(items, wrappers=_default_join_wrappers):
|
||||||
return x.name == wrapper_type and x.subcomponents or [x]
|
return x.name == wrapper_type and x.subcomponents or [x]
|
||||||
|
|
||||||
components = chain(*(_get_item_components(x) for x in items2))
|
components = chain(*(_get_item_components(x) for x in items2))
|
||||||
lines = chain(*uniq(tuple(to_unicode_lines(x)) for x in components))
|
lines = chain(*uniq(tuple(x.dump_lines()) for x in components))
|
||||||
|
|
||||||
if wrapper_type is not None:
|
if wrapper_type is not None:
|
||||||
start = [u'BEGIN:{}'.format(wrapper_type)]
|
start = [u'BEGIN:{}'.format(wrapper_type)]
|
||||||
|
|
@ -218,3 +182,68 @@ def _get_item_type(components, wrappers):
|
||||||
else:
|
else:
|
||||||
return item_type, wrapper_type
|
return item_type, wrapper_type
|
||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
|
|
||||||
|
class _Component(object):
|
||||||
|
'''
|
||||||
|
Raw outline of the components.
|
||||||
|
|
||||||
|
Barely parsing ``BEGIN`` and ``END`` lines, but not any other properties.
|
||||||
|
This gives us better performance and more tolerance towards slightly broken
|
||||||
|
items.
|
||||||
|
|
||||||
|
Original version from https://github.com/collective/icalendar/, but apart
|
||||||
|
from the similar API, very few parts have been reused.
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self, name, lines, subcomponents):
|
||||||
|
'''
|
||||||
|
:param name: The component name.
|
||||||
|
:param lines: The component's own properties, as list of lines
|
||||||
|
(strings).
|
||||||
|
:param subcomponents: List of components.
|
||||||
|
'''
|
||||||
|
self.name = name
|
||||||
|
self.lines = lines
|
||||||
|
self.subcomponents = subcomponents
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def parse(cls, lines, multiple=False):
|
||||||
|
if isinstance(lines, bytes):
|
||||||
|
lines = lines.decode('utf-8')
|
||||||
|
if isinstance(lines, text_type):
|
||||||
|
lines = lines.splitlines()
|
||||||
|
|
||||||
|
stack = []
|
||||||
|
rv = []
|
||||||
|
for line in lines:
|
||||||
|
if line.startswith(u'BEGIN:'):
|
||||||
|
c_name = line[len(u'BEGIN:'):].strip().upper()
|
||||||
|
stack.append(cls(c_name, [], []))
|
||||||
|
elif line.startswith(u'END:'):
|
||||||
|
component = stack.pop()
|
||||||
|
if stack:
|
||||||
|
stack[-1].subcomponents.append(component)
|
||||||
|
else:
|
||||||
|
rv.append(component)
|
||||||
|
else:
|
||||||
|
line = line.strip()
|
||||||
|
if line:
|
||||||
|
stack[-1].lines.append(line)
|
||||||
|
|
||||||
|
if multiple:
|
||||||
|
return rv
|
||||||
|
elif len(rv) != 1:
|
||||||
|
raise ValueError('Found {} components, expected one.'
|
||||||
|
.format(len(rv)))
|
||||||
|
else:
|
||||||
|
return rv[0]
|
||||||
|
|
||||||
|
def dump_lines(self):
|
||||||
|
yield u'BEGIN:{}'.format(self.name)
|
||||||
|
for line in self.lines:
|
||||||
|
yield line
|
||||||
|
for c in self.subcomponents:
|
||||||
|
for line in c.dump_lines():
|
||||||
|
yield line
|
||||||
|
yield u'END:{}'.format(self.name)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue