From 3187e28df28af704aaf0e0ba87e13fa35dfb2e7c Mon Sep 17 00:00:00 2001 From: aviau Date: Fri, 4 Dec 2015 10:51:02 -0500 Subject: [PATCH] Imported Upstream version 6.2 --- CHANGES | 56 ++++ Makefile | 3 + PKG-INFO | 2 +- artwork/.DS_Store | Bin 0 -> 6148 bytes click.egg-info/PKG-INFO | 2 +- click.egg-info/SOURCES.txt | 43 ++- click/__init__.py | 12 +- click/_compat.py | 92 +++--- click/_termui_impl.py | 77 +++-- click/_unicodefun.py | 114 ++++++++ click/_winconsole.py | 264 ++++++++++++++++++ click/core.py | 90 ++++-- click/decorators.py | 3 +- click/exceptions.py | 27 +- click/formatting.py | 8 +- click/parser.py | 79 +++++- click/termui.py | 3 + click/testing.py | 3 + click/types.py | 77 +++-- click/utils.py | 98 +++---- docs/api.rst | 2 + docs/commands.rst | 8 +- docs/index.rst | 1 + docs/options.rst | 25 +- docs/python3.rst | 6 + docs/utils.rst | 19 ++ docs/wincmd.rst | 73 +++++ examples/.DS_Store | Bin 0 -> 6148 bytes examples/demo/demo.py | 47 ++++ examples/imagepipe/.DS_Store | Bin 0 -> 6148 bytes .../click_example_imagepipe.egg-info/PKG-INFO | 10 + .../SOURCES.txt | 8 + .../dependency_links.txt | 0 .../entry_points.txt | 4 + .../requires.txt | 1 + .../top_level.txt | 1 + .../click_example_naval.egg-info}/PKG-INFO | 4 +- .../click_example_naval.egg-info/SOURCES.txt | 8 + .../dependency_links.txt | 0 .../entry_points.txt | 4 + .../click_example_naval.egg-info/requires.txt | 1 + .../top_level.txt | 1 + .../printer_bold.egg-info/SOURCES.txt | 8 - .../printer_bold.egg-info/entry_points.txt | 4 - .../printer_bold.egg-info/top_level.txt | 1 - examples/plugins/printer.egg-info/SOURCES.txt | 8 - .../plugins/printer.egg-info/entry_points.txt | 4 - .../plugins/printer.egg-info/top_level.txt | 1 - .../click_example_repo.egg-info}/PKG-INFO | 4 +- .../click_example_repo.egg-info/SOURCES.txt | 8 + .../dependency_links.txt | 1 + .../entry_points.txt | 4 + .../click_example_repo.egg-info/requires.txt | 1 + .../click_example_repo.egg-info/top_level.txt | 1 + examples/termui/build/lib/termui.py | 145 ---------- .../click_example_termui.egg-info/PKG-INFO | 10 + .../click_example_termui.egg-info/SOURCES.txt | 8 + .../dependency_links.txt | 1 + .../entry_points.txt | 4 + .../requires.txt | 2 + .../top_level.txt | 1 + .../dist/click_example_termui-1.0-py3.4.egg | Bin 4871 -> 0 bytes tests/test_arguments.py | 40 ++- tests/test_chain.py | 98 +++++++ tests/test_context.py | 19 ++ tests/test_imports.py | 8 +- tests/test_options.py | 53 +++- tests/test_testing.py | 4 +- tests/test_utils.py | 18 +- 69 files changed, 1313 insertions(+), 419 deletions(-) create mode 100644 artwork/.DS_Store create mode 100644 click/_unicodefun.py create mode 100644 click/_winconsole.py create mode 100644 docs/wincmd.rst create mode 100644 examples/.DS_Store create mode 100644 examples/demo/demo.py create mode 100644 examples/imagepipe/.DS_Store create mode 100644 examples/imagepipe/click_example_imagepipe.egg-info/PKG-INFO create mode 100644 examples/imagepipe/click_example_imagepipe.egg-info/SOURCES.txt rename examples/{plugins/BrokenPlugin/printer_bold.egg-info => imagepipe/click_example_imagepipe.egg-info}/dependency_links.txt (100%) create mode 100644 examples/imagepipe/click_example_imagepipe.egg-info/entry_points.txt create mode 100644 examples/imagepipe/click_example_imagepipe.egg-info/requires.txt create mode 100644 examples/imagepipe/click_example_imagepipe.egg-info/top_level.txt rename examples/{plugins/printer.egg-info => naval/click_example_naval.egg-info}/PKG-INFO (79%) create mode 100644 examples/naval/click_example_naval.egg-info/SOURCES.txt rename examples/{plugins/printer.egg-info => naval/click_example_naval.egg-info}/dependency_links.txt (100%) create mode 100644 examples/naval/click_example_naval.egg-info/entry_points.txt create mode 100644 examples/naval/click_example_naval.egg-info/requires.txt create mode 100644 examples/naval/click_example_naval.egg-info/top_level.txt delete mode 100644 examples/plugins/BrokenPlugin/printer_bold.egg-info/SOURCES.txt delete mode 100644 examples/plugins/BrokenPlugin/printer_bold.egg-info/entry_points.txt delete mode 100644 examples/plugins/BrokenPlugin/printer_bold.egg-info/top_level.txt delete mode 100644 examples/plugins/printer.egg-info/SOURCES.txt delete mode 100644 examples/plugins/printer.egg-info/entry_points.txt delete mode 100644 examples/plugins/printer.egg-info/top_level.txt rename examples/{plugins/BrokenPlugin/printer_bold.egg-info => repo/click_example_repo.egg-info}/PKG-INFO (80%) create mode 100644 examples/repo/click_example_repo.egg-info/SOURCES.txt create mode 100644 examples/repo/click_example_repo.egg-info/dependency_links.txt create mode 100644 examples/repo/click_example_repo.egg-info/entry_points.txt create mode 100644 examples/repo/click_example_repo.egg-info/requires.txt create mode 100644 examples/repo/click_example_repo.egg-info/top_level.txt delete mode 100644 examples/termui/build/lib/termui.py create mode 100644 examples/termui/click_example_termui.egg-info/PKG-INFO create mode 100644 examples/termui/click_example_termui.egg-info/SOURCES.txt create mode 100644 examples/termui/click_example_termui.egg-info/dependency_links.txt create mode 100644 examples/termui/click_example_termui.egg-info/entry_points.txt create mode 100644 examples/termui/click_example_termui.egg-info/requires.txt create mode 100644 examples/termui/click_example_termui.egg-info/top_level.txt delete mode 100644 examples/termui/dist/click_example_termui-1.0-py3.4.egg diff --git a/CHANGES b/CHANGES index 5ebb65a..5639ea4 100644 --- a/CHANGES +++ b/CHANGES @@ -3,6 +3,62 @@ Click Changelog This contains all major version changes between Click releases. +Version 6.2 +----------- + +(bugfix release, released on November 27th 2015) + +- Correct fix for hidden progress bars. + +Version 6.1 +----------- + +(bugfix release, released on November 27th 2015) + +- Resolved an issue with invisible progress bars no longer rendering. +- Disable chain commands with subcommands as they were inherently broken. +- Fix `MissingParameter` not working without parameters passed. + +Version 6.0 +----------- + +(codename "pow pow", released on November 24th 2015) + +- Optimized the progressbar rendering to not render when it did not + actually change. +- Explicitly disallow nargs=-1 with a set default. +- The context is now closed before it's popped from the stack. +- Added support for short aliases for the false flag on toggles. +- Click will now attempt to aid you with debugging locale errors + better by listing with the help of the OS what locales are + available. +- Click used to return byte strings on Python 2 in some unit-testing + situations. This has been fixed to correctly return unicode strings + now. +- For Windows users on Python 2, Click will now handle Unicode more + correctly handle Unicode coming in from the system. This also has + the disappointing side effect that filenames will now be always + unicode by default in the `Path` type which means that this can + introduce small bugs for code not aware of this. +- Added a `type` parameter to `Path` to force a specific string type + on the value. +- For users running Python on Windows the `echo`) and `prompt` functions + now work with full unicode functionality in the Python windows console + by emulating an output stream. This also applies to getting the + virtual output and input streams via `click.get_text_stream(...)`. +- Unittests now always force a certain virtual terminal width. +- Added support for allowing dashes to indicate standard streams to the + `Path` type. +- Multi commands in chain mode no longer propagate arguments left over + from parsing to the callbacks. It's also now disallowed through an + exception when optional arguments are attached to multi commands if chain + mode is enabled. +- Relaxed restriction that disallowed chained commands to have other + chained commands as child commands. +- Arguments with positive nargs can now have defaults implemented. + Previously this configuration would often result in slightly unexpected + values be returned. + Version 5.1 ----------- diff --git a/Makefile b/Makefile index 7c0f87d..6927e4c 100644 --- a/Makefile +++ b/Makefile @@ -5,4 +5,7 @@ upload-docs: $(MAKE) -C docs dirhtml rsync -a docs/_build/dirhtml/* flow.srv.pocoo.org:/srv/websites/click.pocoo.org/static/ +release: + python setup.py sdist bdist_wheel upload + .PHONY: upload-docs diff --git a/PKG-INFO b/PKG-INFO index a1a4aa5..cd4688c 100644 --- a/PKG-INFO +++ b/PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: click -Version: 5.1 +Version: 6.2 Summary: A simple wrapper around optparse for powerful command line utilities. Home-page: http://github.com/mitsuhiko/click Author: Armin Ronacher diff --git a/artwork/.DS_Store b/artwork/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..cd883014a7b9fb036905ca75ec5eb2ae0dc98877 GIT binary patch literal 6148 zcmeHKyGjH>5Ukb<7ECTQoX|+{4~F9ujQs&Qg-aGT5REtTyZp1e>IdSn8i|QiLv_vc zbno0&uzd}{Hm}b&z#70nIOE;j()`@9u(OI7ncy?7@q#z(@5kwBk^OzZYX^*&uqVFM z@5X7zGyh?C#ML2;E149K0#ZNF{(WeKb1$Bg&+Xd#|1I&u{C`@cofMD)|4IQ{Z6CH9zEbtp#mjlGZNwwtIUj@@*HJX}$mywit&DT8d#dC6$Galt+{TXmwWK!U-6*vM8s2gYi literal 0 HcmV?d00001 diff --git a/click.egg-info/PKG-INFO b/click.egg-info/PKG-INFO index a1a4aa5..cd4688c 100644 --- a/click.egg-info/PKG-INFO +++ b/click.egg-info/PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: click -Version: 5.1 +Version: 6.2 Summary: A simple wrapper around optparse for powerful command line utilities. Home-page: http://github.com/mitsuhiko/click Author: Armin Ronacher diff --git a/click.egg-info/SOURCES.txt b/click.egg-info/SOURCES.txt index 160e199..19999ad 100644 --- a/click.egg-info/SOURCES.txt +++ b/click.egg-info/SOURCES.txt @@ -5,12 +5,15 @@ Makefile README setup.cfg setup.py +artwork/.DS_Store artwork/logo.svg click/__init__.py click/_bashcomplete.py click/_compat.py click/_termui_impl.py click/_textwrap.py +click/_unicodefun.py +click/_winconsole.py click/core.py click/decorators.py click/exceptions.py @@ -51,12 +54,14 @@ docs/testing.rst docs/upgrading.rst docs/utils.rst docs/why.rst +docs/wincmd.rst docs/_static/click-small.png docs/_static/click-small@2x.png docs/_static/click.png docs/_static/click@2x.png docs/_templates/sidebarintro.html docs/_templates/sidebarlogo.html +examples/.DS_Store examples/README examples/aliases/README examples/aliases/aliases.ini @@ -72,36 +77,50 @@ examples/complex/complex/cli.py examples/complex/complex/commands/__init__.py examples/complex/complex/commands/cmd_init.py examples/complex/complex/commands/cmd_status.py +examples/demo/demo.py +examples/imagepipe/.DS_Store examples/imagepipe/.gitignore examples/imagepipe/README examples/imagepipe/example01.jpg examples/imagepipe/example02.jpg examples/imagepipe/imagepipe.py examples/imagepipe/setup.py +examples/imagepipe/click_example_imagepipe.egg-info/PKG-INFO +examples/imagepipe/click_example_imagepipe.egg-info/SOURCES.txt +examples/imagepipe/click_example_imagepipe.egg-info/dependency_links.txt +examples/imagepipe/click_example_imagepipe.egg-info/entry_points.txt +examples/imagepipe/click_example_imagepipe.egg-info/requires.txt +examples/imagepipe/click_example_imagepipe.egg-info/top_level.txt examples/inout/README examples/inout/inout.py examples/inout/setup.py examples/naval/README examples/naval/naval.py examples/naval/setup.py -examples/plugins/BrokenPlugin/printer_bold.egg-info/PKG-INFO -examples/plugins/BrokenPlugin/printer_bold.egg-info/SOURCES.txt -examples/plugins/BrokenPlugin/printer_bold.egg-info/dependency_links.txt -examples/plugins/BrokenPlugin/printer_bold.egg-info/entry_points.txt -examples/plugins/BrokenPlugin/printer_bold.egg-info/top_level.txt -examples/plugins/printer.egg-info/PKG-INFO -examples/plugins/printer.egg-info/SOURCES.txt -examples/plugins/printer.egg-info/dependency_links.txt -examples/plugins/printer.egg-info/entry_points.txt -examples/plugins/printer.egg-info/top_level.txt +examples/naval/click_example_naval.egg-info/PKG-INFO +examples/naval/click_example_naval.egg-info/SOURCES.txt +examples/naval/click_example_naval.egg-info/dependency_links.txt +examples/naval/click_example_naval.egg-info/entry_points.txt +examples/naval/click_example_naval.egg-info/requires.txt +examples/naval/click_example_naval.egg-info/top_level.txt examples/repo/README examples/repo/repo.py examples/repo/setup.py +examples/repo/click_example_repo.egg-info/PKG-INFO +examples/repo/click_example_repo.egg-info/SOURCES.txt +examples/repo/click_example_repo.egg-info/dependency_links.txt +examples/repo/click_example_repo.egg-info/entry_points.txt +examples/repo/click_example_repo.egg-info/requires.txt +examples/repo/click_example_repo.egg-info/top_level.txt examples/termui/README examples/termui/setup.py examples/termui/termui.py -examples/termui/build/lib/termui.py -examples/termui/dist/click_example_termui-1.0-py3.4.egg +examples/termui/click_example_termui.egg-info/PKG-INFO +examples/termui/click_example_termui.egg-info/SOURCES.txt +examples/termui/click_example_termui.egg-info/dependency_links.txt +examples/termui/click_example_termui.egg-info/entry_points.txt +examples/termui/click_example_termui.egg-info/requires.txt +examples/termui/click_example_termui.egg-info/top_level.txt examples/validation/README examples/validation/setup.py examples/validation/validation.py diff --git a/click/__init__.py b/click/__init__.py index 0ed1235..07c21dc 100644 --- a/click/__init__.py +++ b/click/__init__.py @@ -32,7 +32,7 @@ from .types import ParamType, File, Path, Choice, IntRange, Tuple, \ # Utilities from .utils import echo, get_binary_stream, get_text_stream, open_file, \ - format_filename, get_app_dir + format_filename, get_app_dir, get_os_args # Terminal functions from .termui import prompt, confirm, get_terminal_size, echo_via_pager, \ @@ -41,7 +41,8 @@ from .termui import prompt, confirm, get_terminal_size, echo_via_pager, \ # Exceptions from .exceptions import ClickException, UsageError, BadParameter, \ - FileError, Abort, NoSuchOption, BadOptionUsage, MissingParameter + FileError, Abort, NoSuchOption, BadOptionUsage, BadArgumentUsage, \ + MissingParameter # Formatting from .formatting import HelpFormatter, wrap_text @@ -69,7 +70,7 @@ __all__ = [ # Utilities 'echo', 'get_binary_stream', 'get_text_stream', 'open_file', - 'format_filename', 'get_app_dir', + 'format_filename', 'get_app_dir', 'get_os_args', # Terminal functions 'prompt', 'confirm', 'get_terminal_size', 'echo_via_pager', @@ -78,7 +79,8 @@ __all__ = [ # Exceptions 'ClickException', 'UsageError', 'BadParameter', 'FileError', - 'Abort', 'NoSuchOption', 'BadOptionUsage', 'MissingParameter', + 'Abort', 'NoSuchOption', 'BadOptionUsage', 'BadArgumentUsage', + 'MissingParameter', # Formatting 'HelpFormatter', 'wrap_text', @@ -93,4 +95,4 @@ __all__ = [ disable_unicode_literals_warning = False -__version__ = '5.1' +__version__ = '6.2' diff --git a/click/_compat.py b/click/_compat.py index d7a6e16..6bbebd3 100644 --- a/click/_compat.py +++ b/click/_compat.py @@ -5,8 +5,6 @@ import sys import codecs from weakref import WeakKeyDictionary -import click - PY2 = sys.version_info[0] == 2 WIN = sys.platform.startswith('win') @@ -16,39 +14,6 @@ DEFAULT_COLUMNS = 80 _ansi_re = re.compile('\033\[((?:\d|;)*)([a-zA-Z])') -def _find_unicode_literals_frame(): - import __future__ - frm = sys._getframe(1) - idx = 1 - while frm is not None: - if frm.f_globals.get('__name__', '').startswith('click.'): - frm = frm.f_back - idx += 1 - elif frm.f_code.co_flags & __future__.unicode_literals.compiler_flag: - return idx - else: - break - return 0 - - -def _check_for_unicode_literals(): - if not __debug__: - return - if not PY2 or click.disable_unicode_literals_warning: - return - bad_frame = _find_unicode_literals_frame() - if bad_frame <= 0: - return - from warnings import warn - warn(Warning('Click detected the use of the unicode_literals ' - '__future__ import. This is heavily discouraged ' - 'because it can introduce subtle bugs in your ' - 'code. You should instead use explicit u"" literals ' - 'for your unicode strings. For more information see ' - 'http://click.pocoo.org/python3/'), - stacklevel=bad_frame) - - def get_filesystem_encoding(): return sys.getfilesystemencoding() or sys.getdefaultencoding() @@ -192,6 +157,9 @@ if PY2: # binary only, patch it back to the system, and then use a wrapper # stream that converts newlines. It's not quite clear what's the # correct option here. + # + # This code also lives in _winconsole for the fallback to the console + # emulation stream. if WIN: import msvcrt def set_binary_mode(f): @@ -218,12 +186,21 @@ if PY2: return set_binary_mode(sys.stderr) def get_text_stdin(encoding=None, errors=None): + rv = _get_windows_console_stream(sys.stdin, encoding, errors) + if rv is not None: + return rv return _make_text_stream(sys.stdin, encoding, errors) def get_text_stdout(encoding=None, errors=None): + rv = _get_windows_console_stream(sys.stdout, encoding, errors) + if rv is not None: + return rv return _make_text_stream(sys.stdout, encoding, errors) def get_text_stderr(encoding=None, errors=None): + rv = _get_windows_console_stream(sys.stderr, encoding, errors) + if rv is not None: + return rv return _make_text_stream(sys.stderr, encoding, errors) def filename_to_ui(value): @@ -393,12 +370,21 @@ else: return writer def get_text_stdin(encoding=None, errors=None): + rv = _get_windows_console_stream(sys.stdin, encoding, errors) + if rv is not None: + return rv return _force_correct_text_reader(sys.stdin, encoding, errors) def get_text_stdout(encoding=None, errors=None): + rv = _get_windows_console_stream(sys.stdout, encoding, errors) + if rv is not None: + return rv return _force_correct_text_writer(sys.stdout, encoding, errors) def get_text_stderr(encoding=None, errors=None): + rv = _get_windows_console_stream(sys.stderr, encoding, errors) + if rv is not None: + return rv return _force_correct_text_writer(sys.stderr, encoding, errors) def filename_to_ui(value): @@ -472,7 +458,12 @@ def open_stream(filename, mode='r', encoding=None, errors='strict', # Used in a destructor call, needs extra protection from interpreter cleanup. -_rename = os.rename +if hasattr(os, 'replace'): + _replace = os.replace + _can_replace = True +else: + _replace = os.rename + _can_replace = not WIN class _AtomicFile(object): @@ -491,7 +482,12 @@ class _AtomicFile(object): if self.closed: return self._f.close() - _rename(self._tmp_filename, self._real_filename) + if not _can_replace: + try: + os.remove(self._real_filename) + except OSError: + pass + _replace(self._tmp_filename, self._real_filename) self.closed = True def __getattr__(self, name): @@ -531,6 +527,21 @@ if WIN: # Windows has a smaller terminal DEFAULT_COLUMNS = 79 + from ._winconsole import _get_windows_console_stream + + def _get_argv_encoding(): + import locale + return locale.getpreferredencoding() + + if PY2: + def raw_input(prompt=''): + sys.stderr.flush() + if prompt: + stdout = _default_text_stdout() + stdout.write(prompt) + stdin = _default_text_stdin() + return stdin.readline().rstrip('\r\n') + try: import colorama except ImportError: @@ -573,6 +584,11 @@ if WIN: win = colorama.win32.GetConsoleScreenBufferInfo( colorama.win32.STDOUT).srWindow return win.Right - win.Left, win.Bottom - win.Top +else: + def _get_argv_encoding(): + return getattr(sys.stdin, 'encoding', None) or get_filesystem_encoding() + + _get_windows_console_stream = lambda *x: None def term_len(x): @@ -605,6 +621,8 @@ def _make_cached_stream_func(src_func, wrapper_func): return func +_default_text_stdin = _make_cached_stream_func( + lambda: sys.stdin, get_text_stdin) _default_text_stdout = _make_cached_stream_func( lambda: sys.stdout, get_text_stdout) _default_text_stderr = _make_cached_stream_func( diff --git a/click/_termui_impl.py b/click/_termui_impl.py index e86b1bc..a2c1eb0 100644 --- a/click/_termui_impl.py +++ b/click/_termui_impl.py @@ -87,6 +87,7 @@ class ProgressBar(object): self.entered = False self.current_item = None self.is_hidden = not isatty(self.file) + self._last_line = None def __enter__(self): self.entered = True @@ -128,7 +129,18 @@ class ProgressBar(object): def format_eta(self): if self.eta_known: - return time.strftime('%H:%M:%S', time.gmtime(self.eta + 1)) + t = self.eta + 1 + seconds = t % 60 + t /= 60 + minutes = t % 60 + t /= 60 + hours = t % 24 + t /= 24 + if t > 0: + days = t + return '%dd %02d:%02d:%02d' % (days, hours, minutes, seconds) + else: + return '%02d:%02d:%02d' % (hours, minutes, seconds) return '' def format_pos(self): @@ -179,37 +191,44 @@ class ProgressBar(object): def render_progress(self): from .termui import get_terminal_size + nl = False if self.is_hidden: - echo(self.label, file=self.file, color=self.color) + 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() - 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): self.pos += n_steps diff --git a/click/_unicodefun.py b/click/_unicodefun.py new file mode 100644 index 0000000..c0c52aa --- /dev/null +++ b/click/_unicodefun.py @@ -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) diff --git a/click/_winconsole.py b/click/_winconsole.py new file mode 100644 index 0000000..f6cece4 --- /dev/null +++ b/click/_winconsole.py @@ -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 '' % ( + 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) diff --git a/click/core.py b/click/core.py index b3b51c4..33a527a 100644 --- a/click/core.py +++ b/click/core.py @@ -1,12 +1,11 @@ import os import sys -import codecs from contextlib import contextmanager from itertools import repeat from functools import update_wrapper from .types import convert_type, IntRange, BOOL -from .utils import make_str, make_default_short_help, echo +from .utils import make_str, make_default_short_help, echo, get_os_args from .exceptions import ClickException, UsageError, BadParameter, Abort, \ MissingParameter from .termui import prompt, confirm @@ -14,7 +13,8 @@ from .formatting import HelpFormatter, join_options from .parser import OptionParser, split_opt from .globals import push_context, pop_context -from ._compat import PY2, isidentifier, iteritems, _check_for_unicode_literals +from ._compat import PY2, isidentifier, iteritems +from ._unicodefun import _check_for_unicode_literals, _verify_python3_env _missing = object() @@ -37,6 +37,27 @@ def _bashcomplete(cmd, prog_name, complete_var=None): sys.exit(1) +def _check_multicommand(base_command, cmd_name, cmd, register=False): + if not base_command.chain or not isinstance(cmd, MultiCommand): + return + if register: + hint = 'It is not possible to add multi commands as children to ' \ + 'another multi command that is in chain mode' + else: + hint = 'Found a multi command as subcommand to a multi command ' \ + 'that is in chain mode. This is not supported' + raise RuntimeError('%s. Command "%s" is set to chain and "%s" was ' + 'added as subcommand but it in itself is a ' + 'multi command. ("%s" is a %s within a chained ' + '%s named "%s"). This restriction was supposed to ' + 'be lifted in 6.0 but the fix was flawed. This ' + 'will be fixed in Click 7.0' % ( + hint, base_command.name, cmd_name, + cmd_name, cmd.__class__.__name__, + base_command.__class__.__name__, + base_command.name)) + + def batch(iterable, batch_size): return list(zip(*repeat(iter(iterable), batch_size))) @@ -187,6 +208,11 @@ class Context(object): self.params = {} #: the leftover arguments. self.args = [] + #: protected arguments. These are arguments that are prepended + #: to `args` when certain parsing scenarios are encountered but + #: must be never propagated to another arguments. This is used + #: to implement nested parsing. + self.protected_args = [] if obj is None and parent is not None: obj = parent.obj #: the user object stored. @@ -299,10 +325,10 @@ class Context(object): return self def __exit__(self, exc_type, exc_value, tb): - pop_context() self._depth -= 1 if self._depth == 0: self.close() + pop_context() @contextmanager def scope(self, cleanup=True): @@ -646,25 +672,15 @@ class BaseCommand(object): # sane at this point of reject further execution to avoid a # broken script. if not PY2: - try: - import locale - fs_enc = codecs.lookup(locale.getpreferredencoding()).name - except Exception: - fs_enc = 'ascii' - if fs_enc == 'ascii': - raise RuntimeError('Click will abort further execution ' - 'because Python 3 was configured to use ' - 'ASCII as encoding for the environment. ' - 'Either switch to Python 2 or consult ' - 'http://click.pocoo.org/python3/ ' - 'for mitigation steps.') + _verify_python3_env() else: _check_for_unicode_literals() if args is None: - args = sys.argv[1:] + args = get_os_args() else: args = list(args) + if prog_name is None: prog_name = make_str(os.path.basename( sys.argv and sys.argv[0] or __file__)) @@ -918,6 +934,12 @@ class MultiCommand(Command): #: overridden with the :func:`resultcallback` decorator. self.result_callback = result_callback + if self.chain: + for param in self.params: + if isinstance(param, Argument) and not param.required: + raise RuntimeError('Multi commands in chain mode cannot ' + 'have optional arguments.') + def collect_usage_pieces(self, ctx): rv = Command.collect_usage_pieces(self, ctx) rv.append(self.subcommand_metavar) @@ -986,7 +1008,15 @@ class MultiCommand(Command): if not args and self.no_args_is_help and not ctx.resilient_parsing: echo(ctx.get_help(), color=ctx.color) ctx.exit() - return Command.parse_args(self, ctx, args) + + rest = Command.parse_args(self, ctx, args) + if self.chain: + ctx.protected_args = rest + ctx.args = [] + elif rest: + ctx.protected_args, ctx.args = rest[:1], rest[1:] + + return ctx.args def invoke(self, ctx): def _process_result(value): @@ -995,7 +1025,7 @@ class MultiCommand(Command): **ctx.params) return value - if not ctx.args: + if not ctx.protected_args: # If we are invoked without command the chain flag controls # how this happens. If we are not in chain mode, the return # value here is the return value of the command. @@ -1010,7 +1040,10 @@ class MultiCommand(Command): return _process_result([]) ctx.fail('Missing command.') - args = ctx.args + # Fetch args back out + args = ctx.protected_args + ctx.args + ctx.args = [] + ctx.protected_args = [] # If we're not in chain mode, we only allow the invocation of a # single command but we also inform the current context about the @@ -1045,7 +1078,7 @@ class MultiCommand(Command): allow_extra_args=True, allow_interspersed_args=False) contexts.append(sub_ctx) - args = sub_ctx.args + args, sub_ctx.args = sub_ctx.args, [] rv = [] for sub_ctx in contexts: @@ -1111,6 +1144,7 @@ class Group(MultiCommand): name = name or cmd.name if name is None: raise TypeError('Command has no name.') + _check_multicommand(self, name, cmd, register=True) self.commands[name] = cmd def command(self, *args, **kwargs): @@ -1164,6 +1198,8 @@ class CommandCollection(MultiCommand): for source in self.sources: rv = source.get_command(ctx, cmd_name) if rv is not None: + if self.chain: + _check_multicommand(self, cmd_name, rv) return rv def list_commands(self, ctx): @@ -1495,9 +1531,12 @@ class Option(Parameter): if split_char in decl: first, second = decl.split(split_char, 1) first = first.rstrip() - possible_names.append(split_opt(first)) - opts.append(first) - secondary_opts.append(second.lstrip()) + if first: + possible_names.append(split_opt(first)) + opts.append(first) + second = second.lstrip() + if second: + secondary_opts.append(second.lstrip()) else: possible_names.append(split_opt(decl)) opts.append(decl) @@ -1652,6 +1691,9 @@ class Argument(Parameter): else: required = attrs.get('nargs', 1) > 0 Parameter.__init__(self, param_decls, required=required, **attrs) + if self.default is not None and self.nargs < 0: + raise TypeError('nargs=-1 in combination with a default value ' + 'is not supported.') @property def human_readable_name(self): diff --git a/click/decorators.py b/click/decorators.py index 3afd47a..9893452 100644 --- a/click/decorators.py +++ b/click/decorators.py @@ -3,7 +3,8 @@ import inspect from functools import update_wrapper -from ._compat import iteritems, _check_for_unicode_literals +from ._compat import iteritems +from ._unicodefun import _check_for_unicode_literals from .utils import echo from .globals import get_current_context diff --git a/click/exceptions.py b/click/exceptions.py index 042433a..74a4542 100644 --- a/click/exceptions.py +++ b/click/exceptions.py @@ -116,12 +116,13 @@ class MissingParameter(BadParameter): param_type = self.param.param_type_name msg = self.message - msg_extra = self.param.type.get_missing_message(self.param) - if msg_extra: - if msg: - msg += '. ' + msg_extra - else: - msg = msg_extra + if self.param is not None: + msg_extra = self.param.type.get_missing_message(self.param) + if msg_extra: + if msg: + msg += '. ' + msg_extra + else: + msg = msg_extra return 'Missing %s%s%s%s' % ( param_type, @@ -165,7 +166,19 @@ class BadOptionUsage(UsageError): .. versionadded:: 4.0 """ - def __init__(self, option_name, message, ctx=None): + def __init__(self, message, ctx=None): + UsageError.__init__(self, message, ctx) + + +class BadArgumentUsage(UsageError): + """Raised if an argument is generally supplied but the use of the argument + was incorrect. This is for instance raised if the number of values + for an argument is not correct. + + .. versionadded:: 6.0 + """ + + def __init__(self, message, ctx=None): UsageError.__init__(self, message, ctx) diff --git a/click/formatting.py b/click/formatting.py index a4bd081..a3d6a4d 100644 --- a/click/formatting.py +++ b/click/formatting.py @@ -4,6 +4,10 @@ from .parser import split_opt from ._compat import term_len +# Can force a width. This is used by the test system +FORCED_WIDTH = None + + def measure_table(rows): widths = {} for row in rows: @@ -99,7 +103,9 @@ class HelpFormatter(object): if max_width is None: max_width = 80 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.current_indent = 0 self.buffer = [] diff --git a/click/parser.py b/click/parser.py index 426cde6..9775c9f 100644 --- a/click/parser.py +++ b/click/parser.py @@ -16,14 +16,66 @@ and might cause us issues. """ import re -from .exceptions import UsageError, NoSuchOption, BadOptionUsage -from .utils import unpack_args +from collections import deque +from .exceptions import UsageError, NoSuchOption, BadOptionUsage, \ + BadArgumentUsage -def _error_args(nargs, opt): +def _unpack_args(args, nargs_spec): + """Given an iterable of arguments and an iterable of nargs specifications, + it returns a tuple with all the unpacked arguments at the first index + and all remaining arguments as the second. + + The nargs specification is the number of arguments that should be consumed + or `-1` to indicate that this position should eat up all the remainders. + + Missing items are filled with `None`. + """ + args = deque(args) + nargs_spec = deque(nargs_spec) + rv = [] + spos = None + + def _fetch(c): + try: + if spos is None: + return c.popleft() + else: + return c.pop() + except IndexError: + return None + + while nargs_spec: + nargs = _fetch(nargs_spec) + if nargs == 1: + rv.append(_fetch(args)) + elif nargs > 1: + x = [_fetch(args) for _ in range(nargs)] + # If we're reversed, we're pulling in the arguments in reverse, + # so we need to turn them around. + if spos is not None: + x.reverse() + rv.append(tuple(x)) + elif nargs < 0: + if spos is not None: + raise TypeError('Cannot have two nargs < 0') + spos = len(rv) + rv.append(None) + + # spos is the position of the wildcard (star). If it's not `None`, + # we fill it with the remainder. + if spos is not None: + rv[spos] = tuple(args) + args = [] + rv[spos + 1:] = reversed(rv[spos + 1:]) + + return tuple(rv), list(args) + + +def _error_opt_args(nargs, opt): if nargs == 1: - raise BadOptionUsage(opt, '%s option requires an argument' % opt) - raise BadOptionUsage(opt, '%s option requires %d arguments' % (opt, nargs)) + raise BadOptionUsage('%s option requires an argument' % opt) + raise BadOptionUsage('%s option requires %d arguments' % (opt, nargs)) def split_opt(opt): @@ -116,6 +168,13 @@ class Argument(object): self.obj = obj def process(self, value, state): + if self.nargs > 1: + holes = sum(1 for x in value if x is None) + if holes == len(value): + value = None + elif holes != 0: + raise BadArgumentUsage('argument %s takes %d values' + % (self.dest, self.nargs)) state.opts[self.dest] = value state.order.append(self.obj) @@ -213,8 +272,8 @@ class OptionParser(object): return state.opts, state.largs, state.order def _process_args_for_args(self, state): - pargs, args = unpack_args(state.largs + state.rargs, - [x.nargs for x in self._args]) + pargs, args = _unpack_args(state.largs + state.rargs, + [x.nargs for x in self._args]) for idx, arg in enumerate(self._args): arg.process(pargs[idx], state) @@ -275,7 +334,7 @@ class OptionParser(object): nargs = option.nargs if len(state.rargs) < nargs: - _error_args(nargs, opt) + _error_opt_args(nargs, opt) elif nargs == 1: value = state.rargs.pop(0) else: @@ -283,7 +342,7 @@ class OptionParser(object): del state.rargs[:nargs] elif explicit_value is not None: - raise BadOptionUsage(opt, '%s option does not take a value' % opt) + raise BadOptionUsage('%s option does not take a value' % opt) else: value = None @@ -315,7 +374,7 @@ class OptionParser(object): nargs = option.nargs if len(state.rargs) < nargs: - _error_args(nargs, opt) + _error_opt_args(nargs, opt) elif nargs == 1: value = state.rargs.pop(0) else: diff --git a/click/termui.py b/click/termui.py index 9dad8a7..d9fba52 100644 --- a/click/termui.py +++ b/click/termui.py @@ -41,6 +41,9 @@ def prompt(text, default=None, hide_input=False, If the user aborts the input by sending a interrupt signal, this function will catch it and raise a :exc:`Abort` exception. + .. versionadded:: 6.0 + Added unicode support for cmd.exe on Windows. + .. versionadded:: 4.0 Added the `err` parameter. diff --git a/click/testing.py b/click/testing.py index 25ba130..d43581f 100644 --- a/click/testing.py +++ b/click/testing.py @@ -157,6 +157,8 @@ class CliRunner(object): old_stdin = sys.stdin old_stdout = sys.stdout old_stderr = sys.stderr + old_forced_width = clickpkg.formatting.FORCED_WIDTH + clickpkg.formatting.FORCED_WIDTH = 80 env = self.make_env(env) @@ -236,6 +238,7 @@ class CliRunner(object): clickpkg.termui.hidden_prompt_func = old_hidden_prompt_func clickpkg.termui._getchar = old__getchar_func clickpkg.utils.should_strip_ansi = old_should_strip_ansi + clickpkg.formatting.FORCED_WIDTH = old_forced_width def invoke(self, cli, args=None, input=None, env=None, catch_exceptions=True, color=False, **extra): diff --git a/click/types.py b/click/types.py index fd646f9..7a24747 100644 --- a/click/types.py +++ b/click/types.py @@ -1,9 +1,8 @@ import os -import sys import stat from ._compat import open_stream, text_type, filename_to_ui, \ - get_filesystem_encoding, get_streerror + get_filesystem_encoding, get_streerror, _get_argv_encoding, PY2 from .exceptions import BadParameter from .utils import safecall, LazyFile @@ -109,15 +108,16 @@ class StringParamType(ParamType): def convert(self, value, param, ctx): if isinstance(value, bytes): + enc = _get_argv_encoding() try: - enc = getattr(sys.stdin, 'encoding', None) - if enc is not None: - value = value.decode(enc) + value = value.decode(enc) except UnicodeError: - try: - value = value.decode(get_filesystem_encoding()) - except UnicodeError: - value = value.decode('utf-8', 'replace') + fs_enc = get_filesystem_encoding() + if fs_enc != enc: + try: + value = value.decode(fs_enc) + except UnicodeError: + value = value.decode('utf-8', 'replace') return value return value @@ -168,7 +168,7 @@ class IntParamType(ParamType): def convert(self, value, param, ctx): try: return int(value) - except ValueError: + except (ValueError, UnicodeError): self.fail('%s is not a valid integer' % value, param, ctx) def __repr__(self): @@ -237,7 +237,7 @@ class FloatParamType(ParamType): def convert(self, value, param, ctx): try: return float(value) - except ValueError: + except (UnicodeError, ValueError): self.fail('%s is not a valid floating point value' % value, param, ctx) @@ -251,8 +251,10 @@ class UUIDParameterType(ParamType): def convert(self, value, param, ctx): import uuid try: + if PY2 and isinstance(value, text_type): + value = value.encode('ascii') return uuid.UUID(value) - except ValueError: + except (UnicodeError, ValueError): self.fail('%s is not a valid UUID value' % value, param, ctx) def __repr__(self): @@ -343,6 +345,9 @@ class Path(ParamType): handle it returns just the filename. Secondly, it can perform various basic checks about what the file or directory should be. + .. versionchanged:: 6.0 + `allow_dash` was added. + :param exists: if set to true, the file or directory needs to exist for this value to be valid. If this is not required and a file does indeed not exist, then all further checks are @@ -354,17 +359,27 @@ class Path(ParamType): :param resolve_path: if this is true, then the path is fully resolved before the value is passed onwards. This means that it's absolute and symlinks are resolved. + :param allow_dash: If this is set to `True`, a single dash to indicate + standard streams is permitted. + :param type: optionally a string type that should be used to + represent the path. The default is `None` which + means the return value will be either bytes or + unicode depending on what makes most sense given the + input data Click deals with. """ envvar_list_splitter = os.path.pathsep def __init__(self, exists=False, file_okay=True, dir_okay=True, - writable=False, readable=True, resolve_path=False): + writable=False, readable=True, resolve_path=False, + allow_dash=False, path_type=None): self.exists = exists self.file_okay = file_okay self.dir_okay = dir_okay self.writable = writable self.readable = readable self.resolve_path = resolve_path + self.allow_dash = allow_dash + self.type = path_type if self.file_okay and not self.dir_okay: self.name = 'file' @@ -376,20 +391,32 @@ class Path(ParamType): self.name = 'path' self.path_type = 'Path' + def coerce_path_result(self, rv): + if self.type is not None and not isinstance(rv, self.type): + if self.type is text_type: + rv = rv.decode(get_filesystem_encoding()) + else: + rv = rv.encode(get_filesystem_encoding()) + return rv + def convert(self, value, param, ctx): rv = value - if self.resolve_path: - rv = os.path.realpath(rv) - try: - st = os.stat(rv) - except OSError: - if not self.exists: - return rv - self.fail('%s "%s" does not exist.' % ( - self.path_type, - filename_to_ui(value) - ), param, ctx) + is_dash = self.file_okay and self.allow_dash and rv in (b'-', '-') + + if not is_dash: + if self.resolve_path: + rv = os.path.realpath(rv) + + try: + st = os.stat(rv) + 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): self.fail('%s "%s" is a file.' % ( @@ -412,7 +439,7 @@ class Path(ParamType): filename_to_ui(value) ), param, ctx) - return rv + return self.coerce_path_result(rv) class Tuple(CompositeParamType): diff --git a/click/utils.py b/click/utils.py index d6e38c6..eee626d 100644 --- a/click/utils.py +++ b/click/utils.py @@ -1,6 +1,5 @@ import os import sys -from collections import deque from .globals import resolve_color_default @@ -11,6 +10,9 @@ from ._compat import text_type, open_stream, get_filesystem_encoding, \ if not PY2: from ._compat import _find_binary_writer +elif WIN: + from ._winconsole import _get_windows_argv, \ + _hash_py_argv, _initial_argv_hash echo_native_types = string_types + (bytes, bytearray) @@ -20,70 +22,6 @@ def _posixify(name): return '-'.join(name.split()).lower() -def unpack_args(args, nargs_spec): - """Given an iterable of arguments and an iterable of nargs specifications, - it returns a tuple with all the unpacked arguments at the first index - and all remaining arguments as the second. - - The nargs specification is the number of arguments that should be consumed - or `-1` to indicate that this position should eat up all the remainders. - - Missing items are filled with `None`. - - Examples: - - >>> unpack_args(range(6), [1, 2, 1, -1]) - ((0, (1, 2), 3, (4, 5)), []) - >>> unpack_args(range(6), [1, 2, 1]) - ((0, (1, 2), 3), [4, 5]) - >>> unpack_args(range(6), [-1]) - (((0, 1, 2, 3, 4, 5),), []) - >>> unpack_args(range(6), [1, 1]) - ((0, 1), [2, 3, 4, 5]) - >>> unpack_args(range(6), [-1,1,1,1,1]) - (((0, 1), 2, 3, 4, 5), []) - """ - args = deque(args) - nargs_spec = deque(nargs_spec) - rv = [] - spos = None - - def _fetch(c): - try: - if spos is None: - return c.popleft() - else: - return c.pop() - except IndexError: - return None - - while nargs_spec: - nargs = _fetch(nargs_spec) - if nargs == 1: - rv.append(_fetch(args)) - elif nargs > 1: - x = [_fetch(args) for _ in range(nargs)] - # If we're reversed, we're pulling in the arguments in reverse, - # so we need to turn them around. - if spos is not None: - x.reverse() - rv.append(tuple(x)) - elif nargs < 0: - if spos is not None: - raise TypeError('Cannot have two nargs < 0') - spos = len(rv) - rv.append(None) - - # spos is the position of the wildcard (star). If it's not `None`, - # we fill it with the remainder. - if spos is not None: - rv[spos] = tuple(args) - args = [] - rv[spos + 1:] = reversed(rv[spos + 1:]) - - return tuple(rv), list(args) - - def safecall(func): """Wraps a function so that it swallows exceptions.""" def wrapper(*args, **kwargs): @@ -234,7 +172,8 @@ def echo(message=None, file=None, nl=True, err=False, color=None): Primarily it means that you can print binary data as well as Unicode data on both 2.x and 3.x to the given file in the most appropriate way possible. This is a very carefree function as in that it will try its - best to not fail. + best to not fail. As of Click 6.0 this includes support for unicode + output on the Windows console. In addition to that, if `colorama`_ is installed, the echo function will also support clever handling of ANSI codes. Essentially it will then @@ -246,6 +185,12 @@ def echo(message=None, file=None, nl=True, err=False, color=None): .. _colorama: http://pypi.python.org/pypi/colorama + .. versionchanged:: 6.0 + As of Click 6.0 the echo function will properly support unicode + output on the windows console. Not that click does not modify + the interpreter in any way which means that `sys.stdout` or the + print statement or function will still not provide unicode support. + .. versionchanged:: 2.0 Starting with version 2.0 of Click, the echo function will work with colorama if it's installed. @@ -381,6 +326,27 @@ def open_file(filename, mode='r', encoding=None, errors='strict', return f +def get_os_args(): + """This returns the argument part of sys.argv in the most appropriate + form for processing. What this means is that this return value is in + a format that works for Click to process but does not necessarily + correspond well to what's actually standard for the interpreter. + + On most environments the return value is ``sys.argv[:1]`` unchanged. + However if you are on Windows and running Python 2 the return value + will actually be a list of unicode strings instead because the + default behavior on that platform otherwise will not be able to + carry all possible values that sys.argv can have. + + .. versionadded:: 6.0 + """ + # We can only extract the unicode argv if sys.argv has not been + # changed since the startup of the application. + if PY2 and WIN and _initial_argv_hash == _hash_py_argv(): + return _get_windows_argv() + return sys.argv[1:] + + def format_filename(filename, shorten=False): """Formats a filename for user display. The main purpose of this function is to ensure that the filename can be displayed at all. This diff --git a/docs/api.rst b/docs/api.rst index 63f04b1..22dd39f 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -153,6 +153,8 @@ Exceptions .. autoexception:: BadOptionUsage +.. autoexception:: BadArgumentUsage + Formatting ---------- diff --git a/docs/commands.rst b/docs/commands.rst index 642f873..95d4fa8 100644 --- a/docs/commands.rst +++ b/docs/commands.rst @@ -310,7 +310,8 @@ Now you can invoke it like this: invoke(cli, prog_name='setup.py', args=['sdist', 'bdist_wheel']) When using multi command chaining you can only have one command (the last) -use ``nargs=-1`` on an argument. Other than that there are no +use ``nargs=-1`` on an argument. It is also not possible to nest multi +commands below chained multicommands. Other than that there are no restrictions on how they work. They can accept options and arguments as normal. @@ -320,6 +321,11 @@ one command is invoked. This is necessary because the handling of subcommands happens one after another so the exact subcommands that will be handled are not yet available when the callback fires. +.. note:: + + It is currently not possible for chain commands to be nested. This + will be fixed in future versions of Click. + Multi Command Pipelines ----------------------- diff --git a/docs/index.rst b/docs/index.rst index 5bfea5d..8cb7778 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -75,6 +75,7 @@ usage patterns. bashcomplete exceptions python3 + wincmd API Reference ------------- diff --git a/docs/options.rst b/docs/options.rst index 7881117..2a01802 100644 --- a/docs/options.rst +++ b/docs/options.rst @@ -201,6 +201,29 @@ can alternatively split the parameters through ``;`` instead: if __name__ == '__main__': log() +.. versionchanged:: 6.0 + +If you want to define an alias for the second option only, then you will +need to use leading whitespace to disambiguate the format string: + +Example: + +.. click:example:: + + import sys + + @click.command() + @click.option('--shout/--no-shout', ' /-S', default=False) + def info(shout): + rv = sys.platform + if shout: + rv = rv.upper() + '!!!!111' + click.echo(rv) + +.. click:run:: + + invoke(info, args=['--help']) + Feature Switches ---------------- @@ -445,7 +468,7 @@ replaced with the :func:`confirmation_option` decorator: .. click:example:: @click.command() - @click.confirmation_option(help='Are you sure you want to drop the db?') + @click.confirmation_option(prompt='Are you sure you want to drop the db?') def dropdb(): click.echo('Dropped all tables!') diff --git a/docs/python3.rst b/docs/python3.rst index 39e8fe6..e148ba6 100644 --- a/docs/python3.rst +++ b/docs/python3.rst @@ -139,6 +139,12 @@ some newer Linux systems, you could also try ``C.UTF-8`` as the locale:: export LC_ALL=C.UTF-8 export LANG=C.UTF-8 +On some systems it was reported that `UTF-8` has to be written as `UTF8` +and vice versa. To see which locales are supported you can invoke +``locale -a``:: + + locale -a + You need to do this before you invoke your Python script. If you are curious about the reasons for this, you can join the discussions in the Python 3 bug tracker: diff --git a/docs/utils.rst b/docs/utils.rst index 8c7308a..f1798b9 100644 --- a/docs/utils.rst +++ b/docs/utils.rst @@ -30,6 +30,19 @@ suppressed by passing ``nl=False``:: click.echo(b'\xe2\x98\x83', nl=False) +Last but not least :func:`echo` uses click's intelligent internal output +streams to stdout and stderr which support unicode output on the Windows +console. This means for as long as you are using `click.echo` you can +output unicode character (there are some limitations on the default font +with regards to which characters can be displayed). This functionality is +new in Click 6.0. + +.. versionadded:: 6.0 + +Click now emulates output streams on Windows to support unicode to the +Windows console through separate APIs. For more information see +`wincmd`_. + .. versionadded:: 3.0 Starting with Click 3.0 you can also easily print to standard error by @@ -266,6 +279,12 @@ Example:: stdin_text = click.get_text_stream('stdin') stdout_binary = click.get_binary_stream('stdout') +.. versionadded:: 6.0 + +Click now emulates output streams on Windows to support unicode to the +Windows console through separate APIs. For more information see +`wincmd`_. + Intelligent File Opening ------------------------ diff --git a/docs/wincmd.rst b/docs/wincmd.rst new file mode 100644 index 0000000..b9c1161 --- /dev/null +++ b/docs/wincmd.rst @@ -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. diff --git a/examples/.DS_Store b/examples/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..a0cf9ee3a79410d72be94c87ad1ad0eda964c9d4 GIT binary patch literal 6148 zcmeHK%Wl&^6upyof`A}(QFh+JB8#L*(5?s}K|%-#)rc-A6uY(>SFR_Dou*QVlx2DC z`2+;N!UuH0lK+4+k5sYKh#f+7t~GPUGv|(d&x~h0M8rBHuSHZRq6(70LJipkCfY?> zvYf8D3S_d6n0$&KhkV}+m)NGlC}0%$*Ax)nZkZekDL}sP{c;yAVp({Ebo-Yyl!!{S zPaJ$s!}+&9=d$e#w=B6wGyvbH6SXp&v(GZ@D&FA;Z*c(b0ObMsX%G7TXAgHA58O~b z(y6wpO5XtNk(zU8NL*E&zz+1>hg2W!}GG^z6kvB zUVZH$j}j+{{GoIXJRg+Zognh~xWh-07w_7tawqVTzU)+2R4cgl>g$uqR#DRH8t3p0g$wjLwue#3 zqXV=G&mvTeFj6B*(n{uDqjhmqi{LqQEM`K6`7^^T(Khwy0d?sq_0rt@VTN1491&y5 z(>yt+KBwkX0^Sg#KcXPR6=S}Vn~ArkB0$t0B1wRpqI7?9jxP3x?k}n7xmCGxN#i$R z8wHF4s}&IIgM}opt#PJME*;3^5dc^~wKSB)PlNEWG`2O)6ru&jlq*oVGJV8g${qEV z=Cw7>6e@RO`tZT@lbJrDF!^-!Z%I0_wnCE|1&jh|1@h{)F82T4>hpiv$=n$Qi~|3a z0<6%ryB&n2_tvH0#9nJ5y+INtcr%641)1KCWkGDk>qyejW{Co@t#PIhH8ArdAZ0L_ JQQ)sC@Ea%P;JE+* literal 0 HcmV?d00001 diff --git a/examples/demo/demo.py b/examples/demo/demo.py new file mode 100644 index 0000000..c02ff33 --- /dev/null +++ b/examples/demo/demo.py @@ -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() diff --git a/examples/imagepipe/.DS_Store b/examples/imagepipe/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..ea27d0d3b9290ea9b6434e23e41360fdc095d268 GIT binary patch literal 6148 zcmeHKOHKko5Pc2A;4dMrT+Y%B20Xz?z{G_c+^c*>0*=l^A<^9&!$Wxh58yrURd>fQ z3`E@+HC3Ie*Hhh9UGu_p(*R6sHf#bl09C4BWs7Eo$hc@hO5U?cG&aW#E)n4oJ=_*% zt3w%327Vg@^6dr~AjX(>@%v5bi}*eFG2>W_A*M*!VjR*xVyn}ChifFKaWd$~NuRl7 zBYB}7%&oBZS^hEiiXO8?NSQAsekK3&LjGOGxIM?!+(45#Cd@U;S5~@^%BW^Om%>nt zJX#a^#@P~C@{v2NwODtGr~HaIk+_9g_gK|ESsbwD2eyd3*3qy{s!qx1Qq|?jH*!e* zD{|O67CGBM7abgucy z8Tep8_J@Ef7<$Yc+O31eUIB;|x~;G;e*q*W@ECf`9CCzWTqx0n8h^zwE}Z_r&kH?f z4qZ5mzkC?Kvhg<*V^?SXz)pt=9ja9Zlz}`0W%pQ<^S`tH{+|!hCuKkx_*V>=O8dOs z;w$;HweWIs)&|ros))?X9NG{Xe;nJ69L4)oD;x`?LkvA;4(Xxj9|22)8fD-|8F&X- CMwnRu literal 0 HcmV?d00001 diff --git a/examples/imagepipe/click_example_imagepipe.egg-info/PKG-INFO b/examples/imagepipe/click_example_imagepipe.egg-info/PKG-INFO new file mode 100644 index 0000000..9cde7e6 --- /dev/null +++ b/examples/imagepipe/click_example_imagepipe.egg-info/PKG-INFO @@ -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 diff --git a/examples/imagepipe/click_example_imagepipe.egg-info/SOURCES.txt b/examples/imagepipe/click_example_imagepipe.egg-info/SOURCES.txt new file mode 100644 index 0000000..10372d6 --- /dev/null +++ b/examples/imagepipe/click_example_imagepipe.egg-info/SOURCES.txt @@ -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 \ No newline at end of file diff --git a/examples/plugins/BrokenPlugin/printer_bold.egg-info/dependency_links.txt b/examples/imagepipe/click_example_imagepipe.egg-info/dependency_links.txt similarity index 100% rename from examples/plugins/BrokenPlugin/printer_bold.egg-info/dependency_links.txt rename to examples/imagepipe/click_example_imagepipe.egg-info/dependency_links.txt diff --git a/examples/imagepipe/click_example_imagepipe.egg-info/entry_points.txt b/examples/imagepipe/click_example_imagepipe.egg-info/entry_points.txt new file mode 100644 index 0000000..2ed4423 --- /dev/null +++ b/examples/imagepipe/click_example_imagepipe.egg-info/entry_points.txt @@ -0,0 +1,4 @@ + + [console_scripts] + imagepipe=imagepipe:cli + \ No newline at end of file diff --git a/examples/imagepipe/click_example_imagepipe.egg-info/requires.txt b/examples/imagepipe/click_example_imagepipe.egg-info/requires.txt new file mode 100644 index 0000000..4d441ba --- /dev/null +++ b/examples/imagepipe/click_example_imagepipe.egg-info/requires.txt @@ -0,0 +1 @@ +Click \ No newline at end of file diff --git a/examples/imagepipe/click_example_imagepipe.egg-info/top_level.txt b/examples/imagepipe/click_example_imagepipe.egg-info/top_level.txt new file mode 100644 index 0000000..c7f8dcb --- /dev/null +++ b/examples/imagepipe/click_example_imagepipe.egg-info/top_level.txt @@ -0,0 +1 @@ +imagepipe diff --git a/examples/plugins/printer.egg-info/PKG-INFO b/examples/naval/click_example_naval.egg-info/PKG-INFO similarity index 79% rename from examples/plugins/printer.egg-info/PKG-INFO rename to examples/naval/click_example_naval.egg-info/PKG-INFO index 7f22902..cf213e5 100644 --- a/examples/plugins/printer.egg-info/PKG-INFO +++ b/examples/naval/click_example_naval.egg-info/PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 1.0 -Name: printer -Version: 0.1dev0 +Name: click-example-naval +Version: 2.0 Summary: UNKNOWN Home-page: UNKNOWN Author: UNKNOWN diff --git a/examples/naval/click_example_naval.egg-info/SOURCES.txt b/examples/naval/click_example_naval.egg-info/SOURCES.txt new file mode 100644 index 0000000..4cd72f5 --- /dev/null +++ b/examples/naval/click_example_naval.egg-info/SOURCES.txt @@ -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 \ No newline at end of file diff --git a/examples/plugins/printer.egg-info/dependency_links.txt b/examples/naval/click_example_naval.egg-info/dependency_links.txt similarity index 100% rename from examples/plugins/printer.egg-info/dependency_links.txt rename to examples/naval/click_example_naval.egg-info/dependency_links.txt diff --git a/examples/naval/click_example_naval.egg-info/entry_points.txt b/examples/naval/click_example_naval.egg-info/entry_points.txt new file mode 100644 index 0000000..3be14b0 --- /dev/null +++ b/examples/naval/click_example_naval.egg-info/entry_points.txt @@ -0,0 +1,4 @@ + + [console_scripts] + naval=naval:cli + \ No newline at end of file diff --git a/examples/naval/click_example_naval.egg-info/requires.txt b/examples/naval/click_example_naval.egg-info/requires.txt new file mode 100644 index 0000000..b98f660 --- /dev/null +++ b/examples/naval/click_example_naval.egg-info/requires.txt @@ -0,0 +1 @@ +click \ No newline at end of file diff --git a/examples/naval/click_example_naval.egg-info/top_level.txt b/examples/naval/click_example_naval.egg-info/top_level.txt new file mode 100644 index 0000000..5a52231 --- /dev/null +++ b/examples/naval/click_example_naval.egg-info/top_level.txt @@ -0,0 +1 @@ +naval diff --git a/examples/plugins/BrokenPlugin/printer_bold.egg-info/SOURCES.txt b/examples/plugins/BrokenPlugin/printer_bold.egg-info/SOURCES.txt deleted file mode 100644 index 34aba44..0000000 --- a/examples/plugins/BrokenPlugin/printer_bold.egg-info/SOURCES.txt +++ /dev/null @@ -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 \ No newline at end of file diff --git a/examples/plugins/BrokenPlugin/printer_bold.egg-info/entry_points.txt b/examples/plugins/BrokenPlugin/printer_bold.egg-info/entry_points.txt deleted file mode 100644 index 1635389..0000000 --- a/examples/plugins/BrokenPlugin/printer_bold.egg-info/entry_points.txt +++ /dev/null @@ -1,4 +0,0 @@ - - [printer.plugins] - bold=printer_bold.core:bolddddddddddd - \ No newline at end of file diff --git a/examples/plugins/BrokenPlugin/printer_bold.egg-info/top_level.txt b/examples/plugins/BrokenPlugin/printer_bold.egg-info/top_level.txt deleted file mode 100644 index be45b4b..0000000 --- a/examples/plugins/BrokenPlugin/printer_bold.egg-info/top_level.txt +++ /dev/null @@ -1 +0,0 @@ -printer_bold diff --git a/examples/plugins/printer.egg-info/SOURCES.txt b/examples/plugins/printer.egg-info/SOURCES.txt deleted file mode 100644 index a1d305e..0000000 --- a/examples/plugins/printer.egg-info/SOURCES.txt +++ /dev/null @@ -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 \ No newline at end of file diff --git a/examples/plugins/printer.egg-info/entry_points.txt b/examples/plugins/printer.egg-info/entry_points.txt deleted file mode 100644 index f2e6982..0000000 --- a/examples/plugins/printer.egg-info/entry_points.txt +++ /dev/null @@ -1,4 +0,0 @@ - - [console_scripts] - printer=printer.cli:cli - \ No newline at end of file diff --git a/examples/plugins/printer.egg-info/top_level.txt b/examples/plugins/printer.egg-info/top_level.txt deleted file mode 100644 index 24b8a4f..0000000 --- a/examples/plugins/printer.egg-info/top_level.txt +++ /dev/null @@ -1 +0,0 @@ -printer diff --git a/examples/plugins/BrokenPlugin/printer_bold.egg-info/PKG-INFO b/examples/repo/click_example_repo.egg-info/PKG-INFO similarity index 80% rename from examples/plugins/BrokenPlugin/printer_bold.egg-info/PKG-INFO rename to examples/repo/click_example_repo.egg-info/PKG-INFO index 97a4724..a38df1f 100644 --- a/examples/plugins/BrokenPlugin/printer_bold.egg-info/PKG-INFO +++ b/examples/repo/click_example_repo.egg-info/PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 1.0 -Name: printer-bold -Version: 0.1dev0 +Name: click-example-repo +Version: 0.1 Summary: UNKNOWN Home-page: UNKNOWN Author: UNKNOWN diff --git a/examples/repo/click_example_repo.egg-info/SOURCES.txt b/examples/repo/click_example_repo.egg-info/SOURCES.txt new file mode 100644 index 0000000..dca6e07 --- /dev/null +++ b/examples/repo/click_example_repo.egg-info/SOURCES.txt @@ -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 \ No newline at end of file diff --git a/examples/repo/click_example_repo.egg-info/dependency_links.txt b/examples/repo/click_example_repo.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/examples/repo/click_example_repo.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/examples/repo/click_example_repo.egg-info/entry_points.txt b/examples/repo/click_example_repo.egg-info/entry_points.txt new file mode 100644 index 0000000..2399f14 --- /dev/null +++ b/examples/repo/click_example_repo.egg-info/entry_points.txt @@ -0,0 +1,4 @@ + + [console_scripts] + repo=repo:cli + \ No newline at end of file diff --git a/examples/repo/click_example_repo.egg-info/requires.txt b/examples/repo/click_example_repo.egg-info/requires.txt new file mode 100644 index 0000000..b98f660 --- /dev/null +++ b/examples/repo/click_example_repo.egg-info/requires.txt @@ -0,0 +1 @@ +click \ No newline at end of file diff --git a/examples/repo/click_example_repo.egg-info/top_level.txt b/examples/repo/click_example_repo.egg-info/top_level.txt new file mode 100644 index 0000000..f606d5e --- /dev/null +++ b/examples/repo/click_example_repo.egg-info/top_level.txt @@ -0,0 +1 @@ +repo diff --git a/examples/termui/build/lib/termui.py b/examples/termui/build/lib/termui.py deleted file mode 100644 index 007ac42..0000000 --- a/examples/termui/build/lib/termui.py +++ /dev/null @@ -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 diff --git a/examples/termui/click_example_termui.egg-info/PKG-INFO b/examples/termui/click_example_termui.egg-info/PKG-INFO new file mode 100644 index 0000000..46801c5 --- /dev/null +++ b/examples/termui/click_example_termui.egg-info/PKG-INFO @@ -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 diff --git a/examples/termui/click_example_termui.egg-info/SOURCES.txt b/examples/termui/click_example_termui.egg-info/SOURCES.txt new file mode 100644 index 0000000..35c0608 --- /dev/null +++ b/examples/termui/click_example_termui.egg-info/SOURCES.txt @@ -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 \ No newline at end of file diff --git a/examples/termui/click_example_termui.egg-info/dependency_links.txt b/examples/termui/click_example_termui.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/examples/termui/click_example_termui.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/examples/termui/click_example_termui.egg-info/entry_points.txt b/examples/termui/click_example_termui.egg-info/entry_points.txt new file mode 100644 index 0000000..0abfdb5 --- /dev/null +++ b/examples/termui/click_example_termui.egg-info/entry_points.txt @@ -0,0 +1,4 @@ + + [console_scripts] + termui=termui:cli + \ No newline at end of file diff --git a/examples/termui/click_example_termui.egg-info/requires.txt b/examples/termui/click_example_termui.egg-info/requires.txt new file mode 100644 index 0000000..34ba309 --- /dev/null +++ b/examples/termui/click_example_termui.egg-info/requires.txt @@ -0,0 +1,2 @@ +click +colorama \ No newline at end of file diff --git a/examples/termui/click_example_termui.egg-info/top_level.txt b/examples/termui/click_example_termui.egg-info/top_level.txt new file mode 100644 index 0000000..7476d1f --- /dev/null +++ b/examples/termui/click_example_termui.egg-info/top_level.txt @@ -0,0 +1 @@ +termui diff --git a/examples/termui/dist/click_example_termui-1.0-py3.4.egg b/examples/termui/dist/click_example_termui-1.0-py3.4.egg deleted file mode 100644 index 4bb967cca1e816c75489a3779dfbcc83c0fe185f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4871 zcma)A2{e@L+aKA+P7z~}{mn8nc19u$vXr%iY%wBZh_NqW7?OS8myjqKk{V+j*@}_u zB7{iUDx)m<^nbtqo43<<&UZiOI?p-Jd49k9y080pUFW)Q6GLhmP5=M^1WY^`Lts4w zVDD%EfQxJZ0K?%|4AR@f*A40w*lan6exto>!wrG!=`~Yi9XTKkeMR zJdLE{^0dATVXo9TCkHnCU*ptk`hKEF8*kKKHjh6f>1?hMiAmkWFA}{LWF%ZD0YbK8 z4!9)cp)Vdl32eqUMQ#yYKtzG)Uh2Sv8SAv7^S6o>}$=l|2 z08^||l%e;Wqv_22a9{T5mDI{AlHh1px`}Fxfv-^usa)Z*{GCj@mQZCDv*!v=<{hxs zvrq9@x=LJrA`qe&j)D0M0$8%w7;b7cn3x7A`iTkNH78YtAA^H<4RDT%!a6xSPgTPB zdh(G){7G^%Ny;+4aG!azM&DapFr~IhjLcP)v)f>(0kb}@T}EQ86#aTraa`9)F-Ns>$Z-w& z980rBcGDiF6+ioQbjC)Fp+hd~i~aMF7q%l}&HhwDNj^^RL8p0h%E8dW_&I86U~;)_ znYx&&=q#gzihxqp0DJ4nfL$}y_$F@@5TyGo8rC`w&WesZxb#TzYmLT%t3=;rBlUBF zs@E*3qL1$!ylz;4L-EIUoI23Cj%gQKuanwLi<(ugP=JH<-VaG;P)P7yaA5Hdd0Qo} z+}p0DIyO064Q0TrH@SN!!epc1NfE`X+m<%v3o~btC+s zhoyv_^~VW4T}VJsHCRQ4_8i#*Gf?++X0Xm!xKy&O{UaI2im%;%zc`YVWjS8BEJ0|Z zZLVXo4BdcMmP-XTrK6?Wp{t!zYEU&)x+jq!pKz9MyLSw7WtnnCX^a#sbscGdaML-C zH5xy?>@<+5k*FcYYa>;&AI0wfpteJ6HB8dCOFHNSbe?y|N)IG6!1kb-fiaDp$r>0J zTK!#o*e*9YY-V7rXyBK)g2q*z^mB2yjsHQg7yPHS0IWRBcmaM zPG}8aJ-Lm^cAIh_^)%H|>{_0%?{3Pz$z$C+BMoNlnVn%W@_0(r^kK>}OPYIvS-wueId2!?&wR6&Et5(;)i-Zl zX^C(7&Oz7U_GOqk0KJ{yfo-KS`KJ&vx=tb>uP<{{0096H2LK>+SP1Ryy#k#aom`Rj z_OSmgicVgE7*|g;L|);rK00Ms_Ipl^a@scz>gDvoSoOK6xml z&5vDZ>)w-3Jyz#T2n$0{F(@HOw3TW0m(v{O!Rv#eoawTQyvvxz z)nF+`XwNr;)^II|`@A58tRNB@KlWA1RJ_RN^|^OmuAh(n;^VB1Owjv25_CGpi5y)^ zXCdcf-7D?$-gHe`=6Wq2wPurP>r6ItoQRIB%*r0>xljP>fypl!KG7@nH@4j@rI|}Ro6Doi z6`t!&q1z3gw$P;w;~p$YX48}@RbDJH`_U_LqHAT2%`PX-6ueJy*wv;))&gf26>jj* zb;mbJr=87i=@wMbt7R zJ6;VBr6vg8!`1Ie(64dn5;e9h?MWgasK@{x3cpk!G!GvPsPM4XW^f4p$<^i8v z9_L>@OY6Yb7-ozJmB3v#t{Q#N8p+T*Wj%iUl>PwI^UK9*OSx4GCS^^d=**$qy1l}w z+le?i#p*eeoy4j+-uPq-`mF*G!flD*zM8x;JkE5rwX$3+Ru*(G1aq?Xcr`KdoCOe) z2HVKH3MtWFw0Vm1GEdLsC%AXoEwr>U1~b>sa_JWBR?PSWbj2E9E^3R|3wV`bKtm)Y zEC_O%3gRU3NS+3iy6&kIZupM9SKKhOEMQ8gUdYhi)hT&XAbE+W0-!FKpTceM^+eU( z`y)R#s3|0eQo1urZP7lTXc1~CsT6tE!rE#`alO6z%VlQ8Jc7&FZPaa*wfL|yHP&>0 zl<;=ww#|>!Rx>O8RkE|7(tAP_Q87|7E-Q(!{&U7znu>Q7ytqXiR940HJ;5?)Af)0n8T-)D% zd9b%{x^20x=?6|Zlbk$SnBFlQ1@Mp5Osk;74NP1q0L8q%xJ(=KcCeoT_SyN`m5=-| zJOmc<)Hi7J`DG83d+eBpXg?S@hxLe9LJ{|uhUtszS@}{e`-FI76x1K zm)zdW$3HNS0ltaQ(PoQ;5gPrlCT=*bhtuH_&)~Q-V>2wdj`6s#t4)wadi!6RR zdQRyGYgJ6a{TlY1iv@NJixar2ea8x|B)K}(MXHVBQ&tzW`{xEzEYFyK=a`E|)SV-V zeU8#PX8u6sYDb3ZBf+n4)#ynEkCs%0zd0fB7Go+R`~)zzvO@g`$$Tq|es%eLWDOMZL6>f3Yy@RLg`y%hON? z?&sE`MGLs9Q^=Iy#Ef+BqLcB@m-N|+lVEb6mE%!)m#^T|id}cuR+e zS@dgGDG+R+d_zoXtg~auj9(|T4|+as*5sW33MB+pe&hD#2Tw+jGywjRS;HtXc{W+G z=YVVM@Ajl0GQ^4qJGk8ku9;jA z`W)09^0j?F>6H4#mrje7>cHUZwu6yW;A*c#e7ATh*;44)QoT-s%fNd3b@4&!{CxVY z#ofYsi_6Jv4wm|;{*o;<276at$KISrXXF=InFY?cRj!A$X4QCDp*iDhz1UhbSgBwu z-grstyI?(2*`(rDlGqZ+n>!{_$bTZ|q;Kl_^_ zSW+$V{c(?(u4l9b^fl+Nh?kI5`lFniO0~GJO!<$vbO$?jf-63vcEk_k65T6@9D4?;ShKagcI z`*Q17m&0(_HHKk4z3fp)KO_o@3BX9{Lta4o7mfk` z4Nfn|pAI?%wH?}Fm-Gv4V)!?UZNDa?mMk;Pt5D-XCJW4)f(`CSYuZgBtKtp(cZeHf zP_Sll)yu#>85;`~Y$VuJa4bCZgTOx~9Yd(wf9WRKq zLh4%1#ua2<@U;5zSewqkR+;l=mHlHXcTUNjA3L+^5q5Q6S=|42G>vXdj{49+szc*H z>Zmi)3yF3{qMZWmQEq5=pP!4JKf27Ho#0XQzm^%~<^}O_bV2?L@iVGw!kq=|hx4!= z8rRYJypcD3-Mo?ina79RYear;KpyCY8T4JC`7O4JAOi99AryXSpraH>G{!s7-pkVs zjrrSMv+&5}g}tVCd-=oP-Ywkii%flY1)6Q7f5Q~*b`KI&XF%?yc?mc||8q_BHoHE7 zhqNMxA^Cau|7YLLjW3yL>zMxxM8eB67dyZ8+4Qi!KWc zmI&}fcMK>eOq}w(mtA5isUIzDlrlO3U(vm@sCXN@@6W8Qzegc@f4KdJgC)t^NDi|WMT&Cfrc{8!>f;eS&2FSyQ8_))$8PVu|K re^JC9rTAU%M``|~