Imported Upstream version 5.1
This commit is contained in:
parent
bbeeb94e55
commit
c020281111
30
CHANGES
30
CHANGES
|
@ -3,6 +3,36 @@ Click Changelog
|
|||
|
||||
This contains all major version changes between Click releases.
|
||||
|
||||
Version 5.1
|
||||
-----------
|
||||
|
||||
(bugfix release, released on 17th August 2015)
|
||||
|
||||
- Fix a bug in `pass_obj` that would accidentally pass the context too.
|
||||
|
||||
Version 5.0
|
||||
-----------
|
||||
|
||||
(codename "tok tok", released on 16th August 2015)
|
||||
|
||||
- Removed various deprecated functionality.
|
||||
- Atomic files now only accept the `w` mode.
|
||||
- Change the usage part of help output for very long commands to wrap
|
||||
their arguments onto the next line, indented by 4 spaces.
|
||||
- Fix a bug where return code and error messages were incorrect when
|
||||
using ``CliRunner``.
|
||||
- added `get_current_context`.
|
||||
- added a `meta` dictionary to the context which is shared across the
|
||||
linked list of contexts to allow click utilities to place state there.
|
||||
- introduced `Context.scope`.
|
||||
- The `echo` function is now threadsafe: It calls the `write` method of the
|
||||
underlying object only once.
|
||||
- `prompt(hide_input=True)` now prints a newline on `^C`.
|
||||
- Click will now warn if users are using ``unicode_literals``.
|
||||
- Click will now ignore the ``PAGER`` environment variable if it is empty or
|
||||
contains only whitespace.
|
||||
- The `click-contrib` GitHub organization was created.
|
||||
|
||||
Version 4.1
|
||||
-----------
|
||||
|
||||
|
|
2
PKG-INFO
2
PKG-INFO
|
@ -1,6 +1,6 @@
|
|||
Metadata-Version: 1.1
|
||||
Name: click
|
||||
Version: 4.1
|
||||
Version: 5.1
|
||||
Summary: A simple wrapper around optparse for powerful command line utilities.
|
||||
Home-page: http://github.com/mitsuhiko/click
|
||||
Author: Armin Ronacher
|
||||
|
|
2
README
2
README
|
@ -17,4 +17,4 @@ $ click_
|
|||
|
||||
Read the docs at http://click.pocoo.org/
|
||||
|
||||
This library is a work in progress. Please give feedback!
|
||||
This library is stable and active. Feedback is always welcome!
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
Metadata-Version: 1.1
|
||||
Name: click
|
||||
Version: 4.1
|
||||
Version: 5.1
|
||||
Summary: A simple wrapper around optparse for powerful command line utilities.
|
||||
Home-page: http://github.com/mitsuhiko/click
|
||||
Author: Armin Ronacher
|
||||
|
|
|
@ -15,6 +15,7 @@ click/core.py
|
|||
click/decorators.py
|
||||
click/exceptions.py
|
||||
click/formatting.py
|
||||
click/globals.py
|
||||
click/parser.py
|
||||
click/termui.py
|
||||
click/testing.py
|
||||
|
@ -34,6 +35,7 @@ docs/clickdoctools.py
|
|||
docs/commands.rst
|
||||
docs/complex.rst
|
||||
docs/conf.py
|
||||
docs/contrib.rst
|
||||
docs/documentation.rst
|
||||
docs/exceptions.rst
|
||||
docs/index.rst
|
||||
|
@ -55,12 +57,6 @@ docs/_static/click.png
|
|||
docs/_static/click@2x.png
|
||||
docs/_templates/sidebarintro.html
|
||||
docs/_templates/sidebarlogo.html
|
||||
docs/_themes/LICENSE
|
||||
docs/_themes/README
|
||||
docs/_themes/click/layout.html
|
||||
docs/_themes/click/relations.html
|
||||
docs/_themes/click/theme.conf
|
||||
docs/_themes/click/static/click.css_t
|
||||
examples/README
|
||||
examples/aliases/README
|
||||
examples/aliases/aliases.ini
|
||||
|
|
|
@ -18,6 +18,9 @@
|
|||
from .core import Context, BaseCommand, Command, MultiCommand, Group, \
|
||||
CommandCollection, Parameter, Option, Argument
|
||||
|
||||
# Globals
|
||||
from .globals import get_current_context
|
||||
|
||||
# Decorators
|
||||
from .decorators import pass_context, pass_obj, make_pass_decorator, \
|
||||
command, group, argument, option, confirmation_option, \
|
||||
|
@ -52,6 +55,9 @@ __all__ = [
|
|||
'Context', 'BaseCommand', 'Command', 'MultiCommand', 'Group',
|
||||
'CommandCollection', 'Parameter', 'Option', 'Argument',
|
||||
|
||||
# Globals
|
||||
'get_current_context',
|
||||
|
||||
# Decorators
|
||||
'pass_context', 'pass_obj', 'make_pass_decorator', 'command', 'group',
|
||||
'argument', 'option', 'confirmation_option', 'password_option',
|
||||
|
@ -82,4 +88,9 @@ __all__ = [
|
|||
]
|
||||
|
||||
|
||||
__version__ = '4.1'
|
||||
# Controls if click should emit the warning about the use of unicode
|
||||
# literals.
|
||||
disable_unicode_literals_warning = False
|
||||
|
||||
|
||||
__version__ = '5.1'
|
||||
|
|
|
@ -5,6 +5,8 @@ import sys
|
|||
import codecs
|
||||
from weakref import WeakKeyDictionary
|
||||
|
||||
import click
|
||||
|
||||
|
||||
PY2 = sys.version_info[0] == 2
|
||||
WIN = sys.platform.startswith('win')
|
||||
|
@ -14,6 +16,39 @@ DEFAULT_COLUMNS = 80
|
|||
_ansi_re = re.compile('\033\[((?:\d|;)*)([a-zA-Z])')
|
||||
|
||||
|
||||
def _find_unicode_literals_frame():
|
||||
import __future__
|
||||
frm = sys._getframe(1)
|
||||
idx = 1
|
||||
while frm is not None:
|
||||
if frm.f_globals.get('__name__', '').startswith('click.'):
|
||||
frm = frm.f_back
|
||||
idx += 1
|
||||
elif frm.f_code.co_flags & __future__.unicode_literals.compiler_flag:
|
||||
return idx
|
||||
else:
|
||||
break
|
||||
return 0
|
||||
|
||||
|
||||
def _check_for_unicode_literals():
|
||||
if not __debug__:
|
||||
return
|
||||
if not PY2 or click.disable_unicode_literals_warning:
|
||||
return
|
||||
bad_frame = _find_unicode_literals_frame()
|
||||
if bad_frame <= 0:
|
||||
return
|
||||
from warnings import warn
|
||||
warn(Warning('Click detected the use of the unicode_literals '
|
||||
'__future__ import. This is heavily discouraged '
|
||||
'because it can introduce subtle bugs in your '
|
||||
'code. You should instead use explicit u"" literals '
|
||||
'for your unicode strings. For more information see '
|
||||
'http://click.pocoo.org/python3/'),
|
||||
stacklevel=bad_frame)
|
||||
|
||||
|
||||
def get_filesystem_encoding():
|
||||
return sys.getfilesystemencoding() or sys.getdefaultencoding()
|
||||
|
||||
|
@ -407,6 +442,19 @@ def open_stream(filename, mode='r', encoding=None, errors='strict',
|
|||
return open(filename, mode), True
|
||||
return io.open(filename, mode, encoding=encoding, errors=errors), True
|
||||
|
||||
# Some usability stuff for atomic writes
|
||||
if 'a' in mode:
|
||||
raise ValueError(
|
||||
'Appending to an existing file is not supported, because that '
|
||||
'would involve an expensive `copy`-operation to a temporary '
|
||||
'file. Open the file in normal `w`-mode and copy explicitly '
|
||||
'if that\'s what you\'re after.'
|
||||
)
|
||||
if 'x' in mode:
|
||||
raise ValueError('Use the `overwrite`-parameter instead.')
|
||||
if 'w' not in mode:
|
||||
raise ValueError('Atomic writes only make sense with `w`-mode.')
|
||||
|
||||
# Atomic writes are more complicated. They work by opening a file
|
||||
# as a proxy in the same folder and then using the fdopen
|
||||
# functionality to wrap it in a Python file. Then we wrap it in an
|
||||
|
|
|
@ -257,10 +257,11 @@ def pager(text, color=None):
|
|||
stdout = _default_text_stdout()
|
||||
if not isatty(sys.stdin) or not isatty(stdout):
|
||||
return _nullpager(stdout, text, color)
|
||||
if 'PAGER' in os.environ:
|
||||
pager_cmd = (os.environ.get('PAGER', None) or '').strip()
|
||||
if pager_cmd:
|
||||
if WIN:
|
||||
return _tempfilepager(text, os.environ['PAGER'], color)
|
||||
return _pipepager(text, os.environ['PAGER'], color)
|
||||
return _tempfilepager(text, pager_cmd, color)
|
||||
return _pipepager(text, pager_cmd, color)
|
||||
if os.environ.get('TERM') in ('dumb', 'emacs'):
|
||||
return _nullpager(stdout, text, color)
|
||||
if WIN or sys.platform.startswith('os2'):
|
||||
|
|
120
click/core.py
120
click/core.py
|
@ -12,8 +12,10 @@ from .exceptions import ClickException, UsageError, BadParameter, Abort, \
|
|||
from .termui import prompt, confirm
|
||||
from .formatting import HelpFormatter, join_options
|
||||
from .parser import OptionParser, split_opt
|
||||
from .globals import push_context, pop_context
|
||||
|
||||
from ._compat import PY2, isidentifier, iteritems, _check_for_unicode_literals
|
||||
|
||||
from ._compat import PY2, isidentifier, iteritems
|
||||
|
||||
_missing = object()
|
||||
|
||||
|
@ -189,6 +191,8 @@ class Context(object):
|
|||
obj = parent.obj
|
||||
#: the user object stored.
|
||||
self.obj = obj
|
||||
self._meta = getattr(parent, 'meta', {})
|
||||
|
||||
#: A dictionary (-like object) with defaults for parameters.
|
||||
if default_map is None \
|
||||
and parent is not None \
|
||||
|
@ -291,31 +295,80 @@ class Context(object):
|
|||
|
||||
def __enter__(self):
|
||||
self._depth += 1
|
||||
push_context(self)
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, tb):
|
||||
pop_context()
|
||||
self._depth -= 1
|
||||
if self._depth == 0:
|
||||
self.close()
|
||||
|
||||
def _get_invoked_subcommands(self):
|
||||
from warnings import warn
|
||||
warn(Warning('This API does not work properly and has been largely '
|
||||
'removed in Click 3.2 to fix a regression the '
|
||||
'introduction of this API caused. Consult the '
|
||||
'upgrade documentation for more information. For '
|
||||
'more information about this see '
|
||||
'http://click.pocoo.org/upgrading/#upgrading-to-3.2'),
|
||||
stacklevel=2)
|
||||
if self.invoked_subcommand is None:
|
||||
return []
|
||||
return [self.invoked_subcommand]
|
||||
def _set_invoked_subcommands(self, value):
|
||||
self.invoked_subcommand = \
|
||||
len(value) > 1 and '*' or value and value[0] or None
|
||||
invoked_subcommands = property(_get_invoked_subcommands,
|
||||
_set_invoked_subcommands)
|
||||
del _get_invoked_subcommands, _set_invoked_subcommands
|
||||
@contextmanager
|
||||
def scope(self, cleanup=True):
|
||||
"""This helper method can be used with the context object to promote
|
||||
it to the current thread local (see :func:`get_current_context`).
|
||||
The default behavior of this is to invoke the cleanup functions which
|
||||
can be disabled by setting `cleanup` to `False`. The cleanup
|
||||
functions are typically used for things such as closing file handles.
|
||||
|
||||
If the cleanup is intended the context object can also be directly
|
||||
used as a context manager.
|
||||
|
||||
Example usage::
|
||||
|
||||
with ctx.scope():
|
||||
assert get_current_context() is ctx
|
||||
|
||||
This is equivalent::
|
||||
|
||||
with ctx:
|
||||
assert get_current_context() is ctx
|
||||
|
||||
.. versionadded:: 5.0
|
||||
|
||||
:param cleanup: controls if the cleanup functions should be run or
|
||||
not. The default is to run these functions. In
|
||||
some situations the context only wants to be
|
||||
temporarily pushed in which case this can be disabled.
|
||||
Nested pushes automatically defer the cleanup.
|
||||
"""
|
||||
if not cleanup:
|
||||
self._depth += 1
|
||||
try:
|
||||
with self as rv:
|
||||
yield rv
|
||||
finally:
|
||||
if not cleanup:
|
||||
self._depth -= 1
|
||||
|
||||
@property
|
||||
def meta(self):
|
||||
"""This is a dictionary which is shared with all the contexts
|
||||
that are nested. It exists so that click utiltiies can store some
|
||||
state here if they need to. It is however the responsibility of
|
||||
that code to manage this dictionary well.
|
||||
|
||||
The keys are supposed to be unique dotted strings. For instance
|
||||
module paths are a good choice for it. What is stored in there is
|
||||
irrelevant for the operation of click. However what is important is
|
||||
that code that places data here adheres to the general semantics of
|
||||
the system.
|
||||
|
||||
Example usage::
|
||||
|
||||
LANG_KEY = __name__ + '.lang'
|
||||
|
||||
def set_language(value):
|
||||
ctx = get_current_context()
|
||||
ctx.meta[LANG_KEY] = value
|
||||
|
||||
def get_language():
|
||||
return get_current_context().meta.get(LANG_KEY, 'en_US')
|
||||
|
||||
.. versionadded:: 5.0
|
||||
"""
|
||||
return self._meta
|
||||
|
||||
def make_formatter(self):
|
||||
"""Creates the formatter for the help and usage output."""
|
||||
|
@ -434,11 +487,6 @@ class Context(object):
|
|||
self, callback = args[:2]
|
||||
ctx = self
|
||||
|
||||
# This is just to improve the error message in cases where old
|
||||
# code incorrectly invoked this method. This will eventually be
|
||||
# removed.
|
||||
injected_arguments = False
|
||||
|
||||
# It's also possible to invoke another command which might or
|
||||
# might not have a callback. In that case we also fill
|
||||
# in defaults and make a new context for this command.
|
||||
|
@ -453,26 +501,11 @@ class Context(object):
|
|||
for param in other_cmd.params:
|
||||
if param.name not in kwargs and param.expose_value:
|
||||
kwargs[param.name] = param.get_default(ctx)
|
||||
injected_arguments = True
|
||||
|
||||
args = args[2:]
|
||||
if getattr(callback, '__click_pass_context__', False):
|
||||
args = (ctx,) + args
|
||||
with augment_usage_errors(self):
|
||||
try:
|
||||
with ctx:
|
||||
return callback(*args, **kwargs)
|
||||
except TypeError as e:
|
||||
if not injected_arguments:
|
||||
raise
|
||||
if 'got multiple values for' in str(e):
|
||||
raise RuntimeError(
|
||||
'You called .invoke() on the context with a command '
|
||||
'but provided parameters as positional arguments. '
|
||||
'This is not supported but sometimes worked by chance '
|
||||
'in older versions of Click. To fix this see '
|
||||
'http://click.pocoo.org/upgrading/#upgrading-to-3.2')
|
||||
raise
|
||||
with ctx:
|
||||
return callback(*args, **kwargs)
|
||||
|
||||
def forward(*args, **kwargs):
|
||||
"""Similar to :meth:`invoke` but fills in default keyword
|
||||
|
@ -557,7 +590,8 @@ class BaseCommand(object):
|
|||
if key not in extra:
|
||||
extra[key] = value
|
||||
ctx = Context(self, info_name=info_name, parent=parent, **extra)
|
||||
self.parse_args(ctx, args)
|
||||
with ctx.scope(cleanup=False):
|
||||
self.parse_args(ctx, args)
|
||||
return ctx
|
||||
|
||||
def parse_args(self, ctx, args):
|
||||
|
@ -624,6 +658,8 @@ class BaseCommand(object):
|
|||
'Either switch to Python 2 or consult '
|
||||
'http://click.pocoo.org/python3/ '
|
||||
'for mitigation steps.')
|
||||
else:
|
||||
_check_for_unicode_literals()
|
||||
|
||||
if args is None:
|
||||
args = sys.argv[1:]
|
||||
|
|
|
@ -3,16 +3,18 @@ import inspect
|
|||
|
||||
from functools import update_wrapper
|
||||
|
||||
from ._compat import iteritems
|
||||
from ._compat import iteritems, _check_for_unicode_literals
|
||||
from .utils import echo
|
||||
from .globals import get_current_context
|
||||
|
||||
|
||||
def pass_context(f):
|
||||
"""Marks a callback as wanting to receive the current context
|
||||
object as first argument.
|
||||
"""
|
||||
f.__click_pass_context__ = True
|
||||
return f
|
||||
def new_func(*args, **kwargs):
|
||||
return f(get_current_context(), *args, **kwargs)
|
||||
return update_wrapper(new_func, f)
|
||||
|
||||
|
||||
def pass_obj(f):
|
||||
|
@ -20,10 +22,8 @@ def pass_obj(f):
|
|||
context onwards (:attr:`Context.obj`). This is useful if that object
|
||||
represents the state of a nested system.
|
||||
"""
|
||||
@pass_context
|
||||
def new_func(*args, **kwargs):
|
||||
ctx = args[0]
|
||||
return ctx.invoke(f, ctx.obj, *args[1:], **kwargs)
|
||||
return f(get_current_context().obj, *args, **kwargs)
|
||||
return update_wrapper(new_func, f)
|
||||
|
||||
|
||||
|
@ -50,9 +50,8 @@ def make_pass_decorator(object_type, ensure=False):
|
|||
remembered on the context if it's not there yet.
|
||||
"""
|
||||
def decorator(f):
|
||||
@pass_context
|
||||
def new_func(*args, **kwargs):
|
||||
ctx = args[0]
|
||||
ctx = get_current_context()
|
||||
if ensure:
|
||||
obj = ctx.ensure_object(object_type)
|
||||
else:
|
||||
|
@ -84,6 +83,7 @@ def _make_command(f, name, attrs, cls):
|
|||
else:
|
||||
help = inspect.cleandoc(help)
|
||||
attrs['help'] = help
|
||||
_check_for_unicode_literals()
|
||||
return cls(name=name or f.__name__.lower(),
|
||||
callback=f, params=params, **attrs)
|
||||
|
||||
|
@ -111,7 +111,9 @@ def command(name=None, cls=None, **attrs):
|
|||
if cls is None:
|
||||
cls = Command
|
||||
def decorator(f):
|
||||
return _make_command(f, name, attrs, cls)
|
||||
cmd = _make_command(f, name, attrs, cls)
|
||||
cmd.__doc__ = f.__doc__
|
||||
return cmd
|
||||
return decorator
|
||||
|
||||
|
||||
|
|
|
@ -18,12 +18,6 @@ def iter_rows(rows, col_count):
|
|||
yield row + ('',) * (col_count - len(row))
|
||||
|
||||
|
||||
def add_subsequent_indent(text, subsequent_indent):
|
||||
lines = text.splitlines()
|
||||
lines = lines[:1] + [subsequent_indent + line for line in lines[1:]]
|
||||
return '\n'.join(lines)
|
||||
|
||||
|
||||
def wrap_text(text, width=78, initial_indent='', subsequent_indent='',
|
||||
preserve_paragraphs=False):
|
||||
"""A helper function that intelligently wraps text. By default, it
|
||||
|
@ -46,13 +40,11 @@ def wrap_text(text, width=78, initial_indent='', subsequent_indent='',
|
|||
"""
|
||||
from ._textwrap import TextWrapper
|
||||
text = text.expandtabs()
|
||||
post_wrap_indent = subsequent_indent[:-1]
|
||||
subsequent_indent = subsequent_indent[-1:]
|
||||
wrapper = TextWrapper(width, initial_indent=initial_indent,
|
||||
subsequent_indent=subsequent_indent,
|
||||
replace_whitespace=False)
|
||||
if not preserve_paragraphs:
|
||||
return add_subsequent_indent(wrapper.fill(text), post_wrap_indent)
|
||||
return wrapper.fill(text)
|
||||
|
||||
p = []
|
||||
buf = []
|
||||
|
@ -83,11 +75,9 @@ def wrap_text(text, width=78, initial_indent='', subsequent_indent='',
|
|||
for indent, raw, text in p:
|
||||
with wrapper.extra_indent(' ' * indent):
|
||||
if raw:
|
||||
rv.append(add_subsequent_indent(wrapper.indent_only(text),
|
||||
post_wrap_indent))
|
||||
rv.append(wrapper.indent_only(text))
|
||||
else:
|
||||
rv.append(add_subsequent_indent(wrapper.fill(text),
|
||||
post_wrap_indent))
|
||||
rv.append(wrapper.fill(text))
|
||||
|
||||
return '\n\n'.join(rv)
|
||||
|
||||
|
@ -133,14 +123,23 @@ class HelpFormatter(object):
|
|||
:param args: whitespace separated list of arguments.
|
||||
:param prefix: the prefix for the first line.
|
||||
"""
|
||||
prefix = '%*s%s' % (self.current_indent, prefix, prog)
|
||||
self.write(prefix)
|
||||
usage_prefix = '%*s%s ' % (self.current_indent, prefix, prog)
|
||||
text_width = self.width - self.current_indent
|
||||
|
||||
text_width = max(self.width - self.current_indent - term_len(prefix), 10)
|
||||
indent = ' ' * (term_len(prefix) + 1)
|
||||
self.write(wrap_text(args, text_width,
|
||||
initial_indent=' ',
|
||||
subsequent_indent=indent))
|
||||
if text_width >= (term_len(usage_prefix) + 20):
|
||||
# The arguments will fit to the right of the prefix.
|
||||
indent = ' ' * term_len(usage_prefix)
|
||||
self.write(wrap_text(args, text_width,
|
||||
initial_indent=usage_prefix,
|
||||
subsequent_indent=indent))
|
||||
else:
|
||||
# The prefix is too long, put the arguments on the next line.
|
||||
self.write(usage_prefix)
|
||||
self.write('\n')
|
||||
indent = ' ' * (max(self.current_indent, term_len(prefix)) + 4)
|
||||
self.write(wrap_text(args, text_width,
|
||||
initial_indent=indent,
|
||||
subsequent_indent=indent))
|
||||
|
||||
self.write('\n')
|
||||
|
||||
|
|
48
click/globals.py
Normal file
48
click/globals.py
Normal file
|
@ -0,0 +1,48 @@
|
|||
from threading import local
|
||||
|
||||
|
||||
_local = local()
|
||||
|
||||
|
||||
def get_current_context(silent=False):
|
||||
"""Returns the current click context. This can be used as a way to
|
||||
access the current context object from anywhere. This is a more implicit
|
||||
alternative to the :func:`pass_context` decorator. This function is
|
||||
primarily useful for helpers such as :func:`echo` which might be
|
||||
interested in changing it's behavior based on the current context.
|
||||
|
||||
To push the current context, :meth:`Context.scope` can be used.
|
||||
|
||||
.. versionadded:: 5.0
|
||||
|
||||
:param silent: is set to `True` the return value is `None` if no context
|
||||
is available. The default behavior is to raise a
|
||||
:exc:`RuntimeError`.
|
||||
"""
|
||||
try:
|
||||
return getattr(_local, 'stack')[-1]
|
||||
except (AttributeError, IndexError):
|
||||
if not silent:
|
||||
raise RuntimeError('There is no active click context.')
|
||||
|
||||
|
||||
def push_context(ctx):
|
||||
"""Pushes a new context to the current stack."""
|
||||
_local.__dict__.setdefault('stack', []).append(ctx)
|
||||
|
||||
|
||||
def pop_context():
|
||||
"""Removes the top level from the stack."""
|
||||
_local.stack.pop()
|
||||
|
||||
|
||||
def resolve_color_default(color=None):
|
||||
""""Internal helper to get the default value of the color flag. If a
|
||||
value is passed it's returned unchanged, otherwise it's looked up from
|
||||
the current context.
|
||||
"""
|
||||
if color is not None:
|
||||
return color
|
||||
ctx = get_current_context(silent=True)
|
||||
if ctx is not None:
|
||||
return ctx.color
|
|
@ -7,6 +7,7 @@ from ._compat import raw_input, text_type, string_types, \
|
|||
from .utils import echo
|
||||
from .exceptions import Abort, UsageError
|
||||
from .types import convert_type
|
||||
from .globals import resolve_color_default
|
||||
|
||||
|
||||
# The prompt functions to use. The doc tools currently override these
|
||||
|
@ -68,6 +69,11 @@ def prompt(text, default=None, hide_input=False,
|
|||
echo(text, nl=False, err=err)
|
||||
return f('')
|
||||
except (KeyboardInterrupt, EOFError):
|
||||
# getpass doesn't print a newline if the user aborts input with ^C.
|
||||
# Allegedly this behavior is inherited from getpass(3).
|
||||
# A doc bug has been filed at https://bugs.python.org/issue24711
|
||||
if hide_input:
|
||||
echo(None, err=err)
|
||||
raise Abort()
|
||||
|
||||
if value_proc is None:
|
||||
|
@ -197,6 +203,7 @@ def echo_via_pager(text, color=None):
|
|||
:param color: controls if the pager supports ANSI colors or not. The
|
||||
default is autodetection.
|
||||
"""
|
||||
color = resolve_color_default(color)
|
||||
if not isinstance(text, string_types):
|
||||
text = text_type(text)
|
||||
from ._termui_impl import pager
|
||||
|
@ -287,6 +294,7 @@ def progressbar(iterable=None, length=None, label=None, show_eta=True,
|
|||
which is not the case by default.
|
||||
"""
|
||||
from ._termui_impl import ProgressBar
|
||||
color = resolve_color_default(color)
|
||||
return ProgressBar(iterable=iterable, length=length, show_eta=show_eta,
|
||||
show_percent=show_percent, show_pos=show_pos,
|
||||
item_show_func=item_show_func, fill_char=fill_char,
|
||||
|
|
|
@ -277,8 +277,14 @@ class CliRunner(object):
|
|||
except SystemExit as e:
|
||||
if e.code != 0:
|
||||
exception = e
|
||||
exit_code = e.code
|
||||
|
||||
exc_info = sys.exc_info()
|
||||
|
||||
exit_code = e.code
|
||||
if not isinstance(exit_code, int):
|
||||
sys.stdout.write(str(exit_code))
|
||||
sys.stdout.write('\n')
|
||||
exit_code = 1
|
||||
except Exception as e:
|
||||
if not catch_exceptions:
|
||||
raise
|
||||
|
|
|
@ -2,6 +2,8 @@ import os
|
|||
import sys
|
||||
from collections import deque
|
||||
|
||||
from .globals import resolve_color_default
|
||||
|
||||
from ._compat import text_type, open_stream, get_filesystem_encoding, \
|
||||
get_streerror, string_types, PY2, binary_streams, text_streams, \
|
||||
filename_to_ui, auto_wrap_for_ansi, strip_ansi, should_strip_ansi, \
|
||||
|
@ -197,6 +199,10 @@ class LazyFile(object):
|
|||
def __exit__(self, exc_type, exc_value, tb):
|
||||
self.close_intelligently()
|
||||
|
||||
def __iter__(self):
|
||||
self.open()
|
||||
return iter(self._f)
|
||||
|
||||
|
||||
class KeepOpenFile(object):
|
||||
|
||||
|
@ -215,6 +221,9 @@ class KeepOpenFile(object):
|
|||
def __repr__(self):
|
||||
return repr(self._file)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._file)
|
||||
|
||||
|
||||
def echo(message=None, file=None, nl=True, err=False, color=None):
|
||||
"""Prints a message plus a newline to the given file or stdout. On
|
||||
|
@ -266,6 +275,13 @@ def echo(message=None, file=None, nl=True, err=False, color=None):
|
|||
if message is not None and not isinstance(message, echo_native_types):
|
||||
message = text_type(message)
|
||||
|
||||
if nl:
|
||||
message = message or u''
|
||||
if isinstance(message, text_type):
|
||||
message += u'\n'
|
||||
else:
|
||||
message += b'\n'
|
||||
|
||||
# If there is a message, and we're in Python 3, and the value looks
|
||||
# like bytes, we manually need to find the binary stream and write the
|
||||
# message in there. This is done separately so that most stream
|
||||
|
@ -276,8 +292,6 @@ def echo(message=None, file=None, nl=True, err=False, color=None):
|
|||
if binary_file is not None:
|
||||
file.flush()
|
||||
binary_file.write(message)
|
||||
if nl:
|
||||
binary_file.write(b'\n')
|
||||
binary_file.flush()
|
||||
return
|
||||
|
||||
|
@ -287,6 +301,7 @@ def echo(message=None, file=None, nl=True, err=False, color=None):
|
|||
# to strip the color or we use the colorama support to translate the
|
||||
# ansi codes to API calls.
|
||||
if message and not is_bytes(message):
|
||||
color = resolve_color_default(color)
|
||||
if should_strip_ansi(file, color):
|
||||
message = strip_ansi(message)
|
||||
elif WIN:
|
||||
|
@ -297,8 +312,6 @@ def echo(message=None, file=None, nl=True, err=False, color=None):
|
|||
|
||||
if message:
|
||||
file.write(message)
|
||||
if nl:
|
||||
file.write(u'\n')
|
||||
file.flush()
|
||||
|
||||
|
||||
|
|
37
docs/_themes/LICENSE
vendored
37
docs/_themes/LICENSE
vendored
|
@ -1,37 +0,0 @@
|
|||
Copyright (c) 2014 by Armin Ronacher.
|
||||
|
||||
Some rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms of the theme, with or
|
||||
without modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following
|
||||
disclaimer in the documentation and/or other materials provided
|
||||
with the distribution.
|
||||
|
||||
* The names of the contributors may not be used to endorse or
|
||||
promote products derived from this software without specific
|
||||
prior written permission.
|
||||
|
||||
We kindly ask you to only use these themes in an unmodified manner just
|
||||
for Click and Click-related products, not for unrelated projects. If you
|
||||
like the visual style and want to use it for your own projects, please
|
||||
consider making some larger changes to the themes (such as changing
|
||||
font faces, sizes, colors or margins).
|
||||
|
||||
THIS THEME IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS THEME, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
31
docs/_themes/README
vendored
31
docs/_themes/README
vendored
|
@ -1,31 +0,0 @@
|
|||
Flask Sphinx Styles
|
||||
===================
|
||||
|
||||
This repository contains sphinx styles for Flask and Flask related
|
||||
projects. To use this style in your Sphinx documentation, follow
|
||||
this guide:
|
||||
|
||||
1. put this folder as _themes into your docs folder. Alternatively
|
||||
you can also use git submodules to check out the contents there.
|
||||
2. add this to your conf.py:
|
||||
|
||||
sys.path.append(os.path.abspath('_themes'))
|
||||
html_theme_path = ['_themes']
|
||||
html_theme = 'flask'
|
||||
|
||||
The following themes exist:
|
||||
|
||||
- 'flask' - the standard flask documentation theme for large
|
||||
projects
|
||||
- 'flask_small' - small one-page theme. Intended to be used by
|
||||
very small addon libraries for flask.
|
||||
|
||||
The following options exist for the flask_small theme:
|
||||
|
||||
[options]
|
||||
index_logo = '' filename of a picture in _static
|
||||
to be used as replacement for the
|
||||
h1 in the index.rst file.
|
||||
index_logo_height = 120px height of the index logo
|
||||
github_fork = '' repository name on github for the
|
||||
"fork me" badge
|
20
docs/_themes/click/layout.html
vendored
20
docs/_themes/click/layout.html
vendored
|
@ -1,20 +0,0 @@
|
|||
{%- extends "basic/layout.html" %}
|
||||
{%- block extrahead %}
|
||||
{{ super() }}
|
||||
{% endblock %}
|
||||
{%- block relbar2 %}{% endblock %}
|
||||
{% block header %}
|
||||
{{ super() }}
|
||||
{% if pagename == 'index' %}
|
||||
<div class=indexwrapper>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{%- block footer %}
|
||||
<div class="footer">
|
||||
© Copyright {{ copyright }}.
|
||||
Created using <a href="http://sphinx.pocoo.org/">Sphinx</a>.
|
||||
</div>
|
||||
{% if pagename == 'index' %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{%- endblock %}
|
19
docs/_themes/click/relations.html
vendored
19
docs/_themes/click/relations.html
vendored
|
@ -1,19 +0,0 @@
|
|||
<h3>Related Topics</h3>
|
||||
<ul>
|
||||
<li><a href="{{ pathto(master_doc) }}">Documentation overview</a><ul>
|
||||
{%- for parent in parents %}
|
||||
<li><a href="{{ parent.link|e }}">{{ parent.title }}</a><ul>
|
||||
{%- endfor %}
|
||||
{%- if prev %}
|
||||
<li>Previous: <a href="{{ prev.link|e }}" title="{{ _('previous chapter')
|
||||
}}">{{ prev.title }}</a></li>
|
||||
{%- endif %}
|
||||
{%- if next %}
|
||||
<li>Next: <a href="{{ next.link|e }}" title="{{ _('next chapter')
|
||||
}}">{{ next.title }}</a></li>
|
||||
{%- endif %}
|
||||
{%- for parent in parents %}
|
||||
</ul></li>
|
||||
{%- endfor %}
|
||||
</ul></li>
|
||||
</ul>
|
403
docs/_themes/click/static/click.css_t
vendored
403
docs/_themes/click/static/click.css_t
vendored
|
@ -1,403 +0,0 @@
|
|||
/*
|
||||
* click.css_t
|
||||
* ~~~~~~~~~~~
|
||||
*
|
||||
* :copyright: Copyright 2014 by Armin Ronacher.
|
||||
* :license: Flask Design License, see LICENSE for details.
|
||||
*/
|
||||
|
||||
{% set page_width = '940px' %}
|
||||
{% set sidebar_width = '220px' %}
|
||||
|
||||
@import url("basic.css");
|
||||
|
||||
@import url(http://fonts.googleapis.com/css?family=Ubuntu+Mono:400,400italic,700,700italic);
|
||||
@import url(http://fonts.googleapis.com/css?family=Open+Sans:300,400);
|
||||
|
||||
/* -- page layout ----------------------------------------------------------- */
|
||||
|
||||
body {
|
||||
font-family: 'Ubuntu Mono', 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono';
|
||||
font-size: 15px;
|
||||
background-color: white;
|
||||
color: #000;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div.document {
|
||||
width: {{ page_width }};
|
||||
margin: 30px auto 0 auto;
|
||||
}
|
||||
|
||||
div.documentwrapper {
|
||||
float: left;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
div.bodywrapper {
|
||||
margin: 0 0 0 {{ sidebar_width }};
|
||||
}
|
||||
|
||||
div.sphinxsidebar {
|
||||
width: {{ sidebar_width }};
|
||||
}
|
||||
|
||||
div.sphinxsidebar h3,
|
||||
div.sphinxsidebar h4 {
|
||||
font-family: 'Open Sans', 'Helvetica', 'Arial', sans-serif;
|
||||
font-weight: 300;
|
||||
margin: 20px 0px 10px 0px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
hr {
|
||||
border: 1px solid #B1B4B6;
|
||||
}
|
||||
|
||||
div.body {
|
||||
background-color: #ffffff;
|
||||
color: #3E4349;
|
||||
padding: 0 30px 0 30px;
|
||||
}
|
||||
|
||||
img.floatingflask {
|
||||
padding: 0 0 10px 10px;
|
||||
float: right;
|
||||
}
|
||||
|
||||
div.footer {
|
||||
width: {{ page_width }};
|
||||
margin: 20px auto 30px auto;
|
||||
font-size: 14px;
|
||||
color: #888;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
div.footer a {
|
||||
color: #888;
|
||||
}
|
||||
|
||||
div.related {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div.sphinxsidebar a {
|
||||
color: #444;
|
||||
text-decoration: none;
|
||||
border-bottom: 1px dotted #999;
|
||||
}
|
||||
|
||||
div.sphinxsidebar a:hover {
|
||||
border-bottom: 1px solid #999;
|
||||
}
|
||||
|
||||
div.sphinxsidebar {
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
div.sphinxsidebarwrapper {
|
||||
padding: 18px 10px;
|
||||
}
|
||||
|
||||
div.sphinxsidebarwrapper p.logo {
|
||||
padding: 0 0 20px 0;
|
||||
margin: 0;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
div.sphinxsidebar h3,
|
||||
div.sphinxsidebar h4 {
|
||||
color: #444;
|
||||
font-size: 18px;
|
||||
font-weight: normal;
|
||||
margin: 0 0 5px 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div.sphinxsidebar h4 {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
div.sphinxsidebar h3 a {
|
||||
color: #444;
|
||||
}
|
||||
|
||||
div.sphinxsidebar p.logo a,
|
||||
div.sphinxsidebar h3 a,
|
||||
div.sphinxsidebar p.logo a:hover,
|
||||
div.sphinxsidebar h3 a:hover {
|
||||
border: none;
|
||||
}
|
||||
|
||||
div.sphinxsidebar p {
|
||||
color: #555;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
div.sphinxsidebar ul {
|
||||
margin: 10px 0;
|
||||
padding: 0;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
div.sphinxsidebar input {
|
||||
border: 1px solid #ccc;
|
||||
font-family: 'Ubuntu Mono', 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono';
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
div.sphinxsidebar #searchbox input[type="text"] {
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
/* -- body styles ----------------------------------------------------------- */
|
||||
|
||||
a {
|
||||
color: #5D2CD1;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #7546E3;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
div.body h1,
|
||||
div.body h2,
|
||||
div.body h3,
|
||||
div.body h4,
|
||||
div.body h5,
|
||||
div.body h6 {
|
||||
font-family: 'Open Sans', 'Helvetica', 'Arial', sans-serif;
|
||||
font-weight: 400;
|
||||
margin: 30px 0px 10px 0px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div.indexwrapper h1 {
|
||||
text-indent: -999999px;
|
||||
background: url(click.png) no-repeat center center;
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
|
||||
div.indexwrapper h1 {
|
||||
background: url(click@2x.png) no-repeat center center;
|
||||
background-size: 420px 175px;
|
||||
}
|
||||
}
|
||||
|
||||
div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; }
|
||||
div.body h2 { font-size: 180%; }
|
||||
div.body h3 { font-size: 150%; }
|
||||
div.body h4 { font-size: 130%; }
|
||||
div.body h5 { font-size: 100%; }
|
||||
div.body h6 { font-size: 100%; }
|
||||
|
||||
a.headerlink {
|
||||
color: #ddd;
|
||||
padding: 0 4px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a.headerlink:hover {
|
||||
color: #444;
|
||||
background: #eaeaea;
|
||||
}
|
||||
|
||||
div.body p, div.body dd, div.body li {
|
||||
line-height: 1.4em;
|
||||
}
|
||||
|
||||
div.admonition {
|
||||
background: #fafafa;
|
||||
margin: 20px -30px;
|
||||
padding: 10px 30px;
|
||||
border-top: 1px solid #ccc;
|
||||
border-bottom: 1px solid #ccc;
|
||||
}
|
||||
|
||||
div.admonition tt.xref, div.admonition a tt {
|
||||
border-bottom: 1px solid #fafafa;
|
||||
}
|
||||
|
||||
dd div.admonition {
|
||||
margin-left: -60px;
|
||||
padding-left: 60px;
|
||||
}
|
||||
|
||||
div.admonition p.admonition-title {
|
||||
font-family: 'Open Sans', 'Helvetica', 'Arial', sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: 24px;
|
||||
margin: 0 0 10px 0;
|
||||
padding: 0;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
div.admonition p.last {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
div.highlight {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
dt:target, .highlight {
|
||||
background: #FAF3E8;
|
||||
}
|
||||
|
||||
div.note {
|
||||
background-color: #eee;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
div.seealso {
|
||||
background-color: #ffc;
|
||||
border: 1px solid #ff6;
|
||||
}
|
||||
|
||||
div.topic {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
p.admonition-title {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
p.admonition-title:after {
|
||||
content: ":";
|
||||
}
|
||||
|
||||
pre, tt {
|
||||
font-family: 'Ubuntu Mono', 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono';
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
img.screenshot {
|
||||
}
|
||||
|
||||
tt.descname, tt.descclassname {
|
||||
font-size: 0.95em;
|
||||
}
|
||||
|
||||
tt.descname {
|
||||
padding-right: 0.08em;
|
||||
}
|
||||
|
||||
img.screenshot {
|
||||
-moz-box-shadow: 2px 2px 4px #eee;
|
||||
-webkit-box-shadow: 2px 2px 4px #eee;
|
||||
box-shadow: 2px 2px 4px #eee;
|
||||
}
|
||||
|
||||
table.docutils {
|
||||
border: 1px solid #888;
|
||||
-moz-box-shadow: 2px 2px 4px #eee;
|
||||
-webkit-box-shadow: 2px 2px 4px #eee;
|
||||
box-shadow: 2px 2px 4px #eee;
|
||||
}
|
||||
|
||||
table.docutils td, table.docutils th {
|
||||
border: 1px solid #888;
|
||||
padding: 0.25em 0.7em;
|
||||
}
|
||||
|
||||
table.field-list, table.footnote {
|
||||
border: none;
|
||||
-moz-box-shadow: none;
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
table.footnote {
|
||||
margin: 15px 0;
|
||||
width: 100%;
|
||||
border: 1px solid #eee;
|
||||
background: #fdfdfd;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
table.footnote + table.footnote {
|
||||
margin-top: -15px;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
table.field-list th {
|
||||
padding: 0 0.8em 0 0;
|
||||
}
|
||||
|
||||
table.field-list td {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
table.footnote td.label {
|
||||
width: 0px;
|
||||
padding: 0.3em 0 0.3em 0.5em;
|
||||
}
|
||||
|
||||
table.footnote td {
|
||||
padding: 0.3em 0.5em;
|
||||
}
|
||||
|
||||
dl {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
dl dd {
|
||||
margin-left: 30px;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin: 0 0 0 30px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ul, ol {
|
||||
margin: 10px 0 10px 30px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
pre {
|
||||
padding: 7px 0 7px 30px;
|
||||
margin: 15px 0;
|
||||
line-height: 1.3em;
|
||||
}
|
||||
|
||||
tt {
|
||||
background-color: #ecf0f3;
|
||||
color: #222;
|
||||
/* padding: 1px 2px; */
|
||||
}
|
||||
|
||||
tt.xref, a tt {
|
||||
background-color: #FBFBFB;
|
||||
border-bottom: 1px solid white;
|
||||
}
|
||||
|
||||
a.reference {
|
||||
text-decoration: none;
|
||||
border-bottom: 1px dotted #5D2CD1;
|
||||
}
|
||||
|
||||
a.reference:hover {
|
||||
border-bottom: 1px solid #7546E3;
|
||||
}
|
||||
|
||||
a.footnote-reference {
|
||||
text-decoration: none;
|
||||
font-size: 0.7em;
|
||||
vertical-align: top;
|
||||
border-bottom: 1px dotted #5D2CD1;
|
||||
}
|
||||
|
||||
a.footnote-reference:hover {
|
||||
border-bottom: 1px solid #7546E3;
|
||||
}
|
||||
|
||||
a:hover tt {
|
||||
background: #EEE;
|
||||
}
|
4
docs/_themes/click/theme.conf
vendored
4
docs/_themes/click/theme.conf
vendored
|
@ -1,4 +0,0 @@
|
|||
[theme]
|
||||
inherit = basic
|
||||
stylesheet = click.css
|
||||
pygments_style = tango
|
|
@ -338,3 +338,42 @@ your own commands and commands from another application are discouraged
|
|||
and if you can avoid it, you should. It's a much better idea to have
|
||||
everything below a subcommand be forwarded to another application than to
|
||||
handle some arguments yourself.
|
||||
|
||||
|
||||
Global Context Access
|
||||
---------------------
|
||||
|
||||
.. versionadded:: 5.0
|
||||
|
||||
Starting with Click 5.0 it is possible to access the current context from
|
||||
anywhere within the same through through the use of the
|
||||
:func:`get_current_context` function which returns it. This is primarily
|
||||
useful for accessing the context bound object as well as some flags that
|
||||
are stored on it to customize the runtime behavior. For instance the
|
||||
:func:`echo` function does this to infer the default value of the `color`
|
||||
flag.
|
||||
|
||||
Example usage::
|
||||
|
||||
def get_current_command_name():
|
||||
return click.get_current_context().info_name
|
||||
|
||||
It should be noted that this only works within the current thread. If you
|
||||
spawn additional threads then those threads will not have the ability to
|
||||
refer to the current context. If you want to give another thread the
|
||||
ability to refer to this context you need to use the context within the
|
||||
thread as a context manager::
|
||||
|
||||
def spawn_thread(ctx, func):
|
||||
def wrapper():
|
||||
with ctx:
|
||||
func()
|
||||
t = threading.Thread(target=wrapper)
|
||||
t.start()
|
||||
return t
|
||||
|
||||
Now the thread function can access the context like the main thread would
|
||||
do. However if you do use this for threading you need to be very careful
|
||||
as the vast majority of the context is not thread safe! You are only
|
||||
allowed to read from the context, but not to perform any modifications on
|
||||
it.
|
||||
|
|
|
@ -106,6 +106,8 @@ Context
|
|||
.. autoclass:: Context
|
||||
:members:
|
||||
|
||||
.. autofunction:: get_current_context
|
||||
|
||||
Types
|
||||
-----
|
||||
|
||||
|
|
|
@ -331,12 +331,12 @@ process the result of the previous command. There are various ways in
|
|||
which this can be facilitated. The most obvious way is to store a value
|
||||
on the context object and process it from function to function. This
|
||||
works by decorating a function with :func:`pass_context` after which the
|
||||
context object is provided and a subcommand can store it's data there.
|
||||
context object is provided and a subcommand can store its data there.
|
||||
|
||||
Another way to accomplish this is to setup pipelines by returning
|
||||
processing functions. Think of it like this: when a subcommand gets
|
||||
invoked it processes all of it's parameters and comes up with a plan of
|
||||
how to do it's processing. At that point it then returns a processing
|
||||
invoked it processes all of its parameters and comes up with a plan of
|
||||
how to do its processing. At that point it then returns a processing
|
||||
function and returns.
|
||||
|
||||
Where do the returned functions go? The chained multicommand can register
|
||||
|
|
|
@ -17,7 +17,6 @@ import datetime
|
|||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
sys.path.append(os.path.abspath('_themes'))
|
||||
sys.path.append(os.path.abspath('..'))
|
||||
sys.path.append(os.path.abspath('.'))
|
||||
|
||||
|
@ -92,7 +91,7 @@ exclude_patterns = ['_build']
|
|||
|
||||
# The theme to use for HTML and HTML Help pages. Major themes that come with
|
||||
# Sphinx are currently 'default' and 'sphinxdoc'.
|
||||
html_theme = 'click'
|
||||
#html_theme = 'default'
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
|
@ -101,7 +100,7 @@ html_theme_options = {
|
|||
}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
html_theme_path = ['_themes']
|
||||
#html_theme_path = ['_themes']
|
||||
|
||||
# The name for this set of Sphinx documents. If None, it defaults to
|
||||
# "<project> v<release> documentation".
|
||||
|
|
25
docs/contrib.rst
Normal file
25
docs/contrib.rst
Normal file
|
@ -0,0 +1,25 @@
|
|||
.. _contrib:
|
||||
|
||||
=============
|
||||
click-contrib
|
||||
=============
|
||||
|
||||
As the userbase of Click grows, more and more major feature requests pop up in
|
||||
Click's bugtracker. As reasonable as it may be for those features to be bundled
|
||||
with Click instead of being a standalone project, many of those requested
|
||||
features are either highly experimental or have unproven practical use, while
|
||||
potentially being a burden to maintain.
|
||||
|
||||
This is why click-contrib_ exists. The GitHub organization is a collection of
|
||||
possibly experimental third-party packages whose featureset does not belong
|
||||
into Click, but also a playground for major features that may be added to Click
|
||||
in the future. It is also meant to coordinate and concentrate effort on writing
|
||||
third-party extensions for Click, and to ease the effort of searching for such
|
||||
extensions. In that sense it could be described as a low-maintenance
|
||||
alternative to extension repositories of other frameworks.
|
||||
|
||||
Please note that the quality and stability of those packages may be different
|
||||
than what you expect from Click itself. While published under a common
|
||||
organization, they are still projects separate from Click.
|
||||
|
||||
.. _click-contrib: https://github.com/click-contrib/
|
|
@ -93,6 +93,7 @@ Miscellaneous Pages
|
|||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
contrib
|
||||
changelog
|
||||
upgrading
|
||||
license
|
||||
|
|
|
@ -330,6 +330,27 @@ replaced with the :func:`password_option` decorator:
|
|||
def encrypt(password):
|
||||
click.echo('Encrypting password to %s' % password.encode('rot13'))
|
||||
|
||||
Dynamic Defaults for Prompts
|
||||
----------------------------
|
||||
|
||||
The ``auto_envvar_prefix`` and ``default_map`` options for the context
|
||||
allow the program to read option values from the environment or a
|
||||
configuration file. However, this overrides the prompting mechanism, so
|
||||
that the user does not get the option to change the value interactively.
|
||||
|
||||
If you want to let the user configure the default value, but still be
|
||||
prompted if the option isn't specified on the command line, you can do so
|
||||
by supplying a callable as the default value. For example, to get a default
|
||||
from the environment:
|
||||
|
||||
.. click:example::
|
||||
|
||||
@click.command()
|
||||
@click.option('--username', prompt=True,
|
||||
default=lambda: os.environ.get('USER', ''))
|
||||
def hello(username):
|
||||
print("Hello,", username)
|
||||
|
||||
Callbacks and Eager Options
|
||||
---------------------------
|
||||
|
||||
|
|
|
@ -152,3 +152,26 @@ Python 3 bug tracker:
|
|||
* `LC_CTYPE=C: pydoc leaves terminal in an unusable state
|
||||
<http://bugs.python.org/issue21398>`_ (this is relevant to Click
|
||||
because the pager support is provided by the stdlib pydoc module)
|
||||
|
||||
Unicode Literals
|
||||
----------------
|
||||
|
||||
Starting with Click 5.0 there will be a warning for the use of the
|
||||
``unicode_literals`` future import in Python 2. This has been done due to
|
||||
the negative consequences of this import with regards to unintentionally
|
||||
causing bugs due to introducing Unicode data to APIs that are incapable of
|
||||
handling them. For some examples of this issue, see the discussion on
|
||||
this github issue: `python-future#22
|
||||
<https://github.com/PythonCharmers/python-future/issues/22>`_.
|
||||
|
||||
If you use ``unicode_literals`` in any file that defines a Click command
|
||||
or that invokes a click command you will be given a warning. You are
|
||||
strongly encouraged to not use ``unicode_literals`` and instead use
|
||||
explicit ``u`` prefixes for your Unicode strings.
|
||||
|
||||
If you do want to ignore the warning and continue using
|
||||
``unicode_literals`` on your own peril, you can disable the warning as
|
||||
follows::
|
||||
|
||||
import click
|
||||
click.disable_unicode_literals_warning = True
|
||||
|
|
|
@ -13,14 +13,14 @@ Why would you want to do that? There are a bunch of reasons:
|
|||
module the Python interpreter loads has an incorrect name. This might
|
||||
sound like a small issue but it has quite significant implications.
|
||||
|
||||
The first module is not called by it's actual name, but the
|
||||
The first module is not called by its actual name, but the
|
||||
interpreter renames it to ``__main__``. While that is a perfectly
|
||||
valid name it means that if another piece of code wants to import from
|
||||
that module it will trigger the import a second time under it's real
|
||||
name and all the sudden your code is imported twice.
|
||||
that module it will trigger the import a second time under its real
|
||||
name and all of a sudden your code is imported twice.
|
||||
|
||||
2. Not on all platforms are things that easy to execute. On Linux and OS
|
||||
X you can add a comment to the beginning of the file (``#/usr/bin/env
|
||||
X you can add a comment to the beginning of the file (``#!/usr/bin/env
|
||||
python``) and your script works like an executable (assuming it has
|
||||
the executable bit set). This however does not work on Windows.
|
||||
While on Windows you can associate interpreters with file extensions
|
||||
|
|
|
@ -38,6 +38,29 @@ Example::
|
|||
assert result.exit_code == 0
|
||||
assert result.output == 'Hello Peter!\n'
|
||||
|
||||
For subcommand testing, a subcommand name must be specified in the `args` parameter of :meth:`CliRunner.invoke` method.
|
||||
|
||||
Example::
|
||||
|
||||
import click
|
||||
from click.testing import CliRunner
|
||||
|
||||
def test_sync():
|
||||
@click.group()
|
||||
@click.option('--debug/--no-debug', default=False)
|
||||
def cli(debug):
|
||||
click.echo('Debug mode is %s' % ('on' if debug else 'off'))
|
||||
|
||||
@cli.command()
|
||||
def sync():
|
||||
click.echo('Syncing')
|
||||
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(cli, ['--debug', 'sync'])
|
||||
assert result.exit_code == 0
|
||||
assert 'Debug mode is on' in result.output
|
||||
assert 'Syncing' in result.output
|
||||
|
||||
File System Isolation
|
||||
---------------------
|
||||
|
||||
|
|
|
@ -60,7 +60,7 @@ hard:
|
|||
full understanding of the command line how the parser is going to
|
||||
behave. This goes against Click's ambitions of dispatching to
|
||||
subparsers.
|
||||
* argparse currently does not support disabling of interspearsed
|
||||
* argparse currently does not support disabling of interspersed
|
||||
arguments. Without this feature it's not possible to safely implement
|
||||
Click's nested parsing nature.
|
||||
|
||||
|
|
|
@ -103,37 +103,6 @@ def test_chaining_with_arguments(runner):
|
|||
]
|
||||
|
||||
|
||||
def test_context_subcommand_info_sync(recwarn):
|
||||
@click.command()
|
||||
def cli():
|
||||
pass
|
||||
|
||||
def _assert_warning():
|
||||
assert 'removed in Click 3.2' in str(recwarn.pop(Warning).message)
|
||||
|
||||
ctx = click.Context(cli, info_name='cli')
|
||||
|
||||
assert ctx.invoked_subcommand is None
|
||||
|
||||
ctx.invoked_subcommand = 'foo'
|
||||
assert ctx.invoked_subcommand == 'foo'
|
||||
assert ctx.invoked_subcommands == ['foo']
|
||||
|
||||
ctx.invoked_subcommands = ['foo']
|
||||
assert ctx.invoked_subcommand == 'foo'
|
||||
assert ctx.invoked_subcommands == ['foo']
|
||||
|
||||
ctx.invoked_subcommands = []
|
||||
assert ctx.invoked_subcommand is None
|
||||
assert ctx.invoked_subcommands == []
|
||||
|
||||
ctx.invoked_subcommands = ['foo', 'bar']
|
||||
assert ctx.invoked_subcommand == '*'
|
||||
assert ctx.invoked_subcommands == ['*']
|
||||
|
||||
assert 'removed in Click 3.2' in str(recwarn.pop(Warning).message)
|
||||
|
||||
|
||||
def test_pipeline(runner):
|
||||
@click.group(chain=True, invoke_without_command=True)
|
||||
@click.option('-i', '--input', type=click.File('r'))
|
||||
|
|
|
@ -19,23 +19,6 @@ def test_other_command_invoke(runner):
|
|||
assert result.output == '42\n'
|
||||
|
||||
|
||||
def test_other_command_invoke_invalid_custom_error(runner):
|
||||
@click.command()
|
||||
@click.pass_context
|
||||
def cli(ctx):
|
||||
return ctx.invoke(other_cmd, 42)
|
||||
|
||||
@click.command()
|
||||
@click.argument('arg', type=click.INT)
|
||||
def other_cmd(arg):
|
||||
click.echo(arg)
|
||||
|
||||
result = runner.invoke(cli, [])
|
||||
assert isinstance(result.exception, RuntimeError)
|
||||
assert 'upgrading-to-3.2' in str(result.exception)
|
||||
assert click.__version__ < '5.0'
|
||||
|
||||
|
||||
def test_other_command_forward(runner):
|
||||
cli = click.Group()
|
||||
|
||||
|
|
|
@ -110,3 +110,77 @@ def test_multi_enter(runner):
|
|||
result = runner.invoke(cli, [])
|
||||
assert result.exception is None
|
||||
assert called == [True]
|
||||
|
||||
|
||||
def test_global_context_object(runner):
|
||||
@click.command()
|
||||
@click.pass_context
|
||||
def cli(ctx):
|
||||
assert click.get_current_context() is ctx
|
||||
ctx.obj = 'FOOBAR'
|
||||
assert click.get_current_context().obj == 'FOOBAR'
|
||||
|
||||
assert click.get_current_context(silent=True) is None
|
||||
runner.invoke(cli, [], catch_exceptions=False)
|
||||
assert click.get_current_context(silent=True) is None
|
||||
|
||||
|
||||
def test_context_meta(runner):
|
||||
LANG_KEY = __name__ + '.lang'
|
||||
|
||||
def set_language(value):
|
||||
click.get_current_context().meta[LANG_KEY] = value
|
||||
|
||||
def get_language():
|
||||
return click.get_current_context().meta.get(LANG_KEY, 'en_US')
|
||||
|
||||
@click.command()
|
||||
@click.pass_context
|
||||
def cli(ctx):
|
||||
assert get_language() == 'en_US'
|
||||
set_language('de_DE')
|
||||
assert get_language() == 'de_DE'
|
||||
|
||||
runner.invoke(cli, [], catch_exceptions=False)
|
||||
|
||||
|
||||
def test_context_pushing():
|
||||
rv = []
|
||||
|
||||
@click.command()
|
||||
def cli():
|
||||
pass
|
||||
|
||||
ctx = click.Context(cli)
|
||||
|
||||
@ctx.call_on_close
|
||||
def test_callback():
|
||||
rv.append(42)
|
||||
|
||||
with ctx.scope(cleanup=False):
|
||||
# Internal
|
||||
assert ctx._depth == 2
|
||||
|
||||
assert rv == []
|
||||
|
||||
with ctx.scope():
|
||||
# Internal
|
||||
assert ctx._depth == 1
|
||||
|
||||
assert rv == [42]
|
||||
|
||||
|
||||
def test_pass_obj(runner):
|
||||
@click.group()
|
||||
@click.pass_context
|
||||
def cli(ctx):
|
||||
ctx.obj = 'test'
|
||||
|
||||
@cli.command()
|
||||
@click.pass_obj
|
||||
def test(obj):
|
||||
click.echo(obj)
|
||||
|
||||
result = runner.invoke(cli, ['test'])
|
||||
assert not result.exception
|
||||
assert result.output == 'test\n'
|
||||
|
|
|
@ -72,7 +72,7 @@ def test_wrapping_long_options_strings(runner):
|
|||
"""A command.
|
||||
"""
|
||||
|
||||
# 54 is chosen as a lenthg where the second line is one character
|
||||
# 54 is chosen as a length where the second line is one character
|
||||
# longer than the maximum length.
|
||||
result = runner.invoke(cli, ['a_very_long', 'command', '--help'],
|
||||
terminal_width=54)
|
||||
|
@ -89,6 +89,43 @@ def test_wrapping_long_options_strings(runner):
|
|||
]
|
||||
|
||||
|
||||
def test_wrapping_long_command_name(runner):
|
||||
@click.group()
|
||||
def cli():
|
||||
"""Top level command
|
||||
"""
|
||||
|
||||
@cli.group()
|
||||
def a_very_very_very_long():
|
||||
"""Second level
|
||||
"""
|
||||
|
||||
@a_very_very_very_long.command()
|
||||
@click.argument('first')
|
||||
@click.argument('second')
|
||||
@click.argument('third')
|
||||
@click.argument('fourth')
|
||||
@click.argument('fifth')
|
||||
@click.argument('sixth')
|
||||
def command():
|
||||
"""A command.
|
||||
"""
|
||||
|
||||
result = runner.invoke(cli, ['a_very_very_very_long', 'command', '--help'],
|
||||
terminal_width=54)
|
||||
assert not result.exception
|
||||
assert result.output.splitlines() == [
|
||||
'Usage: cli a_very_very_very_long command ',
|
||||
' [OPTIONS] FIRST SECOND THIRD FOURTH FIFTH',
|
||||
' SIXTH',
|
||||
'',
|
||||
' A command.',
|
||||
'',
|
||||
'Options:',
|
||||
' --help Show this message and exit.',
|
||||
]
|
||||
|
||||
|
||||
def test_formatting_empty_help_lines(runner):
|
||||
@click.command()
|
||||
def cli():
|
||||
|
|
|
@ -29,7 +29,8 @@ click.echo(json.dumps(rv))
|
|||
|
||||
ALLOWED_IMPORTS = set([
|
||||
'weakref', 'os', 'struct', 'collections', 'sys', 'contextlib',
|
||||
'functools', 'stat', 're', 'codecs', 'inspect', 'itertools', 'io'
|
||||
'functools', 'stat', 're', 'codecs', 'inspect', 'itertools', 'io',
|
||||
'threading'
|
||||
])
|
||||
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import sys
|
||||
|
||||
import pytest
|
||||
import click
|
||||
|
||||
|
@ -140,3 +142,44 @@ def test_with_color_but_pause_not_blocking():
|
|||
result = runner.invoke(cli, color=True)
|
||||
assert not result.exception
|
||||
assert result.output == ''
|
||||
|
||||
|
||||
def test_exit_code_and_output_from_sys_exit():
|
||||
# See issue #362
|
||||
@click.command()
|
||||
def cli_string():
|
||||
click.echo('hello world')
|
||||
sys.exit('error')
|
||||
|
||||
@click.command()
|
||||
def cli_int():
|
||||
click.echo('hello world')
|
||||
sys.exit(1)
|
||||
|
||||
@click.command()
|
||||
def cli_float():
|
||||
click.echo('hello world')
|
||||
sys.exit(1.0)
|
||||
|
||||
@click.command()
|
||||
def cli_no_error():
|
||||
click.echo('hello world')
|
||||
|
||||
runner = CliRunner()
|
||||
|
||||
result = runner.invoke(cli_string)
|
||||
assert result.exit_code == 1
|
||||
assert result.output == 'hello world\nerror\n'
|
||||
|
||||
result = runner.invoke(cli_int)
|
||||
assert result.exit_code == 1
|
||||
assert result.output == 'hello world\n'
|
||||
|
||||
result = runner.invoke(cli_float)
|
||||
assert result.exit_code == 1
|
||||
assert result.output == 'hello world\n1.0\n'
|
||||
|
||||
result = runner.invoke(cli_no_error)
|
||||
assert result.exit_code == 0
|
||||
assert result.output == 'hello world\n'
|
||||
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
import os
|
||||
import sys
|
||||
import click
|
||||
|
||||
import pytest
|
||||
|
||||
import click
|
||||
import click.utils
|
||||
import click._termui_impl
|
||||
|
||||
|
||||
|
@ -123,8 +126,24 @@ def test_prompts(runner):
|
|||
assert result.output == 'Foo [Y/n]: n\nno :(\n'
|
||||
|
||||
|
||||
def test_echo_via_pager(monkeypatch, capfd):
|
||||
monkeypatch.setitem(os.environ, 'PAGER', 'cat')
|
||||
def test_prompts_abort(monkeypatch, capsys):
|
||||
def f(_):
|
||||
raise KeyboardInterrupt()
|
||||
|
||||
monkeypatch.setattr('click.termui.hidden_prompt_func', f)
|
||||
|
||||
try:
|
||||
click.prompt('Password', hide_input=True)
|
||||
except click.Abort:
|
||||
click.echo('Screw you.')
|
||||
|
||||
out, err = capsys.readouterr()
|
||||
assert out == 'Password: \nScrew you.\n'
|
||||
|
||||
|
||||
@pytest.mark.parametrize('cat', ['cat', 'cat ', 'cat '])
|
||||
def test_echo_via_pager(monkeypatch, capfd, cat):
|
||||
monkeypatch.setitem(os.environ, 'PAGER', cat)
|
||||
monkeypatch.setattr(click._termui_impl, 'isatty', lambda x: True)
|
||||
click.echo_via_pager('haha')
|
||||
out, err = capfd.readouterr()
|
||||
|
@ -234,3 +253,23 @@ def test_open_file(runner):
|
|||
result = runner.invoke(cli, ['-'], input='foobar')
|
||||
assert result.exception is None
|
||||
assert result.output == 'foobar\nmeep\n'
|
||||
|
||||
|
||||
def test_iter_keepopenfile(tmpdir):
|
||||
|
||||
expected = list(map(str, range(10)))
|
||||
p = tmpdir.mkdir('testdir').join('testfile')
|
||||
p.write(os.linesep.join(expected))
|
||||
f = p.open()
|
||||
for e_line, a_line in zip(expected, click.utils.KeepOpenFile(f)):
|
||||
assert e_line == a_line.strip()
|
||||
|
||||
|
||||
def test_iter_lazyfile(tmpdir):
|
||||
|
||||
expected = list(map(str, range(10)))
|
||||
p = tmpdir.mkdir('testdir').join('testfile')
|
||||
p.write(os.linesep.join(expected))
|
||||
f = p.open()
|
||||
for e_line, a_line in zip(expected, click.utils.LazyFile(f.name)):
|
||||
assert e_line == a_line.strip()
|
||||
|
|
Loading…
Reference in a new issue