Imported Upstream version 5.1

This commit is contained in:
aviau 2015-08-22 21:10:31 -04:00
parent bbeeb94e55
commit c020281111
39 changed files with 630 additions and 667 deletions

30
CHANGES
View file

@ -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
-----------

View file

@ -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
View file

@ -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!

View file

@ -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

View file

@ -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

View file

@ -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'

View file

@ -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

View file

@ -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'):

View file

@ -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:]

View file

@ -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

View file

@ -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
View 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

View file

@ -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,

View file

@ -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

View file

@ -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
View file

@ -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
View file

@ -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

View file

@ -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">
&copy; Copyright {{ copyright }}.
Created using <a href="http://sphinx.pocoo.org/">Sphinx</a>.
</div>
{% if pagename == 'index' %}
</div>
{% endif %}
{%- endblock %}

View file

@ -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>

View file

@ -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;
}

View file

@ -1,4 +0,0 @@
[theme]
inherit = basic
stylesheet = click.css
pygments_style = tango

View file

@ -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.

View file

@ -106,6 +106,8 @@ Context
.. autoclass:: Context
:members:
.. autofunction:: get_current_context
Types
-----

View file

@ -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

View file

@ -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
View 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/

View file

@ -93,6 +93,7 @@ Miscellaneous Pages
.. toctree::
:maxdepth: 2
contrib
changelog
upgrading
license

View file

@ -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
---------------------------

View file

@ -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

View file

@ -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

View file

@ -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
---------------------

View file

@ -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.

View file

@ -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'))

View file

@ -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()

View file

@ -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'

View file

@ -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():

View file

@ -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'
])

View file

@ -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'

View file

@ -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()