Imported Upstream version 4.1

This commit is contained in:
aviau 2015-07-16 08:26:14 -04:00
parent 83d25aaa27
commit a57321344a
65 changed files with 1418 additions and 234 deletions

45
CHANGES
View file

@ -3,6 +3,51 @@ Click Changelog
This contains all major version changes between Click releases.
Version 4.1
-----------
(bugfix release, released on July 14th 2015)
- Fix a bug where error messages would include a trailing `None` string.
- Fix a bug where Click would crash on docstrings with trailing newlines.
- Support streams with encoding set to `None` on Python 3 by barfing with
a better error.
- Handle ^C in less-pager properly.
- Handle return value of `None` from `sys.getfilesystemencoding`
- Fix crash when writing to unicode files with `click.echo`.
- Fix type inference with multiple options.
Version 4.0
-----------
(codename "zoom zoom", released on March 31st 2015)
- Added `color` parameters to lots of interfaces that directly or indirectly
call into echoing. This previously was always autodetection (with the
exception of the `echo_via_pager` function). Now you can forcefully
enable or disable it, overriding the auto detection of Click.
- Added an `UNPROCESSED` type which does not perform any type changes which
simplifies text handling on 2.x / 3.x in some special advanced usecases.
- Added `NoSuchOption` and `BadOptionUsage` exceptions for more generic
handling of errors.
- Added support for handling of unprocessed options which can be useful in
situations where arguments are forwarded to underlying tools.
- Added `max_content_width` parameter to the context which can be used to
change the maximum width of help output. By default Click will not format
content for more than 80 characters width.
- Added support for writing prompts to stderr.
- Fix a bug when showing the default for multiple arguments.
- Added support for custom subclasses to `option` and `argument`.
- Fix bug in ``clear()`` on Windows when colorama is installed.
- Reject ``nargs=-1`` for options properly. Options cannot be variadic.
- Fixed an issue with bash completion not working properly for commands with
non ASCII characters or dashes.
- Added a way to manually update the progressbar.
- Changed the formatting of missing arguments. Previously the internal
argument name was shown in error messages, now the metavar is shown if
passed. In case an automated metavar is selected, it's stripped of
extra formatting first.
Version 3.3
-----------

View file

@ -1,6 +1,6 @@
Metadata-Version: 1.1
Name: click
Version: 3.3
Version: 4.1
Summary: A simple wrapper around optparse for powerful command line utilities.
Home-page: http://github.com/mitsuhiko/click
Author: Armin Ronacher

View file

@ -1,6 +1,6 @@
Metadata-Version: 1.1
Name: click
Version: 3.3
Version: 4.1
Summary: A simple wrapper around optparse for powerful command line utilities.
Home-page: http://github.com/mitsuhiko/click
Author: Armin Ronacher

View file

@ -61,7 +61,6 @@ docs/_themes/click/layout.html
docs/_themes/click/relations.html
docs/_themes/click/theme.conf
docs/_themes/click/static/click.css_t
examples/.DS_Store
examples/README
examples/aliases/README
examples/aliases/aliases.ini
@ -77,31 +76,36 @@ examples/complex/complex/cli.py
examples/complex/complex/commands/__init__.py
examples/complex/complex/commands/cmd_init.py
examples/complex/complex/commands/cmd_status.py
examples/imagepipe/.DS_Store
examples/imagepipe/.gitignore
examples/imagepipe/README
examples/imagepipe/example01.jpg
examples/imagepipe/example02.jpg
examples/imagepipe/imagepipe.py
examples/imagepipe/setup.py
examples/imagepipe/click_example_imagepipe.egg-info/PKG-INFO
examples/imagepipe/click_example_imagepipe.egg-info/SOURCES.txt
examples/imagepipe/click_example_imagepipe.egg-info/dependency_links.txt
examples/imagepipe/click_example_imagepipe.egg-info/entry_points.txt
examples/imagepipe/click_example_imagepipe.egg-info/requires.txt
examples/imagepipe/click_example_imagepipe.egg-info/top_level.txt
examples/inout/README
examples/inout/inout.py
examples/inout/setup.py
examples/naval/README
examples/naval/naval.py
examples/naval/setup.py
examples/plugins/BrokenPlugin/printer_bold.egg-info/PKG-INFO
examples/plugins/BrokenPlugin/printer_bold.egg-info/SOURCES.txt
examples/plugins/BrokenPlugin/printer_bold.egg-info/dependency_links.txt
examples/plugins/BrokenPlugin/printer_bold.egg-info/entry_points.txt
examples/plugins/BrokenPlugin/printer_bold.egg-info/top_level.txt
examples/plugins/printer.egg-info/PKG-INFO
examples/plugins/printer.egg-info/SOURCES.txt
examples/plugins/printer.egg-info/dependency_links.txt
examples/plugins/printer.egg-info/entry_points.txt
examples/plugins/printer.egg-info/top_level.txt
examples/repo/README
examples/repo/repo.py
examples/repo/setup.py
examples/termui/README
examples/termui/setup.py
examples/termui/termui.py
examples/termui/build/lib/termui.py
examples/termui/dist/click_example_termui-1.0-py3.4.egg
examples/validation/README
examples/validation/setup.py
examples/validation/validation.py

View file

@ -24,8 +24,8 @@ from .decorators import pass_context, pass_obj, make_pass_decorator, \
password_option, version_option, help_option
# Types
from .types import ParamType, File, Path, Choice, IntRange, STRING, INT, \
FLOAT, BOOL, UUID
from .types import ParamType, File, Path, Choice, IntRange, Tuple, \
STRING, INT, FLOAT, BOOL, UUID, UNPROCESSED
# Utilities
from .utils import echo, get_binary_stream, get_text_stream, open_file, \
@ -38,7 +38,7 @@ from .termui import prompt, confirm, get_terminal_size, echo_via_pager, \
# Exceptions
from .exceptions import ClickException, UsageError, BadParameter, \
FileError, Abort
FileError, Abort, NoSuchOption, BadOptionUsage, MissingParameter
# Formatting
from .formatting import HelpFormatter, wrap_text
@ -58,8 +58,8 @@ __all__ = [
'version_option', 'help_option',
# Types
'ParamType', 'File', 'Path', 'Choice', 'IntRange', 'STRING', 'INT',
'FLOAT', 'BOOL', 'UUID',
'ParamType', 'File', 'Path', 'Choice', 'IntRange', 'Tuple', 'STRING',
'INT', 'FLOAT', 'BOOL', 'UUID', 'UNPROCESSED',
# Utilities
'echo', 'get_binary_stream', 'get_text_stream', 'open_file',
@ -72,7 +72,7 @@ __all__ = [
# Exceptions
'ClickException', 'UsageError', 'BadParameter', 'FileError',
'Abort',
'Abort', 'NoSuchOption', 'BadOptionUsage', 'MissingParameter',
# Formatting
'HelpFormatter', 'wrap_text',
@ -82,4 +82,4 @@ __all__ = [
]
__version__ = '3.3'
__version__ = '4.1'

View file

@ -1,4 +1,5 @@
import os
import re
from .utils import echo
from .parser import split_arg_string
from .core import MultiCommand, Option
@ -6,7 +7,7 @@ from .core import MultiCommand, Option
COMPLETION_SCRIPT = '''
%(complete_func)s() {
COMPREPLY=( $( COMP_WORDS="${COMP_WORDS[*]}" \\
COMPREPLY=( $( env COMP_WORDS="${COMP_WORDS[*]}" \\
COMP_CWORD=$COMP_CWORD \\
%(autocomplete_var)s=complete $1 ) )
return 0
@ -15,10 +16,13 @@ COMPLETION_SCRIPT = '''
complete -F %(complete_func)s -o default %(script_names)s
'''
_invalid_ident_char_re = re.compile(r'[^a-zA-Z0-9_]')
def get_completion_script(prog_name, complete_var):
cf_name = _invalid_ident_char_re.sub('', prog_name.replace('-', '_'))
return (COMPLETION_SCRIPT % {
'complete_func': '_%s_completion' % prog_name,
'complete_func': '_%s_completion' % cf_name,
'script_names': prog_name,
'autocomplete_var': complete_var,
}).strip() + ';'

View file

@ -14,6 +14,10 @@ DEFAULT_COLUMNS = 80
_ansi_re = re.compile('\033\[((?:\d|;)*)([a-zA-Z])')
def get_filesystem_encoding():
return sys.getfilesystemencoding() or sys.getdefaultencoding()
def _make_text_stream(stream, encoding, errors):
if encoding is None:
encoding = get_best_encoding(stream)
@ -189,7 +193,7 @@ if PY2:
def filename_to_ui(value):
if isinstance(value, bytes):
value = value.decode(sys.getfilesystemencoding(), 'replace')
value = value.decode(get_filesystem_encoding(), 'replace')
return value
else:
import io
@ -255,7 +259,11 @@ else:
def _stream_is_misconfigured(stream):
"""A stream is misconfigured if its encoding is ASCII."""
return is_ascii_encoding(getattr(stream, 'encoding', None))
# If the stream does not have an encoding set, we assume it's set
# to ASCII. This appears to happen in certain unittest
# environments. It's not quite clear what the correct behavior is
# but this at least will force Click to recover somehow.
return is_ascii_encoding(getattr(stream, 'encoding', None) or 'ascii')
def _is_compatible_text_stream(stream, encoding, errors):
stream_encoding = getattr(stream, 'encoding', None)
@ -360,7 +368,7 @@ else:
def filename_to_ui(value):
if isinstance(value, bytes):
value = value.decode(sys.getfilesystemencoding(), 'replace')
value = value.decode(get_filesystem_encoding(), 'replace')
else:
value = value.encode('utf-8', 'surrogateescape') \
.decode('utf-8', 'replace')
@ -456,6 +464,18 @@ colorama = None
get_winterm_size = None
def strip_ansi(value):
return _ansi_re.sub('', value)
def should_strip_ansi(stream=None, color=None):
if color is None:
if stream is None:
stream = sys.stdin
return not isatty(stream)
return not color
# If we're on Windows, we provide transparent integration through
# colorama. This will make ANSI colors through the echo function
# work automatically.
@ -470,7 +490,7 @@ if WIN:
else:
_ansi_stream_wrappers = WeakKeyDictionary()
def auto_wrap_for_ansi(stream):
def auto_wrap_for_ansi(stream, color=None):
"""This function wraps a stream so that calls through colorama
are issued to the win32 console API to recolor on demand. It
also ensures to reset the colors if a write call is interrupted
@ -482,7 +502,7 @@ if WIN:
cached = None
if cached is not None:
return cached
strip = not isatty(stream)
strip = should_strip_ansi(stream, color)
ansi_wrapper = colorama.AnsiToWin32(stream, strip=strip)
rv = ansi_wrapper.stream
_write = rv.write
@ -507,10 +527,6 @@ if WIN:
return win.Right - win.Left, win.Bottom - win.Top
def strip_ansi(value):
return _ansi_re.sub('', value)
def term_len(x):
return len(strip_ansi(x))

View file

@ -52,7 +52,7 @@ class ProgressBar(object):
def __init__(self, iterable, length=None, fill_char='#', empty_char=' ',
bar_template='%(bar)s', info_sep=' ', show_eta=True,
show_percent=None, show_pos=False, item_show_func=None,
label=None, file=None, width=30):
label=None, file=None, color=None, width=30):
self.fill_char = fill_char
self.empty_char = empty_char
self.bar_template = bar_template
@ -65,6 +65,7 @@ class ProgressBar(object):
if file is None:
file = _default_text_stdout()
self.file = file
self.color = color
self.width = width
self.autowidth = width == 0
@ -180,7 +181,7 @@ class ProgressBar(object):
from .termui import get_terminal_size
if self.is_hidden:
echo(self.label, file=self.file)
echo(self.label, file=self.file, color=self.color)
self.file.flush()
return
@ -206,12 +207,12 @@ class ProgressBar(object):
if self.max_width is None or self.max_width < line_len:
self.max_width = line_len
# Use echo here so that we get colorama support.
echo(line, file=self.file, nl=False)
echo(line, file=self.file, nl=False, color=self.color)
self.file.write(' ' * (clear_width - line_len))
self.file.flush()
def make_step(self):
self.pos += 1
def make_step(self, n_steps):
self.pos += n_steps
if self.length_known and self.pos >= self.length:
self.finished = True
@ -223,6 +224,10 @@ class ProgressBar(object):
self.eta_known = self.length_known
def update(self, n_steps):
self.make_step(n_steps)
self.render_progress()
def finish(self):
self.eta_known = 0
self.current_item = None
@ -239,8 +244,7 @@ class ProgressBar(object):
self.render_progress()
raise StopIteration()
else:
self.make_step()
self.render_progress()
self.update(1)
return rv
if not PY2:
@ -302,9 +306,24 @@ def _pipepager(text, cmd, color):
try:
c.stdin.write(text.encode(encoding, 'replace'))
c.stdin.close()
except IOError:
except (IOError, KeyboardInterrupt):
pass
c.wait()
# Less doesn't respect ^C, but catches it for its own UI purposes (aborting
# search or other commands inside less).
#
# That means when the user hits ^C, the parent process (click) terminates,
# but less is still alive, paging the output and messing up the terminal.
#
# If the user wants to make the pager exit on ^C, they should set
# `LESS='-K'`. It's not our decision to make.
while True:
try:
c.wait()
except KeyboardInterrupt:
pass
else:
break
def _tempfilepager(text, cmd, color):
@ -348,7 +367,7 @@ class Editor(object):
if WIN:
return 'notepad'
for editor in 'vim', 'nano':
if os.system('which %s &> /dev/null' % editor) == 0:
if os.system('which %s >/dev/null 2>&1' % editor) == 0:
return editor
return 'vi'

View file

@ -2,12 +2,13 @@ import os
import sys
import codecs
from contextlib import contextmanager
from itertools import chain, repeat
from itertools import repeat
from functools import update_wrapper
from .types import convert_type, IntRange, BOOL
from .utils import make_str, make_default_short_help, echo
from .exceptions import ClickException, UsageError, BadParameter, Abort
from .exceptions import ClickException, UsageError, BadParameter, Abort, \
MissingParameter
from .termui import prompt, confirm
from .formatting import HelpFormatter, join_options
from .parser import OptionParser, split_opt
@ -107,11 +108,15 @@ class Context(object):
Added the `allow_extra_args` and `allow_interspersed_args`
parameters.
.. versionadded:: 4.0
Added the `color`, `ignore_unknown_options`, and
`max_content_width` parameters.
:param command: the command class for this context.
:param parent: the parent context.
:param info_name: the info name for this invokation. Generally this
:param info_name: the info name for this invocation. Generally this
is the most descriptive name for the script or
command. For the toplevel script is is usually
command. For the toplevel script it is usually
the name of the script, for commands below it it's
the name of the script.
:param obj: an arbitrary object of user data.
@ -126,6 +131,14 @@ class Context(object):
inherit from parent context. If no context
defines the terminal width then auto
detection will be applied.
:param max_content_width: the maximum width for content rendered by
Click (this currently only affects help
pages). This defaults to 80 characters if
not overridden. In other words: even if the
terminal is larger than that, Click will not
format things wider than 80 characters by
default. In addition to that, formatters might
add some safety mapping on the right.
:param resilient_parsing: if this flag is enabled then Click will
parse without any interactivity or callback
invocation. This is useful for implementing
@ -137,6 +150,9 @@ class Context(object):
:param allow_interspersed_args: if this is set to `False` then options
and arguments cannot be mixed. The
default is to inherit from the command.
:param ignore_unknown_options: instructs click to ignore options it does
not know and keeps them for later
processing.
:param help_option_names: optionally a list of strings that define how
the default help parameter is named. The
default is ``['--help']``.
@ -144,13 +160,20 @@ class Context(object):
normalize tokens (options, choices,
etc.). This for instance can be used to
implement case insensitive behavior.
:param color: controls if the terminal supports ANSI colors or not. The
default is autodetection. This is only needed if ANSI
codes are used in texts that Click prints which is by
default not the case. This for instance would affect
help output.
"""
def __init__(self, command, parent=None, info_name=None, obj=None,
auto_envvar_prefix=None, default_map=None,
terminal_width=None, resilient_parsing=False,
allow_extra_args=None, allow_interspersed_args=None,
help_option_names=None, token_normalize_func=None):
terminal_width=None, max_content_width=None,
resilient_parsing=False, allow_extra_args=None,
allow_interspersed_args=None,
ignore_unknown_options=None, help_option_names=None,
token_normalize_func=None, color=None):
#: the parent context or `None` if none exists.
self.parent = parent
#: the :class:`Command` for this context.
@ -190,6 +213,12 @@ class Context(object):
#: The width of the terminal (None is autodetection).
self.terminal_width = terminal_width
if max_content_width is None and parent is not None:
max_content_width = parent.max_content_width
#: The maximum width of formatted content (None implies a sensible
#: default which is 80 for most things).
self.max_content_width = max_content_width
if allow_extra_args is None:
allow_extra_args = command.allow_extra_args
#: Indicates if the context allows extra args or if it should
@ -206,6 +235,18 @@ class Context(object):
#: .. versionadded:: 3.0
self.allow_interspersed_args = allow_interspersed_args
if ignore_unknown_options is None:
ignore_unknown_options = command.ignore_unknown_options
#: Instructs click to ignore options that a command does not
#: understand and will store it on the context for later
#: processing. This is primarily useful for situations where you
#: want to call into external programs. Generally this pattern is
#: strongly discouraged because it's not possibly to losslessly
#: forward all arguments.
#:
#: .. versionadded:: 4.0
self.ignore_unknown_options = ignore_unknown_options
if help_option_names is None:
if parent is not None:
help_option_names = parent.help_option_names
@ -239,6 +280,12 @@ class Context(object):
self.auto_envvar_prefix = auto_envvar_prefix.upper()
self.auto_envvar_prefix = auto_envvar_prefix
if color is None and parent is not None:
color = parent.color
#: Controls if styling output is wanted or not.
self.color = color
self._close_callbacks = []
self._depth = 0
@ -272,7 +319,8 @@ class Context(object):
def make_formatter(self):
"""Creates the formatter for the help and usage output."""
return HelpFormatter(width=self.terminal_width)
return HelpFormatter(width=self.terminal_width,
max_width=self.max_content_width)
def call_on_close(self, f):
"""This decorator remembers a function as callback that should be
@ -466,8 +514,12 @@ class BaseCommand(object):
:param context_settings: an optional dictionary with defaults that are
passed to the context object.
"""
#: the default for the :attr:`Context.allow_extra_args` flag.
allow_extra_args = False
#: the default for the :attr:`Context.allow_interspersed_args` flag.
allow_interspersed_args = True
#: the default for the :attr:`Context.ignore_unknown_options` flag.
ignore_unknown_options = False
def __init__(self, name, context_settings=None):
#: the name the command thinks it has. Upon registering a command
@ -475,6 +527,8 @@ class BaseCommand(object):
#: with this information. You should instead use the
#: :class:`Context`\'s :attr:`~Context.info_name` attribute.
self.name = name
if context_settings is None:
context_settings = {}
#: an optional dictionary with defaults passed to the context.
self.context_settings = context_settings
@ -499,7 +553,7 @@ class BaseCommand(object):
:param extra: extra keyword arguments forwarded to the context
constructor.
"""
for key, value in iteritems(self.context_settings or {}):
for key, value in iteritems(self.context_settings):
if key not in extra:
extra[key] = value
ctx = Context(self, info_name=info_name, parent=parent, **extra)
@ -689,12 +743,12 @@ class Command(BaseCommand):
def get_help_option(self, ctx):
"""Returns the help option object."""
help_options = self.get_help_option_names(ctx)
if not help_options:
if not help_options or not self.add_help_option:
return
def show_help(ctx, param, value):
if value and not ctx.resilient_parsing:
echo(ctx.get_help())
echo(ctx.get_help(), color=ctx.color)
ctx.exit()
return Option(help_options, is_flag=True,
is_eager=True, expose_value=False,
@ -705,6 +759,7 @@ class Command(BaseCommand):
"""Creates the underlying option parser for this command."""
parser = OptionParser(ctx)
parser.allow_interspersed_args = ctx.allow_interspersed_args
parser.ignore_unknown_options = ctx.ignore_unknown_options
for param in self.get_params(ctx):
param.add_to_parser(parser, ctx)
return parser
@ -785,7 +840,7 @@ class Command(BaseCommand):
class MultiCommand(Command):
"""A multi command is the basic implementation of a command that
dispatches to subcommands. The most common version is the
:class:`Command`.
:class:`Group`.
:param invoke_without_command: this controls how the multi command itself
is invoked. By default it's only invoked
@ -893,7 +948,7 @@ class MultiCommand(Command):
def parse_args(self, ctx, args):
if not args and self.no_args_is_help and not ctx.resilient_parsing:
echo(ctx.get_help())
echo(ctx.get_help(), color=ctx.color)
ctx.exit()
return Command.parse_args(self, ctx, args)
@ -1111,7 +1166,9 @@ class Parameter(object):
value)`` and needs to return the value. Before Click
2.0, the signature was ``(ctx, value)``.
:param nargs: the number of arguments to match. If not ``1`` the return
value is a tuple instead of single value.
value is a tuple instead of single value. The default for
nargs is ``1`` (except if the type is a tuple, then it's
the arity of the tuple).
:param metavar: how the value is represented in the help page.
:param expose_value: if this is `True` then the value is passed onwards
to the command callback and stored on the context,
@ -1125,11 +1182,21 @@ class Parameter(object):
param_type_name = 'parameter'
def __init__(self, param_decls=None, type=None, required=False,
default=None, callback=None, nargs=1, metavar=None,
default=None, callback=None, nargs=None, metavar=None,
expose_value=True, is_eager=False, envvar=None):
self.name, self.opts, self.secondary_opts = \
self._parse_decls(param_decls or (), expose_value)
self.type = convert_type(type, default)
# Default nargs to what the type tells us if we have that
# information available.
if nargs is None:
if self.type.is_composite:
nargs = self.type.arity
else:
nargs = 1
self.required = required
self.callback = callback
self.nargs = nargs
@ -1140,6 +1207,13 @@ class Parameter(object):
self.metavar = metavar
self.envvar = envvar
@property
def human_readable_name(self):
"""Returns the human readable name of this parameter. This is the
same as the name for options, but the metavar for arguments.
"""
return self.name
def make_metavar(self):
if self.metavar is not None:
return self.metavar
@ -1172,8 +1246,19 @@ class Parameter(object):
def type_cast_value(self, ctx, value):
"""Given a value this runs it properly through the type system.
This automatically handles things like `nargs` and `multiple`.
This automatically handles things like `nargs` and `multiple` as
well as composite types.
"""
if self.type.is_composite:
if self.nargs <= 1:
raise TypeError('Attempted to invoke composite type '
'but nargs has been set to %s. This is '
'not supported; nargs needs to be set to '
'a fixed value > 1.' % self.nargs)
if self.multiple:
return tuple(self.type(x or (), self, ctx) for x in value or ())
return self.type(value or (), self, ctx)
def _convert(value, level):
if level == 0:
return self.type(value, self, ctx)
@ -1205,21 +1290,10 @@ class Parameter(object):
value = self.get_default(ctx)
if self.required and self.value_is_missing(value):
ctx.fail(self.get_missing_message(ctx))
raise MissingParameter(ctx=ctx, param=self)
return value
def get_missing_message(self, ctx):
rv = 'Missing %s %s.' % (
self.param_type_name,
' / '.join('"%s"' % x for x in chain(
self.opts, self.secondary_opts)),
)
extra = self.type.get_missing_message(self)
if extra:
rv += ' ' + extra
return rv
def resolve_envvar_value(self, ctx):
if self.envvar is None:
return
@ -1351,6 +1425,8 @@ class Option(Parameter):
# Sanity check for stuff we don't support
if __debug__:
if self.nargs < 0:
raise TypeError('Options cannot have nargs < 0')
if self.prompt and self.is_flag and not self.is_bool_flag:
raise TypeError('Cannot prompt for flags that are not bools.')
if not self.is_bool_flag and self.secondary_opts:
@ -1455,7 +1531,10 @@ class Option(Parameter):
help = self.help or ''
extra = []
if self.default is not None and self.show_default:
extra.append('default: %s' % self.default)
extra.append('default: %s' % (
', '.join('%s' % d for d in self.default)
if isinstance(self.default, (list, tuple))
else self.default, ))
if self.required:
extra.append('required')
if extra:
@ -1538,6 +1617,12 @@ class Argument(Parameter):
required = attrs.get('nargs', 1) > 0
Parameter.__init__(self, param_decls, required=required, **attrs)
@property
def human_readable_name(self):
if self.metavar is not None:
return self.metavar
return self.name.upper()
def make_metavar(self):
if self.metavar is not None:
return self.metavar

View file

@ -134,14 +134,18 @@ def _param_memo(f, param):
def argument(*param_decls, **attrs):
"""Attaches an option to the command. All positional arguments are
"""Attaches an argument to the command. All positional arguments are
passed as parameter declarations to :class:`Argument`; all keyword
arguments are forwarded unchanged. This is equivalent to creating an
:class:`Option` instance manually and attaching it to the
:attr:`Command.params` list.
arguments are forwarded unchanged (except ``cls``).
This is equivalent to creating an :class:`Argument` instance manually
and attaching it to the :attr:`Command.params` list.
:param cls: the argument class to instantiate. This defaults to
:class:`Argument`.
"""
def decorator(f):
_param_memo(f, Argument(param_decls, **attrs))
ArgumentClass = attrs.pop('cls', Argument)
_param_memo(f, ArgumentClass(param_decls, **attrs))
return f
return decorator
@ -149,14 +153,18 @@ def argument(*param_decls, **attrs):
def option(*param_decls, **attrs):
"""Attaches an option to the command. All positional arguments are
passed as parameter declarations to :class:`Option`; all keyword
arguments are forwarded unchanged. This is equivalent to creating an
:class:`Option` instance manually and attaching it to the
:attr:`Command.params` list.
arguments are forwarded unchanged (except ``cls``).
This is equivalent to creating an :class:`Option` instance manually
and attaching it to the :attr:`Command.params` list.
:param cls: the option class to instantiate. This defaults to
:class:`Option`.
"""
def decorator(f):
if 'help' in attrs:
attrs['help'] = inspect.cleandoc(attrs['help'])
_param_memo(f, Option(param_decls, **attrs))
OptionClass = attrs.pop('cls', Option)
_param_memo(f, OptionClass(param_decls, **attrs))
return f
return decorator
@ -253,7 +261,7 @@ def version_option(version=None, *param_decls, **attrs):
echo(message % {
'prog': prog,
'version': ver,
})
}, color=ctx.color)
ctx.exit()
attrs.setdefault('is_flag', True)
@ -278,7 +286,7 @@ def help_option(*param_decls, **attrs):
def decorator(f):
def callback(ctx, param, value):
if value and not ctx.resilient_parsing:
echo(ctx.get_help())
echo(ctx.get_help(), color=ctx.color)
ctx.exit()
attrs.setdefault('is_flag', True)
attrs.setdefault('expose_value', False)

View file

@ -10,9 +10,9 @@ class ClickException(Exception):
def __init__(self, message):
if PY2:
Exception.__init__(self, message.encode('utf-8'))
else:
Exception.__init__(self, message)
if message is not None:
message = message.encode('utf-8')
Exception.__init__(self, message)
self.message = message
def format_message(self):
@ -41,9 +41,11 @@ class UsageError(ClickException):
def show(self, file=None):
if file is None:
file = get_text_stderr()
color = None
if self.ctx is not None:
echo(self.ctx.get_usage() + '\n', file=file)
echo('Error: %s' % self.format_message(), file=file)
color = self.ctx.color
echo(self.ctx.get_usage() + '\n', file=file, color=color)
echo('Error: %s' % self.format_message(), file=file, color=color)
class BadParameter(UsageError):
@ -74,7 +76,7 @@ class BadParameter(UsageError):
if self.param_hint is not None:
param_hint = self.param_hint
elif self.param is not None:
param_hint = self.param.opts or [self.param.name]
param_hint = self.param.opts or [self.param.human_readable_name]
else:
return 'Invalid value: %s' % self.message
if isinstance(param_hint, (tuple, list)):
@ -82,6 +84,91 @@ class BadParameter(UsageError):
return 'Invalid value for %s: %s' % (param_hint, self.message)
class MissingParameter(BadParameter):
"""Raised if click required an option or argument but it was not
provided when invoking the script.
.. versionadded:: 4.0
:param param_type: a string that indicates the type of the parameter.
The default is to inherit the parameter type from
the given `param`. Valid values are ``'parameter'``,
``'option'`` or ``'argument'``.
"""
def __init__(self, message=None, ctx=None, param=None,
param_hint=None, param_type=None):
BadParameter.__init__(self, message, ctx, param, param_hint)
self.param_type = param_type
def format_message(self):
if self.param_hint is not None:
param_hint = self.param_hint
elif self.param is not None:
param_hint = self.param.opts or [self.param.human_readable_name]
else:
param_hint = None
if isinstance(param_hint, (tuple, list)):
param_hint = ' / '.join('"%s"' % x for x in param_hint)
param_type = self.param_type
if param_type is None and self.param is not None:
param_type = self.param.param_type_name
msg = self.message
msg_extra = self.param.type.get_missing_message(self.param)
if msg_extra:
if msg:
msg += '. ' + msg_extra
else:
msg = msg_extra
return 'Missing %s%s%s%s' % (
param_type,
param_hint and ' %s' % param_hint or '',
msg and '. ' or '.',
msg or '',
)
class NoSuchOption(UsageError):
"""Raised if click attempted to handle an option that does not
exist.
.. versionadded:: 4.0
"""
def __init__(self, option_name, message=None, possibilities=None,
ctx=None):
if message is None:
message = 'no such option: %s' % option_name
UsageError.__init__(self, message, ctx)
self.option_name = option_name
self.possibilities = possibilities
def format_message(self):
bits = [self.message]
if self.possibilities:
if len(self.possibilities) == 1:
bits.append('Did you mean %s?' % self.possibilities[0])
else:
possibilities = sorted(self.possibilities)
bits.append('(Possible options: %s)' % ', '.join(possibilities))
return ' '.join(bits)
class BadOptionUsage(UsageError):
"""Raised if an option is generally supplied but the use of the option
was incorrect. This is for instance raised if the number of arguments
for an option is not correct.
.. versionadded:: 4.0
"""
def __init__(self, option_name, message, ctx=None):
UsageError.__init__(self, message, ctx)
class FileError(ClickException):
"""Raised if a file cannot be opened."""

View file

@ -18,6 +18,12 @@ 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
@ -40,11 +46,13 @@ 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 wrapper.fill(text)
return add_subsequent_indent(wrapper.fill(text), post_wrap_indent)
p = []
buf = []
@ -75,9 +83,11 @@ 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(wrapper.indent_only(text))
rv.append(add_subsequent_indent(wrapper.indent_only(text),
post_wrap_indent))
else:
rv.append(wrapper.fill(text))
rv.append(add_subsequent_indent(wrapper.fill(text),
post_wrap_indent))
return '\n\n'.join(rv)
@ -94,10 +104,12 @@ class HelpFormatter(object):
width clamped to a maximum of 78.
"""
def __init__(self, indent_increment=2, width=None):
def __init__(self, indent_increment=2, width=None, max_width=None):
self.indent_increment = indent_increment
if max_width is None:
max_width = 80
if width is None:
width = max(min(get_terminal_size()[0], 80) - 2, 50)
width = max(min(get_terminal_size()[0], max_width) - 2, 50)
self.width = width
self.current_indent = 0
self.buffer = []

View file

@ -16,10 +16,16 @@
and might cause us issues.
"""
import re
from .exceptions import UsageError
from .exceptions import UsageError, NoSuchOption, BadOptionUsage
from .utils import unpack_args
def _error_args(nargs, opt):
if nargs == 1:
raise BadOptionUsage(opt, '%s option requires an argument' % opt)
raise BadOptionUsage(opt, '%s option requires %d arguments' % (opt, nargs))
def split_opt(opt):
first = opt[:1]
if first.isalnum():
@ -146,6 +152,14 @@ class OptionParser(object):
#: non-option. Click uses this to implement nested subcommands
#: safely.
self.allow_interspersed_args = True
#: This tells the parser how to deal with unknown options. By
#: default it will error out (which is sensible), but there is a
#: second mode where it will ignore it and continue processing
#: after shifting all the unknown options into the resulting args.
self.ignore_unknown_options = False
if ctx is not None:
self.allow_interspersed_args = ctx.allow_interspersed_args
self.ignore_unknown_options = ctx.ignore_unknown_options
self._short_opt = {}
self._long_opt = {}
self._opt_prefixes = set(['-', '--'])
@ -194,7 +208,7 @@ class OptionParser(object):
self._process_args_for_options(state)
self._process_args_for_args(state)
except UsageError:
if not self.ctx.resilient_parsing:
if self.ctx is None or not self.ctx.resilient_parsing:
raise
return state.opts, state.largs, state.order
@ -244,77 +258,54 @@ class OptionParser(object):
# *empty* -- still a subset of [arg0, ..., arg(i-1)], but
# not a very interesting subset!
def _match_long_opt(self, opt):
# Is there an exact match?
if opt in self._long_opt:
return opt
def _match_long_opt(self, opt, explicit_value, state):
if opt not in self._long_opt:
possibilities = [word for word in self._long_opt
if word.startswith(opt)]
raise NoSuchOption(opt, possibilities=possibilities)
# Isolate all words with s as a prefix.
possibilities = [word for word in self._long_opt
if word.startswith(opt)]
# No exact match, so there had better be just one possibility.
if not possibilities:
self._error('no such option: %s' % opt)
elif len(possibilities) == 1:
self._error('no such option: %s. Did you mean %s?' %
(opt, possibilities[0]))
return possibilities[0]
else:
# More than one possible completion: ambiguous prefix.
possibilities.sort()
self._error('no such option: %s. (Possible options: %s)'
% (opt, ', '.join(possibilities)))
def _process_long_opt(self, arg, state):
# Value explicitly attached to arg? Pretend it's the next argument.
if '=' in arg:
opt, next_arg = arg.split('=', 1)
state.rargs.insert(0, next_arg)
had_explicit_value = True
else:
opt = arg
had_explicit_value = False
opt = normalize_opt(opt, self.ctx)
opt = self._match_long_opt(opt)
option = self._long_opt[opt]
if option.takes_value:
# At this point it's safe to modify rargs by injecting the
# explicit value, because no exception is raised in this
# branch. This means that the inserted value will be fully
# consumed.
if explicit_value is not None:
state.rargs.insert(0, explicit_value)
nargs = option.nargs
if len(state.rargs) < nargs:
if nargs == 1:
self._error('%s option requires an argument' % opt)
else:
self._error('%s option requires %d arguments' % (opt, nargs))
_error_args(nargs, opt)
elif nargs == 1:
value = state.rargs.pop(0)
else:
value = tuple(state.rargs[:nargs])
del state.rargs[:nargs]
elif had_explicit_value:
self._error('%s option does not take a value' % opt)
elif explicit_value is not None:
raise BadOptionUsage(opt, '%s option does not take a value' % opt)
else:
value = None
option.process(value, state)
def _process_opts(self, arg, state):
if '=' in arg or normalize_opt(arg, self.ctx) in self._long_opt:
return self._process_long_opt(arg, state)
def _match_short_opt(self, arg, state):
stop = False
i = 1
prefix = arg[0]
unknown_options = []
for ch in arg[1:]:
opt = normalize_opt(prefix + ch, self.ctx)
option = self._short_opt.get(opt)
i += 1
if not option:
self._error('no such option: %s' % (arg if arg.startswith('--') else opt))
if self.ignore_unknown_options:
unknown_options.append(ch)
continue
raise NoSuchOption(opt)
if option.takes_value:
# Any characters left in arg? Pretend they're the
# next arg, and stop consuming characters of arg.
@ -324,11 +315,7 @@ class OptionParser(object):
nargs = option.nargs
if len(state.rargs) < nargs:
if nargs == 1:
self._error('%s option requires an argument' % opt)
else:
self._error('%s option requires %d arguments' %
(opt, nargs))
_error_args(nargs, opt)
elif nargs == 1:
value = state.rargs.pop(0)
else:
@ -343,5 +330,38 @@ class OptionParser(object):
if stop:
break
def _error(self, msg):
raise UsageError(msg, self.ctx)
# If we got any unknown options we re-combinate the string of the
# remaining options and re-attach the prefix, then report that
# to the state as new larg. This way there is basic combinatorics
# that can be achieved while still ignoring unknown arguments.
if self.ignore_unknown_options and unknown_options:
state.largs.append(prefix + ''.join(unknown_options))
def _process_opts(self, arg, state):
explicit_value = None
# Long option handling happens in two parts. The first part is
# supporting explicitly attached values. In any case, we will try
# to long match the option first.
if '=' in arg:
long_opt, explicit_value = arg.split('=', 1)
else:
long_opt = arg
norm_long_opt = normalize_opt(long_opt, self.ctx)
# At this point we will match the (assumed) long option through
# the long option matching code. Note that this allows options
# like "-foo" to be matched as long options.
try:
self._match_long_opt(norm_long_opt, explicit_value, state)
except NoSuchOption:
# At this point the long option matching failed, and we need
# to try with short options. However there is a special rule
# which says, that if we have a two character options prefix
# (applies to "--foo" for instance), we do not dispatch to the
# short option code and will instead raise the no option
# error.
if arg[:2] not in self._opt_prefixes:
return self._match_short_opt(arg, state)
if not self.ignore_unknown_options:
raise
state.largs.append(arg)

View file

@ -3,8 +3,7 @@ import sys
import struct
from ._compat import raw_input, text_type, string_types, \
colorama, isatty, strip_ansi, get_winterm_size, \
DEFAULT_COLUMNS, WIN
isatty, strip_ansi, get_winterm_size, DEFAULT_COLUMNS, WIN
from .utils import echo
from .exceptions import Abort, UsageError
from .types import convert_type
@ -33,13 +32,17 @@ def _build_prompt(text, suffix, show_default=False, default=None):
def prompt(text, default=None, hide_input=False,
confirmation_prompt=False, type=None,
value_proc=None, prompt_suffix=': ', show_default=True):
value_proc=None, prompt_suffix=': ',
show_default=True, err=False):
"""Prompts a user for input. This is a convenience function that can
be used to prompt a user for input later.
If the user aborts the input by sending a interrupt signal, this
function will catch it and raise a :exc:`Abort` exception.
.. versionadded:: 4.0
Added the `err` parameter.
:param text: the text to show for the prompt.
:param default: the default value to use if no input happens. If this
is not given it will prompt until it's aborted.
@ -52,6 +55,8 @@ def prompt(text, default=None, hide_input=False,
convert a value.
:param prompt_suffix: a suffix that should be added to the prompt.
:param show_default: shows or hides the default value in the prompt.
:param err: if set to true the file defaults to ``stderr`` instead of
``stdout``, the same as with echo.
"""
result = None
@ -60,7 +65,7 @@ def prompt(text, default=None, hide_input=False,
try:
# Write the prompt separately so that we get nice
# coloring through colorama on Windows
echo(text, nl=False)
echo(text, nl=False, err=err)
return f('')
except (KeyboardInterrupt, EOFError):
raise Abort()
@ -83,7 +88,7 @@ def prompt(text, default=None, hide_input=False,
try:
result = value_proc(value)
except UsageError as e:
echo('Error: %s' % e.message)
echo('Error: %s' % e.message, err=err)
continue
if not confirmation_prompt:
return result
@ -93,22 +98,27 @@ def prompt(text, default=None, hide_input=False,
break
if value == value2:
return result
echo('Error: the two entered values do not match')
echo('Error: the two entered values do not match', err=err)
def confirm(text, default=False, abort=False, prompt_suffix=': ',
show_default=True):
show_default=True, err=False):
"""Prompts for confirmation (yes/no question).
If the user aborts the input by sending a interrupt signal this
function will catch it and raise a :exc:`Abort` exception.
.. versionadded:: 4.0
Added the `err` parameter.
:param text: the question to ask.
:param default: the default for the prompt.
:param abort: if this is set to `True` a negative answer aborts the
exception by raising :exc:`Abort`.
:param prompt_suffix: a suffix that should be added to the prompt.
:param show_default: shows or hides the default value in the prompt.
:param err: if set to true the file defaults to ``stderr`` instead of
``stdout``, the same as with echo.
"""
prompt = _build_prompt(text, prompt_suffix, show_default,
default and 'Y/n' or 'y/N')
@ -116,7 +126,7 @@ def confirm(text, default=False, abort=False, prompt_suffix=': ',
try:
# Write the prompt separately so that we get nice
# coloring through colorama on Windows
echo(prompt, nl=False)
echo(prompt, nl=False, err=err)
value = visible_prompt_func('').lower().strip()
except (KeyboardInterrupt, EOFError):
raise Abort()
@ -127,7 +137,7 @@ def confirm(text, default=False, abort=False, prompt_suffix=': ',
elif value == '':
rv = default
else:
echo('Error: invalid input')
echo('Error: invalid input', err=err)
continue
break
if abort and not rv:
@ -197,7 +207,7 @@ def progressbar(iterable=None, length=None, label=None, show_eta=True,
show_percent=None, show_pos=False,
item_show_func=None, fill_char='#', empty_char='-',
bar_template='%(label)s [%(bar)s] %(info)s',
info_sep=' ', width=36, file=None):
info_sep=' ', width=36, file=None, color=None):
"""This function creates an iterable context manager that can be used
to iterate over something while showing a progress bar. It will
either iterate over the `iterable` or `length` items (that are counted
@ -221,8 +231,22 @@ def progressbar(iterable=None, length=None, label=None, show_eta=True,
for item in bar:
do_something_with(item)
Alternatively, if no iterable is specified, one can manually update the
progress bar through the `update()` method instead of directly
iterating over the progress bar. The update method accepts the number
of steps to increment the bar with::
with progressbar(length=chunks.total_bytes) as bar:
for chunk in chunks:
process_chunk(chunk)
bar.update(chunks.bytes)
.. versionadded:: 2.0
.. versionadded:: 4.0
Added the `color` parameter. Added a `update` method to the
progressbar object.
:param iterable: an iterable to iterate over. If not provided the length
is required.
:param length: the number of items to iterate over. By default the
@ -257,6 +281,10 @@ def progressbar(iterable=None, length=None, label=None, show_eta=True,
terminal width
:param file: the file to write to. If this is not a terminal then
only the label is printed.
:param color: controls if the terminal supports ANSI colors or not. The
default is autodetection. This is only needed if ANSI
codes are included anywhere in the progress bar output
which is not the case by default.
"""
from ._termui_impl import ProgressBar
return ProgressBar(iterable=iterable, length=length, show_eta=show_eta,
@ -264,7 +292,7 @@ def progressbar(iterable=None, length=None, label=None, show_eta=True,
item_show_func=item_show_func, fill_char=fill_char,
empty_char=empty_char, bar_template=bar_template,
info_sep=info_sep, file=file, label=label,
width=width)
width=width, color=color)
def clear():
@ -279,7 +307,7 @@ def clear():
# If we're on Windows and we don't have colorama available, then we
# clear the screen by shelling out. Otherwise we can use an escape
# sequence.
if WIN and colorama is None:
if WIN:
os.system('cls')
else:
sys.stdout.write('\033[2J\033[1;1H')
@ -366,7 +394,7 @@ def unstyle(text):
return strip_ansi(text)
def secho(text, file=None, nl=True, err=False, **styles):
def secho(text, file=None, nl=True, err=False, color=None, **styles):
"""This function combines :func:`echo` and :func:`style` into one
call. As such the following two calls are the same::
@ -378,8 +406,7 @@ def secho(text, file=None, nl=True, err=False, **styles):
.. versionadded:: 2.0
"""
text = style(text, **styles)
return echo(text, file=file, nl=nl, err=err)
return echo(style(text, **styles), file=file, nl=nl, err=err, color=color)
def edit(text=None, editor=None, env=None, require_save=True,
@ -472,7 +499,7 @@ def getchar(echo=False):
return f(echo)
def pause(info='Press any key to continue ...'):
def pause(info='Press any key to continue ...', err=False):
"""This command stops execution and waits for the user to press any
key to continue. This is similar to the Windows batch "pause"
command. If the program is not run through a terminal, this command
@ -480,17 +507,22 @@ def pause(info='Press any key to continue ...'):
.. versionadded:: 2.0
.. versionadded:: 4.0
Added the `err` parameter.
:param info: the info string to print before pausing.
:param err: if set to message goes to ``stderr`` instead of
``stdout``, the same as with echo.
"""
if not isatty(sys.stdin) or not isatty(sys.stdout):
return
try:
if info:
echo(info, nl=False)
echo(info, nl=False, err=err)
try:
getchar()
except (KeyboardInterrupt, EOFError):
pass
finally:
if info:
echo()
echo(err=err)

View file

@ -135,7 +135,7 @@ class CliRunner(object):
return rv
@contextlib.contextmanager
def isolation(self, input=None, env=None):
def isolation(self, input=None, env=None, color=False):
"""A context manager that sets up the isolation for invoking of a
command line tool. This sets up stdin with the given input data
and `os.environ` with the overrides from the given dictionary.
@ -144,8 +144,13 @@ class CliRunner(object):
This is automatically done in the :meth:`invoke` method.
.. versionadded:: 4.0
The ``color`` parameter was added.
:param input: the input stream to put into sys.stdin.
:param env: the environment overrides as dictionary.
:param color: whether the output should contain color codes. The
application can still override this explicitly.
"""
input = make_input_stream(input, self.charset)
@ -188,12 +193,20 @@ class CliRunner(object):
sys.stdout.flush()
return char
default_color = color
def should_strip_ansi(stream=None, color=None):
if color is None:
return not default_color
return not color
old_visible_prompt_func = clickpkg.termui.visible_prompt_func
old_hidden_prompt_func = clickpkg.termui.hidden_prompt_func
old__getchar_func = clickpkg.termui._getchar
old_should_strip_ansi = clickpkg.utils.should_strip_ansi
clickpkg.termui.visible_prompt_func = visible_input
clickpkg.termui.hidden_prompt_func = hidden_input
clickpkg.termui._getchar = _getchar
clickpkg.utils.should_strip_ansi = should_strip_ansi
old_env = {}
try:
@ -222,9 +235,10 @@ class CliRunner(object):
clickpkg.termui.visible_prompt_func = old_visible_prompt_func
clickpkg.termui.hidden_prompt_func = old_hidden_prompt_func
clickpkg.termui._getchar = old__getchar_func
clickpkg.utils.should_strip_ansi = old_should_strip_ansi
def invoke(self, cli, args=None, input=None, env=None,
catch_exceptions=True, **extra):
catch_exceptions=True, color=False, **extra):
"""Invokes a command in an isolated environment. The arguments are
forwarded directly to the command line script, the `extra` keyword
arguments are passed to the :meth:`~clickpkg.Command.main` function of
@ -239,6 +253,9 @@ class CliRunner(object):
The result object now has an `exc_info` attribute with the
traceback if available.
.. versionadded:: 4.0
The ``color`` parameter was added.
:param cli: the command to invoke
:param args: the arguments to invoke
:param input: the input data for `sys.stdin`.
@ -246,9 +263,11 @@ class CliRunner(object):
:param catch_exceptions: Whether to catch any other exceptions than
``SystemExit``.
:param extra: the keyword arguments to pass to :meth:`main`.
:param color: whether the output should contain color codes. The
application can still override this explicitly.
"""
exc_info = None
with self.isolation(input=input, env=env) as out:
with self.isolation(input=input, env=env, color=color) as out:
exception = None
exit_code = 0

View file

@ -2,7 +2,8 @@ import os
import sys
import stat
from ._compat import open_stream, text_type, filename_to_ui, get_streerror
from ._compat import open_stream, text_type, filename_to_ui, \
get_filesystem_encoding, get_streerror
from .exceptions import BadParameter
from .utils import safecall, LazyFile
@ -20,6 +21,7 @@ class ParamType(object):
This can be the case when the object is used with prompt
inputs.
"""
is_composite = False
#: the descriptive name of this type
name = None
@ -67,6 +69,14 @@ class ParamType(object):
raise BadParameter(message, ctx=ctx, param=param)
class CompositeParamType(ParamType):
is_composite = True
@property
def arity(self):
raise NotImplementedError()
class FuncParamType(ParamType):
def __init__(self, func):
@ -84,6 +94,16 @@ class FuncParamType(ParamType):
self.fail(value, param, ctx)
class UnprocessedParamType(ParamType):
name = 'text'
def convert(self, value, param, ctx):
return value
def __repr__(self):
return 'UNPROCESSED'
class StringParamType(ParamType):
name = 'text'
@ -95,7 +115,7 @@ class StringParamType(ParamType):
value = value.decode(enc)
except UnicodeError:
try:
value = value.decode(sys.getfilesystemencoding())
value = value.decode(get_filesystem_encoding())
except UnicodeError:
value = value.decode('utf-8', 'replace')
return value
@ -106,7 +126,7 @@ class StringParamType(ParamType):
class Choice(ParamType):
"""The choice type allows a value to checked against a fixed set of
"""The choice type allows a value to be checked against a fixed set of
supported values. All of these values have to be strings.
See :ref:`choice-opts` for an example.
@ -319,7 +339,7 @@ class File(ParamType):
class Path(ParamType):
"""The path type is similar to the :class:`File` type but it performs
different checks. First of all, instead of returning a open file
different checks. First of all, instead of returning an open file
handle it returns just the filename. Secondly, it can perform various
basic checks about what the file or directory should be.
@ -395,16 +415,54 @@ class Path(ParamType):
return rv
class Tuple(CompositeParamType):
"""The default behavior of Click is to apply a type on a value directly.
This works well in most cases, except for when `nargs` is set to a fixed
count and different types should be used for different items. In this
case the :class:`Tuple` type can be used. This type can only be used
if `nargs` is set to a fixed number.
For more information see :ref:`tuple-type`.
This can be selected by using a Python tuple literal as a type.
:param types: a list of types that should be used for the tuple items.
"""
def __init__(self, types):
self.types = [convert_type(ty) for ty in types]
@property
def name(self):
return "<" + " ".join(ty.name for ty in self.types) + ">"
@property
def arity(self):
return len(self.types)
def convert(self, value, param, ctx):
if len(value) != len(self.types):
raise TypeError('It would appear that nargs is set to conflict '
'with the composite type arity.')
return tuple(ty(x, param, ctx) for ty, x in zip(self.types, value))
def convert_type(ty, default=None):
"""Converts a callable or python ty into the most appropriate param
ty.
"""
if isinstance(ty, ParamType):
return ty
guessed_type = False
if ty is None and default is not None:
ty = type(default)
if isinstance(default, tuple):
ty = tuple(map(type, default))
else:
ty = type(default)
guessed_type = True
if isinstance(ty, tuple):
return Tuple(ty)
if isinstance(ty, ParamType):
return ty
if ty is text_type or ty is str or ty is None:
return STRING
if ty is int:
@ -431,6 +489,20 @@ def convert_type(ty, default=None):
return FuncParamType(ty)
#: A dummy parameter type that just does nothing. From a user's
#: perspective this appears to just be the same as `STRING` but internally
#: no string conversion takes place. This is necessary to achieve the
#: same bytes/unicode behavior on Python 2/3 in situations where you want
#: to not convert argument types. This is usually useful when working
#: with file paths as they can appear in bytes and unicode.
#:
#: For path related uses the :class:`Path` type is a better choice but
#: there are situations where an unprocessed type is useful which is why
#: it is is provided.
#:
#: .. versionadded:: 4.0
UNPROCESSED = UnprocessedParamType()
#: A unicode string parameter type which is the implicit default. This
#: can also be selected by using ``str`` as type.
STRING = StringParamType()

View file

@ -2,10 +2,10 @@ import os
import sys
from collections import deque
from ._compat import text_type, open_stream, get_streerror, string_types, \
PY2, binary_streams, text_streams, filename_to_ui, \
auto_wrap_for_ansi, strip_ansi, isatty, _default_text_stdout, \
_default_text_stderr, is_bytes, WIN
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, \
_default_text_stdout, _default_text_stderr, is_bytes, WIN
if not PY2:
from ._compat import _find_binary_writer
@ -38,6 +38,8 @@ def unpack_args(args, nargs_spec):
(((0, 1, 2, 3, 4, 5),), [])
>>> unpack_args(range(6), [1, 1])
((0, 1), [2, 3, 4, 5])
>>> unpack_args(range(6), [-1,1,1,1,1])
(((0, 1), 2, 3, 4, 5), [])
"""
args = deque(args)
nargs_spec = deque(nargs_spec)
@ -46,7 +48,10 @@ def unpack_args(args, nargs_spec):
def _fetch(c):
try:
return (spos is not None and c.pop() or c.popleft())
if spos is None:
return c.popleft()
else:
return c.pop()
except IndexError:
return None
@ -72,6 +77,7 @@ def unpack_args(args, nargs_spec):
if spos is not None:
rv[spos] = tuple(args)
args = []
rv[spos + 1:] = reversed(rv[spos + 1:])
return tuple(rv), list(args)
@ -90,7 +96,7 @@ def make_str(value):
"""Converts a value into a valid string."""
if isinstance(value, bytes):
try:
return value.decode(sys.getfilesystemencoding())
return value.decode(get_filesystem_encoding())
except UnicodeError:
return value.decode('utf-8', 'replace')
return text_type(value)
@ -210,7 +216,7 @@ class KeepOpenFile(object):
return repr(self._file)
def echo(message=None, file=None, nl=True, err=False):
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
first sight, this looks like the print function, but it has improved
support for handling Unicode and binary data that does not fail no
@ -238,12 +244,17 @@ def echo(message=None, file=None, nl=True, err=False):
.. versionadded:: 3.0
The `err` parameter was added.
.. versionchanged:: 4.0
Added the `color` flag.
:param message: the message to print
:param file: the file to write to (defaults to ``stdout``)
:param err: if set to true the file defaults to ``stderr`` instead of
``stdout``. This is faster and easier than calling
:func:`get_text_stderr` yourself.
:param nl: if set to `True` (the default) a newline is printed afterwards.
:param color: controls if the terminal supports ANSI colors or not. The
default is autodetection.
"""
if file is None:
if err:
@ -276,18 +287,18 @@ def echo(message=None, file=None, nl=True, err=False):
# 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):
if not isatty(file):
if should_strip_ansi(file, color):
message = strip_ansi(message)
elif WIN:
if auto_wrap_for_ansi is not None:
file = auto_wrap_for_ansi(file)
else:
elif not color:
message = strip_ansi(message)
if message:
file.write(message)
if nl:
file.write('\n')
file.write(u'\n')
file.flush()

View file

@ -243,3 +243,98 @@ Missing parameters:
Most of the time you do not need to be concerned about any of this,
but it is important to know how it works for some advanced cases.
.. _forwarding-unknown-options:
Forwarding Unknown Options
--------------------------
In some situations it is interesting to be able to accept all unknown
options for further manual processing. Click can generally do that as of
Click 4.0, but it has some limitations that lie in the nature of the
problem. The support for this is provided through a parser flag called
``ignore_unknown_options`` which will instruct the parser to collect all
unknown options and to put them to the leftover argument instead of
triggering a parsing error.
This can generally be activated in two different ways:
1. It can be enabled on custom :class:`Command` subclasses by changing
the :attr:`~BaseCommand.ignore_unknown_options` attribute.
2. It can be enabled by changing the attribute of the same name on the
context class (:attr:`Context.ignore_unknown_options`). This is best
changed through the ``context_settings`` dictionary on the command.
For most situations the easiest solution is the second. Once the behavior
is changed something needs to pick up those leftover options (which at
this point are considered arguments). For this again you have two
options:
1. You can use :func:`pass_context` to get the context passed. This will
only work if in addition to :attr:`~Context.ignore_unknown_options`
you also set :attr:`~Context.allow_extra_args` as otherwise the
command will abort with an error that there are leftover arguments.
If you go with this solution, the extra arguments will be collected in
:attr:`Context.args`.
2. You can attach a :func:`argument` with ``nargs`` set to `-1` which
will eat up all leftover arguments. In this case it's recommeded to
set the `type` to :data:`UNPROCESSED` to avoid any string processing
on those arguments as otherwise they are forced into unicode strings
automatically which is often not what you want.
In the end you end up with something like this:
.. click:example::
import sys
from subprocess import call
@click.command(context_settings=dict(
ignore_unknown_options=True,
))
@click.option('-v', '--verbose', is_flag=True, help='Enables verbose mode')
@click.argument('timeit_args', nargs=-1, type=click.UNPROCESSED)
def cli(verbose, timeit_args):
"""A wrapper around Python's timeit."""
cmdline = ['python', '-mtimeit'] + list(timeit_args)
if verbose:
click.echo('Invoking: %s' % ' '.join(cmdline))
call(cmdline)
And what it looks like:
.. click:run::
invoke(cli, prog_name='cli', args=['--help'])
println()
invoke(cli, prog_name='cli', args=['-n', '100', 'a = 1; b = 2; a * b'])
println()
invoke(cli, prog_name='cli', args=['-v', 'a = 1; b = 2; a * b'])
As you can see the verbosity flag is handled by Click, everything else
ends up in the `timeit_args` variable for further processing which then
for instance, allows invoking a subprocess. There are a few things that
are important to know about how this ignoring of unhandled flag happens:
* Unknown long options are generally ignored and not processed at all.
So for instance if ``--foo=bar`` or ``--foo bar`` are passed they
generally end up like that. Note that because the parser cannot know
if an option will accept an argument or not, the ``bar`` part might be
handled as an argument.
* Unknown short options might be partially handled and reassmebled if
necessary. For instance in the above example there is an option
called ``-v`` which enables verbose mode. If the command would be
ignored with ``-va`` then the ``-v`` part would be handled by Click
(as it is known) and ``-a`` would end up in the leftover parameters
for further processing.
* Depending on what you plan on doing you might have some success by
disabling interspersed arguments
(:attr:`~Context.allow_interspersed_args`) which instructs the parser
to not allow arguments and options to be mixed. Depending on your
situation this might improve your results.
Generally though the combinated handling of options and arguments from
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.

View file

@ -119,6 +119,8 @@ Types
.. autodata:: UUID
.. autodata:: UNPROCESSED
.. autoclass:: File
.. autoclass:: Path
@ -127,6 +129,8 @@ Types
.. autoclass:: IntRange
.. autoclass:: Tuple
.. autoclass:: ParamType
:members:
@ -143,6 +147,10 @@ Exceptions
.. autoexception:: FileError
.. autoexception:: NoSuchOption
.. autoexception:: BadOptionUsage
Formatting
----------

View file

@ -216,7 +216,7 @@ where the first one is picked.
Generally, this feature is not recommended because it can cause the user
a lot of confusion.
Argument-Like Options
Option-Like Arguments
---------------------
Sometimes, you want to process arguments that look like options. For

View file

@ -4,6 +4,7 @@ import click
import shutil
import tempfile
import contextlib
import subprocess
try:
from StringIO import StringIO
@ -49,6 +50,24 @@ class EchoingStdin(object):
return iter(self._echo(x) for x in self._input)
@contextlib.contextmanager
def fake_modules():
old_call = subprocess.call
def dummy_call(*args, **kwargs):
with tempfile.TemporaryFile('wb+') as f:
kwargs['stdout'] = f
kwargs['stderr'] = f
rv = subprocess.Popen(*args, **kwargs).wait()
f.seek(0)
click.echo(f.read().decode('utf-8', 'replace').rstrip())
return rv
subprocess.call = dummy_call
try:
yield
finally:
subprocess.call = old_call
@contextlib.contextmanager
def isolation(input=None, env=None):
if isinstance(input, unicode):
@ -123,8 +142,9 @@ class ExampleRunner(object):
}
def declare(self, source):
code = compile(source, '<docs>', 'exec')
eval(code, self.namespace)
with fake_modules():
code = compile(source, '<docs>', 'exec')
eval(code, self.namespace)
def run(self, source):
code = compile(source, '<docs>', 'exec')

View file

@ -135,9 +135,9 @@ For instance, the :func:`pass_obj` decorator can be implemented like this:
The :meth:`Context.invoke` command will automatically invoke the function
in the correct way, so the function will either be called with ``f(ctx,
obj)`` or ``f(obj)`` depending on whether or not it itself is decorated with
:func:`with_context`.
:func:`pass_context`.
This is a very powerful context that can be used to build very complex
This is a very powerful concept that can be used to build very complex
nested applications; see :ref:`complex-guide` for more information.
@ -273,6 +273,8 @@ And what it looks like:
In case a command exists in more than one source, the first source wins.
.. _multi-command-chaining:
Multi Command Chaining
----------------------
@ -280,7 +282,7 @@ Multi Command Chaining
Sometimes it is useful to be allowed to invoke more than one subcommand in
one go. For instance if you have installed a setuptools package before
ouy might be familiar with the ``setup.py sdist bdist_wheel upload``
you might be familiar with the ``setup.py sdist bdist_wheel upload``
command chain which invokes ``dist`` before ``bdist_wheel`` before
``upload``. Starting with Click 3.0 this is very simple to implement.
All you have to do is to pass ``chain=True`` to your multicommand:
@ -501,3 +503,50 @@ And again the example in action:
.. click:run::
invoke(cli, prog_name='cli', args=['runserver'])
Command Return Values
---------------------
.. versionadded:: 3.0
One of the new introductions in Click 3.0 is the full support for return
values from command callbacks. This enables a whole range of features
that were previously hard to implement.
In essence any command callback can now return a value. This return value
is bubbled to certain receivers. One usecase for this has already been
show in the example of :ref:`multi-command-chaining` where it has been
demonstrated that chained multi commands can have callbacks that process
all return values.
When working with command return values in Click, this is what you need to
know:
- The return value of a command callback is generally returned from the
:meth:`BaseCommand.invoke` method. The exception to this rule has to
do with :class:`Group`\s:
* In a group the return value is generally the return value of the
subcommand invoked. The only exception to this rule is that the
return value is the return value of the group callback if it's
invoked without arguments and `invoke_without_command` is enabled.
* If a group is set up for chaining then the return value is a list
of all subcommands' results.
* Return values of groups can be processed through a
:attr:`MultiCommand.result_callback`. This is invoked with the
list of all return values in chain mode, or the single return
value in case of non chained commands.
- The return value is bubbled through from the :meth:`Context.invoke`
and :meth:`Context.forward` methods. This is useful in situations
where you internally want to call into another command.
- Click does not have any hard requirements for the return values and
does not use them itself. This allows return values to be used for
custom decorators or workflows (like in the multi command chaining
example).
- When a Click script is invoked as command line application (through
:meth:`BaseCommand.main`) the return value is ignored unless the
`standalone_mode` is disabled in which case it's bubbled through.

View file

@ -12,6 +12,7 @@
# serve to show the default.
import sys, os
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
@ -44,7 +45,7 @@ master_doc = 'index'
# General information about the project.
project = u'click'
copyright = u'2014, Armin Ronacher'
copyright = u'%d, Armin Ronacher' % datetime.datetime.utcnow().year
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the

View file

@ -55,6 +55,42 @@ And on the command line:
invoke(findme, args=['--pos', '2.0', '3.0'])
.. _tuple-type:
Tuples as Multi Value Options
-----------------------------
.. versionadded:: 4.0
As you can see that by using `nargs` set to a specific number each item in
the resulting tuple is of the same type. This might not be what you want.
Commonly you might want to use different types for different indexes in
the tuple. For this you can directly specify a tuple as type:
.. click:example::
@click.command()
@click.option('--item', type=(unicode, int))
def putitem(item):
click.echo('name=%s id=%d' % item)
And on the command line:
.. click:run::
invoke(putitem, args=['--item', 'peter', '1338'])
By using a tuple literal as type, `nargs` gets automatically set to the
length of the tuple and the :class:`click.Tuple` type is automatically
used. The above example is thus equivalent to this:
.. click:example::
@click.command()
@click.option('--item', nargs=2, type=click.Tuple([unicode, int]))
def putitem(item):
click.echo('name=%s id=%d' % item)
Multiple Options
----------------
@ -113,12 +149,12 @@ Example:
.. click:example::
import os
import sys
@click.command()
@click.option('--shout/--no-shout', default=False)
def info(shout):
rv = os.uname()[0]
rv = sys.platform
if shout:
rv = rv.upper() + '!!!!111'
click.echo(rv)
@ -135,12 +171,12 @@ manually inform Click that something is a flag:
.. click:example::
import os
import sys
@click.command()
@click.option('--shout', is_flag=True)
def info(shout):
rv = os.uname()[0]
rv = sys.platform
if shout:
rv = rv.upper() + '!!!!111'
click.echo(rv)
@ -178,14 +214,14 @@ the default.
.. click:example::
import os
import sys
@click.command()
@click.option('--upper', 'transformation', flag_value='upper',
default=True)
@click.option('--lower', 'transformation', flag_value='lower')
def info(transformation):
click.echo(getattr(os.uname()[0], transformation)())
click.echo(getattr(sys.platform, transformation)())
And on the command line:
@ -301,6 +337,10 @@ Sometimes, you want a parameter to completely change the execution flow.
For instance, this is the case when you want to have a ``--version``
parameter that prints out the version and then exits the application.
Note: an actual implementation of a ``--version`` parameter that is
reusable is available in Click as :func:`click.version_option`. The code
here is merely an example of how to implement such a flag.
In such cases, you need two concepts: eager parameters and a callback. An
eager parameter is a parameter that is handled before others, and a
callback is what executes after the parameter is handled. The eagerness

View file

@ -26,7 +26,7 @@ available for options:
(this is intentional as arguments might be too specific to be
automatically documented)
On the other hand arguments unlike options can accept an arbitrary number
On the other hand arguments, unlike options, can accept an arbitrary number
of arguments. Options can strictly ever only accept a fixed number of
arguments (defaults to 1).

View file

@ -18,9 +18,9 @@ Python 3 Limitations
At the moment, Click suffers from a few problems with Python 3:
* The command line in Unix traditionally is in bytes, and not Unicode.
While there are encoding hints for all of this, there are generally
some situations where this can break. The most common one is SSH
* The command line in Unix traditionally is in bytes, not Unicode. While
there are encoding hints for all of this, there are generally some
situations where this can break. The most common one is SSH
connections to machines with different locales.
Misconfigured environments can currently cause a wide range of Unicode
@ -56,7 +56,7 @@ Python 2 and 3 Differences
--------------------------
Click attempts to minimize the differences between Python 2 and Python 3
by following the best practices for both languages.
by following best practices for both languages.
In Python 2, the following is true:
@ -81,7 +81,7 @@ In Python 3, the following is true:
* ``sys.argv`` is always Unicode-based. This also means that the native
type for input values to the types in Click is Unicode, and not bytes.
This causes problems when the terminal is incorrectly set and Python
This causes problems if the terminal is incorrectly set and Python
does not figure out the encoding. In that case, the Unicode string
will contain error bytes encoded as surrogate escapes.
* When dealing with files, Click will always use the Unicode file system
@ -100,9 +100,9 @@ and is subject to its behavior. In Python 2, Click does all the Unicode
handling itself, which means there are differences in error behavior.
The most glaring difference is that in Python 2, Unicode will "just work",
while in Python 3, it requires extra care. The reason for this is that on
Python 3, the encoding detection is done in the interpreter and on Linux
and certain other operating systems its encoding handling is problematic.
while in Python 3, it requires extra care. The reason for this is that in
Python 3, the encoding detection is done in the interpreter, and on Linux
and certain other operating systems, its encoding handling is problematic.
The biggest source of frustration is that Click scripts invoked by
init systems (sysvinit, upstart, systemd, etc.), deployment tools (salt,
@ -120,7 +120,7 @@ If you see something like this error in Python 3::
...
RuntimeError: Click will abort further execution because Python 3 was
configured to use ASCII as encoding for the environment. Either switch
to Python 2 or consult for http://click.pocoo.org/python3/
to Python 2 or consult http://click.pocoo.org/python3/ for
mitigation steps.
You are dealing with an environment where Python 3 thinks you are
@ -133,7 +133,7 @@ by exporting the locale to ``de_DE.utf-8``::
export LC_ALL=de_DE.utf-8
export LANG=de_DE.utf-8
If you are on a US machine, ``en_EN.utf-8`` is the encoding of choice. On
If you are on a US machine, ``en_US.utf-8`` is the encoding of choice. On
some newer Linux systems, you could also try ``C.UTF-8`` as the locale::
export LC_ALL=C.UTF-8

View file

@ -81,7 +81,7 @@ And if you want to go back to the real world, use the following command::
After doing this, the prompt of your shell should be as familar as before.
Now, let's move on. Enter the following command to get Flask activated in your
Now, let's move on. Enter the following command to get Click activated in your
virtualenv::
$ pip install Click

View file

@ -108,9 +108,11 @@ contained in a Python package the changes necessary are minimal. Let's
assume your directory structure changed to this::
yourpackage/
__init__.py
main.py
utils.py
scripts/
__init__.py
yourscript.py
In this case instead of using ``py_modules`` in your ``setup.py`` file you

View file

@ -58,7 +58,7 @@ Example::
runner = CliRunner()
with runner.isolated_filesystem():
with open('hello.txt') as f:
with open('hello.txt', 'w') as f:
f.write('Hello World!')
result = runner.invoke(cat, ['hello.txt'])

View file

@ -366,3 +366,14 @@ will be shown preceding the progress bar::
length=number_of_users) as bar:
for user in bar:
modify_the_user(user)
Sometimes, one may need to iterate over an external iterator, and advance the
progress bar irregularly. To do so, you need to specify the length (and no
iterable), and use the update method on the context return value instead of
iterating directly over it::
with click.progressbar(length=total_size,
label='Unzipping archive') as bar:
for archive in zip_file:
archive.extract()
bar.update(archive.size)

BIN
examples/.DS_Store vendored

Binary file not shown.

View file

@ -6,7 +6,7 @@ setup(
py_modules=['aliases'],
include_package_data=True,
install_requires=[
'Click',
'click',
],
entry_points='''
[console_scripts]

View file

@ -6,7 +6,7 @@ setup(
py_modules=['colors'],
include_package_data=True,
install_requires=[
'Click',
'click',
# Colorama is only required for Windows.
'colorama',
],

View file

@ -6,7 +6,7 @@ setup(
packages=['complex', 'complex.commands'],
include_package_data=True,
install_requires=[
'Click',
'click',
],
entry_points='''
[console_scripts]

Binary file not shown.

View file

@ -1,8 +0,0 @@
README
imagepipe.py
click_example_imagepipe.egg-info/PKG-INFO
click_example_imagepipe.egg-info/SOURCES.txt
click_example_imagepipe.egg-info/dependency_links.txt
click_example_imagepipe.egg-info/entry_points.txt
click_example_imagepipe.egg-info/requires.txt
click_example_imagepipe.egg-info/top_level.txt

View file

@ -1,4 +0,0 @@
[console_scripts]
imagepipe=imagepipe:cli

View file

@ -6,7 +6,7 @@ setup(
py_modules=['imagepipe'],
include_package_data=True,
install_requires=[
'Click',
'click',
'pillow',
],
entry_points='''

View file

@ -6,7 +6,7 @@ setup(
py_modules=['inout'],
include_package_data=True,
install_requires=[
'Click',
'click',
],
entry_points='''
[console_scripts]

View file

@ -6,7 +6,7 @@ setup(
py_modules=['naval'],
include_package_data=True,
install_requires=[
'Click',
'click',
],
entry_points='''
[console_scripts]

View file

@ -1,6 +1,6 @@
Metadata-Version: 1.0
Name: click-example-imagepipe
Version: 1.0
Name: printer-bold
Version: 0.1dev0
Summary: UNKNOWN
Home-page: UNKNOWN
Author: UNKNOWN

View file

@ -0,0 +1,8 @@
README.rst
printer_bold/__init__.py
printer_bold/core.py
printer_bold.egg-info/PKG-INFO
printer_bold.egg-info/SOURCES.txt
printer_bold.egg-info/dependency_links.txt
printer_bold.egg-info/entry_points.txt
printer_bold.egg-info/top_level.txt

View file

@ -0,0 +1,4 @@
[printer.plugins]
bold=printer_bold.core:bolddddddddddd

View file

@ -0,0 +1 @@
printer_bold

View file

@ -0,0 +1,10 @@
Metadata-Version: 1.0
Name: printer
Version: 0.1dev0
Summary: UNKNOWN
Home-page: UNKNOWN
Author: UNKNOWN
Author-email: UNKNOWN
License: UNKNOWN
Description: UNKNOWN
Platform: UNKNOWN

View file

@ -0,0 +1,8 @@
README.rst
printer/__init__.py
printer/cli.py
printer.egg-info/PKG-INFO
printer.egg-info/SOURCES.txt
printer.egg-info/dependency_links.txt
printer.egg-info/entry_points.txt
printer.egg-info/top_level.txt

View file

@ -0,0 +1 @@

View file

@ -0,0 +1,4 @@
[console_scripts]
printer=printer.cli:cli

View file

@ -0,0 +1 @@
printer

View file

@ -6,7 +6,7 @@ setup(
py_modules=['repo'],
include_package_data=True,
install_requires=[
'Click',
'click',
],
entry_points='''
[console_scripts]

View file

@ -0,0 +1,145 @@
# coding: utf-8
import click
import time
import random
try:
range_type = xrange
except NameError:
range_type = range
@click.group()
def cli():
"""This script showcases different terminal UI helpers in Click."""
pass
@cli.command()
def colordemo():
"""Demonstrates ANSI color support."""
for color in 'red', 'green', 'blue':
click.echo(click.style('I am colored %s' % color, fg=color))
click.echo(click.style('I am background colored %s' % color, bg=color))
@cli.command()
def pager():
"""Demonstrates using the pager."""
lines = []
for x in range_type(200):
lines.append('%s. Hello World!' % click.style(str(x), fg='green'))
click.echo_via_pager('\n'.join(lines))
@cli.command()
@click.option('--count', default=8000, type=click.IntRange(1, 100000),
help='The number of items to process.')
def progress(count):
"""Demonstrates the progress bar."""
items = range_type(count)
def process_slowly(item):
time.sleep(0.002 * random.random())
def filter(items):
for item in items:
if random.random() > 0.3:
yield item
with click.progressbar(items, label='Processing accounts',
fill_char=click.style('#', fg='green')) as bar:
for item in bar:
process_slowly(item)
def show_item(item):
if item is not None:
return 'Item #%d' % item
with click.progressbar(filter(items), label='Committing transaction',
fill_char=click.style('#', fg='yellow'),
item_show_func=show_item) as bar:
for item in bar:
process_slowly(item)
with click.progressbar(length=count, label='Counting',
bar_template='%(label)s %(bar)s | %(info)s',
fill_char=click.style(u'', fg='cyan'),
empty_char=' ') as bar:
for item in bar:
process_slowly(item)
with click.progressbar(length=count, width=0, show_percent=False,
show_eta=False,
fill_char=click.style('#', fg='magenta')) as bar:
for item in bar:
process_slowly(item)
@cli.command()
@click.argument('url')
def open(url):
"""Opens a file or URL In the default application."""
click.launch(url)
@cli.command()
@click.argument('url')
def locate(url):
"""Opens a file or URL In the default application."""
click.launch(url, locate=True)
@cli.command()
def edit():
"""Opens an editor with some text in it."""
MARKER = '# Everything below is ignored\n'
message = click.edit('\n\n' + MARKER)
if message is not None:
msg = message.split(MARKER, 1)[0].rstrip('\n')
if not msg:
click.echo('Empty message!')
else:
click.echo('Message:\n' + msg)
else:
click.echo('You did not enter anything!')
@cli.command()
def clear():
"""Clears the entire screen."""
click.clear()
@cli.command()
def pause():
"""Waits for the user to press a button."""
click.pause()
@cli.command()
def menu():
"""Shows a simple menu."""
menu = 'main'
while 1:
if menu == 'main':
click.echo('Main menu:')
click.echo(' d: debug menu')
click.echo(' q: quit')
char = click.getchar()
if char == 'd':
menu = 'debug'
elif char == 'q':
menu = 'quit'
else:
click.echo('Invalid input')
elif menu == 'debug':
click.echo('Debug menu')
click.echo(' b: back')
char = click.getchar()
if char == 'b':
menu = 'main'
else:
click.echo('Invalid input')
elif menu == 'quit':
return

Binary file not shown.

View file

@ -6,7 +6,7 @@ setup(
py_modules=['termui'],
include_package_data=True,
install_requires=[
'Click',
'click',
# Colorama is only required for Windows.
'colorama',
],

View file

@ -1,5 +1,6 @@
# coding: utf-8
import click
import math
import time
import random
@ -75,6 +76,16 @@ def progress(count):
for item in bar:
process_slowly(item)
# 'Non-linear progress bar'
steps = [math.exp( x * 1. / 20) - 1 for x in range(20)]
count = int(sum(steps))
with click.progressbar(length=count, show_percent=False,
label='Slowing progress bar',
fill_char=click.style(u'', fg='green')) as bar:
for item in steps:
time.sleep(item)
bar.update(item)
@cli.command()
@click.argument('url')

View file

@ -6,7 +6,7 @@ setup(
py_modules=['validation'],
include_package_data=True,
install_requires=[
'Click',
'click',
],
entry_points='''
[console_scripts]

View file

@ -34,6 +34,27 @@ def test_nargs_tup(runner):
]
def test_nargs_tup_composite(runner):
variations = [
dict(type=(str, int)),
dict(type=click.Tuple([str, int])),
dict(nargs=2, type=click.Tuple([str, int])),
dict(nargs=2, type=(str, int)),
]
for opts in variations:
@click.command()
@click.argument('item', **opts)
def copy(item):
click.echo('name=%s id=%d' % item)
result = runner.invoke(copy, ['peter', '1'])
assert not result.exception
assert result.output.splitlines() == [
'name=peter id=1',
]
def test_nargs_err(runner):
@click.command()
@click.argument('x')
@ -106,7 +127,7 @@ def test_stdout_default(runner):
def test_nargs_envvar(runner):
@click.command()
@click.option('--arg', nargs=-1)
@click.option('--arg', nargs=2)
def cmd(arg):
click.echo('|'.join(arg))
@ -116,7 +137,7 @@ def test_nargs_envvar(runner):
assert result.output == 'foo|bar\n'
@click.command()
@click.option('--arg', envvar='X', nargs=-1)
@click.option('--arg', envvar='X', nargs=2)
def cmd(arg):
click.echo('|'.join(arg))
@ -189,3 +210,37 @@ def test_eat_options(runner):
'bar',
'-x',
]
def test_nargs_star_ordering(runner):
@click.command()
@click.argument('a', nargs=-1)
@click.argument('b')
@click.argument('c')
def cmd(a, b, c):
for arg in (a, b, c):
click.echo(arg)
result = runner.invoke(cmd, ['a', 'b', 'c'])
assert result.output.splitlines() == [
"('a',)",
'b',
'c',
]
def test_nargs_specified_plus_star_ordering(runner):
@click.command()
@click.argument('a', nargs=-1)
@click.argument('b')
@click.argument('c', nargs=2)
def cmd(a, b, c):
for arg in (a, b, c):
click.echo(arg)
result = runner.invoke(cmd, ['a', 'b', 'c', 'd', 'e', 'f'])
assert result.output.splitlines() == [
"('a', 'b', 'c')",
'd',
"('e', 'f')",
]

View file

@ -252,3 +252,21 @@ def test_invoked_subcommand(runner):
result = runner.invoke(cli)
assert not result.exception
assert result.output == 'no subcommand, use default\nin subcommand\n'
def test_unprocessed_options(runner):
@click.command(context_settings=dict(
ignore_unknown_options=True
))
@click.argument('args', nargs=-1, type=click.UNPROCESSED)
@click.option('--verbose', '-v', count=True)
def cli(verbose, args):
click.echo('Verbosity: %s' % verbose)
click.echo('Args: %s' % '|'.join(args))
result = runner.invoke(cli, ['-foo', '-vvvvx', '--muhaha', 'x', 'y', '-x'])
assert not result.exception
assert result.output.splitlines() == [
'Verbosity: 4',
'Args: -foo|-x|--muhaha|x|y|-x',
]

View file

@ -15,3 +15,10 @@ if click.__version__ >= '3.0':
assert result.exit_code == 0
assert 'WAT' in result.output
assert 'Invoked legacy parameter callback' in result.output
def test_bash_func_name():
from click._bashcomplete import get_completion_script
script = get_completion_script('foo-bar baz_blah', '_COMPLETE_VAR').strip()
assert script.startswith('_foo_barbaz_blah_completion()')
assert '_COMPLETE_VAR=complete $1' in script

View file

@ -48,3 +48,63 @@ def test_basic_functionality(runner):
'Options:',
' --help Show this message and exit.',
]
def test_wrapping_long_options_strings(runner):
@click.group()
def cli():
"""Top level command
"""
@cli.group()
def a_very_long():
"""Second level
"""
@a_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.
"""
# 54 is chosen as a lenthg where the second line is one character
# longer than the maximum length.
result = runner.invoke(cli, ['a_very_long', 'command', '--help'],
terminal_width=54)
assert not result.exception
assert result.output.splitlines() == [
'Usage: cli a_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():
"""Top level command
"""
result = runner.invoke(cli, ['--help'])
assert not result.exception
assert result.output.splitlines() == [
'Usage: cli [OPTIONS]',
'',
' Top level command',
'',
'',
'',
'Options:',
' --help Show this message and exit.',
]

View file

@ -34,6 +34,33 @@ def test_invalid_option(runner):
assert False, 'Expected a type error because of an invalid option.'
def test_invalid_nargs(runner):
try:
@click.command()
@click.option('--foo', nargs=-1)
def cli(foo):
pass
except TypeError as e:
assert 'Options cannot have nargs < 0' in str(e)
else:
assert False, 'Expected a type error because of an invalid option.'
def test_nargs_tup_composite_mult(runner):
@click.command()
@click.option('--item', type=(str, int), multiple=True)
def copy(item):
for item in item:
click.echo('name=%s id=%d' % item)
result = runner.invoke(copy, ['--item', 'peter', '1', '--item', 'max', '2'])
assert not result.exception
assert result.output.splitlines() == [
'name=peter id=1',
'name=max id=2',
]
def test_counting(runner):
@click.command()
@click.option('-v', count=True, help='Verbosity',
@ -115,6 +142,34 @@ def test_multiple_envvar(runner):
assert result.output == 'foo|bar\n'
def test_multiple_default_help(runner):
@click.command()
@click.option("--arg1", multiple=True, default=('foo', 'bar'),
show_default=True)
@click.option("--arg2", multiple=True, default=(1, 2), type=int,
show_default=True)
def cmd(arg, arg2):
pass
result = runner.invoke(cmd, ['--help'])
assert not result.exception
assert "foo, bar" in result.output
assert "1, 2" in result.output
def test_multiple_default_type(runner):
@click.command()
@click.option("--arg1", multiple=True, default=('foo', 'bar'))
@click.option("--arg2", multiple=True, default=(1, "a"))
def cmd(arg1, arg2):
assert all(isinstance(e[0],str) for e in arg1)
assert all(isinstance(e[1],str) for e in arg1)
assert all(isinstance(e[0],int) for e in arg2)
assert all(isinstance(e[1],str) for e in arg2)
result = runner.invoke(cmd, "--arg1 a b --arg1 test 1 --arg2 2 two --arg2 4 four".split())
assert not result.exception
def test_nargs_envvar(runner):
@click.command()
@click.option('--arg', nargs=2)
@ -217,3 +272,35 @@ def test_multiline_help(runner):
assert ' --foo TEXT hello' in out
assert ' i am' in out
assert ' multiline' in out
def test_argument_custom_class(runner):
class CustomArgument(click.Argument):
def get_default(self, ctx):
'''a dumb override of a default value for testing'''
return 'I am a default'
@click.command()
@click.argument('testarg', cls=CustomArgument, default='you wont see me')
def cmd(testarg):
click.echo(testarg)
result = runner.invoke(cmd)
assert 'I am a default' in result.output
assert 'you wont see me' not in result.output
def test_option_custom_class(runner):
class CustomOption(click.Option):
def get_help_record(self, ctx):
'''a dumb override of a help text for testing'''
return ('--help', 'I am a help text')
@click.command()
@click.option('--testoption', cls=CustomOption, help='you wont see me')
def cmd(testoption):
click.echo(testoption)
result = runner.invoke(cmd, ['--help'])
assert 'I am a help text' in result.output
assert 'you wont see me' not in result.output

View file

@ -113,3 +113,30 @@ def test_catch_exceptions():
result = runner.invoke(cli)
assert result.exit_code == 1
def test_with_color():
@click.command()
def cli():
click.secho('hello world', fg='blue')
runner = CliRunner()
result = runner.invoke(cli)
assert result.output == 'hello world\n'
assert not result.exception
result = runner.invoke(cli, color=True)
assert result.output == click.style('hello world', fg='blue') + '\n'
assert not result.exception
def test_with_color_but_pause_not_blocking():
@click.command()
def cli():
click.pause()
runner = CliRunner()
result = runner.invoke(cli, color=True)
assert not result.exception
assert result.output == ''

View file

@ -39,6 +39,13 @@ def test_echo(runner):
assert out.getvalue() == b'\x1b[31mx\x1b[39m'
def test_echo_custom_file():
import io
f = io.StringIO()
click.echo(u'hello', file=f)
assert f.getvalue() == u'hello\n'
def test_styling():
examples = [
('x', dict(fg='black'), '\x1b[30mx\x1b[0m'),
@ -124,6 +131,90 @@ def test_echo_via_pager(monkeypatch, capfd):
assert out == 'haha\n'
def test_echo_color_flag(monkeypatch, capfd):
isatty = True
monkeypatch.setattr(click._compat, 'isatty', lambda x: isatty)
text = 'foo'
styled_text = click.style(text, fg='red')
assert styled_text == '\x1b[31mfoo\x1b[0m'
click.echo(styled_text, color=False)
out, err = capfd.readouterr()
assert out == text + '\n'
click.echo(styled_text, color=True)
out, err = capfd.readouterr()
assert out == styled_text + '\n'
isatty = True
click.echo(styled_text)
out, err = capfd.readouterr()
assert out == styled_text + '\n'
isatty = False
click.echo(styled_text)
out, err = capfd.readouterr()
assert out == text + '\n'
def test_echo_writing_to_standard_error(capfd, monkeypatch):
def emulate_input(text):
"""Emulate keyboard input."""
if sys.version_info[0] == 2:
from StringIO import StringIO
else:
from io import StringIO
monkeypatch.setattr(sys, 'stdin', StringIO(text))
click.echo('Echo to standard output')
out, err = capfd.readouterr()
assert out == 'Echo to standard output\n'
assert err == ''
click.echo('Echo to standard error', err=True)
out, err = capfd.readouterr()
assert out == ''
assert err == 'Echo to standard error\n'
emulate_input('asdlkj\n')
click.prompt('Prompt to stdin')
out, err = capfd.readouterr()
assert out == 'Prompt to stdin: '
assert err == ''
emulate_input('asdlkj\n')
click.prompt('Prompt to stderr', err=True)
out, err = capfd.readouterr()
assert out == ''
assert err == 'Prompt to stderr: '
emulate_input('y\n')
click.confirm('Prompt to stdin')
out, err = capfd.readouterr()
assert out == 'Prompt to stdin [y/N]: '
assert err == ''
emulate_input('y\n')
click.confirm('Prompt to stderr', err=True)
out, err = capfd.readouterr()
assert out == ''
assert err == 'Prompt to stderr [y/N]: '
monkeypatch.setattr(click.termui, 'isatty', lambda x: True)
monkeypatch.setattr(click.termui, 'getchar', lambda: ' ')
click.pause('Pause to stdout')
out, err = capfd.readouterr()
assert out == 'Pause to stdout\n'
assert err == ''
click.pause('Pause to stderr', err=True)
out, err = capfd.readouterr()
assert out == ''
assert err == 'Pause to stderr\n'
def test_open_file(runner):
with runner.isolated_filesystem():
with open('hello.txt', 'w') as f: