From d0a2331d86f75eb8d596a66190ced1ccbac4a718 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Tue, 19 Aug 2014 15:54:25 +0200 Subject: [PATCH] Don't produce overlapping output or prompts. See #96 and #101 --- vdirsyncer/cli.py | 3 +- vdirsyncer/doubleclick.py | 55 ++++++++++++++++++++++++++++++++++++ vdirsyncer/log.py | 2 +- vdirsyncer/utils/__init__.py | 3 +- 4 files changed, 58 insertions(+), 5 deletions(-) create mode 100644 vdirsyncer/doubleclick.py diff --git a/vdirsyncer/cli.py b/vdirsyncer/cli.py index 9e3df43..526c272 100644 --- a/vdirsyncer/cli.py +++ b/vdirsyncer/cli.py @@ -12,9 +12,8 @@ import json import os import sys -import click - from . import __version__, log +from .doubleclick import click from .storage import storage_names from .sync import StorageEmpty, SyncConflict, sync from .utils import expand_path, get_class_init_args, parse_options, split_dict diff --git a/vdirsyncer/doubleclick.py b/vdirsyncer/doubleclick.py new file mode 100644 index 0000000..c8aed1d --- /dev/null +++ b/vdirsyncer/doubleclick.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +''' + vdirsyncer.utils.doubleclick + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Utilities for writing multiprocessing applications with click. + + Currently the only relevant object here is the ``click`` object, which + provides everything importable from click. It also wraps some UI functions + such that they don't produce overlapping output or prompt the user at the + same time. + + :copyright: (c) 2014 Markus Unterwaditzer & contributors + :license: MIT, see LICENSE for more details. +''' + +import functools +import multiprocessing + +UI_FUNCTIONS = frozenset(['echo', 'echo_via_pager', 'prompt', 'clear', 'edit', + 'launch', 'getchar', 'pause']) + + +_ui_lock = multiprocessing.Lock() + + +def _ui_function(f): + @functools.wraps(f) + def inner(*a, **kw): + _ui_lock.acquire() + try: + return f(*a, **kw) + finally: + _ui_lock.release() + return inner + + +class _ClickProxy(object): + def __init__(self, needs_wrapper, click=None): + if click is None: + import click + self._click = click + self._cache = {} + self._needs_wrapper = frozenset(needs_wrapper) + + def __getattr__(self, name): + if name not in self._cache: + f = getattr(self._click, name) + if name in self._needs_wrapper: + f = _ui_function(f) + self._cache[name] = f + + return self._cache[name] + +click = _ClickProxy(UI_FUNCTIONS) diff --git a/vdirsyncer/log.py b/vdirsyncer/log.py index 7ad6f7f..3c2a779 100644 --- a/vdirsyncer/log.py +++ b/vdirsyncer/log.py @@ -9,7 +9,7 @@ import logging import sys -import click +from .doubleclick import click class ColorFormatter(logging.Formatter): diff --git a/vdirsyncer/utils/__init__.py b/vdirsyncer/utils/__init__.py index 402709a..72b3067 100644 --- a/vdirsyncer/utils/__init__.py +++ b/vdirsyncer/utils/__init__.py @@ -9,12 +9,11 @@ import os -import click - import requests from requests.packages.urllib3.poolmanager import PoolManager from .. import exceptions, log +from ..doubleclick import click from .compat import iteritems, urlparse