Imported Upstream version 4.1
This commit is contained in:
parent
83d25aaa27
commit
a57321344a
45
CHANGES
45
CHANGES
|
@ -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
|
||||
-----------
|
||||
|
||||
|
|
2
PKG-INFO
2
PKG-INFO
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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() + ';'
|
||||
|
|
|
@ -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))
|
||||
|
||||
|
|
|
@ -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'
|
||||
|
||||
|
|
143
click/core.py
143
click/core.py
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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."""
|
||||
|
||||
|
|
|
@ -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 = []
|
||||
|
|
128
click/parser.py
128
click/parser.py
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
----------
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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).
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'])
|
||||
|
|
|
@ -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
BIN
examples/.DS_Store
vendored
Binary file not shown.
|
@ -6,7 +6,7 @@ setup(
|
|||
py_modules=['aliases'],
|
||||
include_package_data=True,
|
||||
install_requires=[
|
||||
'Click',
|
||||
'click',
|
||||
],
|
||||
entry_points='''
|
||||
[console_scripts]
|
||||
|
|
|
@ -6,7 +6,7 @@ setup(
|
|||
py_modules=['colors'],
|
||||
include_package_data=True,
|
||||
install_requires=[
|
||||
'Click',
|
||||
'click',
|
||||
# Colorama is only required for Windows.
|
||||
'colorama',
|
||||
],
|
||||
|
|
|
@ -6,7 +6,7 @@ setup(
|
|||
packages=['complex', 'complex.commands'],
|
||||
include_package_data=True,
|
||||
install_requires=[
|
||||
'Click',
|
||||
'click',
|
||||
],
|
||||
entry_points='''
|
||||
[console_scripts]
|
||||
|
|
BIN
examples/imagepipe/.DS_Store
vendored
BIN
examples/imagepipe/.DS_Store
vendored
Binary file not shown.
|
@ -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
|
|
@ -1,4 +0,0 @@
|
|||
|
||||
[console_scripts]
|
||||
imagepipe=imagepipe:cli
|
||||
|
|
@ -1 +0,0 @@
|
|||
Click
|
|
@ -1 +0,0 @@
|
|||
imagepipe
|
|
@ -6,7 +6,7 @@ setup(
|
|||
py_modules=['imagepipe'],
|
||||
include_package_data=True,
|
||||
install_requires=[
|
||||
'Click',
|
||||
'click',
|
||||
'pillow',
|
||||
],
|
||||
entry_points='''
|
||||
|
|
|
@ -6,7 +6,7 @@ setup(
|
|||
py_modules=['inout'],
|
||||
include_package_data=True,
|
||||
install_requires=[
|
||||
'Click',
|
||||
'click',
|
||||
],
|
||||
entry_points='''
|
||||
[console_scripts]
|
||||
|
|
|
@ -6,7 +6,7 @@ setup(
|
|||
py_modules=['naval'],
|
||||
include_package_data=True,
|
||||
install_requires=[
|
||||
'Click',
|
||||
'click',
|
||||
],
|
||||
entry_points='''
|
||||
[console_scripts]
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
[printer.plugins]
|
||||
bold=printer_bold.core:bolddddddddddd
|
||||
|
|
@ -0,0 +1 @@
|
|||
printer_bold
|
10
examples/plugins/printer.egg-info/PKG-INFO
Normal file
10
examples/plugins/printer.egg-info/PKG-INFO
Normal 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
|
8
examples/plugins/printer.egg-info/SOURCES.txt
Normal file
8
examples/plugins/printer.egg-info/SOURCES.txt
Normal 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
|
1
examples/plugins/printer.egg-info/dependency_links.txt
Normal file
1
examples/plugins/printer.egg-info/dependency_links.txt
Normal file
|
@ -0,0 +1 @@
|
|||
|
4
examples/plugins/printer.egg-info/entry_points.txt
Normal file
4
examples/plugins/printer.egg-info/entry_points.txt
Normal file
|
@ -0,0 +1,4 @@
|
|||
|
||||
[console_scripts]
|
||||
printer=printer.cli:cli
|
||||
|
1
examples/plugins/printer.egg-info/top_level.txt
Normal file
1
examples/plugins/printer.egg-info/top_level.txt
Normal file
|
@ -0,0 +1 @@
|
|||
printer
|
|
@ -6,7 +6,7 @@ setup(
|
|||
py_modules=['repo'],
|
||||
include_package_data=True,
|
||||
install_requires=[
|
||||
'Click',
|
||||
'click',
|
||||
],
|
||||
entry_points='''
|
||||
[console_scripts]
|
||||
|
|
145
examples/termui/build/lib/termui.py
Normal file
145
examples/termui/build/lib/termui.py
Normal 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
|
BIN
examples/termui/dist/click_example_termui-1.0-py3.4.egg
vendored
Normal file
BIN
examples/termui/dist/click_example_termui-1.0-py3.4.egg
vendored
Normal file
Binary file not shown.
|
@ -6,7 +6,7 @@ setup(
|
|||
py_modules=['termui'],
|
||||
include_package_data=True,
|
||||
install_requires=[
|
||||
'Click',
|
||||
'click',
|
||||
# Colorama is only required for Windows.
|
||||
'colorama',
|
||||
],
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -6,7 +6,7 @@ setup(
|
|||
py_modules=['validation'],
|
||||
include_package_data=True,
|
||||
install_requires=[
|
||||
'Click',
|
||||
'click',
|
||||
],
|
||||
entry_points='''
|
||||
[console_scripts]
|
||||
|
|
|
@ -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')",
|
||||
]
|
||||
|
|
|
@ -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',
|
||||
]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.',
|
||||
]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 == ''
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Reference in a new issue