mirror of
https://github.com/samsonjs/vdirsyncer.git
synced 2026-04-27 14:57:41 +00:00
Move config parsing into cli module
This commit is contained in:
parent
6ef330aac5
commit
cdb25d61ec
4 changed files with 111 additions and 106 deletions
|
|
@ -435,3 +435,47 @@ def test_invalid_collections_arg(tmpdir, runner):
|
||||||
'Section `pair foobar`: `collections` parameter must be a list of '
|
'Section `pair foobar`: `collections` parameter must be a list of '
|
||||||
'collection names (strings!) or `null`.'
|
'collection names (strings!) or `null`.'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_config_value():
|
||||||
|
x = cli.utils.parse_config_value
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
x('123 # comment!')
|
||||||
|
|
||||||
|
assert x('"123 # comment!"') == '123 # comment!'
|
||||||
|
assert x('True') is True
|
||||||
|
assert x('False') is False
|
||||||
|
assert x('Yes') is True
|
||||||
|
assert x('3.14') == 3.14
|
||||||
|
assert x('') == ''
|
||||||
|
assert x('""') == ''
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_options():
|
||||||
|
o = {
|
||||||
|
'foo': 'yes',
|
||||||
|
'hah': 'true',
|
||||||
|
'bar': '',
|
||||||
|
'baz': 'whatever',
|
||||||
|
'bam': '123',
|
||||||
|
'asd': 'off'
|
||||||
|
}
|
||||||
|
|
||||||
|
a = dict(cli.utils.parse_options(o.items()))
|
||||||
|
|
||||||
|
expected = {
|
||||||
|
'foo': True,
|
||||||
|
'hah': True,
|
||||||
|
'bar': '',
|
||||||
|
'baz': 'whatever',
|
||||||
|
'bam': 123,
|
||||||
|
'asd': False
|
||||||
|
}
|
||||||
|
|
||||||
|
assert a == expected
|
||||||
|
|
||||||
|
for key in a:
|
||||||
|
# Yes, we want a very strong typecheck here, because we actually have
|
||||||
|
# to differentiate between bool and int, and in Python 2, bool is a
|
||||||
|
# subclass of int.
|
||||||
|
assert type(a[key]) is type(expected[key]) # noqa
|
||||||
|
|
|
||||||
|
|
@ -7,28 +7,30 @@
|
||||||
:license: MIT, see LICENSE for more details.
|
:license: MIT, see LICENSE for more details.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
import click
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
from click.testing import CliRunner
|
|
||||||
import os
|
import os
|
||||||
import stat
|
import stat
|
||||||
|
|
||||||
|
import click
|
||||||
|
from click.testing import CliRunner
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
import vdirsyncer.utils as utils
|
|
||||||
import vdirsyncer.doubleclick as doubleclick
|
import vdirsyncer.doubleclick as doubleclick
|
||||||
from vdirsyncer.utils.vobject import split_collection
|
import vdirsyncer.utils as utils
|
||||||
|
|
||||||
from .. import blow_up, normalize_item, SIMPLE_TEMPLATE, BARE_EVENT_TEMPLATE
|
from .. import blow_up
|
||||||
|
|
||||||
|
|
||||||
class EmptyNetrc(object):
|
class EmptyNetrc(object):
|
||||||
def __init__(self, file=None):
|
def __init__(self, file=None):
|
||||||
self._file = file
|
self._file = file
|
||||||
|
|
||||||
def authenticators(self, hostname):
|
def authenticators(self, hostname):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
class EmptyKeyring(object):
|
class EmptyKeyring(object):
|
||||||
def get_password(self, *a, **kw):
|
def get_password(self, *a, **kw):
|
||||||
return None
|
return None
|
||||||
|
|
@ -40,49 +42,6 @@ def empty_password_storages(monkeypatch):
|
||||||
monkeypatch.setattr(utils, 'keyring', EmptyKeyring())
|
monkeypatch.setattr(utils, 'keyring', EmptyKeyring())
|
||||||
|
|
||||||
|
|
||||||
def test_parse_options():
|
|
||||||
o = {
|
|
||||||
'foo': 'yes',
|
|
||||||
'hah': 'true',
|
|
||||||
'bar': '',
|
|
||||||
'baz': 'whatever',
|
|
||||||
'bam': '123',
|
|
||||||
'asd': 'off'
|
|
||||||
}
|
|
||||||
|
|
||||||
a = dict(utils.parse_options(o.items()))
|
|
||||||
|
|
||||||
expected = {
|
|
||||||
'foo': True,
|
|
||||||
'hah': True,
|
|
||||||
'bar': '',
|
|
||||||
'baz': 'whatever',
|
|
||||||
'bam': 123,
|
|
||||||
'asd': False
|
|
||||||
}
|
|
||||||
|
|
||||||
assert a == expected
|
|
||||||
|
|
||||||
for key in a:
|
|
||||||
# Yes, we want a very strong typecheck here, because we actually have
|
|
||||||
# to differentiate between bool and int, and in Python 2, bool is a
|
|
||||||
# subclass of int.
|
|
||||||
assert type(a[key]) is type(expected[key]) # flake8: noqa
|
|
||||||
|
|
||||||
|
|
||||||
def test_parse_config_value():
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
utils.parse_config_value('123 # comment!')
|
|
||||||
|
|
||||||
assert utils.parse_config_value('"123 # comment!"') == '123 # comment!'
|
|
||||||
assert utils.parse_config_value('True') is True
|
|
||||||
assert utils.parse_config_value('False') is False
|
|
||||||
assert utils.parse_config_value('Yes') is True
|
|
||||||
assert utils.parse_config_value('3.14') == 3.14
|
|
||||||
assert utils.parse_config_value('') == ''
|
|
||||||
assert utils.parse_config_value('""') == ''
|
|
||||||
|
|
||||||
|
|
||||||
def test_get_password_from_netrc(monkeypatch):
|
def test_get_password_from_netrc(monkeypatch):
|
||||||
username = 'foouser'
|
username = 'foouser'
|
||||||
password = 'foopass'
|
password = 'foopass'
|
||||||
|
|
@ -133,9 +92,9 @@ def test_get_password_from_command(tmpdir):
|
||||||
filepath = str(tmpdir) + '/' + filename
|
filepath = str(tmpdir) + '/' + filename
|
||||||
f = open(filepath, 'w')
|
f = open(filepath, 'w')
|
||||||
f.write('#!/bin/sh\n'
|
f.write('#!/bin/sh\n'
|
||||||
'[ "$1" != "my_username" ] && exit 1\n'
|
'[ "$1" != "my_username" ] && exit 1\n'
|
||||||
'[ "$2" != "example.com" ] && exit 1\n'
|
'[ "$2" != "example.com" ] && exit 1\n'
|
||||||
'echo "{}"'.format(password))
|
'echo "{}"'.format(password))
|
||||||
f.close()
|
f.close()
|
||||||
|
|
||||||
st = os.stat(filepath)
|
st = os.stat(filepath)
|
||||||
|
|
@ -144,7 +103,7 @@ def test_get_password_from_command(tmpdir):
|
||||||
@doubleclick.click.command()
|
@doubleclick.click.command()
|
||||||
@doubleclick.click.pass_context
|
@doubleclick.click.pass_context
|
||||||
def fake_app(ctx):
|
def fake_app(ctx):
|
||||||
ctx.obj = {'config' : ({'password_command' : filepath},{},{})}
|
ctx.obj = {'config': ({'password_command': filepath}, {}, {})}
|
||||||
_password = utils.get_password(username, resource)
|
_password = utils.get_password(username, resource)
|
||||||
assert _password == password
|
assert _password == password
|
||||||
|
|
||||||
|
|
@ -154,8 +113,6 @@ def test_get_password_from_command(tmpdir):
|
||||||
|
|
||||||
|
|
||||||
def test_get_password_from_prompt():
|
def test_get_password_from_prompt():
|
||||||
getpass_calls = []
|
|
||||||
|
|
||||||
user = 'my_user'
|
user = 'my_user'
|
||||||
resource = 'http://example.com'
|
resource = 'http://example.com'
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,7 @@ from .. import DOCS_HOME, PROJECT_HOME, log
|
||||||
from ..doubleclick import click
|
from ..doubleclick import click
|
||||||
from ..storage import storage_names
|
from ..storage import storage_names
|
||||||
from ..sync import StorageEmpty, SyncConflict
|
from ..sync import StorageEmpty, SyncConflict
|
||||||
from ..utils import expand_path, get_class_init_args, parse_options, \
|
from ..utils import expand_path, get_class_init_args, safe_write
|
||||||
safe_write
|
|
||||||
from ..utils.compat import text_type
|
from ..utils.compat import text_type
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -465,3 +464,56 @@ class WorkerQueue(object):
|
||||||
|
|
||||||
def put(self, f):
|
def put(self, f):
|
||||||
return self._queue.put(f)
|
return self._queue.put(f)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_config_value(value):
|
||||||
|
try:
|
||||||
|
return json.loads(value)
|
||||||
|
except ValueError:
|
||||||
|
rv = value
|
||||||
|
|
||||||
|
if value.lower() in ('on', 'true', 'yes'):
|
||||||
|
cli_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.lower() in ('off', 'false', 'no'):
|
||||||
|
cli_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.lower() == 'none':
|
||||||
|
cli_logger.warning(
|
||||||
|
'None is deprecated for the config, please use null.\n'
|
||||||
|
'The old form will be removed in 0.4.0.'
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
|
||||||
|
if '#' in value:
|
||||||
|
raise ValueError('Invalid value:{}\n'
|
||||||
|
'Use double quotes (") if you want to use hashes in '
|
||||||
|
'your value.')
|
||||||
|
|
||||||
|
if 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:\n{!r}'.format(value))
|
||||||
|
|
||||||
|
return rv
|
||||||
|
|
||||||
|
|
||||||
|
def parse_options(items, section=None):
|
||||||
|
for key, value in items:
|
||||||
|
try:
|
||||||
|
yield key, parse_config_value(value)
|
||||||
|
except ValueError as e:
|
||||||
|
raise ValueError('Section {!r}, option {!r}: {}'
|
||||||
|
.format(section, key, e))
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@
|
||||||
:license: MIT, see LICENSE for more details.
|
:license: MIT, see LICENSE for more details.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
import json
|
|
||||||
import os
|
import os
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
|
|
@ -68,53 +67,6 @@ def uniq(s):
|
||||||
yield x
|
yield x
|
||||||
|
|
||||||
|
|
||||||
def parse_config_value(value):
|
|
||||||
try:
|
|
||||||
return json.loads(value)
|
|
||||||
except ValueError:
|
|
||||||
rv = value
|
|
||||||
|
|
||||||
if value.lower() in ('on', 'true', '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.lower() in ('off', 'false', '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.lower() == '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
|
|
||||||
|
|
||||||
if '#' in value:
|
|
||||||
raise ValueError('Invalid value:{}\n'
|
|
||||||
'Use double quotes (") if you want to use hashes in '
|
|
||||||
'your value.')
|
|
||||||
|
|
||||||
if 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:\n{!r}'.format(value))
|
|
||||||
|
|
||||||
return rv
|
|
||||||
|
|
||||||
|
|
||||||
def parse_options(items, section=None):
|
|
||||||
for key, value in items:
|
|
||||||
try:
|
|
||||||
yield key, parse_config_value(value)
|
|
||||||
except ValueError as e:
|
|
||||||
raise ValueError('Section {!r}, option {!r}: {}'
|
|
||||||
.format(section, key, e))
|
|
||||||
|
|
||||||
|
|
||||||
def get_password(username, resource, _lock=threading.Lock()):
|
def get_password(username, resource, _lock=threading.Lock()):
|
||||||
"""tries to access saved password or asks user for it
|
"""tries to access saved password or asks user for it
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue