New config format

See #141

Basically this tries to parse config values with JSON, if that fails,
the value is interpreted as string.

I'd greatly appreciate feedback on this and #141
This commit is contained in:
Markus Unterwaditzer 2014-12-02 19:09:16 +01:00
parent 566a988f32
commit 08c07c4be4
7 changed files with 76 additions and 39 deletions

View file

@ -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

View file

@ -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/``

View file

@ -29,13 +29,13 @@ b = bob_contacts_remote
# Omitting this parameter implies that the given path and URL in the
# corresponding `[storage <name>]` 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

View file

@ -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()

View file

@ -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]

View file

@ -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, '

View file

@ -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):