Port CLI to Click.

After a while i finally gave in and ported the whole command line
interface of vdirsyncer to Click. While it might seem like a huge
dependency, i hope we can eliminate some helper functions in the
process, and maybe are more motivated to write a more beautiful and
intuitive interface due to all the niceties Click provides.
This commit is contained in:
Markus Unterwaditzer 2014-06-15 20:49:46 +02:00
parent d98c4b37ba
commit 8b889e9ecd
4 changed files with 29 additions and 52 deletions

View file

@ -38,7 +38,7 @@ setup(
'console_scripts': ['vdirsyncer = vdirsyncer.cli:main'] 'console_scripts': ['vdirsyncer = vdirsyncer.cli:main']
}, },
install_requires=[ install_requires=[
'argvard>=0.3.0', 'click',
'requests', 'requests',
'lxml', 'lxml',
'icalendar>=3.6', 'icalendar>=3.6',

View file

@ -11,7 +11,7 @@ import json
import os import os
import sys import sys
import argvard import click
from . import log from . import log
from .storage import storage_names from .storage import storage_names
@ -206,41 +206,29 @@ def parse_pairs_args(pairs_args, all_pairs):
def _main(env, file_cfg): def _main(env, file_cfg):
general, all_pairs, all_storages = file_cfg general, all_pairs, all_storages = file_cfg
app = argvard.Argvard()
@app.option('--verbosity verbosity') @click.group()
def verbose_option(context, verbosity): @click.option('--verbosity', '-v', default='INFO',
help='Either CRITICAL, ERROR, WARNING, INFO or DEBUG')
def app(verbosity):
''' '''
Basically Python logging levels. vdirsyncer -- synchronize calendars and contacts
CRITICAL: Config errors, at most.
ERROR: Normal errors, at most.
WARNING: Problems of which vdirsyncer thinks that it can handle them
itself, but which might crash other clients.
INFO: Normal output.
DEBUG: Show e.g. HTTP traffic. Not supposed to be readable by the
normal user.
''' '''
verbosity = verbosity.upper() verbosity = verbosity.upper()
x = getattr(log.logging, verbosity, None) x = getattr(log.logging, verbosity, None)
if x is None: if x is None:
raise ValueError(u'Invalid verbosity value: {}'.format(verbosity)) cli_logger.critical(u'Invalid verbosity value: {}'
log.set_level(x) .format(verbosity))
sys.exit(1)
else:
log.set_level(x)
sync_command = argvard.Command() @app.command()
@click.argument('pairs', nargs=-1)
@sync_command.option('--force-delete status_name') @click.option('--force-delete', multiple=True,
def force_delete(context, status_name): help=('Disable data-loss protection for the given pairs. '
'''Pretty please delete all my data.''' 'Can be passed multiple times'))
context.setdefault('force_delete', set()).add(status_name) def sync(pairs, force_delete):
@sync_command.main('[pairs...]')
def sync_main(context, pairs=None):
''' '''
Synchronize the given pairs. If no pairs are given, all will be Synchronize the given pairs. If no pairs are given, all will be
synchronized. synchronized.
@ -253,7 +241,7 @@ def _main(env, file_cfg):
''' '''
actions = [] actions = []
handled_collections = set() handled_collections = set()
force_delete = context.get('force_delete', set()) force_delete = set(force_delete)
for pair_name, _collection in parse_pairs_args(pairs, all_pairs): for pair_name, _collection in parse_pairs_args(pairs, all_pairs):
for collection in expand_collection(pair_name, _collection, for collection in expand_collection(pair_name, _collection,
all_pairs, all_storages): all_pairs, all_storages):
@ -293,16 +281,12 @@ def _main(env, file_cfg):
from multiprocessing import Pool from multiprocessing import Pool
p = Pool(processes=general.get('processes', 0) or len(actions)) p = Pool(processes=general.get('processes', 0) or len(actions))
if not all(p.map_async(_sync_collection, actions).get(10**9)): if not all(p.map_async(_sync_collection, actions).get(10**9)):
raise CliError() sys.exit(1)
app.register_command('sync', sync_command)
try: try:
app() app()
except CliError as e: except CliError as e:
msg = str(e) cli_logger.critical(str(e))
if msg:
cli_logger.critical(msg)
sys.exit(1) sys.exit(1)

View file

@ -10,9 +10,10 @@
import os import os
import requests import requests
import click
from .. import exceptions, log from .. import exceptions, log
from .compat import get_raw_input, iteritems, urlparse from .compat import iteritems, urlparse
logger = log.get(__name__) logger = log.get(__name__)
@ -155,8 +156,6 @@ def get_password(username, resource):
""" """
import getpass
for func in (_password_from_netrc, _password_from_keyring): for func in (_password_from_netrc, _password_from_keyring):
password = func(username, resource) password = func(username, resource)
if password is not None: if password is not None:
@ -164,18 +163,14 @@ def get_password(username, resource):
.format(username, func.__doc__)) .format(username, func.__doc__))
return password return password
prompt = ('Server password for {} at the resource {}: ' prompt = ('Server password for {} at the resource {}'
.format(username, resource)) .format(username, resource))
password = getpass.getpass(prompt=prompt) password = click.prompt(prompt=prompt, hide_input=True)
if keyring is not None: if keyring is not None and \
answer = None click.confirm('Save this password in the keyring?', default=False):
while answer not in ['', 'y', 'n']: keyring.set_password(password_key_prefix + resource,
prompt = 'Save this password in the keyring? [y/N] ' username, password)
answer = get_raw_input(prompt).lower()
if answer == 'y':
keyring.set_password(password_key_prefix + resource,
username, password)
return password return password

View file

@ -20,7 +20,6 @@ if PY2:
text_type = unicode # flake8: noqa text_type = unicode # flake8: noqa
iteritems = lambda x: x.iteritems() iteritems = lambda x: x.iteritems()
itervalues = lambda x: x.itervalues() itervalues = lambda x: x.itervalues()
get_raw_input = raw_input
else: else:
import urllib.parse as urlparse import urllib.parse as urlparse
urlquote_plus = urlparse.quote_plus urlquote_plus = urlparse.quote_plus
@ -28,4 +27,3 @@ else:
text_type = str text_type = str
iteritems = lambda x: x.items() iteritems = lambda x: x.items()
itervalues = lambda x: x.values() itervalues = lambda x: x.values()
get_raw_input = input