Merge tag 'upstream/6.2'
Upstream version 6.2
This commit is contained in:
commit
34ba2edbe9
56
CHANGES
56
CHANGES
|
@ -3,6 +3,62 @@ Click Changelog
|
|||
|
||||
This contains all major version changes between Click releases.
|
||||
|
||||
Version 6.2
|
||||
-----------
|
||||
|
||||
(bugfix release, released on November 27th 2015)
|
||||
|
||||
- Correct fix for hidden progress bars.
|
||||
|
||||
Version 6.1
|
||||
-----------
|
||||
|
||||
(bugfix release, released on November 27th 2015)
|
||||
|
||||
- Resolved an issue with invisible progress bars no longer rendering.
|
||||
- Disable chain commands with subcommands as they were inherently broken.
|
||||
- Fix `MissingParameter` not working without parameters passed.
|
||||
|
||||
Version 6.0
|
||||
-----------
|
||||
|
||||
(codename "pow pow", released on November 24th 2015)
|
||||
|
||||
- Optimized the progressbar rendering to not render when it did not
|
||||
actually change.
|
||||
- Explicitly disallow nargs=-1 with a set default.
|
||||
- The context is now closed before it's popped from the stack.
|
||||
- Added support for short aliases for the false flag on toggles.
|
||||
- Click will now attempt to aid you with debugging locale errors
|
||||
better by listing with the help of the OS what locales are
|
||||
available.
|
||||
- Click used to return byte strings on Python 2 in some unit-testing
|
||||
situations. This has been fixed to correctly return unicode strings
|
||||
now.
|
||||
- For Windows users on Python 2, Click will now handle Unicode more
|
||||
correctly handle Unicode coming in from the system. This also has
|
||||
the disappointing side effect that filenames will now be always
|
||||
unicode by default in the `Path` type which means that this can
|
||||
introduce small bugs for code not aware of this.
|
||||
- Added a `type` parameter to `Path` to force a specific string type
|
||||
on the value.
|
||||
- For users running Python on Windows the `echo`) and `prompt` functions
|
||||
now work with full unicode functionality in the Python windows console
|
||||
by emulating an output stream. This also applies to getting the
|
||||
virtual output and input streams via `click.get_text_stream(...)`.
|
||||
- Unittests now always force a certain virtual terminal width.
|
||||
- Added support for allowing dashes to indicate standard streams to the
|
||||
`Path` type.
|
||||
- Multi commands in chain mode no longer propagate arguments left over
|
||||
from parsing to the callbacks. It's also now disallowed through an
|
||||
exception when optional arguments are attached to multi commands if chain
|
||||
mode is enabled.
|
||||
- Relaxed restriction that disallowed chained commands to have other
|
||||
chained commands as child commands.
|
||||
- Arguments with positive nargs can now have defaults implemented.
|
||||
Previously this configuration would often result in slightly unexpected
|
||||
values be returned.
|
||||
|
||||
Version 5.1
|
||||
-----------
|
||||
|
||||
|
|
3
Makefile
3
Makefile
|
@ -5,4 +5,7 @@ upload-docs:
|
|||
$(MAKE) -C docs dirhtml
|
||||
rsync -a docs/_build/dirhtml/* flow.srv.pocoo.org:/srv/websites/click.pocoo.org/static/
|
||||
|
||||
release:
|
||||
python setup.py sdist bdist_wheel upload
|
||||
|
||||
.PHONY: upload-docs
|
||||
|
|
2
PKG-INFO
2
PKG-INFO
|
@ -1,6 +1,6 @@
|
|||
Metadata-Version: 1.1
|
||||
Name: click
|
||||
Version: 5.1
|
||||
Version: 6.2
|
||||
Summary: A simple wrapper around optparse for powerful command line utilities.
|
||||
Home-page: http://github.com/mitsuhiko/click
|
||||
Author: Armin Ronacher
|
||||
|
|
BIN
artwork/.DS_Store
vendored
Normal file
BIN
artwork/.DS_Store
vendored
Normal file
Binary file not shown.
|
@ -1,6 +1,6 @@
|
|||
Metadata-Version: 1.1
|
||||
Name: click
|
||||
Version: 5.1
|
||||
Version: 6.2
|
||||
Summary: A simple wrapper around optparse for powerful command line utilities.
|
||||
Home-page: http://github.com/mitsuhiko/click
|
||||
Author: Armin Ronacher
|
||||
|
|
|
@ -5,12 +5,15 @@ Makefile
|
|||
README
|
||||
setup.cfg
|
||||
setup.py
|
||||
artwork/.DS_Store
|
||||
artwork/logo.svg
|
||||
click/__init__.py
|
||||
click/_bashcomplete.py
|
||||
click/_compat.py
|
||||
click/_termui_impl.py
|
||||
click/_textwrap.py
|
||||
click/_unicodefun.py
|
||||
click/_winconsole.py
|
||||
click/core.py
|
||||
click/decorators.py
|
||||
click/exceptions.py
|
||||
|
@ -51,12 +54,14 @@ docs/testing.rst
|
|||
docs/upgrading.rst
|
||||
docs/utils.rst
|
||||
docs/why.rst
|
||||
docs/wincmd.rst
|
||||
docs/_static/click-small.png
|
||||
docs/_static/click-small@2x.png
|
||||
docs/_static/click.png
|
||||
docs/_static/click@2x.png
|
||||
docs/_templates/sidebarintro.html
|
||||
docs/_templates/sidebarlogo.html
|
||||
examples/.DS_Store
|
||||
examples/README
|
||||
examples/aliases/README
|
||||
examples/aliases/aliases.ini
|
||||
|
@ -72,36 +77,50 @@ 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/demo/demo.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/naval/click_example_naval.egg-info/PKG-INFO
|
||||
examples/naval/click_example_naval.egg-info/SOURCES.txt
|
||||
examples/naval/click_example_naval.egg-info/dependency_links.txt
|
||||
examples/naval/click_example_naval.egg-info/entry_points.txt
|
||||
examples/naval/click_example_naval.egg-info/requires.txt
|
||||
examples/naval/click_example_naval.egg-info/top_level.txt
|
||||
examples/repo/README
|
||||
examples/repo/repo.py
|
||||
examples/repo/setup.py
|
||||
examples/repo/click_example_repo.egg-info/PKG-INFO
|
||||
examples/repo/click_example_repo.egg-info/SOURCES.txt
|
||||
examples/repo/click_example_repo.egg-info/dependency_links.txt
|
||||
examples/repo/click_example_repo.egg-info/entry_points.txt
|
||||
examples/repo/click_example_repo.egg-info/requires.txt
|
||||
examples/repo/click_example_repo.egg-info/top_level.txt
|
||||
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/termui/click_example_termui.egg-info/PKG-INFO
|
||||
examples/termui/click_example_termui.egg-info/SOURCES.txt
|
||||
examples/termui/click_example_termui.egg-info/dependency_links.txt
|
||||
examples/termui/click_example_termui.egg-info/entry_points.txt
|
||||
examples/termui/click_example_termui.egg-info/requires.txt
|
||||
examples/termui/click_example_termui.egg-info/top_level.txt
|
||||
examples/validation/README
|
||||
examples/validation/setup.py
|
||||
examples/validation/validation.py
|
||||
|
|
|
@ -32,7 +32,7 @@ from .types import ParamType, File, Path, Choice, IntRange, Tuple, \
|
|||
|
||||
# Utilities
|
||||
from .utils import echo, get_binary_stream, get_text_stream, open_file, \
|
||||
format_filename, get_app_dir
|
||||
format_filename, get_app_dir, get_os_args
|
||||
|
||||
# Terminal functions
|
||||
from .termui import prompt, confirm, get_terminal_size, echo_via_pager, \
|
||||
|
@ -41,7 +41,8 @@ from .termui import prompt, confirm, get_terminal_size, echo_via_pager, \
|
|||
|
||||
# Exceptions
|
||||
from .exceptions import ClickException, UsageError, BadParameter, \
|
||||
FileError, Abort, NoSuchOption, BadOptionUsage, MissingParameter
|
||||
FileError, Abort, NoSuchOption, BadOptionUsage, BadArgumentUsage, \
|
||||
MissingParameter
|
||||
|
||||
# Formatting
|
||||
from .formatting import HelpFormatter, wrap_text
|
||||
|
@ -69,7 +70,7 @@ __all__ = [
|
|||
|
||||
# Utilities
|
||||
'echo', 'get_binary_stream', 'get_text_stream', 'open_file',
|
||||
'format_filename', 'get_app_dir',
|
||||
'format_filename', 'get_app_dir', 'get_os_args',
|
||||
|
||||
# Terminal functions
|
||||
'prompt', 'confirm', 'get_terminal_size', 'echo_via_pager',
|
||||
|
@ -78,7 +79,8 @@ __all__ = [
|
|||
|
||||
# Exceptions
|
||||
'ClickException', 'UsageError', 'BadParameter', 'FileError',
|
||||
'Abort', 'NoSuchOption', 'BadOptionUsage', 'MissingParameter',
|
||||
'Abort', 'NoSuchOption', 'BadOptionUsage', 'BadArgumentUsage',
|
||||
'MissingParameter',
|
||||
|
||||
# Formatting
|
||||
'HelpFormatter', 'wrap_text',
|
||||
|
@ -93,4 +95,4 @@ __all__ = [
|
|||
disable_unicode_literals_warning = False
|
||||
|
||||
|
||||
__version__ = '5.1'
|
||||
__version__ = '6.2'
|
||||
|
|
|
@ -5,8 +5,6 @@ import sys
|
|||
import codecs
|
||||
from weakref import WeakKeyDictionary
|
||||
|
||||
import click
|
||||
|
||||
|
||||
PY2 = sys.version_info[0] == 2
|
||||
WIN = sys.platform.startswith('win')
|
||||
|
@ -16,39 +14,6 @@ DEFAULT_COLUMNS = 80
|
|||
_ansi_re = re.compile('\033\[((?:\d|;)*)([a-zA-Z])')
|
||||
|
||||
|
||||
def _find_unicode_literals_frame():
|
||||
import __future__
|
||||
frm = sys._getframe(1)
|
||||
idx = 1
|
||||
while frm is not None:
|
||||
if frm.f_globals.get('__name__', '').startswith('click.'):
|
||||
frm = frm.f_back
|
||||
idx += 1
|
||||
elif frm.f_code.co_flags & __future__.unicode_literals.compiler_flag:
|
||||
return idx
|
||||
else:
|
||||
break
|
||||
return 0
|
||||
|
||||
|
||||
def _check_for_unicode_literals():
|
||||
if not __debug__:
|
||||
return
|
||||
if not PY2 or click.disable_unicode_literals_warning:
|
||||
return
|
||||
bad_frame = _find_unicode_literals_frame()
|
||||
if bad_frame <= 0:
|
||||
return
|
||||
from warnings import warn
|
||||
warn(Warning('Click detected the use of the unicode_literals '
|
||||
'__future__ import. This is heavily discouraged '
|
||||
'because it can introduce subtle bugs in your '
|
||||
'code. You should instead use explicit u"" literals '
|
||||
'for your unicode strings. For more information see '
|
||||
'http://click.pocoo.org/python3/'),
|
||||
stacklevel=bad_frame)
|
||||
|
||||
|
||||
def get_filesystem_encoding():
|
||||
return sys.getfilesystemencoding() or sys.getdefaultencoding()
|
||||
|
||||
|
@ -192,6 +157,9 @@ if PY2:
|
|||
# binary only, patch it back to the system, and then use a wrapper
|
||||
# stream that converts newlines. It's not quite clear what's the
|
||||
# correct option here.
|
||||
#
|
||||
# This code also lives in _winconsole for the fallback to the console
|
||||
# emulation stream.
|
||||
if WIN:
|
||||
import msvcrt
|
||||
def set_binary_mode(f):
|
||||
|
@ -218,12 +186,21 @@ if PY2:
|
|||
return set_binary_mode(sys.stderr)
|
||||
|
||||
def get_text_stdin(encoding=None, errors=None):
|
||||
rv = _get_windows_console_stream(sys.stdin, encoding, errors)
|
||||
if rv is not None:
|
||||
return rv
|
||||
return _make_text_stream(sys.stdin, encoding, errors)
|
||||
|
||||
def get_text_stdout(encoding=None, errors=None):
|
||||
rv = _get_windows_console_stream(sys.stdout, encoding, errors)
|
||||
if rv is not None:
|
||||
return rv
|
||||
return _make_text_stream(sys.stdout, encoding, errors)
|
||||
|
||||
def get_text_stderr(encoding=None, errors=None):
|
||||
rv = _get_windows_console_stream(sys.stderr, encoding, errors)
|
||||
if rv is not None:
|
||||
return rv
|
||||
return _make_text_stream(sys.stderr, encoding, errors)
|
||||
|
||||
def filename_to_ui(value):
|
||||
|
@ -393,12 +370,21 @@ else:
|
|||
return writer
|
||||
|
||||
def get_text_stdin(encoding=None, errors=None):
|
||||
rv = _get_windows_console_stream(sys.stdin, encoding, errors)
|
||||
if rv is not None:
|
||||
return rv
|
||||
return _force_correct_text_reader(sys.stdin, encoding, errors)
|
||||
|
||||
def get_text_stdout(encoding=None, errors=None):
|
||||
rv = _get_windows_console_stream(sys.stdout, encoding, errors)
|
||||
if rv is not None:
|
||||
return rv
|
||||
return _force_correct_text_writer(sys.stdout, encoding, errors)
|
||||
|
||||
def get_text_stderr(encoding=None, errors=None):
|
||||
rv = _get_windows_console_stream(sys.stderr, encoding, errors)
|
||||
if rv is not None:
|
||||
return rv
|
||||
return _force_correct_text_writer(sys.stderr, encoding, errors)
|
||||
|
||||
def filename_to_ui(value):
|
||||
|
@ -472,7 +458,12 @@ def open_stream(filename, mode='r', encoding=None, errors='strict',
|
|||
|
||||
|
||||
# Used in a destructor call, needs extra protection from interpreter cleanup.
|
||||
_rename = os.rename
|
||||
if hasattr(os, 'replace'):
|
||||
_replace = os.replace
|
||||
_can_replace = True
|
||||
else:
|
||||
_replace = os.rename
|
||||
_can_replace = not WIN
|
||||
|
||||
|
||||
class _AtomicFile(object):
|
||||
|
@ -491,7 +482,12 @@ class _AtomicFile(object):
|
|||
if self.closed:
|
||||
return
|
||||
self._f.close()
|
||||
_rename(self._tmp_filename, self._real_filename)
|
||||
if not _can_replace:
|
||||
try:
|
||||
os.remove(self._real_filename)
|
||||
except OSError:
|
||||
pass
|
||||
_replace(self._tmp_filename, self._real_filename)
|
||||
self.closed = True
|
||||
|
||||
def __getattr__(self, name):
|
||||
|
@ -531,6 +527,21 @@ if WIN:
|
|||
# Windows has a smaller terminal
|
||||
DEFAULT_COLUMNS = 79
|
||||
|
||||
from ._winconsole import _get_windows_console_stream
|
||||
|
||||
def _get_argv_encoding():
|
||||
import locale
|
||||
return locale.getpreferredencoding()
|
||||
|
||||
if PY2:
|
||||
def raw_input(prompt=''):
|
||||
sys.stderr.flush()
|
||||
if prompt:
|
||||
stdout = _default_text_stdout()
|
||||
stdout.write(prompt)
|
||||
stdin = _default_text_stdin()
|
||||
return stdin.readline().rstrip('\r\n')
|
||||
|
||||
try:
|
||||
import colorama
|
||||
except ImportError:
|
||||
|
@ -573,6 +584,11 @@ if WIN:
|
|||
win = colorama.win32.GetConsoleScreenBufferInfo(
|
||||
colorama.win32.STDOUT).srWindow
|
||||
return win.Right - win.Left, win.Bottom - win.Top
|
||||
else:
|
||||
def _get_argv_encoding():
|
||||
return getattr(sys.stdin, 'encoding', None) or get_filesystem_encoding()
|
||||
|
||||
_get_windows_console_stream = lambda *x: None
|
||||
|
||||
|
||||
def term_len(x):
|
||||
|
@ -605,6 +621,8 @@ def _make_cached_stream_func(src_func, wrapper_func):
|
|||
return func
|
||||
|
||||
|
||||
_default_text_stdin = _make_cached_stream_func(
|
||||
lambda: sys.stdin, get_text_stdin)
|
||||
_default_text_stdout = _make_cached_stream_func(
|
||||
lambda: sys.stdout, get_text_stdout)
|
||||
_default_text_stderr = _make_cached_stream_func(
|
||||
|
|
|
@ -87,6 +87,7 @@ class ProgressBar(object):
|
|||
self.entered = False
|
||||
self.current_item = None
|
||||
self.is_hidden = not isatty(self.file)
|
||||
self._last_line = None
|
||||
|
||||
def __enter__(self):
|
||||
self.entered = True
|
||||
|
@ -128,7 +129,18 @@ class ProgressBar(object):
|
|||
|
||||
def format_eta(self):
|
||||
if self.eta_known:
|
||||
return time.strftime('%H:%M:%S', time.gmtime(self.eta + 1))
|
||||
t = self.eta + 1
|
||||
seconds = t % 60
|
||||
t /= 60
|
||||
minutes = t % 60
|
||||
t /= 60
|
||||
hours = t % 24
|
||||
t /= 24
|
||||
if t > 0:
|
||||
days = t
|
||||
return '%dd %02d:%02d:%02d' % (days, hours, minutes, seconds)
|
||||
else:
|
||||
return '%02d:%02d:%02d' % (hours, minutes, seconds)
|
||||
return ''
|
||||
|
||||
def format_pos(self):
|
||||
|
@ -179,12 +191,13 @@ class ProgressBar(object):
|
|||
|
||||
def render_progress(self):
|
||||
from .termui import get_terminal_size
|
||||
nl = False
|
||||
|
||||
if self.is_hidden:
|
||||
echo(self.label, file=self.file, color=self.color)
|
||||
self.file.flush()
|
||||
return
|
||||
|
||||
buf = [self.label]
|
||||
nl = True
|
||||
else:
|
||||
buf = []
|
||||
# Update width in case the terminal has been resized
|
||||
if self.autowidth:
|
||||
old_width = self.width
|
||||
|
@ -192,8 +205,8 @@ class ProgressBar(object):
|
|||
clutter_length = term_len(self.format_progress_line())
|
||||
new_width = max(0, get_terminal_size()[0] - clutter_length)
|
||||
if new_width < old_width:
|
||||
self.file.write(BEFORE_BAR)
|
||||
self.file.write(' ' * self.max_width)
|
||||
buf.append(BEFORE_BAR)
|
||||
buf.append(' ' * self.max_width)
|
||||
self.max_width = new_width
|
||||
self.width = new_width
|
||||
|
||||
|
@ -201,14 +214,20 @@ class ProgressBar(object):
|
|||
if self.max_width is not None:
|
||||
clear_width = self.max_width
|
||||
|
||||
self.file.write(BEFORE_BAR)
|
||||
buf.append(BEFORE_BAR)
|
||||
line = self.format_progress_line()
|
||||
line_len = term_len(line)
|
||||
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, color=self.color)
|
||||
self.file.write(' ' * (clear_width - line_len))
|
||||
buf.append(line)
|
||||
|
||||
buf.append(' ' * (clear_width - line_len))
|
||||
line = ''.join(buf)
|
||||
|
||||
# Render the line only if it changed.
|
||||
if line != self._last_line:
|
||||
self._last_line = line
|
||||
echo(line, file=self.file, color=self.color, nl=nl)
|
||||
self.file.flush()
|
||||
|
||||
def make_step(self, n_steps):
|
||||
|
|
114
click/_unicodefun.py
Normal file
114
click/_unicodefun.py
Normal file
|
@ -0,0 +1,114 @@
|
|||
import os
|
||||
import sys
|
||||
import codecs
|
||||
|
||||
from ._compat import PY2
|
||||
|
||||
|
||||
# If someone wants to vendor click, we want to ensure the
|
||||
# correct package is discovered. Ideally we could use a
|
||||
# relative import here but unfortunately Python does not
|
||||
# support that.
|
||||
click = sys.modules[__name__.rsplit('.', 1)[0]]
|
||||
|
||||
|
||||
def _find_unicode_literals_frame():
|
||||
import __future__
|
||||
frm = sys._getframe(1)
|
||||
idx = 1
|
||||
while frm is not None:
|
||||
if frm.f_globals.get('__name__', '').startswith('click.'):
|
||||
frm = frm.f_back
|
||||
idx += 1
|
||||
elif frm.f_code.co_flags & __future__.unicode_literals.compiler_flag:
|
||||
return idx
|
||||
else:
|
||||
break
|
||||
return 0
|
||||
|
||||
|
||||
def _check_for_unicode_literals():
|
||||
if not __debug__:
|
||||
return
|
||||
if not PY2 or click.disable_unicode_literals_warning:
|
||||
return
|
||||
bad_frame = _find_unicode_literals_frame()
|
||||
if bad_frame <= 0:
|
||||
return
|
||||
from warnings import warn
|
||||
warn(Warning('Click detected the use of the unicode_literals '
|
||||
'__future__ import. This is heavily discouraged '
|
||||
'because it can introduce subtle bugs in your '
|
||||
'code. You should instead use explicit u"" literals '
|
||||
'for your unicode strings. For more information see '
|
||||
'http://click.pocoo.org/python3/'),
|
||||
stacklevel=bad_frame)
|
||||
|
||||
|
||||
def _verify_python3_env():
|
||||
"""Ensures that the environment is good for unicode on Python 3."""
|
||||
if PY2:
|
||||
return
|
||||
try:
|
||||
import locale
|
||||
fs_enc = codecs.lookup(locale.getpreferredencoding()).name
|
||||
except Exception:
|
||||
fs_enc = 'ascii'
|
||||
if fs_enc != 'ascii':
|
||||
return
|
||||
|
||||
extra = ''
|
||||
if os.name == 'posix':
|
||||
import subprocess
|
||||
rv = subprocess.Popen(['locale', '-a'], stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE).communicate()[0]
|
||||
good_locales = set()
|
||||
has_c_utf8 = False
|
||||
for line in rv.splitlines():
|
||||
locale = line.strip()
|
||||
if locale.lower().endswith(('.utf-8', '.utf8')):
|
||||
good_locales.add(locale)
|
||||
if locale.lower() in ('c.utf8', 'c.utf-8'):
|
||||
has_c_utf8 = True
|
||||
|
||||
extra += '\n\n'
|
||||
if not good_locales:
|
||||
extra += (
|
||||
'Additional information: on this system no suitable UTF-8\n'
|
||||
'locales were discovered. This most likely requires resolving\n'
|
||||
'by reconfiguring the locale system.'
|
||||
)
|
||||
elif has_c_utf8:
|
||||
extra += (
|
||||
'This system supports the C.UTF-8 locale which is recommended.\n'
|
||||
'You might be able to resolve your issue by exporting the\n'
|
||||
'following environment variables:\n\n'
|
||||
' export LC_ALL=C.UTF-8\n'
|
||||
' export LANG=C.UTF-8'
|
||||
)
|
||||
else:
|
||||
extra += (
|
||||
'This system lists a couple of UTF-8 supporting locales that\n'
|
||||
'you can pick from. The following suitable locales where\n'
|
||||
'discovered: %s'
|
||||
) % ', '.join(sorted(good_locales))
|
||||
|
||||
bad_locale = None
|
||||
for locale in os.environ.get('LC_ALL'), os.environ.get('LANG'):
|
||||
if locale and locale.lower().endswith(('.utf-8', '.utf8')):
|
||||
bad_locale = locale
|
||||
if locale is not None:
|
||||
break
|
||||
if bad_locale is not None:
|
||||
extra += (
|
||||
'\n\nClick discovered that you exported a UTF-8 locale\n'
|
||||
'but the locale system could not pick up from it because\n'
|
||||
'it does not exist. The exported locale is "%s" but it\n'
|
||||
'is not supported'
|
||||
) % bad_locale
|
||||
|
||||
raise RuntimeError('Click will abort further execution because Python 3 '
|
||||
'was configured to use ASCII as encoding for the '
|
||||
'environment. Either run this under Python 2 or '
|
||||
'consult http://click.pocoo.org/python3/ for '
|
||||
'mitigation steps.' + extra)
|
264
click/_winconsole.py
Normal file
264
click/_winconsole.py
Normal file
|
@ -0,0 +1,264 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# This module is based on the excellent work by Adam Bartoš who
|
||||
# provided a lot of what went into the implementation here in
|
||||
# the discussion to issue1602 in the Python bug tracker.
|
||||
#
|
||||
# There are some general differences in regards to how this works
|
||||
# compared to the original patches as we do not need to patch
|
||||
# the entire interpreter but just work in our little world of
|
||||
# echo and prmopt.
|
||||
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
import zlib
|
||||
import time
|
||||
import ctypes
|
||||
import msvcrt
|
||||
from click._compat import _NonClosingTextIOWrapper, text_type, PY2
|
||||
from ctypes import byref, POINTER, pythonapi, c_int, c_char, c_char_p, \
|
||||
c_void_p, py_object, c_ssize_t, c_ulong, windll, WINFUNCTYPE
|
||||
from ctypes.wintypes import LPWSTR, LPCWSTR
|
||||
|
||||
|
||||
c_ssize_p = POINTER(c_ssize_t)
|
||||
|
||||
PyObject_GetBuffer = pythonapi.PyObject_GetBuffer
|
||||
PyBuffer_Release = pythonapi.PyBuffer_Release
|
||||
|
||||
kernel32 = windll.kernel32
|
||||
GetStdHandle = kernel32.GetStdHandle
|
||||
ReadConsoleW = kernel32.ReadConsoleW
|
||||
WriteConsoleW = kernel32.WriteConsoleW
|
||||
GetLastError = kernel32.GetLastError
|
||||
GetCommandLineW = WINFUNCTYPE(LPWSTR)(
|
||||
('GetCommandLineW', windll.kernel32))
|
||||
CommandLineToArgvW = WINFUNCTYPE(
|
||||
POINTER(LPWSTR), LPCWSTR, POINTER(c_int))(
|
||||
('CommandLineToArgvW', windll.shell32))
|
||||
|
||||
|
||||
STDIN_HANDLE = GetStdHandle(-10)
|
||||
STDOUT_HANDLE = GetStdHandle(-11)
|
||||
STDERR_HANDLE = GetStdHandle(-12)
|
||||
|
||||
|
||||
PyBUF_SIMPLE = 0
|
||||
PyBUF_WRITABLE = 1
|
||||
|
||||
ERROR_SUCCESS = 0
|
||||
ERROR_NOT_ENOUGH_MEMORY = 8
|
||||
ERROR_OPERATION_ABORTED = 995
|
||||
|
||||
STDIN_FILENO = 0
|
||||
STDOUT_FILENO = 1
|
||||
STDERR_FILENO = 2
|
||||
|
||||
EOF = b'\x1a'
|
||||
MAX_BYTES_WRITTEN = 32767
|
||||
|
||||
|
||||
class Py_buffer(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('buf', c_void_p),
|
||||
('obj', py_object),
|
||||
('len', c_ssize_t),
|
||||
('itemsize', c_ssize_t),
|
||||
('readonly', c_int),
|
||||
('ndim', c_int),
|
||||
('format', c_char_p),
|
||||
('shape', c_ssize_p),
|
||||
('strides', c_ssize_p),
|
||||
('suboffsets', c_ssize_p),
|
||||
('internal', c_void_p)
|
||||
]
|
||||
|
||||
if PY2:
|
||||
_fields_.insert(-1, ('smalltable', c_ssize_t * 2))
|
||||
|
||||
|
||||
def get_buffer(obj, writable=False):
|
||||
buf = Py_buffer()
|
||||
flags = PyBUF_WRITABLE if writable else PyBUF_SIMPLE
|
||||
PyObject_GetBuffer(py_object(obj), byref(buf), flags)
|
||||
try:
|
||||
buffer_type = c_char * buf.len
|
||||
return buffer_type.from_address(buf.buf)
|
||||
finally:
|
||||
PyBuffer_Release(byref(buf))
|
||||
|
||||
|
||||
class _WindowsConsoleRawIOBase(io.RawIOBase):
|
||||
|
||||
def __init__(self, handle):
|
||||
self.handle = handle
|
||||
|
||||
def isatty(self):
|
||||
io.RawIOBase.isatty(self)
|
||||
return True
|
||||
|
||||
|
||||
class _WindowsConsoleReader(_WindowsConsoleRawIOBase):
|
||||
|
||||
def readable(self):
|
||||
return True
|
||||
|
||||
def readinto(self, b):
|
||||
bytes_to_be_read = len(b)
|
||||
if not bytes_to_be_read:
|
||||
return 0
|
||||
elif bytes_to_be_read % 2:
|
||||
raise ValueError('cannot read odd number of bytes from '
|
||||
'UTF-16-LE encoded console')
|
||||
|
||||
buffer = get_buffer(b, writable=True)
|
||||
code_units_to_be_read = bytes_to_be_read // 2
|
||||
code_units_read = c_ulong()
|
||||
|
||||
rv = ReadConsoleW(self.handle, buffer, code_units_to_be_read,
|
||||
byref(code_units_read), None)
|
||||
if GetLastError() == ERROR_OPERATION_ABORTED:
|
||||
# wait for KeyboardInterrupt
|
||||
time.sleep(0.1)
|
||||
if not rv:
|
||||
raise OSError('Windows error: %s' % GetLastError())
|
||||
|
||||
if buffer[0] == EOF:
|
||||
return 0
|
||||
return 2 * code_units_read.value
|
||||
|
||||
|
||||
class _WindowsConsoleWriter(_WindowsConsoleRawIOBase):
|
||||
|
||||
def writable(self):
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def _get_error_message(errno):
|
||||
if errno == ERROR_SUCCESS:
|
||||
return 'ERROR_SUCCESS'
|
||||
elif errno == ERROR_NOT_ENOUGH_MEMORY:
|
||||
return 'ERROR_NOT_ENOUGH_MEMORY'
|
||||
return 'Windows error %s' % errno
|
||||
|
||||
def write(self, b):
|
||||
bytes_to_be_written = len(b)
|
||||
buf = get_buffer(b)
|
||||
code_units_to_be_written = min(bytes_to_be_written,
|
||||
MAX_BYTES_WRITTEN) // 2
|
||||
code_units_written = c_ulong()
|
||||
|
||||
WriteConsoleW(self.handle, buf, code_units_to_be_written,
|
||||
byref(code_units_written), None)
|
||||
bytes_written = 2 * code_units_written.value
|
||||
|
||||
if bytes_written == 0 and bytes_to_be_written > 0:
|
||||
raise OSError(self._get_error_message(GetLastError()))
|
||||
return bytes_written
|
||||
|
||||
|
||||
class ConsoleStream(object):
|
||||
|
||||
def __init__(self, text_stream, byte_stream):
|
||||
self._text_stream = text_stream
|
||||
self.buffer = byte_stream
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self.buffer.name
|
||||
|
||||
def write(self, x):
|
||||
if isinstance(x, text_type):
|
||||
return self._text_stream.write(x)
|
||||
try:
|
||||
self.flush()
|
||||
except Exception:
|
||||
pass
|
||||
return self.buffer.write(x)
|
||||
|
||||
def writelines(self, lines):
|
||||
for line in lines:
|
||||
self.write(line)
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self._text_stream, name)
|
||||
|
||||
def isatty(self):
|
||||
return self.buffer.isatty()
|
||||
|
||||
def __repr__(self):
|
||||
return '<ConsoleStream name=%r encoding=%r>' % (
|
||||
self.name,
|
||||
self.encoding,
|
||||
)
|
||||
|
||||
|
||||
def _get_text_stdin(buffer_stream):
|
||||
text_stream = _NonClosingTextIOWrapper(
|
||||
io.BufferedReader(_WindowsConsoleReader(STDIN_HANDLE)),
|
||||
'utf-16-le', 'strict', line_buffering=True)
|
||||
return ConsoleStream(text_stream, buffer_stream)
|
||||
|
||||
|
||||
def _get_text_stdout(buffer_stream):
|
||||
text_stream = _NonClosingTextIOWrapper(
|
||||
_WindowsConsoleWriter(STDOUT_HANDLE),
|
||||
'utf-16-le', 'strict', line_buffering=True)
|
||||
return ConsoleStream(text_stream, buffer_stream)
|
||||
|
||||
|
||||
def _get_text_stderr(buffer_stream):
|
||||
text_stream = _NonClosingTextIOWrapper(
|
||||
_WindowsConsoleWriter(STDERR_HANDLE),
|
||||
'utf-16-le', 'strict', line_buffering=True)
|
||||
return ConsoleStream(text_stream, buffer_stream)
|
||||
|
||||
|
||||
if PY2:
|
||||
def _hash_py_argv():
|
||||
return zlib.crc32('\x00'.join(sys.argv[1:]))
|
||||
|
||||
_initial_argv_hash = _hash_py_argv()
|
||||
|
||||
def _get_windows_argv():
|
||||
argc = c_int(0)
|
||||
argv_unicode = CommandLineToArgvW(GetCommandLineW(), byref(argc))
|
||||
argv = [argv_unicode[i] for i in range(0, argc.value)]
|
||||
|
||||
if not hasattr(sys, 'frozen'):
|
||||
argv = argv[1:]
|
||||
while len(argv) > 0:
|
||||
arg = argv[0]
|
||||
if not arg.startswith('-') or arg == '-':
|
||||
break
|
||||
argv = argv[1:]
|
||||
if arg in ('-c', '-m'):
|
||||
break
|
||||
|
||||
return argv[1:]
|
||||
|
||||
|
||||
_stream_factories = {
|
||||
0: _get_text_stdin,
|
||||
1: _get_text_stdout,
|
||||
2: _get_text_stderr,
|
||||
}
|
||||
|
||||
|
||||
def _get_windows_console_stream(f, encoding, errors):
|
||||
if encoding in ('utf-16-le', None) \
|
||||
and errors in ('strict', None) and \
|
||||
hasattr(f, 'isatty') and f.isatty():
|
||||
func = _stream_factories.get(f.fileno())
|
||||
if func is not None:
|
||||
if not PY2:
|
||||
f = getattr(f, 'buffer')
|
||||
if f is None:
|
||||
return None
|
||||
else:
|
||||
# If we are on Python 2 we need to set the stream that we
|
||||
# deal with to binary mode as otherwise the exercise if a
|
||||
# bit moot. The same problems apply as for
|
||||
# get_binary_stdin and friends from _compat.
|
||||
msvcrt.setmode(f.fileno(), os.O_BINARY)
|
||||
return func(f)
|
|
@ -1,12 +1,11 @@
|
|||
import os
|
||||
import sys
|
||||
import codecs
|
||||
from contextlib import contextmanager
|
||||
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 .utils import make_str, make_default_short_help, echo, get_os_args
|
||||
from .exceptions import ClickException, UsageError, BadParameter, Abort, \
|
||||
MissingParameter
|
||||
from .termui import prompt, confirm
|
||||
|
@ -14,7 +13,8 @@ from .formatting import HelpFormatter, join_options
|
|||
from .parser import OptionParser, split_opt
|
||||
from .globals import push_context, pop_context
|
||||
|
||||
from ._compat import PY2, isidentifier, iteritems, _check_for_unicode_literals
|
||||
from ._compat import PY2, isidentifier, iteritems
|
||||
from ._unicodefun import _check_for_unicode_literals, _verify_python3_env
|
||||
|
||||
|
||||
_missing = object()
|
||||
|
@ -37,6 +37,27 @@ def _bashcomplete(cmd, prog_name, complete_var=None):
|
|||
sys.exit(1)
|
||||
|
||||
|
||||
def _check_multicommand(base_command, cmd_name, cmd, register=False):
|
||||
if not base_command.chain or not isinstance(cmd, MultiCommand):
|
||||
return
|
||||
if register:
|
||||
hint = 'It is not possible to add multi commands as children to ' \
|
||||
'another multi command that is in chain mode'
|
||||
else:
|
||||
hint = 'Found a multi command as subcommand to a multi command ' \
|
||||
'that is in chain mode. This is not supported'
|
||||
raise RuntimeError('%s. Command "%s" is set to chain and "%s" was '
|
||||
'added as subcommand but it in itself is a '
|
||||
'multi command. ("%s" is a %s within a chained '
|
||||
'%s named "%s"). This restriction was supposed to '
|
||||
'be lifted in 6.0 but the fix was flawed. This '
|
||||
'will be fixed in Click 7.0' % (
|
||||
hint, base_command.name, cmd_name,
|
||||
cmd_name, cmd.__class__.__name__,
|
||||
base_command.__class__.__name__,
|
||||
base_command.name))
|
||||
|
||||
|
||||
def batch(iterable, batch_size):
|
||||
return list(zip(*repeat(iter(iterable), batch_size)))
|
||||
|
||||
|
@ -187,6 +208,11 @@ class Context(object):
|
|||
self.params = {}
|
||||
#: the leftover arguments.
|
||||
self.args = []
|
||||
#: protected arguments. These are arguments that are prepended
|
||||
#: to `args` when certain parsing scenarios are encountered but
|
||||
#: must be never propagated to another arguments. This is used
|
||||
#: to implement nested parsing.
|
||||
self.protected_args = []
|
||||
if obj is None and parent is not None:
|
||||
obj = parent.obj
|
||||
#: the user object stored.
|
||||
|
@ -299,10 +325,10 @@ class Context(object):
|
|||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, tb):
|
||||
pop_context()
|
||||
self._depth -= 1
|
||||
if self._depth == 0:
|
||||
self.close()
|
||||
pop_context()
|
||||
|
||||
@contextmanager
|
||||
def scope(self, cleanup=True):
|
||||
|
@ -646,25 +672,15 @@ class BaseCommand(object):
|
|||
# sane at this point of reject further execution to avoid a
|
||||
# broken script.
|
||||
if not PY2:
|
||||
try:
|
||||
import locale
|
||||
fs_enc = codecs.lookup(locale.getpreferredencoding()).name
|
||||
except Exception:
|
||||
fs_enc = 'ascii'
|
||||
if fs_enc == 'ascii':
|
||||
raise 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 '
|
||||
'http://click.pocoo.org/python3/ '
|
||||
'for mitigation steps.')
|
||||
_verify_python3_env()
|
||||
else:
|
||||
_check_for_unicode_literals()
|
||||
|
||||
if args is None:
|
||||
args = sys.argv[1:]
|
||||
args = get_os_args()
|
||||
else:
|
||||
args = list(args)
|
||||
|
||||
if prog_name is None:
|
||||
prog_name = make_str(os.path.basename(
|
||||
sys.argv and sys.argv[0] or __file__))
|
||||
|
@ -918,6 +934,12 @@ class MultiCommand(Command):
|
|||
#: overridden with the :func:`resultcallback` decorator.
|
||||
self.result_callback = result_callback
|
||||
|
||||
if self.chain:
|
||||
for param in self.params:
|
||||
if isinstance(param, Argument) and not param.required:
|
||||
raise RuntimeError('Multi commands in chain mode cannot '
|
||||
'have optional arguments.')
|
||||
|
||||
def collect_usage_pieces(self, ctx):
|
||||
rv = Command.collect_usage_pieces(self, ctx)
|
||||
rv.append(self.subcommand_metavar)
|
||||
|
@ -986,7 +1008,15 @@ class MultiCommand(Command):
|
|||
if not args and self.no_args_is_help and not ctx.resilient_parsing:
|
||||
echo(ctx.get_help(), color=ctx.color)
|
||||
ctx.exit()
|
||||
return Command.parse_args(self, ctx, args)
|
||||
|
||||
rest = Command.parse_args(self, ctx, args)
|
||||
if self.chain:
|
||||
ctx.protected_args = rest
|
||||
ctx.args = []
|
||||
elif rest:
|
||||
ctx.protected_args, ctx.args = rest[:1], rest[1:]
|
||||
|
||||
return ctx.args
|
||||
|
||||
def invoke(self, ctx):
|
||||
def _process_result(value):
|
||||
|
@ -995,7 +1025,7 @@ class MultiCommand(Command):
|
|||
**ctx.params)
|
||||
return value
|
||||
|
||||
if not ctx.args:
|
||||
if not ctx.protected_args:
|
||||
# If we are invoked without command the chain flag controls
|
||||
# how this happens. If we are not in chain mode, the return
|
||||
# value here is the return value of the command.
|
||||
|
@ -1010,7 +1040,10 @@ class MultiCommand(Command):
|
|||
return _process_result([])
|
||||
ctx.fail('Missing command.')
|
||||
|
||||
args = ctx.args
|
||||
# Fetch args back out
|
||||
args = ctx.protected_args + ctx.args
|
||||
ctx.args = []
|
||||
ctx.protected_args = []
|
||||
|
||||
# If we're not in chain mode, we only allow the invocation of a
|
||||
# single command but we also inform the current context about the
|
||||
|
@ -1045,7 +1078,7 @@ class MultiCommand(Command):
|
|||
allow_extra_args=True,
|
||||
allow_interspersed_args=False)
|
||||
contexts.append(sub_ctx)
|
||||
args = sub_ctx.args
|
||||
args, sub_ctx.args = sub_ctx.args, []
|
||||
|
||||
rv = []
|
||||
for sub_ctx in contexts:
|
||||
|
@ -1111,6 +1144,7 @@ class Group(MultiCommand):
|
|||
name = name or cmd.name
|
||||
if name is None:
|
||||
raise TypeError('Command has no name.')
|
||||
_check_multicommand(self, name, cmd, register=True)
|
||||
self.commands[name] = cmd
|
||||
|
||||
def command(self, *args, **kwargs):
|
||||
|
@ -1164,6 +1198,8 @@ class CommandCollection(MultiCommand):
|
|||
for source in self.sources:
|
||||
rv = source.get_command(ctx, cmd_name)
|
||||
if rv is not None:
|
||||
if self.chain:
|
||||
_check_multicommand(self, cmd_name, rv)
|
||||
return rv
|
||||
|
||||
def list_commands(self, ctx):
|
||||
|
@ -1495,8 +1531,11 @@ class Option(Parameter):
|
|||
if split_char in decl:
|
||||
first, second = decl.split(split_char, 1)
|
||||
first = first.rstrip()
|
||||
if first:
|
||||
possible_names.append(split_opt(first))
|
||||
opts.append(first)
|
||||
second = second.lstrip()
|
||||
if second:
|
||||
secondary_opts.append(second.lstrip())
|
||||
else:
|
||||
possible_names.append(split_opt(decl))
|
||||
|
@ -1652,6 +1691,9 @@ class Argument(Parameter):
|
|||
else:
|
||||
required = attrs.get('nargs', 1) > 0
|
||||
Parameter.__init__(self, param_decls, required=required, **attrs)
|
||||
if self.default is not None and self.nargs < 0:
|
||||
raise TypeError('nargs=-1 in combination with a default value '
|
||||
'is not supported.')
|
||||
|
||||
@property
|
||||
def human_readable_name(self):
|
||||
|
|
|
@ -3,7 +3,8 @@ import inspect
|
|||
|
||||
from functools import update_wrapper
|
||||
|
||||
from ._compat import iteritems, _check_for_unicode_literals
|
||||
from ._compat import iteritems
|
||||
from ._unicodefun import _check_for_unicode_literals
|
||||
from .utils import echo
|
||||
from .globals import get_current_context
|
||||
|
||||
|
|
|
@ -116,6 +116,7 @@ class MissingParameter(BadParameter):
|
|||
param_type = self.param.param_type_name
|
||||
|
||||
msg = self.message
|
||||
if self.param is not None:
|
||||
msg_extra = self.param.type.get_missing_message(self.param)
|
||||
if msg_extra:
|
||||
if msg:
|
||||
|
@ -165,7 +166,19 @@ class BadOptionUsage(UsageError):
|
|||
.. versionadded:: 4.0
|
||||
"""
|
||||
|
||||
def __init__(self, option_name, message, ctx=None):
|
||||
def __init__(self, message, ctx=None):
|
||||
UsageError.__init__(self, message, ctx)
|
||||
|
||||
|
||||
class BadArgumentUsage(UsageError):
|
||||
"""Raised if an argument is generally supplied but the use of the argument
|
||||
was incorrect. This is for instance raised if the number of values
|
||||
for an argument is not correct.
|
||||
|
||||
.. versionadded:: 6.0
|
||||
"""
|
||||
|
||||
def __init__(self, message, ctx=None):
|
||||
UsageError.__init__(self, message, ctx)
|
||||
|
||||
|
||||
|
|
|
@ -4,6 +4,10 @@ from .parser import split_opt
|
|||
from ._compat import term_len
|
||||
|
||||
|
||||
# Can force a width. This is used by the test system
|
||||
FORCED_WIDTH = None
|
||||
|
||||
|
||||
def measure_table(rows):
|
||||
widths = {}
|
||||
for row in rows:
|
||||
|
@ -98,6 +102,8 @@ class HelpFormatter(object):
|
|||
self.indent_increment = indent_increment
|
||||
if max_width is None:
|
||||
max_width = 80
|
||||
if width is None:
|
||||
width = FORCED_WIDTH
|
||||
if width is None:
|
||||
width = max(min(get_terminal_size()[0], max_width) - 2, 50)
|
||||
self.width = width
|
||||
|
|
|
@ -16,14 +16,66 @@
|
|||
and might cause us issues.
|
||||
"""
|
||||
import re
|
||||
from .exceptions import UsageError, NoSuchOption, BadOptionUsage
|
||||
from .utils import unpack_args
|
||||
from collections import deque
|
||||
from .exceptions import UsageError, NoSuchOption, BadOptionUsage, \
|
||||
BadArgumentUsage
|
||||
|
||||
|
||||
def _error_args(nargs, opt):
|
||||
def _unpack_args(args, nargs_spec):
|
||||
"""Given an iterable of arguments and an iterable of nargs specifications,
|
||||
it returns a tuple with all the unpacked arguments at the first index
|
||||
and all remaining arguments as the second.
|
||||
|
||||
The nargs specification is the number of arguments that should be consumed
|
||||
or `-1` to indicate that this position should eat up all the remainders.
|
||||
|
||||
Missing items are filled with `None`.
|
||||
"""
|
||||
args = deque(args)
|
||||
nargs_spec = deque(nargs_spec)
|
||||
rv = []
|
||||
spos = None
|
||||
|
||||
def _fetch(c):
|
||||
try:
|
||||
if spos is None:
|
||||
return c.popleft()
|
||||
else:
|
||||
return c.pop()
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
while nargs_spec:
|
||||
nargs = _fetch(nargs_spec)
|
||||
if nargs == 1:
|
||||
raise BadOptionUsage(opt, '%s option requires an argument' % opt)
|
||||
raise BadOptionUsage(opt, '%s option requires %d arguments' % (opt, nargs))
|
||||
rv.append(_fetch(args))
|
||||
elif nargs > 1:
|
||||
x = [_fetch(args) for _ in range(nargs)]
|
||||
# If we're reversed, we're pulling in the arguments in reverse,
|
||||
# so we need to turn them around.
|
||||
if spos is not None:
|
||||
x.reverse()
|
||||
rv.append(tuple(x))
|
||||
elif nargs < 0:
|
||||
if spos is not None:
|
||||
raise TypeError('Cannot have two nargs < 0')
|
||||
spos = len(rv)
|
||||
rv.append(None)
|
||||
|
||||
# spos is the position of the wildcard (star). If it's not `None`,
|
||||
# we fill it with the remainder.
|
||||
if spos is not None:
|
||||
rv[spos] = tuple(args)
|
||||
args = []
|
||||
rv[spos + 1:] = reversed(rv[spos + 1:])
|
||||
|
||||
return tuple(rv), list(args)
|
||||
|
||||
|
||||
def _error_opt_args(nargs, opt):
|
||||
if nargs == 1:
|
||||
raise BadOptionUsage('%s option requires an argument' % opt)
|
||||
raise BadOptionUsage('%s option requires %d arguments' % (opt, nargs))
|
||||
|
||||
|
||||
def split_opt(opt):
|
||||
|
@ -116,6 +168,13 @@ class Argument(object):
|
|||
self.obj = obj
|
||||
|
||||
def process(self, value, state):
|
||||
if self.nargs > 1:
|
||||
holes = sum(1 for x in value if x is None)
|
||||
if holes == len(value):
|
||||
value = None
|
||||
elif holes != 0:
|
||||
raise BadArgumentUsage('argument %s takes %d values'
|
||||
% (self.dest, self.nargs))
|
||||
state.opts[self.dest] = value
|
||||
state.order.append(self.obj)
|
||||
|
||||
|
@ -213,7 +272,7 @@ class OptionParser(object):
|
|||
return state.opts, state.largs, state.order
|
||||
|
||||
def _process_args_for_args(self, state):
|
||||
pargs, args = unpack_args(state.largs + state.rargs,
|
||||
pargs, args = _unpack_args(state.largs + state.rargs,
|
||||
[x.nargs for x in self._args])
|
||||
|
||||
for idx, arg in enumerate(self._args):
|
||||
|
@ -275,7 +334,7 @@ class OptionParser(object):
|
|||
|
||||
nargs = option.nargs
|
||||
if len(state.rargs) < nargs:
|
||||
_error_args(nargs, opt)
|
||||
_error_opt_args(nargs, opt)
|
||||
elif nargs == 1:
|
||||
value = state.rargs.pop(0)
|
||||
else:
|
||||
|
@ -283,7 +342,7 @@ class OptionParser(object):
|
|||
del state.rargs[:nargs]
|
||||
|
||||
elif explicit_value is not None:
|
||||
raise BadOptionUsage(opt, '%s option does not take a value' % opt)
|
||||
raise BadOptionUsage('%s option does not take a value' % opt)
|
||||
|
||||
else:
|
||||
value = None
|
||||
|
@ -315,7 +374,7 @@ class OptionParser(object):
|
|||
|
||||
nargs = option.nargs
|
||||
if len(state.rargs) < nargs:
|
||||
_error_args(nargs, opt)
|
||||
_error_opt_args(nargs, opt)
|
||||
elif nargs == 1:
|
||||
value = state.rargs.pop(0)
|
||||
else:
|
||||
|
|
|
@ -41,6 +41,9 @@ def prompt(text, default=None, hide_input=False,
|
|||
If the user aborts the input by sending a interrupt signal, this
|
||||
function will catch it and raise a :exc:`Abort` exception.
|
||||
|
||||
.. versionadded:: 6.0
|
||||
Added unicode support for cmd.exe on Windows.
|
||||
|
||||
.. versionadded:: 4.0
|
||||
Added the `err` parameter.
|
||||
|
||||
|
|
|
@ -157,6 +157,8 @@ class CliRunner(object):
|
|||
old_stdin = sys.stdin
|
||||
old_stdout = sys.stdout
|
||||
old_stderr = sys.stderr
|
||||
old_forced_width = clickpkg.formatting.FORCED_WIDTH
|
||||
clickpkg.formatting.FORCED_WIDTH = 80
|
||||
|
||||
env = self.make_env(env)
|
||||
|
||||
|
@ -236,6 +238,7 @@ class CliRunner(object):
|
|||
clickpkg.termui.hidden_prompt_func = old_hidden_prompt_func
|
||||
clickpkg.termui._getchar = old__getchar_func
|
||||
clickpkg.utils.should_strip_ansi = old_should_strip_ansi
|
||||
clickpkg.formatting.FORCED_WIDTH = old_forced_width
|
||||
|
||||
def invoke(self, cli, args=None, input=None, env=None,
|
||||
catch_exceptions=True, color=False, **extra):
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import os
|
||||
import sys
|
||||
import stat
|
||||
|
||||
from ._compat import open_stream, text_type, filename_to_ui, \
|
||||
get_filesystem_encoding, get_streerror
|
||||
get_filesystem_encoding, get_streerror, _get_argv_encoding, PY2
|
||||
from .exceptions import BadParameter
|
||||
from .utils import safecall, LazyFile
|
||||
|
||||
|
@ -109,13 +108,14 @@ class StringParamType(ParamType):
|
|||
|
||||
def convert(self, value, param, ctx):
|
||||
if isinstance(value, bytes):
|
||||
enc = _get_argv_encoding()
|
||||
try:
|
||||
enc = getattr(sys.stdin, 'encoding', None)
|
||||
if enc is not None:
|
||||
value = value.decode(enc)
|
||||
except UnicodeError:
|
||||
fs_enc = get_filesystem_encoding()
|
||||
if fs_enc != enc:
|
||||
try:
|
||||
value = value.decode(get_filesystem_encoding())
|
||||
value = value.decode(fs_enc)
|
||||
except UnicodeError:
|
||||
value = value.decode('utf-8', 'replace')
|
||||
return value
|
||||
|
@ -168,7 +168,7 @@ class IntParamType(ParamType):
|
|||
def convert(self, value, param, ctx):
|
||||
try:
|
||||
return int(value)
|
||||
except ValueError:
|
||||
except (ValueError, UnicodeError):
|
||||
self.fail('%s is not a valid integer' % value, param, ctx)
|
||||
|
||||
def __repr__(self):
|
||||
|
@ -237,7 +237,7 @@ class FloatParamType(ParamType):
|
|||
def convert(self, value, param, ctx):
|
||||
try:
|
||||
return float(value)
|
||||
except ValueError:
|
||||
except (UnicodeError, ValueError):
|
||||
self.fail('%s is not a valid floating point value' %
|
||||
value, param, ctx)
|
||||
|
||||
|
@ -251,8 +251,10 @@ class UUIDParameterType(ParamType):
|
|||
def convert(self, value, param, ctx):
|
||||
import uuid
|
||||
try:
|
||||
if PY2 and isinstance(value, text_type):
|
||||
value = value.encode('ascii')
|
||||
return uuid.UUID(value)
|
||||
except ValueError:
|
||||
except (UnicodeError, ValueError):
|
||||
self.fail('%s is not a valid UUID value' % value, param, ctx)
|
||||
|
||||
def __repr__(self):
|
||||
|
@ -343,6 +345,9 @@ class Path(ParamType):
|
|||
handle it returns just the filename. Secondly, it can perform various
|
||||
basic checks about what the file or directory should be.
|
||||
|
||||
.. versionchanged:: 6.0
|
||||
`allow_dash` was added.
|
||||
|
||||
:param exists: if set to true, the file or directory needs to exist for
|
||||
this value to be valid. If this is not required and a
|
||||
file does indeed not exist, then all further checks are
|
||||
|
@ -354,17 +359,27 @@ class Path(ParamType):
|
|||
:param resolve_path: if this is true, then the path is fully resolved
|
||||
before the value is passed onwards. This means
|
||||
that it's absolute and symlinks are resolved.
|
||||
:param allow_dash: If this is set to `True`, a single dash to indicate
|
||||
standard streams is permitted.
|
||||
:param type: optionally a string type that should be used to
|
||||
represent the path. The default is `None` which
|
||||
means the return value will be either bytes or
|
||||
unicode depending on what makes most sense given the
|
||||
input data Click deals with.
|
||||
"""
|
||||
envvar_list_splitter = os.path.pathsep
|
||||
|
||||
def __init__(self, exists=False, file_okay=True, dir_okay=True,
|
||||
writable=False, readable=True, resolve_path=False):
|
||||
writable=False, readable=True, resolve_path=False,
|
||||
allow_dash=False, path_type=None):
|
||||
self.exists = exists
|
||||
self.file_okay = file_okay
|
||||
self.dir_okay = dir_okay
|
||||
self.writable = writable
|
||||
self.readable = readable
|
||||
self.resolve_path = resolve_path
|
||||
self.allow_dash = allow_dash
|
||||
self.type = path_type
|
||||
|
||||
if self.file_okay and not self.dir_okay:
|
||||
self.name = 'file'
|
||||
|
@ -376,8 +391,20 @@ class Path(ParamType):
|
|||
self.name = 'path'
|
||||
self.path_type = 'Path'
|
||||
|
||||
def coerce_path_result(self, rv):
|
||||
if self.type is not None and not isinstance(rv, self.type):
|
||||
if self.type is text_type:
|
||||
rv = rv.decode(get_filesystem_encoding())
|
||||
else:
|
||||
rv = rv.encode(get_filesystem_encoding())
|
||||
return rv
|
||||
|
||||
def convert(self, value, param, ctx):
|
||||
rv = value
|
||||
|
||||
is_dash = self.file_okay and self.allow_dash and rv in (b'-', '-')
|
||||
|
||||
if not is_dash:
|
||||
if self.resolve_path:
|
||||
rv = os.path.realpath(rv)
|
||||
|
||||
|
@ -385,7 +412,7 @@ class Path(ParamType):
|
|||
st = os.stat(rv)
|
||||
except OSError:
|
||||
if not self.exists:
|
||||
return rv
|
||||
return self.coerce_path_result(rv)
|
||||
self.fail('%s "%s" does not exist.' % (
|
||||
self.path_type,
|
||||
filename_to_ui(value)
|
||||
|
@ -412,7 +439,7 @@ class Path(ParamType):
|
|||
filename_to_ui(value)
|
||||
), param, ctx)
|
||||
|
||||
return rv
|
||||
return self.coerce_path_result(rv)
|
||||
|
||||
|
||||
class Tuple(CompositeParamType):
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import os
|
||||
import sys
|
||||
from collections import deque
|
||||
|
||||
from .globals import resolve_color_default
|
||||
|
||||
|
@ -11,6 +10,9 @@ from ._compat import text_type, open_stream, get_filesystem_encoding, \
|
|||
|
||||
if not PY2:
|
||||
from ._compat import _find_binary_writer
|
||||
elif WIN:
|
||||
from ._winconsole import _get_windows_argv, \
|
||||
_hash_py_argv, _initial_argv_hash
|
||||
|
||||
|
||||
echo_native_types = string_types + (bytes, bytearray)
|
||||
|
@ -20,70 +22,6 @@ def _posixify(name):
|
|||
return '-'.join(name.split()).lower()
|
||||
|
||||
|
||||
def unpack_args(args, nargs_spec):
|
||||
"""Given an iterable of arguments and an iterable of nargs specifications,
|
||||
it returns a tuple with all the unpacked arguments at the first index
|
||||
and all remaining arguments as the second.
|
||||
|
||||
The nargs specification is the number of arguments that should be consumed
|
||||
or `-1` to indicate that this position should eat up all the remainders.
|
||||
|
||||
Missing items are filled with `None`.
|
||||
|
||||
Examples:
|
||||
|
||||
>>> unpack_args(range(6), [1, 2, 1, -1])
|
||||
((0, (1, 2), 3, (4, 5)), [])
|
||||
>>> unpack_args(range(6), [1, 2, 1])
|
||||
((0, (1, 2), 3), [4, 5])
|
||||
>>> unpack_args(range(6), [-1])
|
||||
(((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)
|
||||
rv = []
|
||||
spos = None
|
||||
|
||||
def _fetch(c):
|
||||
try:
|
||||
if spos is None:
|
||||
return c.popleft()
|
||||
else:
|
||||
return c.pop()
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
while nargs_spec:
|
||||
nargs = _fetch(nargs_spec)
|
||||
if nargs == 1:
|
||||
rv.append(_fetch(args))
|
||||
elif nargs > 1:
|
||||
x = [_fetch(args) for _ in range(nargs)]
|
||||
# If we're reversed, we're pulling in the arguments in reverse,
|
||||
# so we need to turn them around.
|
||||
if spos is not None:
|
||||
x.reverse()
|
||||
rv.append(tuple(x))
|
||||
elif nargs < 0:
|
||||
if spos is not None:
|
||||
raise TypeError('Cannot have two nargs < 0')
|
||||
spos = len(rv)
|
||||
rv.append(None)
|
||||
|
||||
# spos is the position of the wildcard (star). If it's not `None`,
|
||||
# we fill it with the remainder.
|
||||
if spos is not None:
|
||||
rv[spos] = tuple(args)
|
||||
args = []
|
||||
rv[spos + 1:] = reversed(rv[spos + 1:])
|
||||
|
||||
return tuple(rv), list(args)
|
||||
|
||||
|
||||
def safecall(func):
|
||||
"""Wraps a function so that it swallows exceptions."""
|
||||
def wrapper(*args, **kwargs):
|
||||
|
@ -234,7 +172,8 @@ def echo(message=None, file=None, nl=True, err=False, color=None):
|
|||
Primarily it means that you can print binary data as well as Unicode
|
||||
data on both 2.x and 3.x to the given file in the most appropriate way
|
||||
possible. This is a very carefree function as in that it will try its
|
||||
best to not fail.
|
||||
best to not fail. As of Click 6.0 this includes support for unicode
|
||||
output on the Windows console.
|
||||
|
||||
In addition to that, if `colorama`_ is installed, the echo function will
|
||||
also support clever handling of ANSI codes. Essentially it will then
|
||||
|
@ -246,6 +185,12 @@ def echo(message=None, file=None, nl=True, err=False, color=None):
|
|||
|
||||
.. _colorama: http://pypi.python.org/pypi/colorama
|
||||
|
||||
.. versionchanged:: 6.0
|
||||
As of Click 6.0 the echo function will properly support unicode
|
||||
output on the windows console. Not that click does not modify
|
||||
the interpreter in any way which means that `sys.stdout` or the
|
||||
print statement or function will still not provide unicode support.
|
||||
|
||||
.. versionchanged:: 2.0
|
||||
Starting with version 2.0 of Click, the echo function will work
|
||||
with colorama if it's installed.
|
||||
|
@ -381,6 +326,27 @@ def open_file(filename, mode='r', encoding=None, errors='strict',
|
|||
return f
|
||||
|
||||
|
||||
def get_os_args():
|
||||
"""This returns the argument part of sys.argv in the most appropriate
|
||||
form for processing. What this means is that this return value is in
|
||||
a format that works for Click to process but does not necessarily
|
||||
correspond well to what's actually standard for the interpreter.
|
||||
|
||||
On most environments the return value is ``sys.argv[:1]`` unchanged.
|
||||
However if you are on Windows and running Python 2 the return value
|
||||
will actually be a list of unicode strings instead because the
|
||||
default behavior on that platform otherwise will not be able to
|
||||
carry all possible values that sys.argv can have.
|
||||
|
||||
.. versionadded:: 6.0
|
||||
"""
|
||||
# We can only extract the unicode argv if sys.argv has not been
|
||||
# changed since the startup of the application.
|
||||
if PY2 and WIN and _initial_argv_hash == _hash_py_argv():
|
||||
return _get_windows_argv()
|
||||
return sys.argv[1:]
|
||||
|
||||
|
||||
def format_filename(filename, shorten=False):
|
||||
"""Formats a filename for user display. The main purpose of this
|
||||
function is to ensure that the filename can be displayed at all. This
|
||||
|
|
|
@ -153,6 +153,8 @@ Exceptions
|
|||
|
||||
.. autoexception:: BadOptionUsage
|
||||
|
||||
.. autoexception:: BadArgumentUsage
|
||||
|
||||
Formatting
|
||||
----------
|
||||
|
||||
|
|
|
@ -310,7 +310,8 @@ Now you can invoke it like this:
|
|||
invoke(cli, prog_name='setup.py', args=['sdist', 'bdist_wheel'])
|
||||
|
||||
When using multi command chaining you can only have one command (the last)
|
||||
use ``nargs=-1`` on an argument. Other than that there are no
|
||||
use ``nargs=-1`` on an argument. It is also not possible to nest multi
|
||||
commands below chained multicommands. Other than that there are no
|
||||
restrictions on how they work. They can accept options and arguments as
|
||||
normal.
|
||||
|
||||
|
@ -320,6 +321,11 @@ one command is invoked. This is necessary because the handling of
|
|||
subcommands happens one after another so the exact subcommands that will
|
||||
be handled are not yet available when the callback fires.
|
||||
|
||||
.. note::
|
||||
|
||||
It is currently not possible for chain commands to be nested. This
|
||||
will be fixed in future versions of Click.
|
||||
|
||||
|
||||
Multi Command Pipelines
|
||||
-----------------------
|
||||
|
|
|
@ -75,6 +75,7 @@ usage patterns.
|
|||
bashcomplete
|
||||
exceptions
|
||||
python3
|
||||
wincmd
|
||||
|
||||
API Reference
|
||||
-------------
|
||||
|
|
|
@ -201,6 +201,29 @@ can alternatively split the parameters through ``;`` instead:
|
|||
if __name__ == '__main__':
|
||||
log()
|
||||
|
||||
.. versionchanged:: 6.0
|
||||
|
||||
If you want to define an alias for the second option only, then you will
|
||||
need to use leading whitespace to disambiguate the format string:
|
||||
|
||||
Example:
|
||||
|
||||
.. click:example::
|
||||
|
||||
import sys
|
||||
|
||||
@click.command()
|
||||
@click.option('--shout/--no-shout', ' /-S', default=False)
|
||||
def info(shout):
|
||||
rv = sys.platform
|
||||
if shout:
|
||||
rv = rv.upper() + '!!!!111'
|
||||
click.echo(rv)
|
||||
|
||||
.. click:run::
|
||||
|
||||
invoke(info, args=['--help'])
|
||||
|
||||
Feature Switches
|
||||
----------------
|
||||
|
||||
|
@ -445,7 +468,7 @@ replaced with the :func:`confirmation_option` decorator:
|
|||
.. click:example::
|
||||
|
||||
@click.command()
|
||||
@click.confirmation_option(help='Are you sure you want to drop the db?')
|
||||
@click.confirmation_option(prompt='Are you sure you want to drop the db?')
|
||||
def dropdb():
|
||||
click.echo('Dropped all tables!')
|
||||
|
||||
|
|
|
@ -139,6 +139,12 @@ some newer Linux systems, you could also try ``C.UTF-8`` as the locale::
|
|||
export LC_ALL=C.UTF-8
|
||||
export LANG=C.UTF-8
|
||||
|
||||
On some systems it was reported that `UTF-8` has to be written as `UTF8`
|
||||
and vice versa. To see which locales are supported you can invoke
|
||||
``locale -a``::
|
||||
|
||||
locale -a
|
||||
|
||||
You need to do this before you invoke your Python script. If you are
|
||||
curious about the reasons for this, you can join the discussions in the
|
||||
Python 3 bug tracker:
|
||||
|
|
|
@ -30,6 +30,19 @@ suppressed by passing ``nl=False``::
|
|||
|
||||
click.echo(b'\xe2\x98\x83', nl=False)
|
||||
|
||||
Last but not least :func:`echo` uses click's intelligent internal output
|
||||
streams to stdout and stderr which support unicode output on the Windows
|
||||
console. This means for as long as you are using `click.echo` you can
|
||||
output unicode character (there are some limitations on the default font
|
||||
with regards to which characters can be displayed). This functionality is
|
||||
new in Click 6.0.
|
||||
|
||||
.. versionadded:: 6.0
|
||||
|
||||
Click now emulates output streams on Windows to support unicode to the
|
||||
Windows console through separate APIs. For more information see
|
||||
`wincmd`_.
|
||||
|
||||
.. versionadded:: 3.0
|
||||
|
||||
Starting with Click 3.0 you can also easily print to standard error by
|
||||
|
@ -266,6 +279,12 @@ Example::
|
|||
stdin_text = click.get_text_stream('stdin')
|
||||
stdout_binary = click.get_binary_stream('stdout')
|
||||
|
||||
.. versionadded:: 6.0
|
||||
|
||||
Click now emulates output streams on Windows to support unicode to the
|
||||
Windows console through separate APIs. For more information see
|
||||
`wincmd`_.
|
||||
|
||||
|
||||
Intelligent File Opening
|
||||
------------------------
|
||||
|
|
73
docs/wincmd.rst
Normal file
73
docs/wincmd.rst
Normal file
|
@ -0,0 +1,73 @@
|
|||
Windows Console Notes
|
||||
=====================
|
||||
|
||||
.. versionadded:: 6.0
|
||||
|
||||
Until Click 6.0 there are various bugs and limitations with using Click on
|
||||
a Windows console. Most notably the decoding of command line arguments
|
||||
was performed with the wrong encoding on Python 2 and on all versions of
|
||||
Python output of unicode characters was impossible. Starting with Click
|
||||
6.0 we now emulate output streams on Windows to support unicode to the
|
||||
Windows console through separate APIs and we perform different decoding of
|
||||
parameters.
|
||||
|
||||
Here is a brief overview of how this works and what it means to you.
|
||||
|
||||
Unicode Arguments
|
||||
-----------------
|
||||
|
||||
Click internally is generally based on the concept that any argument can
|
||||
come in as either byte string or unicode string and conversion is
|
||||
performed to the type expected value as late as possible. This has some
|
||||
advantages as it allows us to accept the data in the most appropriate form
|
||||
for the operating system and Python version.
|
||||
|
||||
For instance paths are left as bytes on Python 2 unless you explicitly
|
||||
tell it otherwise.
|
||||
|
||||
This caused some problems on Windows where initially the wrong encoding
|
||||
was used and garbage ended up in your input data. We not only fixed the
|
||||
encoding part, but we also now extract unicode parameters from `sys.argv`.
|
||||
|
||||
This means that on Python 2 under Windows, the arguments processed will
|
||||
*most likely* be of unicode nature and not bytes. This was something that
|
||||
previously did not really happen unless you explicitly passed in unicode
|
||||
parameters so your custom types need to be aware of this.
|
||||
|
||||
There is also another limitation with this: if `sys.argv` was modified
|
||||
prior to invoking a click handler, we have to fall back to the regular
|
||||
byte input in which case not all unicode values are available but only a
|
||||
subset of the codepage used for parameters.
|
||||
|
||||
Unicode Output and Input
|
||||
------------------------
|
||||
|
||||
Unicode output and input on Windows is implemented through the concept of
|
||||
a dispatching text stream. What this means is that when click first needs
|
||||
a text output (or input) stream on windows it goes through a few checks to
|
||||
figure out of a windows console is connected or not. If no Windows
|
||||
console is present then the text output stream is returned as such and the
|
||||
encoding for that stream is set to ``utf-8`` like on all platforms.
|
||||
|
||||
However if a console is connected the stream will instead be emulated and
|
||||
use the cmd.exe unicode APIs to output text information. In this case the
|
||||
stream will also use ``utf-16-le`` as internal encoding. However there is
|
||||
some hackery going on that the underlying raw IO buffer is still bypassing
|
||||
the unicode APIs and byte output through an indirection is still possible.
|
||||
|
||||
This hackery is used on both Python 2 and Python 3 as neither version of
|
||||
Python has native support for cmd.exe with unicode characters. There are
|
||||
some limitations you need to be aware of:
|
||||
|
||||
* this unicode support is limited to ``click.echo``, ``click.prompt`` as
|
||||
well as ``click.get_text_stream``.
|
||||
* depending on if unicode values or byte strings are passed the control
|
||||
flow goes completely different places internally which can have some
|
||||
odd artifacts if data partially ends up being buffered. Click
|
||||
attempts to protect against that by manually always flushing but if
|
||||
you are mixing and matching different string types to ``stdout`` or
|
||||
``stderr`` you will need to manually flush.
|
||||
|
||||
Another important thing to note is that the Windows console's default
|
||||
fonts do not support a lot of characters which means that you are mostly
|
||||
limited to international letters but no emojis or special characters.
|
BIN
examples/.DS_Store
vendored
Normal file
BIN
examples/.DS_Store
vendored
Normal file
Binary file not shown.
47
examples/demo/demo.py
Normal file
47
examples/demo/demo.py
Normal file
|
@ -0,0 +1,47 @@
|
|||
import click
|
||||
|
||||
|
||||
class VcsContext(object):
|
||||
|
||||
def __init__(self):
|
||||
self.debug = False
|
||||
|
||||
|
||||
class FancyContext(object):
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
|
||||
pass_vcs = click.make_pass_decorator(VcsContext, ensure=True)
|
||||
pass_fancy = click.make_pass_decorator(FancyContext, ensure=True)
|
||||
|
||||
|
||||
@click.group()
|
||||
@pass_vcs
|
||||
def cli(obj):
|
||||
"""Write stuff here."""
|
||||
print obj
|
||||
|
||||
|
||||
@click.group()
|
||||
@pass_vcs
|
||||
def commit(obj):
|
||||
"""This commits some stuff."""
|
||||
print obj
|
||||
|
||||
|
||||
@click.command()
|
||||
@pass_fancy
|
||||
def fancy_thing(obj):
|
||||
ctx = click.get_current_context()
|
||||
print obj
|
||||
print 'find vcs context', ctx.find_object(VcsContext)
|
||||
|
||||
|
||||
cli.add_command(commit)
|
||||
commit.add_command(fancy_thing)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
cli()
|
BIN
examples/imagepipe/.DS_Store
vendored
Normal file
BIN
examples/imagepipe/.DS_Store
vendored
Normal file
Binary file not shown.
10
examples/imagepipe/click_example_imagepipe.egg-info/PKG-INFO
Normal file
10
examples/imagepipe/click_example_imagepipe.egg-info/PKG-INFO
Normal file
|
@ -0,0 +1,10 @@
|
|||
Metadata-Version: 1.0
|
||||
Name: click-example-imagepipe
|
||||
Version: 1.0
|
||||
Summary: UNKNOWN
|
||||
Home-page: UNKNOWN
|
||||
Author: UNKNOWN
|
||||
Author-email: UNKNOWN
|
||||
License: UNKNOWN
|
||||
Description: UNKNOWN
|
||||
Platform: UNKNOWN
|
|
@ -0,0 +1,8 @@
|
|||
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
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
[console_scripts]
|
||||
imagepipe=imagepipe:cli
|
||||
|
|
@ -0,0 +1 @@
|
|||
Click
|
|
@ -0,0 +1 @@
|
|||
imagepipe
|
|
@ -1,6 +1,6 @@
|
|||
Metadata-Version: 1.0
|
||||
Name: printer
|
||||
Version: 0.1dev0
|
||||
Name: click-example-naval
|
||||
Version: 2.0
|
||||
Summary: UNKNOWN
|
||||
Home-page: UNKNOWN
|
||||
Author: UNKNOWN
|
8
examples/naval/click_example_naval.egg-info/SOURCES.txt
Normal file
8
examples/naval/click_example_naval.egg-info/SOURCES.txt
Normal file
|
@ -0,0 +1,8 @@
|
|||
README
|
||||
naval.py
|
||||
click_example_naval.egg-info/PKG-INFO
|
||||
click_example_naval.egg-info/SOURCES.txt
|
||||
click_example_naval.egg-info/dependency_links.txt
|
||||
click_example_naval.egg-info/entry_points.txt
|
||||
click_example_naval.egg-info/requires.txt
|
||||
click_example_naval.egg-info/top_level.txt
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
[console_scripts]
|
||||
naval=naval:cli
|
||||
|
1
examples/naval/click_example_naval.egg-info/requires.txt
Normal file
1
examples/naval/click_example_naval.egg-info/requires.txt
Normal file
|
@ -0,0 +1 @@
|
|||
click
|
|
@ -0,0 +1 @@
|
|||
naval
|
|
@ -1,8 +0,0 @@
|
|||
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
|
|
@ -1,4 +0,0 @@
|
|||
|
||||
[printer.plugins]
|
||||
bold=printer_bold.core:bolddddddddddd
|
||||
|
|
@ -1 +0,0 @@
|
|||
printer_bold
|
|
@ -1,8 +0,0 @@
|
|||
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,4 +0,0 @@
|
|||
|
||||
[console_scripts]
|
||||
printer=printer.cli:cli
|
||||
|
|
@ -1 +0,0 @@
|
|||
printer
|
|
@ -1,6 +1,6 @@
|
|||
Metadata-Version: 1.0
|
||||
Name: printer-bold
|
||||
Version: 0.1dev0
|
||||
Name: click-example-repo
|
||||
Version: 0.1
|
||||
Summary: UNKNOWN
|
||||
Home-page: UNKNOWN
|
||||
Author: UNKNOWN
|
8
examples/repo/click_example_repo.egg-info/SOURCES.txt
Normal file
8
examples/repo/click_example_repo.egg-info/SOURCES.txt
Normal file
|
@ -0,0 +1,8 @@
|
|||
README
|
||||
repo.py
|
||||
click_example_repo.egg-info/PKG-INFO
|
||||
click_example_repo.egg-info/SOURCES.txt
|
||||
click_example_repo.egg-info/dependency_links.txt
|
||||
click_example_repo.egg-info/entry_points.txt
|
||||
click_example_repo.egg-info/requires.txt
|
||||
click_example_repo.egg-info/top_level.txt
|
|
@ -0,0 +1 @@
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
[console_scripts]
|
||||
repo=repo:cli
|
||||
|
1
examples/repo/click_example_repo.egg-info/requires.txt
Normal file
1
examples/repo/click_example_repo.egg-info/requires.txt
Normal file
|
@ -0,0 +1 @@
|
|||
click
|
1
examples/repo/click_example_repo.egg-info/top_level.txt
Normal file
1
examples/repo/click_example_repo.egg-info/top_level.txt
Normal file
|
@ -0,0 +1 @@
|
|||
repo
|
|
@ -1,145 +0,0 @@
|
|||
# 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
|
10
examples/termui/click_example_termui.egg-info/PKG-INFO
Normal file
10
examples/termui/click_example_termui.egg-info/PKG-INFO
Normal file
|
@ -0,0 +1,10 @@
|
|||
Metadata-Version: 1.0
|
||||
Name: click-example-termui
|
||||
Version: 1.0
|
||||
Summary: UNKNOWN
|
||||
Home-page: UNKNOWN
|
||||
Author: UNKNOWN
|
||||
Author-email: UNKNOWN
|
||||
License: UNKNOWN
|
||||
Description: UNKNOWN
|
||||
Platform: UNKNOWN
|
|
@ -0,0 +1,8 @@
|
|||
README
|
||||
termui.py
|
||||
click_example_termui.egg-info/PKG-INFO
|
||||
click_example_termui.egg-info/SOURCES.txt
|
||||
click_example_termui.egg-info/dependency_links.txt
|
||||
click_example_termui.egg-info/entry_points.txt
|
||||
click_example_termui.egg-info/requires.txt
|
||||
click_example_termui.egg-info/top_level.txt
|
|
@ -0,0 +1 @@
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
[console_scripts]
|
||||
termui=termui:cli
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
click
|
||||
colorama
|
|
@ -0,0 +1 @@
|
|||
termui
|
Binary file not shown.
|
@ -1,5 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import click
|
||||
from click._compat import PY2
|
||||
|
||||
|
||||
def test_nargs_star(runner):
|
||||
|
@ -18,6 +19,18 @@ def test_nargs_star(runner):
|
|||
]
|
||||
|
||||
|
||||
def test_nargs_default(runner):
|
||||
try:
|
||||
@click.command()
|
||||
@click.argument('src', nargs=-1, default=42)
|
||||
def copy(src):
|
||||
pass
|
||||
except TypeError as e:
|
||||
assert 'nargs=-1' in str(e)
|
||||
else:
|
||||
assert False
|
||||
|
||||
|
||||
def test_nargs_tup(runner):
|
||||
@click.command()
|
||||
@click.argument('name', nargs=1)
|
||||
|
@ -106,7 +119,8 @@ def test_file_atomics(runner):
|
|||
with runner.isolated_filesystem():
|
||||
with open('foo.txt', 'wb') as f:
|
||||
f.write(b'OLD\n')
|
||||
result = runner.invoke(inout, ['foo.txt'], input='Hey!')
|
||||
result = runner.invoke(inout, ['foo.txt'], input='Hey!',
|
||||
catch_exceptions=False)
|
||||
assert result.output == ''
|
||||
assert result.exit_code == 0
|
||||
with open('foo.txt', 'rb') as f:
|
||||
|
@ -223,7 +237,7 @@ def test_nargs_star_ordering(runner):
|
|||
|
||||
result = runner.invoke(cmd, ['a', 'b', 'c'])
|
||||
assert result.output.splitlines() == [
|
||||
"('a',)",
|
||||
PY2 and "(u'a',)" or "('a',)",
|
||||
'b',
|
||||
'c',
|
||||
]
|
||||
|
@ -240,7 +254,25 @@ def test_nargs_specified_plus_star_ordering(runner):
|
|||
|
||||
result = runner.invoke(cmd, ['a', 'b', 'c', 'd', 'e', 'f'])
|
||||
assert result.output.splitlines() == [
|
||||
"('a', 'b', 'c')",
|
||||
PY2 and "(u'a', u'b', u'c')" or "('a', 'b', 'c')",
|
||||
'd',
|
||||
"('e', 'f')",
|
||||
PY2 and "(u'e', u'f')" or "('e', 'f')",
|
||||
]
|
||||
|
||||
|
||||
def test_defaults_for_nargs(runner):
|
||||
@click.command()
|
||||
@click.argument('a', nargs=2, type=int, default=(1, 2))
|
||||
def cmd(a):
|
||||
x, y = a
|
||||
click.echo(x + y)
|
||||
|
||||
result = runner.invoke(cmd, [])
|
||||
assert result.output.strip() == '3'
|
||||
|
||||
result = runner.invoke(cmd, ['3', '4'])
|
||||
assert result.output.strip() == '7'
|
||||
|
||||
result = runner.invoke(cmd, ['3'])
|
||||
assert result.exception is not None
|
||||
assert 'argument a takes 2 values' in result.output
|
||||
|
|
|
@ -1,4 +1,13 @@
|
|||
import sys
|
||||
import click
|
||||
import pytest
|
||||
|
||||
|
||||
def debug():
|
||||
click.echo('%s=%s' % (
|
||||
sys._getframe(1).f_code.co_name,
|
||||
'|'.join(click.get_current_context().args),
|
||||
))
|
||||
|
||||
|
||||
def test_basic_chaining(runner):
|
||||
|
@ -152,3 +161,92 @@ def test_pipeline(runner):
|
|||
'FOO',
|
||||
'BAR',
|
||||
]
|
||||
|
||||
|
||||
def test_args_and_chain(runner):
|
||||
@click.group(chain=True)
|
||||
def cli():
|
||||
debug()
|
||||
|
||||
@cli.command()
|
||||
def a():
|
||||
debug()
|
||||
|
||||
@cli.command()
|
||||
def b():
|
||||
debug()
|
||||
|
||||
@cli.command()
|
||||
def c():
|
||||
debug()
|
||||
|
||||
result = runner.invoke(cli, ['a', 'b', 'c'])
|
||||
assert not result.exception
|
||||
assert result.output.splitlines() == [
|
||||
'cli=',
|
||||
'a=',
|
||||
'b=',
|
||||
'c=',
|
||||
]
|
||||
|
||||
|
||||
def test_multicommand_arg_behavior(runner):
|
||||
with pytest.raises(RuntimeError):
|
||||
@click.group(chain=True)
|
||||
@click.argument('forbidden', required=False)
|
||||
def bad_cli():
|
||||
pass
|
||||
|
||||
with pytest.raises(RuntimeError):
|
||||
@click.group(chain=True)
|
||||
@click.argument('forbidden', nargs=-1)
|
||||
def bad_cli2():
|
||||
pass
|
||||
|
||||
@click.group(chain=True)
|
||||
@click.argument('arg')
|
||||
def cli(arg):
|
||||
click.echo('cli:%s' % arg)
|
||||
|
||||
@cli.command()
|
||||
def a():
|
||||
click.echo('a')
|
||||
|
||||
result = runner.invoke(cli, ['foo', 'a'])
|
||||
assert not result.exception
|
||||
assert result.output.splitlines() == [
|
||||
'cli:foo',
|
||||
'a',
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.xfail
|
||||
def test_multicommand_chaining(runner):
|
||||
@click.group(chain=True)
|
||||
def cli():
|
||||
debug()
|
||||
|
||||
@cli.group()
|
||||
def l1a():
|
||||
debug()
|
||||
|
||||
@l1a.command()
|
||||
def l2a():
|
||||
debug()
|
||||
|
||||
@l1a.command()
|
||||
def l2b():
|
||||
debug()
|
||||
|
||||
@cli.command()
|
||||
def l1b():
|
||||
debug()
|
||||
|
||||
result = runner.invoke(cli, ['l1a', 'l2a', 'l1b'])
|
||||
assert not result.exception
|
||||
assert result.output.splitlines() == [
|
||||
'cli=',
|
||||
'l1a=',
|
||||
'l2a=',
|
||||
'l1b=',
|
||||
]
|
||||
|
|
|
@ -184,3 +184,22 @@ def test_pass_obj(runner):
|
|||
result = runner.invoke(cli, ['test'])
|
||||
assert not result.exception
|
||||
assert result.output == 'test\n'
|
||||
|
||||
|
||||
def test_close_before_pop(runner):
|
||||
called = []
|
||||
|
||||
@click.command()
|
||||
@click.pass_context
|
||||
def cli(ctx):
|
||||
ctx.obj = 'test'
|
||||
@ctx.call_on_close
|
||||
def foo():
|
||||
assert click.get_current_context().obj == 'test'
|
||||
called.append(True)
|
||||
click.echo('aha!')
|
||||
|
||||
result = runner.invoke(cli, [])
|
||||
assert not result.exception
|
||||
assert result.output == 'aha!\n'
|
||||
assert called == [True]
|
||||
|
|
|
@ -2,6 +2,8 @@ import sys
|
|||
import json
|
||||
import subprocess
|
||||
|
||||
from click._compat import WIN
|
||||
|
||||
|
||||
IMPORT_TEST = b'''\
|
||||
try:
|
||||
|
@ -30,9 +32,13 @@ click.echo(json.dumps(rv))
|
|||
ALLOWED_IMPORTS = set([
|
||||
'weakref', 'os', 'struct', 'collections', 'sys', 'contextlib',
|
||||
'functools', 'stat', 're', 'codecs', 'inspect', 'itertools', 'io',
|
||||
'threading'
|
||||
'threading', 'colorama'
|
||||
])
|
||||
|
||||
if WIN:
|
||||
ALLOWED_IMPORTS.update(['ctypes', 'ctypes.wintypes', 'msvcrt', 'time',
|
||||
'zlib'])
|
||||
|
||||
|
||||
def test_light_imports():
|
||||
c = subprocess.Popen([sys.executable, '-'], stdin=subprocess.PIPE,
|
||||
|
|
|
@ -4,6 +4,8 @@ import os
|
|||
import click
|
||||
import pytest
|
||||
|
||||
from click._compat import text_type
|
||||
|
||||
|
||||
def test_prefixes(runner):
|
||||
@click.command()
|
||||
|
@ -144,32 +146,35 @@ def test_multiple_envvar(runner):
|
|||
|
||||
def test_multiple_default_help(runner):
|
||||
@click.command()
|
||||
@click.option("--arg1", multiple=True, default=('foo', 'bar'),
|
||||
@click.option('--arg1', multiple=True, default=('foo', 'bar'),
|
||||
show_default=True)
|
||||
@click.option("--arg2", multiple=True, default=(1, 2), type=int,
|
||||
@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
|
||||
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"))
|
||||
@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], text_type) for e in arg1)
|
||||
assert all(isinstance(e[1], text_type) for e in arg1)
|
||||
|
||||
assert all(isinstance(e[0],int) for e in arg2)
|
||||
assert all(isinstance(e[1],str) for e in arg2)
|
||||
assert all(isinstance(e[0], int) for e in arg2)
|
||||
assert all(isinstance(e[1], text_type) for e in arg2)
|
||||
|
||||
result = runner.invoke(cmd, "--arg1 a b --arg1 test 1 --arg2 2 two --arg2 4 four".split())
|
||||
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)
|
||||
|
@ -304,3 +309,29 @@ def test_option_custom_class(runner):
|
|||
result = runner.invoke(cmd, ['--help'])
|
||||
assert 'I am a help text' in result.output
|
||||
assert 'you wont see me' not in result.output
|
||||
|
||||
|
||||
def test_aliases_for_flags(runner):
|
||||
@click.command()
|
||||
@click.option('--warnings/--no-warnings', ' /-W', default=True)
|
||||
def cli(warnings):
|
||||
click.echo(warnings)
|
||||
|
||||
result = runner.invoke(cli, ['--warnings'])
|
||||
assert result.output == 'True\n'
|
||||
result = runner.invoke(cli, ['--no-warnings'])
|
||||
assert result.output == 'False\n'
|
||||
result = runner.invoke(cli, ['-W'])
|
||||
assert result.output == 'False\n'
|
||||
|
||||
@click.command()
|
||||
@click.option('--warnings/--no-warnings', '-w', default=True)
|
||||
def cli_alt(warnings):
|
||||
click.echo(warnings)
|
||||
|
||||
result = runner.invoke(cli_alt, ['--warnings'])
|
||||
assert result.output == 'True\n'
|
||||
result = runner.invoke(cli_alt, ['--no-warnings'])
|
||||
assert result.output == 'False\n'
|
||||
result = runner.invoke(cli_alt, ['-w'])
|
||||
assert result.output == 'True\n'
|
||||
|
|
|
@ -5,7 +5,7 @@ import click
|
|||
|
||||
from click.testing import CliRunner
|
||||
|
||||
from click._compat import PY2
|
||||
from click._compat import PY2, WIN
|
||||
|
||||
# Use the most reasonable io that users would use for the python version.
|
||||
if PY2:
|
||||
|
@ -117,6 +117,7 @@ def test_catch_exceptions():
|
|||
assert result.exit_code == 1
|
||||
|
||||
|
||||
@pytest.mark.skipif(WIN, reason='Test does not make sense on Windows.')
|
||||
def test_with_color():
|
||||
@click.command()
|
||||
def cli():
|
||||
|
@ -182,4 +183,3 @@ def test_exit_code_and_output_from_sys_exit():
|
|||
result = runner.invoke(cli_no_error)
|
||||
assert result.exit_code == 0
|
||||
assert result.output == 'hello world\n'
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import pytest
|
|||
import click
|
||||
import click.utils
|
||||
import click._termui_impl
|
||||
from click._compat import WIN, PY2
|
||||
|
||||
|
||||
def test_echo(runner):
|
||||
|
@ -15,7 +16,7 @@ def test_echo(runner):
|
|||
click.echo(42, nl=False)
|
||||
click.echo(b'a', nl=False)
|
||||
click.echo('\x1b[31mx\x1b[39m', nl=False)
|
||||
bytes = out.getvalue()
|
||||
bytes = out.getvalue().replace(b'\r\n', b'\n')
|
||||
assert bytes == b'\xe2\x98\x83\nDD\n42ax'
|
||||
|
||||
# If we are in Python 2, we expect that writing bytes into a string io
|
||||
|
@ -82,6 +83,9 @@ def test_filename_formatting():
|
|||
assert click.format_filename(b'/x/foo.txt') == '/x/foo.txt'
|
||||
assert click.format_filename(u'/x/foo.txt') == '/x/foo.txt'
|
||||
assert click.format_filename(u'/x/foo.txt', shorten=True) == 'foo.txt'
|
||||
|
||||
# filesystem encoding on windows permits this.
|
||||
if not WIN:
|
||||
assert click.format_filename(b'/x/foo\xff.txt', shorten=True) \
|
||||
== u'foo\ufffd.txt'
|
||||
|
||||
|
@ -126,6 +130,7 @@ def test_prompts(runner):
|
|||
assert result.output == 'Foo [Y/n]: n\nno :(\n'
|
||||
|
||||
|
||||
@pytest.mark.skipif(WIN, reason='Different behavior on windows.')
|
||||
def test_prompts_abort(monkeypatch, capsys):
|
||||
def f(_):
|
||||
raise KeyboardInterrupt()
|
||||
|
@ -141,6 +146,7 @@ def test_prompts_abort(monkeypatch, capsys):
|
|||
assert out == 'Password: \nScrew you.\n'
|
||||
|
||||
|
||||
@pytest.mark.skipif(WIN, reason='Different behavior on windows.')
|
||||
@pytest.mark.parametrize('cat', ['cat', 'cat ', 'cat '])
|
||||
def test_echo_via_pager(monkeypatch, capfd, cat):
|
||||
monkeypatch.setitem(os.environ, 'PAGER', cat)
|
||||
|
@ -150,6 +156,7 @@ def test_echo_via_pager(monkeypatch, capfd, cat):
|
|||
assert out == 'haha\n'
|
||||
|
||||
|
||||
@pytest.mark.skipif(WIN, reason='Test does not make sense on Windows.')
|
||||
def test_echo_color_flag(monkeypatch, capfd):
|
||||
isatty = True
|
||||
monkeypatch.setattr(click._compat, 'isatty', lambda x: isatty)
|
||||
|
@ -177,6 +184,7 @@ def test_echo_color_flag(monkeypatch, capfd):
|
|||
assert out == text + '\n'
|
||||
|
||||
|
||||
@pytest.mark.skipif(WIN, reason='Test too complex to make work windows.')
|
||||
def test_echo_writing_to_standard_error(capfd, monkeypatch):
|
||||
def emulate_input(text):
|
||||
"""Emulate keyboard input."""
|
||||
|
@ -255,8 +263,8 @@ def test_open_file(runner):
|
|||
assert result.output == 'foobar\nmeep\n'
|
||||
|
||||
|
||||
@pytest.mark.xfail(WIN and not PY2, reason='God knows ...')
|
||||
def test_iter_keepopenfile(tmpdir):
|
||||
|
||||
expected = list(map(str, range(10)))
|
||||
p = tmpdir.mkdir('testdir').join('testfile')
|
||||
p.write(os.linesep.join(expected))
|
||||
|
@ -265,8 +273,8 @@ def test_iter_keepopenfile(tmpdir):
|
|||
assert e_line == a_line.strip()
|
||||
|
||||
|
||||
@pytest.mark.xfail(WIN and not PY2, reason='God knows ...')
|
||||
def test_iter_lazyfile(tmpdir):
|
||||
|
||||
expected = list(map(str, range(10)))
|
||||
p = tmpdir.mkdir('testdir').join('testfile')
|
||||
p.write(os.linesep.join(expected))
|
||||
|
|
Loading…
Reference in a new issue