config: Add warning about unquoted strings

This commit is contained in:
Markus Unterwaditzer 2016-10-23 00:56:26 +02:00
parent 8171c46b10
commit 5a257ec2cd
17 changed files with 281 additions and 274 deletions

View file

@ -16,6 +16,8 @@ Version 0.14.0
exit code in such situations is still non-zero. exit code in such situations is still non-zero.
- Add ``partial_sync`` option to pair section. See :ref:`the config docs - Add ``partial_sync`` option to pair section. See :ref:`the config docs
<partial_sync_def>`. <partial_sync_def>`.
- Vdirsyner will now warn if there's a string without quotes in your config.
Please file issues if you find documentation that uses unquoted strings.
Version 0.13.1 Version 0.13.1
============== ==============

View file

@ -16,8 +16,8 @@ status_path = ~/.vdirsyncer/status/
# A `[pair <name>]` block defines two storages `a` and `b` that should be # A `[pair <name>]` block defines two storages `a` and `b` that should be
# synchronized. The definition of these storages follows in `[storage <name>]` # synchronized. The definition of these storages follows in `[storage <name>]`
# blocks. This is similar to accounts in OfflineIMAP. # blocks. This is similar to accounts in OfflineIMAP.
a = bob_contacts_local a = "bob_contacts_local"
b = bob_contacts_remote b = "bob_contacts_remote"
# Synchronize all collections that can be found. # Synchronize all collections that can be found.
# You need to run `vdirsyncer discover` if new calendars/addressbooks are added # You need to run `vdirsyncer discover` if new calendars/addressbooks are added
@ -37,13 +37,13 @@ metadata = ["displayname"]
[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.
# Similar to repositories in OfflineIMAP. # Similar to repositories in OfflineIMAP.
type = filesystem type = "filesystem"
path = ~/.contacts/ path = "~/.contacts/"
fileext = .vcf fileext = ".vcf"
[storage bob_contacts_remote] [storage bob_contacts_remote]
type = carddav type = "carddav"
url = https://owncloud.example.com/remote.php/carddav/ url = "https://owncloud.example.com/remote.php/carddav/"
#username = #username =
# The password can also be fetched from the system password storage, netrc or a # The password can also be fetched from the system password storage, netrc or a
# custom command. See http://vdirsyncer.pimutils.org/en/stable/keyring.html # custom command. See http://vdirsyncer.pimutils.org/en/stable/keyring.html
@ -51,20 +51,20 @@ url = https://owncloud.example.com/remote.php/carddav/
# CALDAV # CALDAV
[pair bob_calendar] [pair bob_calendar]
a = bob_calendar_local a = "bob_calendar_local"
b = bob_calendar_remote b = "bob_calendar_remote"
collections = ["from a", "from b"] collections = ["from a", "from b"]
# Calendars also have a color property # Calendars also have a color property
metadata = ["displayname", "color"] metadata = ["displayname", "color"]
[storage bob_calendar_local] [storage bob_calendar_local]
type = filesystem type = "filesystem"
path = ~/.calendars/ path = "~/.calendars/"
fileext = .ics fileext = ".ics"
[storage bob_calendar_remote] [storage bob_calendar_remote]
type = caldav type = "caldav"
url = https://owncloud.example.com/remote.php/caldav/ url = "https://owncloud.example.com/remote.php/caldav/"
#username = #username =
#password = #password =

View file

@ -14,24 +14,24 @@ Command
Say you have the following configuration:: Say you have the following configuration::
[storage foo] [storage foo]
type = caldav type = "caldav"
url = ... url = ...
username = foo username = "foo"
password = bar password = "bar"
But it bugs you that the password is stored in cleartext in the config file. But it bugs you that the password is stored in cleartext in the config file.
You can do this:: You can do this::
[storage foo] [storage foo]
type = caldav type = "caldav"
url = ... url = ...
username = foo username = "foo"
password.fetch = ["command", "~/get-password.sh", "more", "args"] password.fetch = ["command", "~/get-password.sh", "more", "args"]
You can fetch the username as well:: You can fetch the username as well::
[storage foo] [storage foo]
type = caldav type = "caldav"
url = ... url = ...
username.fetch = ["command", "~/get-username.sh"] username.fetch = ["command", "~/get-username.sh"]
password.fetch = ["command", "~/get-password.sh"] password.fetch = ["command", "~/get-password.sh"]
@ -62,6 +62,6 @@ Password Prompt
You can also simply prompt for the password:: You can also simply prompt for the password::
[storage foo] [storage foo]
type = caldav type = "caldav"
username = myusername username = "myusername"
password.fetch = ["prompt", "Password for CalDAV"] password.fetch = ["prompt", "Password for CalDAV"]

View file

@ -25,17 +25,17 @@ Step 2: Creating the config
Paste this into your vdirsyncer config:: Paste this into your vdirsyncer config::
[pair holidays] [pair holidays]
a = holidays_public a = "holidays_public"
b = holidays_private b = "holidays_private"
collections = null collections = null
[storage holidays_public] [storage holidays_public]
type = http type = "http"
# The URL to your iCalendar file. # The URL to your iCalendar file.
url = ... url = ...
[storage holidays_private] [storage holidays_private]
type = caldav type = "caldav"
# The direct URL to your calendar. # The direct URL to your calendar.
url = ... url = ...
# The credentials to your CalDAV server # The credentials to your CalDAV server
@ -60,8 +60,8 @@ doesn't have the rights to do so.
For such purposes you can set the ``partial_sync`` parameter to ``ignore``:: For such purposes you can set the ``partial_sync`` parameter to ``ignore``::
[pair holidays] [pair holidays]
a = holidays_public a = "holidays_public"
b = holidays_private b = "holidays_private"
collections = null collections = null
partial_sync = ignore partial_sync = ignore

View file

@ -12,7 +12,7 @@ Pinning by fingerprint
To pin the certificate by fingerprint:: To pin the certificate by fingerprint::
[storage foo] [storage foo]
type = caldav type = "caldav"
... ...
verify_fingerprint = "94:FD:7A:CB:50:75:A4:69:82:0A:F8:23:DF:07:FC:69:3E:CD:90:CA" verify_fingerprint = "94:FD:7A:CB:50:75:A4:69:82:0A:F8:23:DF:07:FC:69:3E:CD:90:CA"
#verify = false # Optional: Disable CA validation, useful for self-signed certs #verify = false # Optional: Disable CA validation, useful for self-signed certs
@ -32,7 +32,7 @@ Custom root CAs
To point vdirsyncer to a custom set of root CAs:: To point vdirsyncer to a custom set of root CAs::
[storage foo] [storage foo]
type = caldav type = "caldav"
... ...
verify = "/path/to/cert.pem" verify = "/path/to/cert.pem"
@ -62,13 +62,13 @@ Client certificates may be specified with the ``auth_cert`` parameter. If the
key and certificate are stored in the same file, it may be a string:: key and certificate are stored in the same file, it may be a string::
[storage foo] [storage foo]
type = caldav type = "caldav"
... ...
auth_cert = "/path/to/certificate.pem" auth_cert = "/path/to/certificate.pem"
If the key and certificate are separate, a list may be used:: If the key and certificate are separate, a list may be used::
[storage foo] [storage foo]
type = caldav type = "caldav"
... ...
auth_cert = ["/path/to/certificate.crt", "/path/to/key.key"] auth_cert = ["/path/to/certificate.crt", "/path/to/key.key"]

View file

@ -99,14 +99,14 @@ ownCloud
Vdirsyncer is continuously tested against the latest version of ownCloud_:: Vdirsyncer is continuously tested against the latest version of ownCloud_::
[storage cal] [storage cal]
type = caldav type = "caldav"
url = https://example.com/owncloud/remote.php/caldav/ url = "https://example.com/owncloud/remote.php/caldav/"
username = ... username = ...
password = ... password = ...
[storage card] [storage card]
type = carddav type = "carddav"
url = https://example.com/owncloud/remote.php/carddav/ url = "https://example.com/owncloud/remote.php/carddav/"
username = ... username = ...
password = ... password = ...
@ -123,14 +123,14 @@ nextCloud
Vdirsyncer is continuously tested against the latest version of nextCloud_:: Vdirsyncer is continuously tested against the latest version of nextCloud_::
[storage cal] [storage cal]
type = caldav type = "caldav"
url = https://nextcloud.example.com/ url = "https://nextcloud.example.com/"
username = ... username = ...
password = ... password = ...
[storage card] [storage card]
type = carddav type = "carddav"
url = https://nextcloud.example.com/ url = "https://nextcloud.example.com/"
- WebCAL-subscriptions can't be discovered by vdirsyncer. See `this relevant - WebCAL-subscriptions can't be discovered by vdirsyncer. See `this relevant
issue <https://github.com/nextcloud/calendar/issues/63>`_. issue <https://github.com/nextcloud/calendar/issues/63>`_.
@ -147,14 +147,14 @@ with it. `FastMail's support pages
the settings to use:: the settings to use::
[storage cal] [storage cal]
type = caldav type = "caldav"
url = https://caldav.messagingengine.com/ url = "https://caldav.messagingengine.com/"
username = ... username = ...
password = ... password = ...
[storage card] [storage card]
type = carddav type = "carddav"
url = https://carddav.messagingengine.com/ url = "https://carddav.messagingengine.com/"
username = ... username = ...
password = ... password = ...
@ -170,14 +170,14 @@ Vdirsyncer is irregularly tested against iCloud_.
:: ::
[storage cal] [storage cal]
type = caldav type = "caldav"
url = https://caldav.icloud.com/ url = "https://caldav.icloud.com/"
username = ... username = ...
password = ... password = ...
[storage card] [storage card]
type = carddav type = "carddav"
url = https://contacts.icloud.com/ url = "https://contacts.icloud.com/"
username = ... username = ...
password = ... password = ...
@ -202,9 +202,9 @@ special characters and/or using an old DavMail version.
**Make absolutely sure you use the latest DavMail**:: **Make absolutely sure you use the latest DavMail**::
[storage outlook] [storage outlook]
type = caldav type = "caldav"
url = http://localhost:1080/ url = "http://localhost:1080/"
username = user@example.com username = "user@example.com"
password = ... password = ...
- Older versions of DavMail handle URLs case-insensitively. See :gh:`144`. - Older versions of DavMail handle URLs case-insensitively. See :gh:`144`.

View file

@ -56,22 +56,22 @@ The following example synchronizes ownCloud's addressbooks to ``~/.contacts/``::
[pair my_contacts] [pair my_contacts]
a = my_contacts_local a = "my_contacts_local"
b = my_contacts_remote b = "my_contacts_remote"
collections = ["from a", "from b"] collections = ["from a", "from b"]
[storage my_contacts_local] [storage my_contacts_local]
type = filesystem type = "filesystem"
path = ~/.contacts/ path = "~/.contacts/"
fileext = .vcf fileext = ".vcf"
[storage my_contacts_remote] [storage my_contacts_remote]
type = carddav type = "carddav"
# We can simplify this URL here as well. In theory it shouldn't matter. # We can simplify this URL here as well. In theory it shouldn't matter.
url = https://owncloud.example.com/remote.php/carddav/ url = "https://owncloud.example.com/remote.php/carddav/"
username = bob username = "bob"
password = asdf password = "asdf"
.. note:: .. note::
@ -127,22 +127,22 @@ color associated with a calendar. For the purpose of explaining this feature,
let's switch to a different base example. This time we'll synchronize calendars:: let's switch to a different base example. This time we'll synchronize calendars::
[pair my_calendars] [pair my_calendars]
a = my_calendars_local a = "my_calendars_local"
b = my_calendars_remote b = "my_calendars_remote"
collections = ["from a", "from b"] collections = ["from a", "from b"]
metadata = ["color"] metadata = ["color"]
[storage my_calendars_local] [storage my_calendars_local]
type = filesystem type = "filesystem"
path = ~/.calendars/ path = "~/.calendars/"
fileext = .ics fileext = ".ics"
[storage my_calendars_remote] [storage my_calendars_remote]
type = caldav type = "caldav"
url = https://owncloud.example.com/remote.php/caldav/ url = "https://owncloud.example.com/remote.php/caldav/"
username = bob username = "bob"
password = asdf password = "asdf"
Run ``vdirsyncer discover`` for discovery. Then you can use ``vdirsyncer Run ``vdirsyncer discover`` for discovery. Then you can use ``vdirsyncer
metasync`` to synchronize the ``color`` property between your local calendars metasync`` to synchronize the ``color`` property between your local calendars
@ -179,19 +179,19 @@ The last one requires a bit more explanation. Assume this config which
synchronizes two directories of addressbooks:: synchronizes two directories of addressbooks::
[pair foobar] [pair foobar]
a = foo a = "foo"
b = bar b = "bar"
collections = ["from a", "from b"] collections = ["from a", "from b"]
[storage foo] [storage foo]
type = filesystem type = "filesystem"
fileext = .vcf fileext = ".vcf"
path = ./contacts_foo/ path = "./contacts_foo/"
[storage bar] [storage bar]
type = filesystem type = "filesystem"
fileext = .vcf fileext = ".vcf"
path = ./contacts_bar/ path = "./contacts_bar/"
As we saw previously this will synchronize all collections in As we saw previously this will synchronize all collections in
``./contacts_foo/`` with each same-named collection in ``./contacts_bar/``. If ``./contacts_foo/`` with each same-named collection in ``./contacts_bar/``. If

View file

@ -23,7 +23,7 @@ class _CustomRunner(object):
def write_with_general(self, data): def write_with_general(self, data):
self.cfg.write(dedent(''' self.cfg.write(dedent('''
[general] [general]
status_path = {}/status/ status_path = "{}/status/"
''').format(str(self.tmpdir))) ''').format(str(self.tmpdir)))
self.cfg.write(data, mode='a') self.cfg.write(data, mode='a')

View file

@ -43,19 +43,19 @@ def test_read_config(read_config):
status_path = /tmp/status/ status_path = /tmp/status/
[pair bob] [pair bob]
a = bob_a a = "bob_a"
b = bob_b b = "bob_b"
collections = null collections = null
[storage bob_a] [storage bob_a]
type = filesystem type = "filesystem"
path = /tmp/contacts/ path = "/tmp/contacts/"
fileext = .vcf fileext = ".vcf"
yesno = false yesno = false
number = 42 number = 42
[storage bob_b] [storage bob_b]
type = carddav type = "carddav"
''') ''')
assert c.general == {'status_path': '/tmp/status/'} assert c.general == {'status_path': '/tmp/status/'}
@ -79,14 +79,14 @@ def test_missing_collections_param(read_config):
status_path = /tmp/status/ status_path = /tmp/status/
[pair bob] [pair bob]
a = bob_a a = "bob_a"
b = bob_b b = "bob_b"
[storage bob_a] [storage bob_a]
type = lmao type = "lmao"
[storage bob_b] [storage bob_b]
type = lmao type = "lmao"
''') ''')
assert 'collections parameter missing' in str(excinfo.value) assert 'collections parameter missing' in str(excinfo.value)
@ -120,19 +120,19 @@ def test_missing_general_section(read_config):
with pytest.raises(exceptions.UserError) as excinfo: with pytest.raises(exceptions.UserError) as excinfo:
read_config(u''' read_config(u'''
[pair my_pair] [pair my_pair]
a = my_a a = "my_a"
b = my_b b = "my_b"
collections = null collections = null
[storage my_a] [storage my_a]
type = filesystem type = "filesystem"
path = {base}/path_a/ path = "{base}/path_a/"
fileext = .txt fileext = ".txt"
[storage my_b] [storage my_b]
type = filesystem type = "filesystem"
path = {base}/path_b/ path = "{base}/path_b/"
fileext = .txt fileext = ".txt"
''') ''')
assert 'Invalid general section.' in str(excinfo.value) assert 'Invalid general section.' in str(excinfo.value)
@ -171,19 +171,19 @@ def test_invalid_collections_arg(read_config):
status_path = /tmp/status/ status_path = /tmp/status/
[pair foobar] [pair foobar]
a = foo a = "foo"
b = bar b = "bar"
collections = [null] collections = [null]
[storage foo] [storage foo]
type = filesystem type = "filesystem"
path = /tmp/foo/ path = "/tmp/foo/"
fileext = .txt fileext = ".txt"
[storage bar] [storage bar]
type = filesystem type = "filesystem"
path = /tmp/bar/ path = "/tmp/bar/"
fileext = .txt fileext = ".txt"
''') ''')
assert 'Expected string' in str(excinfo.value) assert 'Expected string' in str(excinfo.value)
@ -196,19 +196,19 @@ def test_duplicate_sections(read_config):
status_path = /tmp/status/ status_path = /tmp/status/
[pair foobar] [pair foobar]
a = foobar a = "foobar"
b = bar b = "bar"
collections = null collections = null
[storage foobar] [storage foobar]
type = filesystem type = "filesystem"
path = /tmp/foo/ path = "/tmp/foo/"
fileext = .txt fileext = ".txt"
[storage bar] [storage bar]
type = filesystem type = "filesystem"
path = /tmp/bar/ path = "/tmp/bar/"
fileext = .txt fileext = ".txt"
''') ''')
assert 'Name "foobar" already used' in str(excinfo.value) assert 'Name "foobar" already used' in str(excinfo.value)
@ -219,10 +219,10 @@ def test_parse_config_value(parse_config_value):
assert x('123 # comment!') is invalid assert x('123 # comment!') is invalid
assert x('True') == ('True', 1) assert x('True') is invalid
assert x('False') == ('False', 1) assert x('False') is invalid
assert x('Yes') == ('Yes', 1) assert x('Yes') is invalid
assert x('None') == ('None', 1) assert x('None') is invalid
assert x('"True"') == ('True', 0) assert x('"True"') == ('True', 0)
assert x('"False"') == ('False', 0) assert x('"False"') == ('False', 0)
@ -231,7 +231,7 @@ def test_parse_config_value(parse_config_value):
assert x('false') == (False, 0) assert x('false') == (False, 0)
assert x('null') == (None, 0) assert x('null') == (None, 0)
assert x('3.14') == (3.14, 0) assert x('3.14') == (3.14, 0)
assert x('') == ('', 0) assert x('') == ('', 1)
assert x('""') == ('', 0) assert x('""') == ('', 0)

View file

@ -1,3 +1,4 @@
import json
from textwrap import dedent from textwrap import dedent
import hypothesis.strategies as st import hypothesis.strategies as st
@ -10,18 +11,18 @@ from vdirsyncer.storage.base import Storage
def test_discover_command(tmpdir, runner): def test_discover_command(tmpdir, runner):
runner.write_with_general(dedent(''' runner.write_with_general(dedent('''
[storage foo] [storage foo]
type = filesystem type = "filesystem"
path = {0}/foo/ path = "{0}/foo/"
fileext = .txt fileext = ".txt"
[storage bar] [storage bar]
type = filesystem type = "filesystem"
path = {0}/bar/ path = "{0}/bar/"
fileext = .txt fileext = ".txt"
[pair foobar] [pair foobar]
a = foo a = "foo"
b = bar b = "bar"
collections = ["from a"] collections = ["from a"]
''').format(str(tmpdir))) ''').format(str(tmpdir)))
@ -68,18 +69,18 @@ def test_discover_different_collection_names(tmpdir, runner):
bar = tmpdir.mkdir('bar') bar = tmpdir.mkdir('bar')
runner.write_with_general(dedent(''' runner.write_with_general(dedent('''
[storage foo] [storage foo]
type = filesystem type = "filesystem"
fileext = .txt fileext = ".txt"
path = {foo} path = "{foo}"
[storage bar] [storage bar]
type = filesystem type = "filesystem"
fileext = .txt fileext = ".txt"
path = {bar} path = "{bar}"
[pair foobar] [pair foobar]
a = foo a = "foo"
b = bar b = "bar"
collections = [ collections = [
["coll1", "coll_a1", "coll_b1"], ["coll1", "coll_a1", "coll_b1"],
"coll2" "coll2"
@ -114,18 +115,18 @@ def test_discover_direct_path(tmpdir, runner):
runner.write_with_general(dedent(''' runner.write_with_general(dedent('''
[storage foo] [storage foo]
type = filesystem type = "filesystem"
fileext = .txt fileext = ".txt"
path = {foo} path = "{foo}"
[storage bar] [storage bar]
type = filesystem type = "filesystem"
fileext = .txt fileext = ".txt"
path = {bar} path = "{bar}"
[pair foobar] [pair foobar]
a = foo a = "foo"
b = bar b = "bar"
collections = null collections = null
''').format(foo=str(foo), bar=str(bar))) ''').format(foo=str(foo), bar=str(bar)))
@ -142,18 +143,18 @@ def test_discover_direct_path(tmpdir, runner):
def test_null_collection_with_named_collection(tmpdir, runner): def test_null_collection_with_named_collection(tmpdir, runner):
runner.write_with_general(dedent(''' runner.write_with_general(dedent('''
[pair foobar] [pair foobar]
a = foo a = "foo"
b = bar b = "bar"
collections = [["baz", "baz", null]] collections = [["baz", "baz", null]]
[storage foo] [storage foo]
type = filesystem type = "filesystem"
path = {base}/foo/ path = "{base}/foo/"
fileext = .txt fileext = ".txt"
[storage bar] [storage bar]
type = singlefile type = "singlefile"
path = {base}/bar.txt path = "{base}/bar.txt"
'''.format(base=str(tmpdir)))) '''.format(base=str(tmpdir))))
result = runner.invoke(['discover'], input='y\n' * 2) result = runner.invoke(['discover'], input='y\n' * 2)
@ -192,18 +193,18 @@ def test_collection_required(a_requires, b_requires, tmpdir, runner,
runner.write_with_general(dedent(''' runner.write_with_general(dedent('''
[pair foobar] [pair foobar]
a = foo a = "foo"
b = bar b = "bar"
collections = null collections = null
[storage foo] [storage foo]
type = test type = "test"
require_collection = {a} require_collection = {a}
[storage bar] [storage bar]
type = test type = "test"
require_collection = {b} require_collection = {b}
'''.format(a=a_requires, b=b_requires))) '''.format(a=json.dumps(a_requires), b=json.dumps(b_requires))))
result = runner.invoke(['discover']) result = runner.invoke(['discover'])
if a_requires or b_requires: if a_requires or b_requires:

View file

@ -41,18 +41,18 @@ def value_cache(monkeypatch):
def test_get_password_from_command(tmpdir, runner): def test_get_password_from_command(tmpdir, runner):
runner.write_with_general(dedent(''' runner.write_with_general(dedent('''
[pair foobar] [pair foobar]
a = foo a = "foo"
b = bar b = "bar"
collections = ["a", "b", "c"] collections = ["a", "b", "c"]
[storage foo] [storage foo]
type = filesystem type = "filesystem"
path = {base}/foo/ path = "{base}/foo/"
fileext.fetch = ["command", "echo", ".txt"] fileext.fetch = ["command", "echo", ".txt"]
[storage bar] [storage bar]
type = filesystem type = "filesystem"
path = {base}/bar/ path = "{base}/bar/"
fileext.fetch = ["prompt", "Fileext for bar"] fileext.fetch = ["prompt", "Fileext for bar"]
'''.format(base=str(tmpdir)))) '''.format(base=str(tmpdir))))

View file

@ -9,9 +9,9 @@ import pytest
def test_full(tmpdir, runner, collection): def test_full(tmpdir, runner, collection):
runner.write_with_general(dedent(''' runner.write_with_general(dedent('''
[storage foo] [storage foo]
type = filesystem type = "filesystem"
path = {base}/foo/ path = "{base}/foo/"
fileext = .txt fileext = ".txt"
''').format(base=str(tmpdir))) ''').format(base=str(tmpdir)))
storage = tmpdir.mkdir('foo') storage = tmpdir.mkdir('foo')

View file

@ -13,19 +13,19 @@ import pytest
def test_simple_run(tmpdir, runner): def test_simple_run(tmpdir, runner):
runner.write_with_general(dedent(''' runner.write_with_general(dedent('''
[pair my_pair] [pair my_pair]
a = my_a a = "my_a"
b = my_b b = "my_b"
collections = null collections = null
[storage my_a] [storage my_a]
type = filesystem type = "filesystem"
path = {0}/path_a/ path = "{0}/path_a/"
fileext = .txt fileext = ".txt"
[storage my_b] [storage my_b]
type = filesystem type = "filesystem"
path = {0}/path_b/ path = "{0}/path_b/"
fileext = .txt fileext = ".txt"
''').format(str(tmpdir))) ''').format(str(tmpdir)))
tmpdir.mkdir('path_a') tmpdir.mkdir('path_a')
@ -54,19 +54,19 @@ def test_sync_inexistant_pair(tmpdir, runner):
def test_debug_connections(tmpdir, runner): def test_debug_connections(tmpdir, runner):
runner.write_with_general(dedent(''' runner.write_with_general(dedent('''
[pair my_pair] [pair my_pair]
a = my_a a = "my_a"
b = my_b b = "my_b"
collections = null collections = null
[storage my_a] [storage my_a]
type = filesystem type = "filesystem"
path = {0}/path_a/ path = "{0}/path_a/"
fileext = .txt fileext = ".txt"
[storage my_b] [storage my_b]
type = filesystem type = "filesystem"
path = {0}/path_b/ path = "{0}/path_b/"
fileext = .txt fileext = ".txt"
''').format(str(tmpdir))) ''').format(str(tmpdir)))
tmpdir.mkdir('path_a') tmpdir.mkdir('path_a')
@ -85,19 +85,19 @@ def test_debug_connections(tmpdir, runner):
def test_empty_storage(tmpdir, runner): def test_empty_storage(tmpdir, runner):
runner.write_with_general(dedent(''' runner.write_with_general(dedent('''
[pair my_pair] [pair my_pair]
a = my_a a = "my_a"
b = my_b b = "my_b"
collections = null collections = null
[storage my_a] [storage my_a]
type = filesystem type = "filesystem"
path = {0}/path_a/ path = "{0}/path_a/"
fileext = .txt fileext = ".txt"
[storage my_b] [storage my_b]
type = filesystem type = "filesystem"
path = {0}/path_b/ path = "{0}/path_b/"
fileext = .txt fileext = ".txt"
''').format(str(tmpdir))) ''').format(str(tmpdir)))
tmpdir.mkdir('path_a') tmpdir.mkdir('path_a')
@ -137,18 +137,18 @@ def test_collections_cache_invalidation(tmpdir, runner):
runner.write_with_general(dedent(''' runner.write_with_general(dedent('''
[storage foo] [storage foo]
type = filesystem type = "filesystem"
path = {0}/foo/ path = "{0}/foo/"
fileext = .txt fileext = ".txt"
[storage bar] [storage bar]
type = filesystem type = "filesystem"
path = {0}/bar/ path = "{0}/bar/"
fileext = .txt fileext = ".txt"
[pair foobar] [pair foobar]
a = foo a = "foo"
b = bar b = "bar"
collections = ["a", "b", "c"] collections = ["a", "b", "c"]
''').format(str(tmpdir))) ''').format(str(tmpdir)))
@ -167,18 +167,18 @@ def test_collections_cache_invalidation(tmpdir, runner):
runner.write_with_general(dedent(''' runner.write_with_general(dedent('''
[storage foo] [storage foo]
type = filesystem type = "filesystem"
path = {0}/foo/ path = "{0}/foo/"
fileext = .txt fileext = ".txt"
[storage bar] [storage bar]
type = filesystem type = "filesystem"
path = {0}/bar2/ path = "{0}/bar2/"
fileext = .txt fileext = ".txt"
[pair foobar] [pair foobar]
a = foo a = "foo"
b = bar b = "bar"
collections = ["a", "b", "c"] collections = ["a", "b", "c"]
''').format(str(tmpdir))) ''').format(str(tmpdir)))
@ -207,18 +207,18 @@ def test_collections_cache_invalidation(tmpdir, runner):
def test_invalid_pairs_as_cli_arg(tmpdir, runner): def test_invalid_pairs_as_cli_arg(tmpdir, runner):
runner.write_with_general(dedent(''' runner.write_with_general(dedent('''
[storage foo] [storage foo]
type = filesystem type = "filesystem"
path = {0}/foo/ path = "{0}/foo/"
fileext = .txt fileext = ".txt"
[storage bar] [storage bar]
type = filesystem type = "filesystem"
path = {0}/bar/ path = "{0}/bar/"
fileext = .txt fileext = ".txt"
[pair foobar] [pair foobar]
a = foo a = "foo"
b = bar b = "bar"
collections = ["a", "b", "c"] collections = ["a", "b", "c"]
''').format(str(tmpdir))) ''').format(str(tmpdir)))
@ -240,17 +240,17 @@ def test_multiple_pairs(tmpdir, runner):
for name_a, name_b in ('foo', 'bar'), ('bam', 'baz'): for name_a, name_b in ('foo', 'bar'), ('bam', 'baz'):
yield dedent(''' yield dedent('''
[pair {a}{b}] [pair {a}{b}]
a = {a} a = "{a}"
b = {b} b = "{b}"
collections = null collections = null
''').format(a=name_a, b=name_b) ''').format(a=name_a, b=name_b)
for name in name_a, name_b: for name in name_a, name_b:
yield dedent(''' yield dedent('''
[storage {name}] [storage {name}]
type = filesystem type = "filesystem"
path = {path} path = "{path}"
fileext = .txt fileext = ".txt"
''').format(name=name, path=str(tmpdir.mkdir(name))) ''').format(name=name, path=str(tmpdir.mkdir(name)))
runner.write_with_general(''.join(get_cfg())) runner.write_with_general(''.join(get_cfg()))
@ -291,19 +291,19 @@ def test_create_collections(subtest, collections):
def test_inner(tmpdir, runner): def test_inner(tmpdir, runner):
runner.write_with_general(dedent(''' runner.write_with_general(dedent('''
[pair foobar] [pair foobar]
a = foo a = "foo"
b = bar b = "bar"
collections = {colls} collections = {colls}
[storage foo] [storage foo]
type = filesystem type = "filesystem"
path = {base}/foo/ path = "{base}/foo/"
fileext = .txt fileext = ".txt"
[storage bar] [storage bar]
type = filesystem type = "filesystem"
path = {base}/bar/ path = "{base}/bar/"
fileext = .txt fileext = ".txt"
'''.format(base=str(tmpdir), colls=json.dumps(list(collections))))) '''.format(base=str(tmpdir), colls=json.dumps(list(collections)))))
result = runner.invoke( result = runner.invoke(
@ -337,19 +337,19 @@ def test_create_collections(subtest, collections):
def test_ident_conflict(tmpdir, runner): def test_ident_conflict(tmpdir, runner):
runner.write_with_general(dedent(''' runner.write_with_general(dedent('''
[pair foobar] [pair foobar]
a = foo a = "foo"
b = bar b = "bar"
collections = null collections = null
[storage foo] [storage foo]
type = filesystem type = "filesystem"
path = {base}/foo/ path = "{base}/foo/"
fileext = .txt fileext = ".txt"
[storage bar] [storage bar]
type = filesystem type = "filesystem"
path = {base}/bar/ path = "{base}/bar/"
fileext = .txt fileext = ".txt"
'''.format(base=str(tmpdir)))) '''.format(base=str(tmpdir))))
foo = tmpdir.mkdir('foo') foo = tmpdir.mkdir('foo')
@ -380,14 +380,14 @@ def test_ident_conflict(tmpdir, runner):
def test_unknown_storage(tmpdir, runner, existing, missing): def test_unknown_storage(tmpdir, runner, existing, missing):
runner.write_with_general(dedent(''' runner.write_with_general(dedent('''
[pair foobar] [pair foobar]
a = foo a = "foo"
b = bar b = "bar"
collections = null collections = null
[storage {existing}] [storage {existing}]
type = filesystem type = "filesystem"
path = {base}/{existing}/ path = "{base}/{existing}/"
fileext = .txt fileext = ".txt"
'''.format(base=str(tmpdir), existing=existing))) '''.format(base=str(tmpdir), existing=existing)))
tmpdir.mkdir(existing) tmpdir.mkdir(existing)
@ -418,20 +418,20 @@ def test_conflict_resolution(tmpdir, runner, resolution, expect_foo,
expect_bar): expect_bar):
runner.write_with_general(dedent(''' runner.write_with_general(dedent('''
[pair foobar] [pair foobar]
a = foo a = "foo"
b = bar b = "bar"
collections = null collections = null
conflict_resolution = {val} conflict_resolution = {val}
[storage foo] [storage foo]
type = filesystem type = "filesystem"
fileext = .txt fileext = ".txt"
path = {base}/foo path = "{base}/foo"
[storage bar] [storage bar]
type = filesystem type = "filesystem"
fileext = .txt fileext = ".txt"
path = {base}/bar path = "{base}/bar"
'''.format(base=str(tmpdir), val=json.dumps(resolution)))) '''.format(base=str(tmpdir), val=json.dumps(resolution))))
foo = tmpdir.join('foo') foo = tmpdir.join('foo')
@ -455,21 +455,21 @@ def test_conflict_resolution(tmpdir, runner, resolution, expect_foo,
def test_partial_sync(tmpdir, runner, partial_sync): def test_partial_sync(tmpdir, runner, partial_sync):
runner.write_with_general(dedent(''' runner.write_with_general(dedent('''
[pair foobar] [pair foobar]
a = foo a = "foo"
b = bar b = "bar"
collections = null collections = null
{partial_sync} {partial_sync}
[storage foo] [storage foo]
type = filesystem type = "filesystem"
fileext = .txt fileext = ".txt"
path = {base}/foo path = "{base}/foo"
[storage bar] [storage bar]
type = filesystem type = "filesystem"
read_only = true read_only = true
fileext = .txt fileext = ".txt"
path = {base}/bar path = "{base}/bar"
'''.format( '''.format(
partial_sync=('partial_sync = {}\n'.format(partial_sync) partial_sync=('partial_sync = {}\n'.format(partial_sync)
if partial_sync else ''), if partial_sync else ''),
@ -523,28 +523,28 @@ def test_fetch_only_necessary_params(tmpdir, runner):
runner.write_with_general(dedent(''' runner.write_with_general(dedent('''
[pair foobar] [pair foobar]
a = foo a = "foo"
b = bar b = "bar"
collections = null collections = null
[pair bambar] [pair bambar]
a = bam a = "bam"
b = bar b = "bar"
collections = null collections = null
[storage foo] [storage foo]
type = filesystem type = "filesystem"
path = {path} path = "{path}"
fileext = .txt fileext = ".txt"
[storage bar] [storage bar]
type = filesystem type = "filesystem"
path = {path} path = "{path}"
fileext = .txt fileext = ".txt"
[storage bam] [storage bam]
type = filesystem type = "filesystem"
path = {path} path = "{path}"
fileext.fetch = ["command", "sh", "{script}"] fileext.fetch = ["command", "sh", "{script}"]
'''.format(path=str(tmpdir.mkdir('bogus')), script=str(fetch_script)))) '''.format(path=str(tmpdir.mkdir('bogus')), script=str(fetch_script))))

View file

@ -71,7 +71,7 @@ def test_list(monkeypatch):
def test_readonly_param(): def test_readonly_param():
url = u'http://example.com/' url = 'http://example.com/'
with pytest.raises(ValueError): with pytest.raises(ValueError):
HttpStorage(url=url, read_only=False) HttpStorage(url=url, read_only=False)

View file

@ -158,8 +158,8 @@ def _parse_config_value(value):
(('none',), 'null') (('none',), 'null')
]: ]:
if value.lower() in wrong + (right,): if value.lower() in wrong + (right,):
cli_logger.warning('You probably meant {} instead of "{}", which ' raise ValueError('You probably meant {} instead of {}. Surround '
'will now be interpreted as a literal string.' 'your string with double-quotes if not.'
.format(right, value)) .format(right, value))
if '#' in value: if '#' in value:
@ -176,6 +176,10 @@ def _parse_config_value(value):
# # my comment # # my comment
raise ValueError('No multiline-values allowed:\n{}'.format(value)) raise ValueError('No multiline-values allowed:\n{}'.format(value))
cli_logger.warning('Soon, all strings have to be in double quotes. Please '
'replace {} with {}'
.format(value, json.dumps(value)))
return value return value

View file

@ -117,12 +117,12 @@ class HttpStorage(Storage):
collections = null collections = null
[storage holidays_local] [storage holidays_local]
type = filesystem type = "filesystem"
path = ~/.config/vdir/calendars/holidays/ path = ~/.config/vdir/calendars/holidays/
fileext = .ics fileext = .ics
[storage holidays_remote] [storage holidays_remote]
type = http type = "http"
url = https://example.com/holidays_from_hicksville.ics url = https://example.com/holidays_from_hicksville.ics
''' '''

View file

@ -53,11 +53,11 @@ class SingleFileStorage(Storage):
collections = ["from a", "from b"] collections = ["from a", "from b"]
[storage my_calendar_local] [storage my_calendar_local]
type = singlefile type = "singlefile"
path = ~/.calendars/%s.ics path = ~/.calendars/%s.ics
[storage my_calendar_remote] [storage my_calendar_remote]
type = caldav type = "caldav"
url = https://caldav.example.org/ url = https://caldav.example.org/
#username = #username =
#password = #password =
@ -69,11 +69,11 @@ class SingleFileStorage(Storage):
b = my_calendar_remote b = my_calendar_remote
[storage my_calendar_local] [storage my_calendar_local]
type = singlefile type = "singlefile"
path = ~/my_calendar.ics path = ~/my_calendar.ics
[storage my_calendar_remote] [storage my_calendar_remote]
type = caldav type = "caldav"
url = https://caldav.example.org/username/my_calendar/ url = https://caldav.example.org/username/my_calendar/
#username = #username =
#password = #password =