diff --git a/CHANGES b/CHANGES index c76edbb..07b1694 100644 --- a/CHANGES +++ b/CHANGES @@ -3,6 +3,17 @@ Click Changelog This contains all major version changes between Click releases. +Version 6.7 +----------- + +(bugfix release; released on January 6th 2017) + +- Make `click.progressbar` work with `codecs.open` files. See #637. +- Fix bug in bash completion with nested subcommands. See #639. +- Fix test runner not saving caller env correctly. See #644. +- Fix handling of SIGPIPE. See #626 +- Deal with broken Windows environments such as Google App Engine's. See #711. + Version 6.6 ----------- diff --git a/PKG-INFO b/PKG-INFO index d569270..bbb17b3 100644 --- a/PKG-INFO +++ b/PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: click -Version: 6.6 +Version: 6.7 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/PKG-INFO b/click.egg-info/PKG-INFO index d569270..bbb17b3 100644 --- a/click.egg-info/PKG-INFO +++ b/click.egg-info/PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: click -Version: 6.6 +Version: 6.7 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 e524cba..5eed434 100644 --- a/click.egg-info/SOURCES.txt +++ b/click.egg-info/SOURCES.txt @@ -87,24 +87,12 @@ examples/inout/setup.py examples/naval/README examples/naval/naval.py examples/naval/setup.py -examples/plugins/BrokenPlugin/printer_bold.egg-info/PKG-INFO -examples/plugins/BrokenPlugin/printer_bold.egg-info/SOURCES.txt -examples/plugins/BrokenPlugin/printer_bold.egg-info/dependency_links.txt -examples/plugins/BrokenPlugin/printer_bold.egg-info/entry_points.txt -examples/plugins/BrokenPlugin/printer_bold.egg-info/top_level.txt -examples/plugins/printer.egg-info/PKG-INFO -examples/plugins/printer.egg-info/SOURCES.txt -examples/plugins/printer.egg-info/dependency_links.txt -examples/plugins/printer.egg-info/entry_points.txt -examples/plugins/printer.egg-info/top_level.txt examples/repo/README examples/repo/repo.py examples/repo/setup.py examples/termui/README examples/termui/setup.py examples/termui/termui.py -examples/termui/build/lib/termui.py -examples/termui/dist/click_example_termui-1.0-py3.4.egg examples/validation/README examples/validation/setup.py examples/validation/validation.py diff --git a/click/__init__.py b/click/__init__.py index 3a7e3ee..971e55d 100644 --- a/click/__init__.py +++ b/click/__init__.py @@ -95,4 +95,4 @@ __all__ = [ disable_unicode_literals_warning = False -__version__ = '6.6' +__version__ = '6.7' diff --git a/click/_bashcomplete.py b/click/_bashcomplete.py index d97b66a..d9d26d2 100644 --- a/click/_bashcomplete.py +++ b/click/_bashcomplete.py @@ -30,8 +30,8 @@ def get_completion_script(prog_name, complete_var): def resolve_ctx(cli, prog_name, args): ctx = cli.make_context(prog_name, args, resilient_parsing=True) - while ctx.args + ctx.protected_args and isinstance(ctx.command, MultiCommand): - a = ctx.args + ctx.protected_args + while ctx.protected_args + ctx.args and isinstance(ctx.command, MultiCommand): + a = ctx.protected_args + ctx.args cmd = ctx.command.get_command(ctx, a[0]) if cmd is None: return None diff --git a/click/_compat.py b/click/_compat.py index 6bbebd3..2b43412 100644 --- a/click/_compat.py +++ b/click/_compat.py @@ -160,8 +160,16 @@ if PY2: # # This code also lives in _winconsole for the fallback to the console # emulation stream. - if WIN: + # + # There are also Windows environments where the `msvcrt` module is not + # available (which is why we use try-catch instead of the WIN variable + # here), such as the Google App Engine development server on Windows. In + # those cases there is just nothing we can do. + try: import msvcrt + except ImportError: + set_binary_mode = lambda x: x + else: def set_binary_mode(f): try: fileno = f.fileno() @@ -170,8 +178,6 @@ if PY2: else: msvcrt.setmode(fileno, os.O_BINARY) return f - else: - set_binary_mode = lambda x: x def isidentifier(x): return _identifier_re.search(x) is not None diff --git a/click/_termui_impl.py b/click/_termui_impl.py index a2c1eb0..7cfd3d5 100644 --- a/click/_termui_impl.py +++ b/click/_termui_impl.py @@ -31,7 +31,7 @@ def _length_hint(obj): """Returns the length hint of an object.""" try: return len(obj) - except TypeError: + except (AttributeError, TypeError): try: get_hint = type(obj).__length_hint__ except AttributeError: diff --git a/click/_unicodefun.py b/click/_unicodefun.py index 24b7031..9e17a38 100644 --- a/click/_unicodefun.py +++ b/click/_unicodefun.py @@ -114,6 +114,5 @@ def _verify_python3_env(): 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) + 'environment. Consult http://click.pocoo.org/python3/' + 'for mitigation steps.' + extra) diff --git a/click/core.py b/click/core.py index 33a527a..7456451 100644 --- a/click/core.py +++ b/click/core.py @@ -1,3 +1,4 @@ +import errno import os import sys from contextlib import contextmanager @@ -705,6 +706,11 @@ class BaseCommand(object): raise e.show() sys.exit(e.exit_code) + except IOError as e: + if e.errno == errno.EPIPE: + sys.exit(1) + else: + raise except Abort: if not standalone_mode: raise diff --git a/click/testing.py b/click/testing.py index d43581f..4416c77 100644 --- a/click/testing.py +++ b/click/testing.py @@ -213,7 +213,7 @@ class CliRunner(object): old_env = {} try: for key, value in iteritems(env): - old_env[key] = os.environ.get(value) + old_env[key] = os.environ.get(key) if value is None: try: del os.environ[key] diff --git a/examples/plugins/BrokenPlugin/printer_bold.egg-info/PKG-INFO b/examples/plugins/BrokenPlugin/printer_bold.egg-info/PKG-INFO deleted file mode 100644 index 97a4724..0000000 --- a/examples/plugins/BrokenPlugin/printer_bold.egg-info/PKG-INFO +++ /dev/null @@ -1,10 +0,0 @@ -Metadata-Version: 1.0 -Name: printer-bold -Version: 0.1dev0 -Summary: UNKNOWN -Home-page: UNKNOWN -Author: UNKNOWN -Author-email: UNKNOWN -License: UNKNOWN -Description: UNKNOWN -Platform: UNKNOWN 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/dependency_links.txt b/examples/plugins/BrokenPlugin/printer_bold.egg-info/dependency_links.txt deleted file mode 100644 index 8b13789..0000000 --- a/examples/plugins/BrokenPlugin/printer_bold.egg-info/dependency_links.txt +++ /dev/null @@ -1 +0,0 @@ - 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/PKG-INFO b/examples/plugins/printer.egg-info/PKG-INFO deleted file mode 100644 index 7f22902..0000000 --- a/examples/plugins/printer.egg-info/PKG-INFO +++ /dev/null @@ -1,10 +0,0 @@ -Metadata-Version: 1.0 -Name: printer -Version: 0.1dev0 -Summary: UNKNOWN -Home-page: UNKNOWN -Author: UNKNOWN -Author-email: UNKNOWN -License: UNKNOWN -Description: UNKNOWN -Platform: UNKNOWN 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/dependency_links.txt b/examples/plugins/printer.egg-info/dependency_links.txt deleted file mode 100644 index 8b13789..0000000 --- a/examples/plugins/printer.egg-info/dependency_links.txt +++ /dev/null @@ -1 +0,0 @@ - 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/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/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 4bb967c..0000000 Binary files a/examples/termui/dist/click_example_termui-1.0-py3.4.egg and /dev/null differ diff --git a/tests/test_bashcomplete.py b/tests/test_bashcomplete.py index f75a236..fea096c 100644 --- a/tests/test_bashcomplete.py +++ b/tests/test_bashcomplete.py @@ -3,7 +3,18 @@ import click from click._bashcomplete import get_choices -def test_basic(): + +def test_single_command(): + @click.command() + @click.option('--local-opt') + def cli(local_opt): + pass + + assert list(get_choices(cli, 'lol', [], '-')) == ['--local-opt'] + assert list(get_choices(cli, 'lol', [], '')) == [] + + +def test_small_chain(): @click.group() @click.option('--global-opt') def cli(global_opt): @@ -18,3 +29,34 @@ def test_basic(): assert list(get_choices(cli, 'lol', [], '-')) == ['--global-opt'] assert list(get_choices(cli, 'lol', ['sub'], '')) == [] assert list(get_choices(cli, 'lol', ['sub'], '-')) == ['--local-opt'] + + +def test_long_chain(): + @click.group('cli') + @click.option('--cli-opt') + def cli(cli_opt): + pass + + @cli.group('asub') + @click.option('--asub-opt') + def asub(asub_opt): + pass + + @asub.group('bsub') + @click.option('--bsub-opt') + def bsub(bsub_opt): + pass + + @bsub.command('csub') + @click.option('--csub-opt') + def csub(csub_opt): + pass + + assert list(get_choices(cli, 'lol', [], '-')) == ['--cli-opt'] + assert list(get_choices(cli, 'lol', [], '')) == ['asub'] + assert list(get_choices(cli, 'lol', ['asub'], '-')) == ['--asub-opt'] + assert list(get_choices(cli, 'lol', ['asub'], '')) == ['bsub'] + assert list(get_choices(cli, 'lol', ['asub', 'bsub'], '-')) == ['--bsub-opt'] + assert list(get_choices(cli, 'lol', ['asub', 'bsub'], '')) == ['csub'] + assert list(get_choices(cli, 'lol', ['asub', 'bsub', 'csub'], '-')) == ['--csub-opt'] + assert list(get_choices(cli, 'lol', ['asub', 'bsub', 'csub'], '')) == [] diff --git a/tests/test_imports.py b/tests/test_imports.py index 6a8a122..bc54533 100644 --- a/tests/test_imports.py +++ b/tests/test_imports.py @@ -32,7 +32,7 @@ click.echo(json.dumps(rv)) ALLOWED_IMPORTS = set([ 'weakref', 'os', 'struct', 'collections', 'sys', 'contextlib', 'functools', 'stat', 're', 'codecs', 'inspect', 'itertools', 'io', - 'threading', 'colorama' + 'threading', 'colorama', 'errno' ]) if WIN: diff --git a/tests/test_testing.py b/tests/test_testing.py index fab2875..7fc284a 100644 --- a/tests/test_testing.py +++ b/tests/test_testing.py @@ -1,3 +1,4 @@ +import os import sys import pytest @@ -183,3 +184,21 @@ def test_exit_code_and_output_from_sys_exit(): result = runner.invoke(cli_no_error) assert result.exit_code == 0 assert result.output == 'hello world\n' + + +def test_env(): + @click.command() + def cli_env(): + click.echo('ENV=%s' % os.environ['TEST_CLICK_ENV']) + + runner = CliRunner() + + env_orig = dict(os.environ) + env = dict(env_orig) + assert 'TEST_CLICK_ENV' not in env + env['TEST_CLICK_ENV'] = 'some_value' + result = runner.invoke(cli_env, env=env) + assert result.exit_code == 0 + assert result.output == 'ENV=some_value\n' + + assert os.environ == env_orig