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.
|
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
|
Version 5.1
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
|
|
3
Makefile
3
Makefile
|
@ -5,4 +5,7 @@ upload-docs:
|
||||||
$(MAKE) -C docs dirhtml
|
$(MAKE) -C docs dirhtml
|
||||||
rsync -a docs/_build/dirhtml/* flow.srv.pocoo.org:/srv/websites/click.pocoo.org/static/
|
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
|
.PHONY: upload-docs
|
||||||
|
|
2
PKG-INFO
2
PKG-INFO
|
@ -1,6 +1,6 @@
|
||||||
Metadata-Version: 1.1
|
Metadata-Version: 1.1
|
||||||
Name: click
|
Name: click
|
||||||
Version: 5.1
|
Version: 6.2
|
||||||
Summary: A simple wrapper around optparse for powerful command line utilities.
|
Summary: A simple wrapper around optparse for powerful command line utilities.
|
||||||
Home-page: http://github.com/mitsuhiko/click
|
Home-page: http://github.com/mitsuhiko/click
|
||||||
Author: Armin Ronacher
|
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
|
Metadata-Version: 1.1
|
||||||
Name: click
|
Name: click
|
||||||
Version: 5.1
|
Version: 6.2
|
||||||
Summary: A simple wrapper around optparse for powerful command line utilities.
|
Summary: A simple wrapper around optparse for powerful command line utilities.
|
||||||
Home-page: http://github.com/mitsuhiko/click
|
Home-page: http://github.com/mitsuhiko/click
|
||||||
Author: Armin Ronacher
|
Author: Armin Ronacher
|
||||||
|
|
|
@ -5,12 +5,15 @@ Makefile
|
||||||
README
|
README
|
||||||
setup.cfg
|
setup.cfg
|
||||||
setup.py
|
setup.py
|
||||||
|
artwork/.DS_Store
|
||||||
artwork/logo.svg
|
artwork/logo.svg
|
||||||
click/__init__.py
|
click/__init__.py
|
||||||
click/_bashcomplete.py
|
click/_bashcomplete.py
|
||||||
click/_compat.py
|
click/_compat.py
|
||||||
click/_termui_impl.py
|
click/_termui_impl.py
|
||||||
click/_textwrap.py
|
click/_textwrap.py
|
||||||
|
click/_unicodefun.py
|
||||||
|
click/_winconsole.py
|
||||||
click/core.py
|
click/core.py
|
||||||
click/decorators.py
|
click/decorators.py
|
||||||
click/exceptions.py
|
click/exceptions.py
|
||||||
|
@ -51,12 +54,14 @@ docs/testing.rst
|
||||||
docs/upgrading.rst
|
docs/upgrading.rst
|
||||||
docs/utils.rst
|
docs/utils.rst
|
||||||
docs/why.rst
|
docs/why.rst
|
||||||
|
docs/wincmd.rst
|
||||||
docs/_static/click-small.png
|
docs/_static/click-small.png
|
||||||
docs/_static/click-small@2x.png
|
docs/_static/click-small@2x.png
|
||||||
docs/_static/click.png
|
docs/_static/click.png
|
||||||
docs/_static/click@2x.png
|
docs/_static/click@2x.png
|
||||||
docs/_templates/sidebarintro.html
|
docs/_templates/sidebarintro.html
|
||||||
docs/_templates/sidebarlogo.html
|
docs/_templates/sidebarlogo.html
|
||||||
|
examples/.DS_Store
|
||||||
examples/README
|
examples/README
|
||||||
examples/aliases/README
|
examples/aliases/README
|
||||||
examples/aliases/aliases.ini
|
examples/aliases/aliases.ini
|
||||||
|
@ -72,36 +77,50 @@ examples/complex/complex/cli.py
|
||||||
examples/complex/complex/commands/__init__.py
|
examples/complex/complex/commands/__init__.py
|
||||||
examples/complex/complex/commands/cmd_init.py
|
examples/complex/complex/commands/cmd_init.py
|
||||||
examples/complex/complex/commands/cmd_status.py
|
examples/complex/complex/commands/cmd_status.py
|
||||||
|
examples/demo/demo.py
|
||||||
|
examples/imagepipe/.DS_Store
|
||||||
examples/imagepipe/.gitignore
|
examples/imagepipe/.gitignore
|
||||||
examples/imagepipe/README
|
examples/imagepipe/README
|
||||||
examples/imagepipe/example01.jpg
|
examples/imagepipe/example01.jpg
|
||||||
examples/imagepipe/example02.jpg
|
examples/imagepipe/example02.jpg
|
||||||
examples/imagepipe/imagepipe.py
|
examples/imagepipe/imagepipe.py
|
||||||
examples/imagepipe/setup.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/README
|
||||||
examples/inout/inout.py
|
examples/inout/inout.py
|
||||||
examples/inout/setup.py
|
examples/inout/setup.py
|
||||||
examples/naval/README
|
examples/naval/README
|
||||||
examples/naval/naval.py
|
examples/naval/naval.py
|
||||||
examples/naval/setup.py
|
examples/naval/setup.py
|
||||||
examples/plugins/BrokenPlugin/printer_bold.egg-info/PKG-INFO
|
examples/naval/click_example_naval.egg-info/PKG-INFO
|
||||||
examples/plugins/BrokenPlugin/printer_bold.egg-info/SOURCES.txt
|
examples/naval/click_example_naval.egg-info/SOURCES.txt
|
||||||
examples/plugins/BrokenPlugin/printer_bold.egg-info/dependency_links.txt
|
examples/naval/click_example_naval.egg-info/dependency_links.txt
|
||||||
examples/plugins/BrokenPlugin/printer_bold.egg-info/entry_points.txt
|
examples/naval/click_example_naval.egg-info/entry_points.txt
|
||||||
examples/plugins/BrokenPlugin/printer_bold.egg-info/top_level.txt
|
examples/naval/click_example_naval.egg-info/requires.txt
|
||||||
examples/plugins/printer.egg-info/PKG-INFO
|
examples/naval/click_example_naval.egg-info/top_level.txt
|
||||||
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/README
|
||||||
examples/repo/repo.py
|
examples/repo/repo.py
|
||||||
examples/repo/setup.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/README
|
||||||
examples/termui/setup.py
|
examples/termui/setup.py
|
||||||
examples/termui/termui.py
|
examples/termui/termui.py
|
||||||
examples/termui/build/lib/termui.py
|
examples/termui/click_example_termui.egg-info/PKG-INFO
|
||||||
examples/termui/dist/click_example_termui-1.0-py3.4.egg
|
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/README
|
||||||
examples/validation/setup.py
|
examples/validation/setup.py
|
||||||
examples/validation/validation.py
|
examples/validation/validation.py
|
||||||
|
|
|
@ -32,7 +32,7 @@ from .types import ParamType, File, Path, Choice, IntRange, Tuple, \
|
||||||
|
|
||||||
# Utilities
|
# Utilities
|
||||||
from .utils import echo, get_binary_stream, get_text_stream, open_file, \
|
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
|
# Terminal functions
|
||||||
from .termui import prompt, confirm, get_terminal_size, echo_via_pager, \
|
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
|
# Exceptions
|
||||||
from .exceptions import ClickException, UsageError, BadParameter, \
|
from .exceptions import ClickException, UsageError, BadParameter, \
|
||||||
FileError, Abort, NoSuchOption, BadOptionUsage, MissingParameter
|
FileError, Abort, NoSuchOption, BadOptionUsage, BadArgumentUsage, \
|
||||||
|
MissingParameter
|
||||||
|
|
||||||
# Formatting
|
# Formatting
|
||||||
from .formatting import HelpFormatter, wrap_text
|
from .formatting import HelpFormatter, wrap_text
|
||||||
|
@ -69,7 +70,7 @@ __all__ = [
|
||||||
|
|
||||||
# Utilities
|
# Utilities
|
||||||
'echo', 'get_binary_stream', 'get_text_stream', 'open_file',
|
'echo', 'get_binary_stream', 'get_text_stream', 'open_file',
|
||||||
'format_filename', 'get_app_dir',
|
'format_filename', 'get_app_dir', 'get_os_args',
|
||||||
|
|
||||||
# Terminal functions
|
# Terminal functions
|
||||||
'prompt', 'confirm', 'get_terminal_size', 'echo_via_pager',
|
'prompt', 'confirm', 'get_terminal_size', 'echo_via_pager',
|
||||||
|
@ -78,7 +79,8 @@ __all__ = [
|
||||||
|
|
||||||
# Exceptions
|
# Exceptions
|
||||||
'ClickException', 'UsageError', 'BadParameter', 'FileError',
|
'ClickException', 'UsageError', 'BadParameter', 'FileError',
|
||||||
'Abort', 'NoSuchOption', 'BadOptionUsage', 'MissingParameter',
|
'Abort', 'NoSuchOption', 'BadOptionUsage', 'BadArgumentUsage',
|
||||||
|
'MissingParameter',
|
||||||
|
|
||||||
# Formatting
|
# Formatting
|
||||||
'HelpFormatter', 'wrap_text',
|
'HelpFormatter', 'wrap_text',
|
||||||
|
@ -93,4 +95,4 @@ __all__ = [
|
||||||
disable_unicode_literals_warning = False
|
disable_unicode_literals_warning = False
|
||||||
|
|
||||||
|
|
||||||
__version__ = '5.1'
|
__version__ = '6.2'
|
||||||
|
|
|
@ -5,8 +5,6 @@ import sys
|
||||||
import codecs
|
import codecs
|
||||||
from weakref import WeakKeyDictionary
|
from weakref import WeakKeyDictionary
|
||||||
|
|
||||||
import click
|
|
||||||
|
|
||||||
|
|
||||||
PY2 = sys.version_info[0] == 2
|
PY2 = sys.version_info[0] == 2
|
||||||
WIN = sys.platform.startswith('win')
|
WIN = sys.platform.startswith('win')
|
||||||
|
@ -16,39 +14,6 @@ DEFAULT_COLUMNS = 80
|
||||||
_ansi_re = re.compile('\033\[((?:\d|;)*)([a-zA-Z])')
|
_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():
|
def get_filesystem_encoding():
|
||||||
return sys.getfilesystemencoding() or sys.getdefaultencoding()
|
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
|
# 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
|
# stream that converts newlines. It's not quite clear what's the
|
||||||
# correct option here.
|
# correct option here.
|
||||||
|
#
|
||||||
|
# This code also lives in _winconsole for the fallback to the console
|
||||||
|
# emulation stream.
|
||||||
if WIN:
|
if WIN:
|
||||||
import msvcrt
|
import msvcrt
|
||||||
def set_binary_mode(f):
|
def set_binary_mode(f):
|
||||||
|
@ -218,12 +186,21 @@ if PY2:
|
||||||
return set_binary_mode(sys.stderr)
|
return set_binary_mode(sys.stderr)
|
||||||
|
|
||||||
def get_text_stdin(encoding=None, errors=None):
|
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)
|
return _make_text_stream(sys.stdin, encoding, errors)
|
||||||
|
|
||||||
def get_text_stdout(encoding=None, errors=None):
|
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)
|
return _make_text_stream(sys.stdout, encoding, errors)
|
||||||
|
|
||||||
def get_text_stderr(encoding=None, errors=None):
|
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)
|
return _make_text_stream(sys.stderr, encoding, errors)
|
||||||
|
|
||||||
def filename_to_ui(value):
|
def filename_to_ui(value):
|
||||||
|
@ -393,12 +370,21 @@ else:
|
||||||
return writer
|
return writer
|
||||||
|
|
||||||
def get_text_stdin(encoding=None, errors=None):
|
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)
|
return _force_correct_text_reader(sys.stdin, encoding, errors)
|
||||||
|
|
||||||
def get_text_stdout(encoding=None, errors=None):
|
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)
|
return _force_correct_text_writer(sys.stdout, encoding, errors)
|
||||||
|
|
||||||
def get_text_stderr(encoding=None, errors=None):
|
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)
|
return _force_correct_text_writer(sys.stderr, encoding, errors)
|
||||||
|
|
||||||
def filename_to_ui(value):
|
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.
|
# 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):
|
class _AtomicFile(object):
|
||||||
|
@ -491,7 +482,12 @@ class _AtomicFile(object):
|
||||||
if self.closed:
|
if self.closed:
|
||||||
return
|
return
|
||||||
self._f.close()
|
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
|
self.closed = True
|
||||||
|
|
||||||
def __getattr__(self, name):
|
def __getattr__(self, name):
|
||||||
|
@ -531,6 +527,21 @@ if WIN:
|
||||||
# Windows has a smaller terminal
|
# Windows has a smaller terminal
|
||||||
DEFAULT_COLUMNS = 79
|
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:
|
try:
|
||||||
import colorama
|
import colorama
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
@ -573,6 +584,11 @@ if WIN:
|
||||||
win = colorama.win32.GetConsoleScreenBufferInfo(
|
win = colorama.win32.GetConsoleScreenBufferInfo(
|
||||||
colorama.win32.STDOUT).srWindow
|
colorama.win32.STDOUT).srWindow
|
||||||
return win.Right - win.Left, win.Bottom - win.Top
|
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):
|
def term_len(x):
|
||||||
|
@ -605,6 +621,8 @@ def _make_cached_stream_func(src_func, wrapper_func):
|
||||||
return func
|
return func
|
||||||
|
|
||||||
|
|
||||||
|
_default_text_stdin = _make_cached_stream_func(
|
||||||
|
lambda: sys.stdin, get_text_stdin)
|
||||||
_default_text_stdout = _make_cached_stream_func(
|
_default_text_stdout = _make_cached_stream_func(
|
||||||
lambda: sys.stdout, get_text_stdout)
|
lambda: sys.stdout, get_text_stdout)
|
||||||
_default_text_stderr = _make_cached_stream_func(
|
_default_text_stderr = _make_cached_stream_func(
|
||||||
|
|
|
@ -87,6 +87,7 @@ class ProgressBar(object):
|
||||||
self.entered = False
|
self.entered = False
|
||||||
self.current_item = None
|
self.current_item = None
|
||||||
self.is_hidden = not isatty(self.file)
|
self.is_hidden = not isatty(self.file)
|
||||||
|
self._last_line = None
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
self.entered = True
|
self.entered = True
|
||||||
|
@ -128,7 +129,18 @@ class ProgressBar(object):
|
||||||
|
|
||||||
def format_eta(self):
|
def format_eta(self):
|
||||||
if self.eta_known:
|
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 ''
|
return ''
|
||||||
|
|
||||||
def format_pos(self):
|
def format_pos(self):
|
||||||
|
@ -179,37 +191,44 @@ class ProgressBar(object):
|
||||||
|
|
||||||
def render_progress(self):
|
def render_progress(self):
|
||||||
from .termui import get_terminal_size
|
from .termui import get_terminal_size
|
||||||
|
nl = False
|
||||||
|
|
||||||
if self.is_hidden:
|
if self.is_hidden:
|
||||||
echo(self.label, file=self.file, color=self.color)
|
buf = [self.label]
|
||||||
|
nl = True
|
||||||
|
else:
|
||||||
|
buf = []
|
||||||
|
# Update width in case the terminal has been resized
|
||||||
|
if self.autowidth:
|
||||||
|
old_width = self.width
|
||||||
|
self.width = 0
|
||||||
|
clutter_length = term_len(self.format_progress_line())
|
||||||
|
new_width = max(0, get_terminal_size()[0] - clutter_length)
|
||||||
|
if new_width < old_width:
|
||||||
|
buf.append(BEFORE_BAR)
|
||||||
|
buf.append(' ' * self.max_width)
|
||||||
|
self.max_width = new_width
|
||||||
|
self.width = new_width
|
||||||
|
|
||||||
|
clear_width = self.width
|
||||||
|
if self.max_width is not None:
|
||||||
|
clear_width = self.max_width
|
||||||
|
|
||||||
|
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
|
||||||
|
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()
|
self.file.flush()
|
||||||
return
|
|
||||||
|
|
||||||
# Update width in case the terminal has been resized
|
|
||||||
if self.autowidth:
|
|
||||||
old_width = self.width
|
|
||||||
self.width = 0
|
|
||||||
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)
|
|
||||||
self.max_width = new_width
|
|
||||||
self.width = new_width
|
|
||||||
|
|
||||||
clear_width = self.width
|
|
||||||
if self.max_width is not None:
|
|
||||||
clear_width = self.max_width
|
|
||||||
|
|
||||||
self.file.write(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))
|
|
||||||
self.file.flush()
|
|
||||||
|
|
||||||
def make_step(self, n_steps):
|
def make_step(self, n_steps):
|
||||||
self.pos += n_steps
|
self.pos += 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 os
|
||||||
import sys
|
import sys
|
||||||
import codecs
|
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from itertools import repeat
|
from itertools import repeat
|
||||||
from functools import update_wrapper
|
from functools import update_wrapper
|
||||||
|
|
||||||
from .types import convert_type, IntRange, BOOL
|
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, \
|
from .exceptions import ClickException, UsageError, BadParameter, Abort, \
|
||||||
MissingParameter
|
MissingParameter
|
||||||
from .termui import prompt, confirm
|
from .termui import prompt, confirm
|
||||||
|
@ -14,7 +13,8 @@ from .formatting import HelpFormatter, join_options
|
||||||
from .parser import OptionParser, split_opt
|
from .parser import OptionParser, split_opt
|
||||||
from .globals import push_context, pop_context
|
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()
|
_missing = object()
|
||||||
|
@ -37,6 +37,27 @@ def _bashcomplete(cmd, prog_name, complete_var=None):
|
||||||
sys.exit(1)
|
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):
|
def batch(iterable, batch_size):
|
||||||
return list(zip(*repeat(iter(iterable), batch_size)))
|
return list(zip(*repeat(iter(iterable), batch_size)))
|
||||||
|
|
||||||
|
@ -187,6 +208,11 @@ class Context(object):
|
||||||
self.params = {}
|
self.params = {}
|
||||||
#: the leftover arguments.
|
#: the leftover arguments.
|
||||||
self.args = []
|
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:
|
if obj is None and parent is not None:
|
||||||
obj = parent.obj
|
obj = parent.obj
|
||||||
#: the user object stored.
|
#: the user object stored.
|
||||||
|
@ -299,10 +325,10 @@ class Context(object):
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def __exit__(self, exc_type, exc_value, tb):
|
def __exit__(self, exc_type, exc_value, tb):
|
||||||
pop_context()
|
|
||||||
self._depth -= 1
|
self._depth -= 1
|
||||||
if self._depth == 0:
|
if self._depth == 0:
|
||||||
self.close()
|
self.close()
|
||||||
|
pop_context()
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def scope(self, cleanup=True):
|
def scope(self, cleanup=True):
|
||||||
|
@ -646,25 +672,15 @@ class BaseCommand(object):
|
||||||
# sane at this point of reject further execution to avoid a
|
# sane at this point of reject further execution to avoid a
|
||||||
# broken script.
|
# broken script.
|
||||||
if not PY2:
|
if not PY2:
|
||||||
try:
|
_verify_python3_env()
|
||||||
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.')
|
|
||||||
else:
|
else:
|
||||||
_check_for_unicode_literals()
|
_check_for_unicode_literals()
|
||||||
|
|
||||||
if args is None:
|
if args is None:
|
||||||
args = sys.argv[1:]
|
args = get_os_args()
|
||||||
else:
|
else:
|
||||||
args = list(args)
|
args = list(args)
|
||||||
|
|
||||||
if prog_name is None:
|
if prog_name is None:
|
||||||
prog_name = make_str(os.path.basename(
|
prog_name = make_str(os.path.basename(
|
||||||
sys.argv and sys.argv[0] or __file__))
|
sys.argv and sys.argv[0] or __file__))
|
||||||
|
@ -918,6 +934,12 @@ class MultiCommand(Command):
|
||||||
#: overridden with the :func:`resultcallback` decorator.
|
#: overridden with the :func:`resultcallback` decorator.
|
||||||
self.result_callback = result_callback
|
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):
|
def collect_usage_pieces(self, ctx):
|
||||||
rv = Command.collect_usage_pieces(self, ctx)
|
rv = Command.collect_usage_pieces(self, ctx)
|
||||||
rv.append(self.subcommand_metavar)
|
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:
|
if not args and self.no_args_is_help and not ctx.resilient_parsing:
|
||||||
echo(ctx.get_help(), color=ctx.color)
|
echo(ctx.get_help(), color=ctx.color)
|
||||||
ctx.exit()
|
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 invoke(self, ctx):
|
||||||
def _process_result(value):
|
def _process_result(value):
|
||||||
|
@ -995,7 +1025,7 @@ class MultiCommand(Command):
|
||||||
**ctx.params)
|
**ctx.params)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
if not ctx.args:
|
if not ctx.protected_args:
|
||||||
# If we are invoked without command the chain flag controls
|
# If we are invoked without command the chain flag controls
|
||||||
# how this happens. If we are not in chain mode, the return
|
# how this happens. If we are not in chain mode, the return
|
||||||
# value here is the return value of the command.
|
# value here is the return value of the command.
|
||||||
|
@ -1010,7 +1040,10 @@ class MultiCommand(Command):
|
||||||
return _process_result([])
|
return _process_result([])
|
||||||
ctx.fail('Missing command.')
|
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
|
# 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
|
# single command but we also inform the current context about the
|
||||||
|
@ -1045,7 +1078,7 @@ class MultiCommand(Command):
|
||||||
allow_extra_args=True,
|
allow_extra_args=True,
|
||||||
allow_interspersed_args=False)
|
allow_interspersed_args=False)
|
||||||
contexts.append(sub_ctx)
|
contexts.append(sub_ctx)
|
||||||
args = sub_ctx.args
|
args, sub_ctx.args = sub_ctx.args, []
|
||||||
|
|
||||||
rv = []
|
rv = []
|
||||||
for sub_ctx in contexts:
|
for sub_ctx in contexts:
|
||||||
|
@ -1111,6 +1144,7 @@ class Group(MultiCommand):
|
||||||
name = name or cmd.name
|
name = name or cmd.name
|
||||||
if name is None:
|
if name is None:
|
||||||
raise TypeError('Command has no name.')
|
raise TypeError('Command has no name.')
|
||||||
|
_check_multicommand(self, name, cmd, register=True)
|
||||||
self.commands[name] = cmd
|
self.commands[name] = cmd
|
||||||
|
|
||||||
def command(self, *args, **kwargs):
|
def command(self, *args, **kwargs):
|
||||||
|
@ -1164,6 +1198,8 @@ class CommandCollection(MultiCommand):
|
||||||
for source in self.sources:
|
for source in self.sources:
|
||||||
rv = source.get_command(ctx, cmd_name)
|
rv = source.get_command(ctx, cmd_name)
|
||||||
if rv is not None:
|
if rv is not None:
|
||||||
|
if self.chain:
|
||||||
|
_check_multicommand(self, cmd_name, rv)
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
def list_commands(self, ctx):
|
def list_commands(self, ctx):
|
||||||
|
@ -1495,9 +1531,12 @@ class Option(Parameter):
|
||||||
if split_char in decl:
|
if split_char in decl:
|
||||||
first, second = decl.split(split_char, 1)
|
first, second = decl.split(split_char, 1)
|
||||||
first = first.rstrip()
|
first = first.rstrip()
|
||||||
possible_names.append(split_opt(first))
|
if first:
|
||||||
opts.append(first)
|
possible_names.append(split_opt(first))
|
||||||
secondary_opts.append(second.lstrip())
|
opts.append(first)
|
||||||
|
second = second.lstrip()
|
||||||
|
if second:
|
||||||
|
secondary_opts.append(second.lstrip())
|
||||||
else:
|
else:
|
||||||
possible_names.append(split_opt(decl))
|
possible_names.append(split_opt(decl))
|
||||||
opts.append(decl)
|
opts.append(decl)
|
||||||
|
@ -1652,6 +1691,9 @@ class Argument(Parameter):
|
||||||
else:
|
else:
|
||||||
required = attrs.get('nargs', 1) > 0
|
required = attrs.get('nargs', 1) > 0
|
||||||
Parameter.__init__(self, param_decls, required=required, **attrs)
|
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
|
@property
|
||||||
def human_readable_name(self):
|
def human_readable_name(self):
|
||||||
|
|
|
@ -3,7 +3,8 @@ import inspect
|
||||||
|
|
||||||
from functools import update_wrapper
|
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 .utils import echo
|
||||||
from .globals import get_current_context
|
from .globals import get_current_context
|
||||||
|
|
||||||
|
|
|
@ -116,12 +116,13 @@ class MissingParameter(BadParameter):
|
||||||
param_type = self.param.param_type_name
|
param_type = self.param.param_type_name
|
||||||
|
|
||||||
msg = self.message
|
msg = self.message
|
||||||
msg_extra = self.param.type.get_missing_message(self.param)
|
if self.param is not None:
|
||||||
if msg_extra:
|
msg_extra = self.param.type.get_missing_message(self.param)
|
||||||
if msg:
|
if msg_extra:
|
||||||
msg += '. ' + msg_extra
|
if msg:
|
||||||
else:
|
msg += '. ' + msg_extra
|
||||||
msg = msg_extra
|
else:
|
||||||
|
msg = msg_extra
|
||||||
|
|
||||||
return 'Missing %s%s%s%s' % (
|
return 'Missing %s%s%s%s' % (
|
||||||
param_type,
|
param_type,
|
||||||
|
@ -165,7 +166,19 @@ class BadOptionUsage(UsageError):
|
||||||
.. versionadded:: 4.0
|
.. 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)
|
UsageError.__init__(self, message, ctx)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,10 @@ from .parser import split_opt
|
||||||
from ._compat import term_len
|
from ._compat import term_len
|
||||||
|
|
||||||
|
|
||||||
|
# Can force a width. This is used by the test system
|
||||||
|
FORCED_WIDTH = None
|
||||||
|
|
||||||
|
|
||||||
def measure_table(rows):
|
def measure_table(rows):
|
||||||
widths = {}
|
widths = {}
|
||||||
for row in rows:
|
for row in rows:
|
||||||
|
@ -99,7 +103,9 @@ class HelpFormatter(object):
|
||||||
if max_width is None:
|
if max_width is None:
|
||||||
max_width = 80
|
max_width = 80
|
||||||
if width is None:
|
if width is None:
|
||||||
width = max(min(get_terminal_size()[0], max_width) - 2, 50)
|
width = FORCED_WIDTH
|
||||||
|
if width is None:
|
||||||
|
width = max(min(get_terminal_size()[0], max_width) - 2, 50)
|
||||||
self.width = width
|
self.width = width
|
||||||
self.current_indent = 0
|
self.current_indent = 0
|
||||||
self.buffer = []
|
self.buffer = []
|
||||||
|
|
|
@ -16,14 +16,66 @@
|
||||||
and might cause us issues.
|
and might cause us issues.
|
||||||
"""
|
"""
|
||||||
import re
|
import re
|
||||||
from .exceptions import UsageError, NoSuchOption, BadOptionUsage
|
from collections import deque
|
||||||
from .utils import unpack_args
|
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:
|
||||||
|
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:
|
if nargs == 1:
|
||||||
raise BadOptionUsage(opt, '%s option requires an argument' % opt)
|
raise BadOptionUsage('%s option requires an argument' % opt)
|
||||||
raise BadOptionUsage(opt, '%s option requires %d arguments' % (opt, nargs))
|
raise BadOptionUsage('%s option requires %d arguments' % (opt, nargs))
|
||||||
|
|
||||||
|
|
||||||
def split_opt(opt):
|
def split_opt(opt):
|
||||||
|
@ -116,6 +168,13 @@ class Argument(object):
|
||||||
self.obj = obj
|
self.obj = obj
|
||||||
|
|
||||||
def process(self, value, state):
|
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.opts[self.dest] = value
|
||||||
state.order.append(self.obj)
|
state.order.append(self.obj)
|
||||||
|
|
||||||
|
@ -213,8 +272,8 @@ class OptionParser(object):
|
||||||
return state.opts, state.largs, state.order
|
return state.opts, state.largs, state.order
|
||||||
|
|
||||||
def _process_args_for_args(self, state):
|
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])
|
[x.nargs for x in self._args])
|
||||||
|
|
||||||
for idx, arg in enumerate(self._args):
|
for idx, arg in enumerate(self._args):
|
||||||
arg.process(pargs[idx], state)
|
arg.process(pargs[idx], state)
|
||||||
|
@ -275,7 +334,7 @@ class OptionParser(object):
|
||||||
|
|
||||||
nargs = option.nargs
|
nargs = option.nargs
|
||||||
if len(state.rargs) < nargs:
|
if len(state.rargs) < nargs:
|
||||||
_error_args(nargs, opt)
|
_error_opt_args(nargs, opt)
|
||||||
elif nargs == 1:
|
elif nargs == 1:
|
||||||
value = state.rargs.pop(0)
|
value = state.rargs.pop(0)
|
||||||
else:
|
else:
|
||||||
|
@ -283,7 +342,7 @@ class OptionParser(object):
|
||||||
del state.rargs[:nargs]
|
del state.rargs[:nargs]
|
||||||
|
|
||||||
elif explicit_value is not None:
|
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:
|
else:
|
||||||
value = None
|
value = None
|
||||||
|
@ -315,7 +374,7 @@ class OptionParser(object):
|
||||||
|
|
||||||
nargs = option.nargs
|
nargs = option.nargs
|
||||||
if len(state.rargs) < nargs:
|
if len(state.rargs) < nargs:
|
||||||
_error_args(nargs, opt)
|
_error_opt_args(nargs, opt)
|
||||||
elif nargs == 1:
|
elif nargs == 1:
|
||||||
value = state.rargs.pop(0)
|
value = state.rargs.pop(0)
|
||||||
else:
|
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
|
If the user aborts the input by sending a interrupt signal, this
|
||||||
function will catch it and raise a :exc:`Abort` exception.
|
function will catch it and raise a :exc:`Abort` exception.
|
||||||
|
|
||||||
|
.. versionadded:: 6.0
|
||||||
|
Added unicode support for cmd.exe on Windows.
|
||||||
|
|
||||||
.. versionadded:: 4.0
|
.. versionadded:: 4.0
|
||||||
Added the `err` parameter.
|
Added the `err` parameter.
|
||||||
|
|
||||||
|
|
|
@ -157,6 +157,8 @@ class CliRunner(object):
|
||||||
old_stdin = sys.stdin
|
old_stdin = sys.stdin
|
||||||
old_stdout = sys.stdout
|
old_stdout = sys.stdout
|
||||||
old_stderr = sys.stderr
|
old_stderr = sys.stderr
|
||||||
|
old_forced_width = clickpkg.formatting.FORCED_WIDTH
|
||||||
|
clickpkg.formatting.FORCED_WIDTH = 80
|
||||||
|
|
||||||
env = self.make_env(env)
|
env = self.make_env(env)
|
||||||
|
|
||||||
|
@ -236,6 +238,7 @@ class CliRunner(object):
|
||||||
clickpkg.termui.hidden_prompt_func = old_hidden_prompt_func
|
clickpkg.termui.hidden_prompt_func = old_hidden_prompt_func
|
||||||
clickpkg.termui._getchar = old__getchar_func
|
clickpkg.termui._getchar = old__getchar_func
|
||||||
clickpkg.utils.should_strip_ansi = old_should_strip_ansi
|
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,
|
def invoke(self, cli, args=None, input=None, env=None,
|
||||||
catch_exceptions=True, color=False, **extra):
|
catch_exceptions=True, color=False, **extra):
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import os
|
import os
|
||||||
import sys
|
|
||||||
import stat
|
import stat
|
||||||
|
|
||||||
from ._compat import open_stream, text_type, filename_to_ui, \
|
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 .exceptions import BadParameter
|
||||||
from .utils import safecall, LazyFile
|
from .utils import safecall, LazyFile
|
||||||
|
|
||||||
|
@ -109,15 +108,16 @@ class StringParamType(ParamType):
|
||||||
|
|
||||||
def convert(self, value, param, ctx):
|
def convert(self, value, param, ctx):
|
||||||
if isinstance(value, bytes):
|
if isinstance(value, bytes):
|
||||||
|
enc = _get_argv_encoding()
|
||||||
try:
|
try:
|
||||||
enc = getattr(sys.stdin, 'encoding', None)
|
value = value.decode(enc)
|
||||||
if enc is not None:
|
|
||||||
value = value.decode(enc)
|
|
||||||
except UnicodeError:
|
except UnicodeError:
|
||||||
try:
|
fs_enc = get_filesystem_encoding()
|
||||||
value = value.decode(get_filesystem_encoding())
|
if fs_enc != enc:
|
||||||
except UnicodeError:
|
try:
|
||||||
value = value.decode('utf-8', 'replace')
|
value = value.decode(fs_enc)
|
||||||
|
except UnicodeError:
|
||||||
|
value = value.decode('utf-8', 'replace')
|
||||||
return value
|
return value
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
@ -168,7 +168,7 @@ class IntParamType(ParamType):
|
||||||
def convert(self, value, param, ctx):
|
def convert(self, value, param, ctx):
|
||||||
try:
|
try:
|
||||||
return int(value)
|
return int(value)
|
||||||
except ValueError:
|
except (ValueError, UnicodeError):
|
||||||
self.fail('%s is not a valid integer' % value, param, ctx)
|
self.fail('%s is not a valid integer' % value, param, ctx)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
|
@ -237,7 +237,7 @@ class FloatParamType(ParamType):
|
||||||
def convert(self, value, param, ctx):
|
def convert(self, value, param, ctx):
|
||||||
try:
|
try:
|
||||||
return float(value)
|
return float(value)
|
||||||
except ValueError:
|
except (UnicodeError, ValueError):
|
||||||
self.fail('%s is not a valid floating point value' %
|
self.fail('%s is not a valid floating point value' %
|
||||||
value, param, ctx)
|
value, param, ctx)
|
||||||
|
|
||||||
|
@ -251,8 +251,10 @@ class UUIDParameterType(ParamType):
|
||||||
def convert(self, value, param, ctx):
|
def convert(self, value, param, ctx):
|
||||||
import uuid
|
import uuid
|
||||||
try:
|
try:
|
||||||
|
if PY2 and isinstance(value, text_type):
|
||||||
|
value = value.encode('ascii')
|
||||||
return uuid.UUID(value)
|
return uuid.UUID(value)
|
||||||
except ValueError:
|
except (UnicodeError, ValueError):
|
||||||
self.fail('%s is not a valid UUID value' % value, param, ctx)
|
self.fail('%s is not a valid UUID value' % value, param, ctx)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
|
@ -343,6 +345,9 @@ class Path(ParamType):
|
||||||
handle it returns just the filename. Secondly, it can perform various
|
handle it returns just the filename. Secondly, it can perform various
|
||||||
basic checks about what the file or directory should be.
|
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
|
: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
|
this value to be valid. If this is not required and a
|
||||||
file does indeed not exist, then all further checks are
|
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
|
:param resolve_path: if this is true, then the path is fully resolved
|
||||||
before the value is passed onwards. This means
|
before the value is passed onwards. This means
|
||||||
that it's absolute and symlinks are resolved.
|
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
|
envvar_list_splitter = os.path.pathsep
|
||||||
|
|
||||||
def __init__(self, exists=False, file_okay=True, dir_okay=True,
|
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.exists = exists
|
||||||
self.file_okay = file_okay
|
self.file_okay = file_okay
|
||||||
self.dir_okay = dir_okay
|
self.dir_okay = dir_okay
|
||||||
self.writable = writable
|
self.writable = writable
|
||||||
self.readable = readable
|
self.readable = readable
|
||||||
self.resolve_path = resolve_path
|
self.resolve_path = resolve_path
|
||||||
|
self.allow_dash = allow_dash
|
||||||
|
self.type = path_type
|
||||||
|
|
||||||
if self.file_okay and not self.dir_okay:
|
if self.file_okay and not self.dir_okay:
|
||||||
self.name = 'file'
|
self.name = 'file'
|
||||||
|
@ -376,20 +391,32 @@ class Path(ParamType):
|
||||||
self.name = 'path'
|
self.name = 'path'
|
||||||
self.path_type = '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):
|
def convert(self, value, param, ctx):
|
||||||
rv = value
|
rv = value
|
||||||
if self.resolve_path:
|
|
||||||
rv = os.path.realpath(rv)
|
|
||||||
|
|
||||||
try:
|
is_dash = self.file_okay and self.allow_dash and rv in (b'-', '-')
|
||||||
st = os.stat(rv)
|
|
||||||
except OSError:
|
if not is_dash:
|
||||||
if not self.exists:
|
if self.resolve_path:
|
||||||
return rv
|
rv = os.path.realpath(rv)
|
||||||
self.fail('%s "%s" does not exist.' % (
|
|
||||||
self.path_type,
|
try:
|
||||||
filename_to_ui(value)
|
st = os.stat(rv)
|
||||||
), param, ctx)
|
except OSError:
|
||||||
|
if not self.exists:
|
||||||
|
return self.coerce_path_result(rv)
|
||||||
|
self.fail('%s "%s" does not exist.' % (
|
||||||
|
self.path_type,
|
||||||
|
filename_to_ui(value)
|
||||||
|
), param, ctx)
|
||||||
|
|
||||||
if not self.file_okay and stat.S_ISREG(st.st_mode):
|
if not self.file_okay and stat.S_ISREG(st.st_mode):
|
||||||
self.fail('%s "%s" is a file.' % (
|
self.fail('%s "%s" is a file.' % (
|
||||||
|
@ -412,7 +439,7 @@ class Path(ParamType):
|
||||||
filename_to_ui(value)
|
filename_to_ui(value)
|
||||||
), param, ctx)
|
), param, ctx)
|
||||||
|
|
||||||
return rv
|
return self.coerce_path_result(rv)
|
||||||
|
|
||||||
|
|
||||||
class Tuple(CompositeParamType):
|
class Tuple(CompositeParamType):
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from collections import deque
|
|
||||||
|
|
||||||
from .globals import resolve_color_default
|
from .globals import resolve_color_default
|
||||||
|
|
||||||
|
@ -11,6 +10,9 @@ from ._compat import text_type, open_stream, get_filesystem_encoding, \
|
||||||
|
|
||||||
if not PY2:
|
if not PY2:
|
||||||
from ._compat import _find_binary_writer
|
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)
|
echo_native_types = string_types + (bytes, bytearray)
|
||||||
|
@ -20,70 +22,6 @@ def _posixify(name):
|
||||||
return '-'.join(name.split()).lower()
|
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):
|
def safecall(func):
|
||||||
"""Wraps a function so that it swallows exceptions."""
|
"""Wraps a function so that it swallows exceptions."""
|
||||||
def wrapper(*args, **kwargs):
|
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
|
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
|
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
|
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
|
In addition to that, if `colorama`_ is installed, the echo function will
|
||||||
also support clever handling of ANSI codes. Essentially it will then
|
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
|
.. _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
|
.. versionchanged:: 2.0
|
||||||
Starting with version 2.0 of Click, the echo function will work
|
Starting with version 2.0 of Click, the echo function will work
|
||||||
with colorama if it's installed.
|
with colorama if it's installed.
|
||||||
|
@ -381,6 +326,27 @@ def open_file(filename, mode='r', encoding=None, errors='strict',
|
||||||
return f
|
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):
|
def format_filename(filename, shorten=False):
|
||||||
"""Formats a filename for user display. The main purpose of this
|
"""Formats a filename for user display. The main purpose of this
|
||||||
function is to ensure that the filename can be displayed at all. This
|
function is to ensure that the filename can be displayed at all. This
|
||||||
|
|
|
@ -153,6 +153,8 @@ Exceptions
|
||||||
|
|
||||||
.. autoexception:: BadOptionUsage
|
.. autoexception:: BadOptionUsage
|
||||||
|
|
||||||
|
.. autoexception:: BadArgumentUsage
|
||||||
|
|
||||||
Formatting
|
Formatting
|
||||||
----------
|
----------
|
||||||
|
|
||||||
|
|
|
@ -310,7 +310,8 @@ Now you can invoke it like this:
|
||||||
invoke(cli, prog_name='setup.py', args=['sdist', 'bdist_wheel'])
|
invoke(cli, prog_name='setup.py', args=['sdist', 'bdist_wheel'])
|
||||||
|
|
||||||
When using multi command chaining you can only have one command (the last)
|
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
|
restrictions on how they work. They can accept options and arguments as
|
||||||
normal.
|
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
|
subcommands happens one after another so the exact subcommands that will
|
||||||
be handled are not yet available when the callback fires.
|
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
|
Multi Command Pipelines
|
||||||
-----------------------
|
-----------------------
|
||||||
|
|
|
@ -75,6 +75,7 @@ usage patterns.
|
||||||
bashcomplete
|
bashcomplete
|
||||||
exceptions
|
exceptions
|
||||||
python3
|
python3
|
||||||
|
wincmd
|
||||||
|
|
||||||
API Reference
|
API Reference
|
||||||
-------------
|
-------------
|
||||||
|
|
|
@ -201,6 +201,29 @@ can alternatively split the parameters through ``;`` instead:
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
log()
|
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
|
Feature Switches
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
|
@ -445,7 +468,7 @@ replaced with the :func:`confirmation_option` decorator:
|
||||||
.. click:example::
|
.. click:example::
|
||||||
|
|
||||||
@click.command()
|
@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():
|
def dropdb():
|
||||||
click.echo('Dropped all tables!')
|
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 LC_ALL=C.UTF-8
|
||||||
export LANG=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
|
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
|
curious about the reasons for this, you can join the discussions in the
|
||||||
Python 3 bug tracker:
|
Python 3 bug tracker:
|
||||||
|
|
|
@ -30,6 +30,19 @@ suppressed by passing ``nl=False``::
|
||||||
|
|
||||||
click.echo(b'\xe2\x98\x83', 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
|
.. versionadded:: 3.0
|
||||||
|
|
||||||
Starting with Click 3.0 you can also easily print to standard error by
|
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')
|
stdin_text = click.get_text_stream('stdin')
|
||||||
stdout_binary = click.get_binary_stream('stdout')
|
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
|
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
|
Metadata-Version: 1.0
|
||||||
Name: printer
|
Name: click-example-naval
|
||||||
Version: 0.1dev0
|
Version: 2.0
|
||||||
Summary: UNKNOWN
|
Summary: UNKNOWN
|
||||||
Home-page: UNKNOWN
|
Home-page: UNKNOWN
|
||||||
Author: 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
|
Metadata-Version: 1.0
|
||||||
Name: printer-bold
|
Name: click-example-repo
|
||||||
Version: 0.1dev0
|
Version: 0.1
|
||||||
Summary: UNKNOWN
|
Summary: UNKNOWN
|
||||||
Home-page: UNKNOWN
|
Home-page: UNKNOWN
|
||||||
Author: 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 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import click
|
import click
|
||||||
|
from click._compat import PY2
|
||||||
|
|
||||||
|
|
||||||
def test_nargs_star(runner):
|
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):
|
def test_nargs_tup(runner):
|
||||||
@click.command()
|
@click.command()
|
||||||
@click.argument('name', nargs=1)
|
@click.argument('name', nargs=1)
|
||||||
|
@ -106,7 +119,8 @@ def test_file_atomics(runner):
|
||||||
with runner.isolated_filesystem():
|
with runner.isolated_filesystem():
|
||||||
with open('foo.txt', 'wb') as f:
|
with open('foo.txt', 'wb') as f:
|
||||||
f.write(b'OLD\n')
|
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.output == ''
|
||||||
assert result.exit_code == 0
|
assert result.exit_code == 0
|
||||||
with open('foo.txt', 'rb') as f:
|
with open('foo.txt', 'rb') as f:
|
||||||
|
@ -223,7 +237,7 @@ def test_nargs_star_ordering(runner):
|
||||||
|
|
||||||
result = runner.invoke(cmd, ['a', 'b', 'c'])
|
result = runner.invoke(cmd, ['a', 'b', 'c'])
|
||||||
assert result.output.splitlines() == [
|
assert result.output.splitlines() == [
|
||||||
"('a',)",
|
PY2 and "(u'a',)" or "('a',)",
|
||||||
'b',
|
'b',
|
||||||
'c',
|
'c',
|
||||||
]
|
]
|
||||||
|
@ -240,7 +254,25 @@ def test_nargs_specified_plus_star_ordering(runner):
|
||||||
|
|
||||||
result = runner.invoke(cmd, ['a', 'b', 'c', 'd', 'e', 'f'])
|
result = runner.invoke(cmd, ['a', 'b', 'c', 'd', 'e', 'f'])
|
||||||
assert result.output.splitlines() == [
|
assert result.output.splitlines() == [
|
||||||
"('a', 'b', 'c')",
|
PY2 and "(u'a', u'b', u'c')" or "('a', 'b', 'c')",
|
||||||
'd',
|
'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 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):
|
def test_basic_chaining(runner):
|
||||||
|
@ -152,3 +161,92 @@ def test_pipeline(runner):
|
||||||
'FOO',
|
'FOO',
|
||||||
'BAR',
|
'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'])
|
result = runner.invoke(cli, ['test'])
|
||||||
assert not result.exception
|
assert not result.exception
|
||||||
assert result.output == 'test\n'
|
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 json
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
|
from click._compat import WIN
|
||||||
|
|
||||||
|
|
||||||
IMPORT_TEST = b'''\
|
IMPORT_TEST = b'''\
|
||||||
try:
|
try:
|
||||||
|
@ -30,9 +32,13 @@ click.echo(json.dumps(rv))
|
||||||
ALLOWED_IMPORTS = set([
|
ALLOWED_IMPORTS = set([
|
||||||
'weakref', 'os', 'struct', 'collections', 'sys', 'contextlib',
|
'weakref', 'os', 'struct', 'collections', 'sys', 'contextlib',
|
||||||
'functools', 'stat', 're', 'codecs', 'inspect', 'itertools', 'io',
|
'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():
|
def test_light_imports():
|
||||||
c = subprocess.Popen([sys.executable, '-'], stdin=subprocess.PIPE,
|
c = subprocess.Popen([sys.executable, '-'], stdin=subprocess.PIPE,
|
||||||
|
|
|
@ -4,6 +4,8 @@ import os
|
||||||
import click
|
import click
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from click._compat import text_type
|
||||||
|
|
||||||
|
|
||||||
def test_prefixes(runner):
|
def test_prefixes(runner):
|
||||||
@click.command()
|
@click.command()
|
||||||
|
@ -144,32 +146,35 @@ def test_multiple_envvar(runner):
|
||||||
|
|
||||||
def test_multiple_default_help(runner):
|
def test_multiple_default_help(runner):
|
||||||
@click.command()
|
@click.command()
|
||||||
@click.option("--arg1", multiple=True, default=('foo', 'bar'),
|
@click.option('--arg1', multiple=True, default=('foo', 'bar'),
|
||||||
show_default=True)
|
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)
|
show_default=True)
|
||||||
def cmd(arg, arg2):
|
def cmd(arg, arg2):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
result = runner.invoke(cmd, ['--help'])
|
result = runner.invoke(cmd, ['--help'])
|
||||||
assert not result.exception
|
assert not result.exception
|
||||||
assert "foo, bar" in result.output
|
assert 'foo, bar' in result.output
|
||||||
assert "1, 2" in result.output
|
assert '1, 2' in result.output
|
||||||
|
|
||||||
|
|
||||||
def test_multiple_default_type(runner):
|
def test_multiple_default_type(runner):
|
||||||
@click.command()
|
@click.command()
|
||||||
@click.option("--arg1", multiple=True, default=('foo', 'bar'))
|
@click.option('--arg1', multiple=True, default=('foo', 'bar'))
|
||||||
@click.option("--arg2", multiple=True, default=(1, "a"))
|
@click.option('--arg2', multiple=True, default=(1, 'a'))
|
||||||
def cmd(arg1, arg2):
|
def cmd(arg1, arg2):
|
||||||
assert all(isinstance(e[0],str) for e in arg1)
|
assert all(isinstance(e[0], text_type) for e in arg1)
|
||||||
assert all(isinstance(e[1],str) 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[0], int) for e in arg2)
|
||||||
assert all(isinstance(e[1],str) 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
|
assert not result.exception
|
||||||
|
|
||||||
|
|
||||||
def test_nargs_envvar(runner):
|
def test_nargs_envvar(runner):
|
||||||
@click.command()
|
@click.command()
|
||||||
@click.option('--arg', nargs=2)
|
@click.option('--arg', nargs=2)
|
||||||
|
@ -304,3 +309,29 @@ def test_option_custom_class(runner):
|
||||||
result = runner.invoke(cmd, ['--help'])
|
result = runner.invoke(cmd, ['--help'])
|
||||||
assert 'I am a help text' in result.output
|
assert 'I am a help text' in result.output
|
||||||
assert 'you wont see me' not 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.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.
|
# Use the most reasonable io that users would use for the python version.
|
||||||
if PY2:
|
if PY2:
|
||||||
|
@ -117,6 +117,7 @@ def test_catch_exceptions():
|
||||||
assert result.exit_code == 1
|
assert result.exit_code == 1
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif(WIN, reason='Test does not make sense on Windows.')
|
||||||
def test_with_color():
|
def test_with_color():
|
||||||
@click.command()
|
@click.command()
|
||||||
def cli():
|
def cli():
|
||||||
|
@ -182,4 +183,3 @@ def test_exit_code_and_output_from_sys_exit():
|
||||||
result = runner.invoke(cli_no_error)
|
result = runner.invoke(cli_no_error)
|
||||||
assert result.exit_code == 0
|
assert result.exit_code == 0
|
||||||
assert result.output == 'hello world\n'
|
assert result.output == 'hello world\n'
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ import pytest
|
||||||
import click
|
import click
|
||||||
import click.utils
|
import click.utils
|
||||||
import click._termui_impl
|
import click._termui_impl
|
||||||
|
from click._compat import WIN, PY2
|
||||||
|
|
||||||
|
|
||||||
def test_echo(runner):
|
def test_echo(runner):
|
||||||
|
@ -15,7 +16,7 @@ def test_echo(runner):
|
||||||
click.echo(42, nl=False)
|
click.echo(42, nl=False)
|
||||||
click.echo(b'a', nl=False)
|
click.echo(b'a', nl=False)
|
||||||
click.echo('\x1b[31mx\x1b[39m', 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'
|
assert bytes == b'\xe2\x98\x83\nDD\n42ax'
|
||||||
|
|
||||||
# If we are in Python 2, we expect that writing bytes into a string io
|
# If we are in Python 2, we expect that writing bytes into a string io
|
||||||
|
@ -82,8 +83,11 @@ def test_filename_formatting():
|
||||||
assert click.format_filename(b'/x/foo.txt') == '/x/foo.txt'
|
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') == '/x/foo.txt'
|
||||||
assert click.format_filename(u'/x/foo.txt', shorten=True) == 'foo.txt'
|
assert click.format_filename(u'/x/foo.txt', shorten=True) == 'foo.txt'
|
||||||
assert click.format_filename(b'/x/foo\xff.txt', shorten=True) \
|
|
||||||
== u'foo\ufffd.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'
|
||||||
|
|
||||||
|
|
||||||
def test_prompts(runner):
|
def test_prompts(runner):
|
||||||
|
@ -126,6 +130,7 @@ def test_prompts(runner):
|
||||||
assert result.output == 'Foo [Y/n]: n\nno :(\n'
|
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 test_prompts_abort(monkeypatch, capsys):
|
||||||
def f(_):
|
def f(_):
|
||||||
raise KeyboardInterrupt()
|
raise KeyboardInterrupt()
|
||||||
|
@ -141,6 +146,7 @@ def test_prompts_abort(monkeypatch, capsys):
|
||||||
assert out == 'Password: \nScrew you.\n'
|
assert out == 'Password: \nScrew you.\n'
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif(WIN, reason='Different behavior on windows.')
|
||||||
@pytest.mark.parametrize('cat', ['cat', 'cat ', 'cat '])
|
@pytest.mark.parametrize('cat', ['cat', 'cat ', 'cat '])
|
||||||
def test_echo_via_pager(monkeypatch, capfd, cat):
|
def test_echo_via_pager(monkeypatch, capfd, cat):
|
||||||
monkeypatch.setitem(os.environ, 'PAGER', cat)
|
monkeypatch.setitem(os.environ, 'PAGER', cat)
|
||||||
|
@ -150,6 +156,7 @@ def test_echo_via_pager(monkeypatch, capfd, cat):
|
||||||
assert out == 'haha\n'
|
assert out == 'haha\n'
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif(WIN, reason='Test does not make sense on Windows.')
|
||||||
def test_echo_color_flag(monkeypatch, capfd):
|
def test_echo_color_flag(monkeypatch, capfd):
|
||||||
isatty = True
|
isatty = True
|
||||||
monkeypatch.setattr(click._compat, 'isatty', lambda x: isatty)
|
monkeypatch.setattr(click._compat, 'isatty', lambda x: isatty)
|
||||||
|
@ -177,6 +184,7 @@ def test_echo_color_flag(monkeypatch, capfd):
|
||||||
assert out == text + '\n'
|
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 test_echo_writing_to_standard_error(capfd, monkeypatch):
|
||||||
def emulate_input(text):
|
def emulate_input(text):
|
||||||
"""Emulate keyboard input."""
|
"""Emulate keyboard input."""
|
||||||
|
@ -255,8 +263,8 @@ def test_open_file(runner):
|
||||||
assert result.output == 'foobar\nmeep\n'
|
assert result.output == 'foobar\nmeep\n'
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.xfail(WIN and not PY2, reason='God knows ...')
|
||||||
def test_iter_keepopenfile(tmpdir):
|
def test_iter_keepopenfile(tmpdir):
|
||||||
|
|
||||||
expected = list(map(str, range(10)))
|
expected = list(map(str, range(10)))
|
||||||
p = tmpdir.mkdir('testdir').join('testfile')
|
p = tmpdir.mkdir('testdir').join('testfile')
|
||||||
p.write(os.linesep.join(expected))
|
p.write(os.linesep.join(expected))
|
||||||
|
@ -265,8 +273,8 @@ def test_iter_keepopenfile(tmpdir):
|
||||||
assert e_line == a_line.strip()
|
assert e_line == a_line.strip()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.xfail(WIN and not PY2, reason='God knows ...')
|
||||||
def test_iter_lazyfile(tmpdir):
|
def test_iter_lazyfile(tmpdir):
|
||||||
|
|
||||||
expected = list(map(str, range(10)))
|
expected = list(map(str, range(10)))
|
||||||
p = tmpdir.mkdir('testdir').join('testfile')
|
p = tmpdir.mkdir('testdir').join('testfile')
|
||||||
p.write(os.linesep.join(expected))
|
p.write(os.linesep.join(expected))
|
||||||
|
|
Loading…
Reference in a new issue