mirror of
https://github.com/samsonjs/vdirsyncer.git
synced 2026-03-25 08:55:50 +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)
|
||||
|
||||
- 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
|
||||
|
||||
#- 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``
|
||||
parameter, see :gh:`182` and :ghpr:`183`.
|
||||
- The ``icalendar`` package is now not a dependency anymore.
|
||||
|
||||
Version 0.4.3
|
||||
=============
|
||||
|
|
|
|||
1
setup.py
1
setup.py
|
|
@ -36,7 +36,6 @@ setup(
|
|||
'click>=3.1',
|
||||
'requests',
|
||||
'lxml>=3.0',
|
||||
'icalendar>=3.6',
|
||||
# https://github.com/sigmavirus24/requests-toolbelt/pull/28
|
||||
'requests_toolbelt>=0.3.0',
|
||||
'atomicwrites'
|
||||
|
|
|
|||
|
|
@ -2,8 +2,6 @@
|
|||
|
||||
from textwrap import dedent
|
||||
|
||||
import icalendar
|
||||
|
||||
import pytest
|
||||
|
||||
import vdirsyncer.utils.vobject as vobject
|
||||
|
|
@ -30,9 +28,8 @@ def test_split_collection_simple(benchmark):
|
|||
assert [normalize_item(item) for item in given] == \
|
||||
[normalize_item(item) for item in _simple_split]
|
||||
|
||||
if vobject.ICALENDAR_ORIGINAL_ORDER_SUPPORT:
|
||||
assert [x.splitlines() for x in given] == \
|
||||
[x.splitlines() for x in _simple_split]
|
||||
assert [x.splitlines() for x in given] == \
|
||||
[x.splitlines() for x in _simple_split]
|
||||
|
||||
|
||||
def test_split_collection_multiple_wrappers(benchmark):
|
||||
|
|
@ -47,9 +44,8 @@ def test_split_collection_multiple_wrappers(benchmark):
|
|||
assert [normalize_item(item) for item in given] == \
|
||||
[normalize_item(item) for item in _simple_split]
|
||||
|
||||
if vobject.ICALENDAR_ORIGINAL_ORDER_SUPPORT:
|
||||
assert [x.splitlines() for x in given] == \
|
||||
[x.splitlines() for x in _simple_split]
|
||||
assert [x.splitlines() for x in given] == \
|
||||
[x.splitlines() for x in _simple_split]
|
||||
|
||||
|
||||
def test_split_collection_different_wrappers():
|
||||
|
|
@ -70,8 +66,7 @@ def test_split_collection_different_wrappers():
|
|||
def test_join_collection_simple(benchmark):
|
||||
given = benchmark(lambda: vobject.join_collection(_simple_split))
|
||||
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()
|
||||
|
||||
|
||||
def test_join_collection_vevents(benchmark):
|
||||
|
|
@ -201,46 +196,3 @@ def test_multiline_uid_complex():
|
|||
assert vobject.Item(a).uid == (u'040000008200E00074C5B7101A82E008000000005'
|
||||
u'0AAABEEF50DCF001000000062548482FA830A46B9'
|
||||
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
|
||||
|
||||
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,
|
||||
|
|
@ -145,7 +145,7 @@ def _repair_collection(storage):
|
|||
else:
|
||||
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
|
||||
seen_uids.add(new_item.uid)
|
||||
if changed:
|
||||
|
|
|
|||
|
|
@ -3,10 +3,6 @@
|
|||
import hashlib
|
||||
from itertools import chain, tee
|
||||
|
||||
import icalendar.cal
|
||||
import icalendar.caselessdict
|
||||
import icalendar.parser
|
||||
|
||||
from . import cached_property, split_sequence, uniq
|
||||
from .compat import text_type
|
||||
|
||||
|
|
@ -34,20 +30,6 @@ IGNORE_PROPS = _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):
|
||||
|
||||
'''Immutable wrapper class for VCALENDAR (VEVENT, VTODO) and
|
||||
|
|
@ -107,7 +89,7 @@ class Item(object):
|
|||
@cached_property
|
||||
def parsed(self):
|
||||
try:
|
||||
return icalendar.cal.Component.from_ical(self.raw)
|
||||
return _Component.parse(self.raw)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
|
@ -130,7 +112,7 @@ def split_collection(text, inline=(u'VTIMEZONE',),
|
|||
wrap_items_with=(u'VCALENDAR',)):
|
||||
'''Emits items in the order they occur in the text.'''
|
||||
assert isinstance(text, text_type)
|
||||
collections = icalendar.cal.Component.from_ical(text, multiple=True)
|
||||
collections = _Component.parse(text, multiple=True)
|
||||
collection_name = None
|
||||
|
||||
for collection in collections:
|
||||
|
|
@ -150,32 +132,14 @@ def split_collection(text, inline=(u'VTIMEZONE',),
|
|||
collection.subcomponents,
|
||||
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 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)
|
||||
|
||||
|
||||
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 = {
|
||||
u'VCALENDAR': 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)
|
||||
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]
|
||||
|
||||
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:
|
||||
start = [u'BEGIN:{}'.format(wrapper_type)]
|
||||
|
|
@ -218,3 +182,68 @@ def _get_item_type(components, wrappers):
|
|||
else:
|
||||
return item_type, wrapper_type
|
||||
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