mirror of
https://github.com/samsonjs/vdirsyncer.git
synced 2026-03-25 08:55:50 +00:00
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:
parent
566a988f32
commit
08c07c4be4
7 changed files with 76 additions and 39 deletions
|
|
@ -2,6 +2,18 @@
|
||||||
Configuration
|
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:
|
.. _general_config:
|
||||||
|
|
||||||
|
|
@ -46,11 +58,11 @@ Pair Section
|
||||||
already directly pointing to one collection each. Specifying a collection
|
already directly pointing to one collection each. Specifying a collection
|
||||||
multiple times won't make vdirsyncer sync that collection more than once.
|
multiple times won't make vdirsyncer sync that collection more than once.
|
||||||
|
|
||||||
Furthermore, there are the special values ``from a`` and ``from b``, which
|
Furthermore, there are the special values ``"from a"`` and ``"from b"``,
|
||||||
tell vdirsyncer to try autodiscovery on a specific storage::
|
which tell vdirsyncer to try autodiscovery on a specific storage::
|
||||||
|
|
||||||
collections = from b,foo,bar # all in storage b + "foo" + "bar"
|
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", from a"] # all in storage a + all in storage b
|
||||||
|
|
||||||
- ``conflict_resolution``: Optional, define how conflicts should be handled. A
|
- ``conflict_resolution``: Optional, define how conflicts should be handled. A
|
||||||
conflict occurs when one item (event, task) changed on both sides since the
|
conflict occurs when one item (event, task) changed on both sides since the
|
||||||
|
|
@ -58,9 +70,9 @@ Pair Section
|
||||||
|
|
||||||
Valid values are:
|
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.
|
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:
|
.. _storage_config:
|
||||||
|
|
||||||
|
|
@ -75,8 +87,8 @@ Storage Section
|
||||||
- ``type`` defines which kind of storage is defined. See :ref:`storages`.
|
- ``type`` defines which kind of storage is defined. See :ref:`storages`.
|
||||||
|
|
||||||
- ``read_only`` defines whether the storage should be regarded as a read-only
|
- ``read_only`` defines whether the storage should be regarded as a read-only
|
||||||
storage. The value ``True`` means synchronization will discard any changes
|
storage. The value ``true`` means synchronization will discard any changes
|
||||||
made to the other side. The value ``False`` implies normal 2-way
|
made to the other side. The value ``false`` implies normal 2-way
|
||||||
synchronization.
|
synchronization.
|
||||||
|
|
||||||
- Any further parameters are passed on to the storage class.
|
- 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
|
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.
|
wished.
|
||||||
|
|
||||||
.. autoclass:: CaldavStorage
|
.. autoclass:: CaldavStorage
|
||||||
|
|
@ -107,7 +119,7 @@ Read-only storages
|
||||||
~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
These storages don't support writing of their items, consequently ``read_only``
|
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.
|
leads to an error.
|
||||||
|
|
||||||
.. autoclass:: HttpStorage
|
.. autoclass:: HttpStorage
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,7 @@ is very tedious to do. Instead we will use a shortcut:
|
||||||
|
|
||||||
[pair my_contacts]
|
[pair my_contacts]
|
||||||
...
|
...
|
||||||
collections = default,work
|
collections = ["default", "work"]
|
||||||
|
|
||||||
This will synchronize
|
This will synchronize
|
||||||
``https://owncloud.example.com/remote.php/carddav/addressbooks/bob/default/``
|
``https://owncloud.example.com/remote.php/carddav/addressbooks/bob/default/``
|
||||||
|
|
|
||||||
|
|
@ -29,13 +29,13 @@ b = bob_contacts_remote
|
||||||
# Omitting this parameter implies that the given path and URL in the
|
# Omitting this parameter implies that the given path and URL in the
|
||||||
# corresponding `[storage <name>]` blocks are already pointing to a
|
# corresponding `[storage <name>]` blocks are already pointing to a
|
||||||
# collection each.
|
# collection each.
|
||||||
collections = default,work
|
collections = ["default", "work"]
|
||||||
|
|
||||||
# To resolve a conflict the following values are possible:
|
# To resolve a conflict the following values are possible:
|
||||||
# `None` - abort when collisions occur (default)
|
# `None` - abort when collisions occur (default)
|
||||||
# `a wins` - assume a's items to be more up-to-date
|
# `a wins` - assume a's items to be more up-to-date
|
||||||
# `b wins` - assume b'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]
|
[storage bob_contacts_local]
|
||||||
# A storage references actual data on a remote server or on the local disk.
|
# 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]
|
[pair bob_calendar]
|
||||||
a = bob_calendar_local
|
a = bob_calendar_local
|
||||||
b = bob_calendar_remote
|
b = bob_calendar_remote
|
||||||
collections = private,work
|
collections = ["private", "work"]
|
||||||
|
|
||||||
[storage bob_calendar_local]
|
[storage bob_calendar_local]
|
||||||
type = filesystem
|
type = filesystem
|
||||||
|
|
|
||||||
|
|
@ -201,7 +201,7 @@ def test_wrong_general_section(tmpdir):
|
||||||
config_file = tmpdir.join('config')
|
config_file = tmpdir.join('config')
|
||||||
config_file.write(dedent('''
|
config_file.write(dedent('''
|
||||||
[general]
|
[general]
|
||||||
wrong = yes
|
wrong = true
|
||||||
'''))
|
'''))
|
||||||
|
|
||||||
runner = CliRunner()
|
runner = CliRunner()
|
||||||
|
|
|
||||||
|
|
@ -213,8 +213,16 @@ def parse_pairs_args(pairs_args, all_pairs):
|
||||||
.format(pair, list(all_pairs)))
|
.format(pair, list(all_pairs)))
|
||||||
|
|
||||||
if collection is None:
|
if collection is None:
|
||||||
collections = [x.strip() or None for x in
|
collections = pair_options.get('collections', [None])
|
||||||
pair_options.get('collections', '').split(',')]
|
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:
|
else:
|
||||||
collections = [collection]
|
collections = [collection]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -484,11 +484,19 @@ class CaldavStorage(DavStorage):
|
||||||
get_multi_data_query = '{urn:ietf:params:xml:ns:caldav}calendar-data'
|
get_multi_data_query = '{urn:ietf:params:xml:ns:caldav}calendar-data'
|
||||||
|
|
||||||
def __init__(self, start_date=None, end_date=None,
|
def __init__(self, start_date=None, end_date=None,
|
||||||
item_types='VTODO, VEVENT', **kwargs):
|
item_types=('VTODO', 'VEVENT'), **kwargs):
|
||||||
super(CaldavStorage, self).__init__(**kwargs)
|
super(CaldavStorage, self).__init__(**kwargs)
|
||||||
if isinstance(item_types, str):
|
if isinstance(item_types, str):
|
||||||
item_types = filter(bool,
|
orig_item_types = item_types
|
||||||
(x.strip() for x in item_types.split(',')))
|
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)
|
self.item_types = tuple(item_types)
|
||||||
if (start_date is None) != (end_date is None):
|
if (start_date is None) != (end_date is None):
|
||||||
raise ValueError('If start_date is given, '
|
raise ValueError('If start_date is given, '
|
||||||
|
|
|
||||||
|
|
@ -7,13 +7,14 @@
|
||||||
:license: MIT, see LICENSE for more details.
|
:license: MIT, see LICENSE for more details.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
import json
|
||||||
import os
|
import os
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
from requests.packages.urllib3.poolmanager import PoolManager
|
from requests.packages.urllib3.poolmanager import PoolManager
|
||||||
|
|
||||||
from .compat import iteritems, urlparse
|
from .compat import iteritems, text_type, urlparse
|
||||||
from .. import exceptions, log
|
from .. import exceptions, log
|
||||||
from ..doubleclick import click, ctx
|
from ..doubleclick import click, ctx
|
||||||
|
|
||||||
|
|
@ -57,28 +58,36 @@ def split_sequence(s, f):
|
||||||
|
|
||||||
|
|
||||||
def parse_config_value(value):
|
def parse_config_value(value):
|
||||||
if len(value.splitlines()) > 1:
|
if value in ('on', 'yes'):
|
||||||
# The reason we use comma-separated values instead of
|
logger.warning('{} is deprecated for the config, please use true.\n'
|
||||||
# multiline-values for lists is simple: ConfigParser's barrier for
|
'The old form will be removed in 0.4.0.'
|
||||||
# mistaking an arbitrary line for the continuation of a value is
|
.format(value))
|
||||||
# awfully low. The following example will also contain the second
|
return True
|
||||||
# line in the value:
|
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
|
# foo = bar
|
||||||
# # my comment
|
# # 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 rv
|
||||||
return True
|
|
||||||
elif value.lower() in ('no', 'false', 'off'):
|
|
||||||
return False
|
|
||||||
|
|
||||||
try:
|
|
||||||
return int(value)
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
return value
|
|
||||||
|
|
||||||
|
|
||||||
def parse_options(items, section=None):
|
def parse_options(items, section=None):
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue