diff --git a/tests/test_cli.py b/tests/test_cli.py index 47bd571..12e1947 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -435,3 +435,47 @@ def test_invalid_collections_arg(tmpdir, runner): 'Section `pair foobar`: `collections` parameter must be a list of ' '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 diff --git a/tests/utils/test_main.py b/tests/utils/test_main.py index 6a87bb4..cf84838 100644 --- a/tests/utils/test_main.py +++ b/tests/utils/test_main.py @@ -7,28 +7,30 @@ :license: MIT, see LICENSE for more details. ''' -import click -import pytest - -from click.testing import CliRunner import os import stat + +import click +from click.testing import CliRunner + import pytest + import requests -import vdirsyncer.utils as utils 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): def __init__(self, file=None): self._file = file + def authenticators(self, hostname): return None + class EmptyKeyring(object): def get_password(self, *a, **kw): return None @@ -40,49 +42,6 @@ def empty_password_storages(monkeypatch): 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): username = 'foouser' password = 'foopass' @@ -133,9 +92,9 @@ def test_get_password_from_command(tmpdir): filepath = str(tmpdir) + '/' + filename f = open(filepath, 'w') f.write('#!/bin/sh\n' - '[ "$1" != "my_username" ] && exit 1\n' - '[ "$2" != "example.com" ] && exit 1\n' - 'echo "{}"'.format(password)) + '[ "$1" != "my_username" ] && exit 1\n' + '[ "$2" != "example.com" ] && exit 1\n' + 'echo "{}"'.format(password)) f.close() st = os.stat(filepath) @@ -144,7 +103,7 @@ def test_get_password_from_command(tmpdir): @doubleclick.click.command() @doubleclick.click.pass_context def fake_app(ctx): - ctx.obj = {'config' : ({'password_command' : filepath},{},{})} + ctx.obj = {'config': ({'password_command': filepath}, {}, {})} _password = utils.get_password(username, resource) assert _password == password @@ -154,8 +113,6 @@ def test_get_password_from_command(tmpdir): def test_get_password_from_prompt(): - getpass_calls = [] - user = 'my_user' resource = 'http://example.com' diff --git a/vdirsyncer/cli/utils.py b/vdirsyncer/cli/utils.py index 7d2a1a0..39839ee 100644 --- a/vdirsyncer/cli/utils.py +++ b/vdirsyncer/cli/utils.py @@ -20,8 +20,7 @@ from .. import DOCS_HOME, PROJECT_HOME, log from ..doubleclick import click from ..storage import storage_names from ..sync import StorageEmpty, SyncConflict -from ..utils import expand_path, get_class_init_args, parse_options, \ - safe_write +from ..utils import expand_path, get_class_init_args, safe_write from ..utils.compat import text_type @@ -465,3 +464,56 @@ class WorkerQueue(object): def put(self, 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)) diff --git a/vdirsyncer/utils/__init__.py b/vdirsyncer/utils/__init__.py index ec00091..6f9e1a1 100644 --- a/vdirsyncer/utils/__init__.py +++ b/vdirsyncer/utils/__init__.py @@ -7,7 +7,6 @@ :license: MIT, see LICENSE for more details. ''' -import json import os import threading @@ -68,53 +67,6 @@ def uniq(s): 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()): """tries to access saved password or asks user for it