diff --git a/docs/config.rst b/docs/config.rst index c41e05d..a54652e 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -2,6 +2,18 @@ Configuration ============= +Vdirsyncer uses an ini-like format for storing its configuration. All values +are JSON, invalid JSON will get interpreted as string:: + + "foo" + foo # Same as "foo" + 42 + ["a", "b", "c"] + [a, b, c] # This doesn't work though! + true + false + null # Also known as None + .. _general_config: @@ -46,11 +58,11 @@ Pair Section already directly pointing to one collection each. Specifying a collection multiple times won't make vdirsyncer sync that collection more than once. - Furthermore, there are the special values ``from a`` and ``from b``, which - tell vdirsyncer to try autodiscovery on a specific storage:: + Furthermore, there are the special values ``"from a"`` and ``"from b"``, + which tell vdirsyncer to try autodiscovery on a specific storage:: - collections = from b,foo,bar # all in storage b + "foo" + "bar" - collections = from b,from a # all in storage a + all in storage b + collections = ["from b", "foo", "bar"] # all in storage b + "foo" + "bar" + collections = ["from b", from a"] # all in storage a + all in storage b - ``conflict_resolution``: Optional, define how conflicts should be handled. A conflict occurs when one item (event, task) changed on both sides since the @@ -58,9 +70,9 @@ Pair Section Valid values are: - - ``a wins`` and ``b wins``, where the whole item is taken from one side. + - ``"a wins"`` and ``"b wins"``, where the whole item is taken from one side. Vdirsyncer will not attempt to merge the two items. - - ``None``, the default, where an error is shown and no changes are done. + - ``null``, the default, where an error is shown and no changes are done. .. _storage_config: @@ -75,8 +87,8 @@ Storage Section - ``type`` defines which kind of storage is defined. See :ref:`storages`. - ``read_only`` defines whether the storage should be regarded as a read-only - storage. The value ``True`` means synchronization will discard any changes - made to the other side. The value ``False`` implies normal 2-way + storage. The value ``true`` means synchronization will discard any changes + made to the other side. The value ``false`` implies normal 2-way synchronization. - Any further parameters are passed on to the storage class. @@ -92,7 +104,7 @@ Read-write storages ~~~~~~~~~~~~~~~~~~~ These storages generally support reading and changing of their items. Their -default value for ``read_only`` is ``False``, but can be set to ``True`` if +default value for ``read_only`` is ``false``, but can be set to ``true`` if wished. .. autoclass:: CaldavStorage @@ -107,7 +119,7 @@ Read-only storages ~~~~~~~~~~~~~~~~~~ These storages don't support writing of their items, consequently ``read_only`` -is set to ``True`` by default. Changing ``read_only`` to ``False`` on them +is set to ``true`` by default. Changing ``read_only`` to ``false`` on them leads to an error. .. autoclass:: HttpStorage diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 24e6bbd..584feb5 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -97,7 +97,7 @@ is very tedious to do. Instead we will use a shortcut: [pair my_contacts] ... - collections = default,work + collections = ["default", "work"] This will synchronize ``https://owncloud.example.com/remote.php/carddav/addressbooks/bob/default/`` diff --git a/example.cfg b/example.cfg index e957d30..2991aaf 100644 --- a/example.cfg +++ b/example.cfg @@ -29,13 +29,13 @@ b = bob_contacts_remote # Omitting this parameter implies that the given path and URL in the # corresponding `[storage ]` blocks are already pointing to a # collection each. -collections = default,work +collections = ["default", "work"] # To resolve a conflict the following values are possible: # `None` - abort when collisions occur (default) # `a wins` - assume a's items to be more up-to-date # `b wins` - assume b's items to be more up-to-date -#conflict_resolution = None +#conflict_resolution = null [storage bob_contacts_local] # A storage references actual data on a remote server or on the local disk. @@ -63,7 +63,7 @@ url = https://owncloud.example.com/remote.php/carddav/addressbooks/bob/ [pair bob_calendar] a = bob_calendar_local b = bob_calendar_remote -collections = private,work +collections = ["private", "work"] [storage bob_calendar_local] type = filesystem diff --git a/tests/test_cli.py b/tests/test_cli.py index a50a2f2..3b0deaa 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -201,7 +201,7 @@ def test_wrong_general_section(tmpdir): config_file = tmpdir.join('config') config_file.write(dedent(''' [general] - wrong = yes + wrong = true ''')) runner = CliRunner() diff --git a/vdirsyncer/cli.py b/vdirsyncer/cli.py index 2928f6b..2847317 100644 --- a/vdirsyncer/cli.py +++ b/vdirsyncer/cli.py @@ -213,8 +213,16 @@ def parse_pairs_args(pairs_args, all_pairs): .format(pair, list(all_pairs))) if collection is None: - collections = [x.strip() or None for x in - pair_options.get('collections', '').split(',')] + collections = pair_options.get('collections', [None]) + if isinstance(collections, str): + # XXX: Deprecation + orig_collections = collections + collections = [x.strip() or None + for x in collections.split(',')] + cli_logger.warning( + '{!r} is deprecated, please use:\ncollections = {}\n' + 'The old form will be removed in 0.4.0.' + .format(orig_collections, json.dumps(collections))) else: collections = [collection] diff --git a/vdirsyncer/storage/dav.py b/vdirsyncer/storage/dav.py index adc9ca6..7ac7c6a 100644 --- a/vdirsyncer/storage/dav.py +++ b/vdirsyncer/storage/dav.py @@ -484,11 +484,19 @@ class CaldavStorage(DavStorage): get_multi_data_query = '{urn:ietf:params:xml:ns:caldav}calendar-data' def __init__(self, start_date=None, end_date=None, - item_types='VTODO, VEVENT', **kwargs): + item_types=('VTODO', 'VEVENT'), **kwargs): super(CaldavStorage, self).__init__(**kwargs) if isinstance(item_types, str): - item_types = filter(bool, - (x.strip() for x in item_types.split(','))) + orig_item_types = item_types + item_types = [x.strip() for x in item_types.split(',')] + + # XXX: Deprecation + import json + dav_logger.warning( + '{!r} is deprecated, please use:\nitem_types = {}' + 'The old form will be removed in 0.4.0.' + .format(orig_item_types, json.dumps(item_types))) + self.item_types = tuple(item_types) if (start_date is None) != (end_date is None): raise ValueError('If start_date is given, ' diff --git a/vdirsyncer/utils/__init__.py b/vdirsyncer/utils/__init__.py index 9b1e123..07cdc64 100644 --- a/vdirsyncer/utils/__init__.py +++ b/vdirsyncer/utils/__init__.py @@ -7,13 +7,14 @@ :license: MIT, see LICENSE for more details. ''' +import json import os import threading import requests from requests.packages.urllib3.poolmanager import PoolManager -from .compat import iteritems, urlparse +from .compat import iteritems, text_type, urlparse from .. import exceptions, log from ..doubleclick import click, ctx @@ -57,28 +58,36 @@ def split_sequence(s, f): def parse_config_value(value): - if len(value.splitlines()) > 1: - # The reason we use comma-separated values instead of - # multiline-values for lists is simple: ConfigParser's barrier for - # mistaking an arbitrary line for the continuation of a value is - # awfully low. The following example will also contain the second - # line in the value: + if value in ('on', 'yes'): + logger.warning('{} is deprecated for the config, please use true.\n' + 'The old form will be removed in 0.4.0.' + .format(value)) + return True + if value in ('off', 'no'): + logger.warning('{} is deprecated for the config, please use false.\n' + 'The old form will be removed in 0.4.0.' + .format(value)) + return False + if value == 'None': + logger.warning('None is deprecated for the config, please use null.\n' + 'The old form will be removed in 0.4.0.') + return None + + try: + rv = json.loads(value) + except ValueError: + rv = value + + if isinstance(rv, (bytes, text_type)) and len(value.splitlines()) > 1: + # ConfigParser's barrier for mistaking an arbitrary line for the + # continuation of a value is awfully low. The following example will + # also contain the second line in the value: # # foo = bar # # my comment - raise ValueError('No multiline-values allowed.') + raise ValueError('No multiline-values allowed:\n{!r}'.format(value)) - if value.lower() in ('yes', 'true', 'on'): - return True - elif value.lower() in ('no', 'false', 'off'): - return False - - try: - return int(value) - except ValueError: - pass - - return value + return rv def parse_options(items, section=None):