pax_global_header00006660000000000000000000000064126711372630014522gustar00rootroot0000000000000052 comment=80913ac6907d531346ce98e5902bf3a5707d5bda click-didyoumean-0.0.3/000077500000000000000000000000001267113726300147435ustar00rootroot00000000000000click-didyoumean-0.0.3/.gitignore000066400000000000000000000002451267113726300167340ustar00rootroot00000000000000# vim swap files *.swp # python bytecode files *.pyc __pycache__/ # python setuptools *.egg-info dist/ # python tox files .tox/ .cache/ # python virtualenv env/ click-didyoumean-0.0.3/.travis.yml000066400000000000000000000002201267113726300170460ustar00rootroot00000000000000language: python python: - "2.6" - "2.7" - "3.3" - "3.4" - "3.5" - "pypy" install: - pip install --editable . script: make test click-didyoumean-0.0.3/LICENSE000066400000000000000000000020401267113726300157440ustar00rootroot00000000000000Copyright (c) 2016 Timo Furrer Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. click-didyoumean-0.0.3/Makefile000066400000000000000000000001141267113726300163770ustar00rootroot00000000000000test: py.test --tb=short publish: @python setup.py sdist register upload click-didyoumean-0.0.3/README.rst000066400000000000000000000061041267113726300164330ustar00rootroot00000000000000click-didyoumean ================ |pypi| |build| |license| Enable git-like *did-you-mean* feature in click. It's as simple as this: .. code:: python import click from click_didyoumean import DYMGroup @click.group(cls=DYMGroup) def cli(): ... |demo| Usage ----- Install this extension with pip: .. code:: pip install click-didyoumean Use specific *did-you-mean* `group` class for your cli: .. code:: python import click from click_didyoumean import DYMGroup @click.group(cls=DYMGroup) def cli(): pass @cli.command() def foo(): pass @cli.command() def bar(): pass @cli.command() def barrr(): pass if __name__ == "__main__": cli() Or you it in a `CommandCollection`: .. code:: python import click from click_didyoumean import DYMCommandCollection @click.group() def cli1(): pass @cli1.command() def foo(): pass @cli1.command() def bar(): pass @click.group() def cli2(): pass @cli2.command() def barrr(): pass cli = DYMCommandCollection(sources=[cli1, cli2]) if __name__ == "__main__": cli() Change configuration -------------------- There are two configuration for the ``DYMGroup`` and ``DYMCommandCollection``: +-----------------+-------+---------+---------------------------------------------------------------------------+ | Parameter | Type | Default | Description | +=================+=======+=========+===========================================================================+ | max_suggestions | int | 3 | Maximal number of *did-you-mean* suggestions | +-----------------+-------+---------+---------------------------------------------------------------------------+ | cutoff | float | 0.5 | Possibilities that don’t score at least that similar to word are ignored. | +-----------------+-------+---------+---------------------------------------------------------------------------+ Examples ~~~~~~~~ .. code:: python @cli.group(cls=DYMGroup, max_suggestions=2, cutoff=0.7) def cli(): pass ... or ... cli = DYMCommandCollection(sources=[cli1, cli2], max_suggestions=2, cutoff=0.7) .. |pypi| image:: https://img.shields.io/pypi/v/click-didyoumean.svg?style=flat&label=version :target: https://pypi.python.org/pypi/click-didyoumean :alt: Latest version released on PyPi .. |build| image:: https://img.shields.io/travis/timofurrer/click-didyoumean/master.svg?style=flat :target: http://travis-ci.org/timofurrer/click-didyoumean :alt: Build status of the master branch .. |demo| image:: https://asciinema.org/a/duyr2j5d7w7fhpe7xf71rafgr.png :target: https://asciinema.org/a/duyr2j5d7w7fhpe7xf71rafgr :alt: Demo .. |license| image:: https://img.shields.io/badge/license-MIT-blue.svg?style=flat :target: https://raw.githubusercontent.com/timofurrer/click-didyoumean/master/LICENSE :alt: Package license click-didyoumean-0.0.3/click_didyoumean/000077500000000000000000000000001267113726300202465ustar00rootroot00000000000000click-didyoumean-0.0.3/click_didyoumean/__init__.py000066400000000000000000000036501267113726300223630ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Extension for the python ``click`` module to provide a group with a git-like *did-you-mean* feature. """ import click import difflib __version__ = "0.0.3" class DYMMixin(object): # pylint: disable=too-few-public-methods """ Mixin class for click MultiCommand inherited classes to provide git-like *did-you-mean* functionality when a certain command is not registered. """ def __init__(self, *args, **kwargs): self.max_suggestions = kwargs.pop("max_suggestions", 3) self.cutoff = kwargs.pop("cutoff", 0.5) super(DYMMixin, self).__init__(*args, **kwargs) def resolve_command(self, ctx, args): """ Overrides clicks ``resolve_command`` method and appends *Did you mean ...* suggestions to the raised exception message. """ original_cmd_name = click.utils.make_str(args[0]) try: return super(DYMMixin, self).resolve_command(ctx, args) except click.exceptions.UsageError as error: error_msg = str(error) matches = difflib.get_close_matches(original_cmd_name, self.list_commands(ctx), self.max_suggestions, self.cutoff) if matches: error_msg += '\n\nDid you mean one of these?\n %s' % '\n '.join(matches) # pylint: disable=line-too-long raise click.exceptions.UsageError(error_msg, error.ctx) class DYMGroup(DYMMixin, click.Group): # pylint: disable=too-many-public-methods """ click Group to provide git-like *did-you-mean* functionality when a certain command is not found in the group. """ class DYMCommandCollection(DYMMixin, click.CommandCollection): # pylint: disable=too-many-public-methods """ click CommandCollection to provide git-like *did-you-mean* functionality when a certain command is not found in the group. """ click-didyoumean-0.0.3/examples/000077500000000000000000000000001267113726300165615ustar00rootroot00000000000000click-didyoumean-0.0.3/examples/naval.py000066400000000000000000000035261267113726300202420ustar00rootroot00000000000000import click from click_didyoumean import DYMGroup @click.group(cls=DYMGroup) @click.version_option() def cli(): """Naval Fate. This is the docopt example adopted to Click but with some actual commands implemented and not just the empty parsing which really is not all that interesting. """ @cli.group(cls=DYMGroup) def ship(): """Manages ships.""" @ship.command('new') @click.argument('name') def ship_new(name): """Creates a new ship.""" click.echo('Created ship %s' % name) @ship.command('move') @click.argument('ship') @click.argument('x', type=float) @click.argument('y', type=float) @click.option('--speed', metavar='KN', default=10, help='Speed in knots.') def ship_move(ship, x, y, speed): """Moves SHIP to the new location X,Y.""" click.echo('Moving ship %s to %s,%s with speed %s' % (ship, x, y, speed)) @ship.command('shoot') @click.argument('ship') @click.argument('x', type=float) @click.argument('y', type=float) def ship_shoot(ship, x, y): """Makes SHIP fire to X,Y.""" click.echo('Ship %s fires to %s,%s' % (ship, x, y)) @cli.group('mine', cls=DYMGroup) def mine(): """Manages mines.""" @mine.command('set') @click.argument('x', type=float) @click.argument('y', type=float) @click.option('ty', '--moored', flag_value='moored', default=True, help='Moored (anchored) mine. Default.') @click.option('ty', '--drifting', flag_value='drifting', help='Drifting mine.') def mine_set(x, y, ty): """Sets a mine at a specific coordinate.""" click.echo('Set %s mine at %s,%s' % (ty, x, y)) @mine.command('remove') @click.argument('x', type=float) @click.argument('y', type=float) def mine_remove(x, y): """Removes a mine at a specific coordinate.""" click.echo('Removed mine at %s,%s' % (x, y)) if __name__ == "__main__": cli() click-didyoumean-0.0.3/setup.py000066400000000000000000000013441267113726300164570ustar00rootroot00000000000000import re import ast from setuptools import setup _version_re = re.compile(r"__version__\s+=\s+(.*)") with open("click_didyoumean/__init__.py", "rb") as f: version = str(ast.literal_eval(_version_re.search( f.read().decode("utf-8")).group(1))) setup( name="click-didyoumean", author="Timo Furrer", author_email="tuxtimo@gmail.com", version=version, url="https://github.com/timofurrer/click-didyoumean", packages=["click_didyoumean"], install_requires=["click"], description="Enable git-like did-you-mean feature in click.", classifiers=[ "License :: OSI Approved :: BSD License", "Programming Language :: Python", "Programming Language :: Python :: 3", ], ) click-didyoumean-0.0.3/tests/000077500000000000000000000000001267113726300161055ustar00rootroot00000000000000click-didyoumean-0.0.3/tests/test_core.py000066400000000000000000000050361267113726300204520ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Module to test functionality of the click ``did-you-mean`` extension. """ import pytest import click from click.testing import CliRunner from click_didyoumean import DYMGroup, DYMCommandCollection @pytest.fixture(scope="function") def runner(request): return CliRunner() def test_basic_functionality_with_group(runner): @click.group(cls=DYMGroup) def cli(): pass @cli.command() def foo(): pass @cli.command() def bar(): pass @cli.command() def barrr(): pass result = runner.invoke(cli, ["barr"]) assert result.output == ( "Usage: cli [OPTIONS] COMMAND [ARGS]...\n\n" "Error: No such command \"barr\".\n\n" "Did you mean one of these?\n" " barrr\n" " bar\n" ) def test_basic_functionality_with_commandcollection(runner): @click.group() def cli1(): pass @cli1.command() def foo(): pass @cli1.command() def bar(): pass @click.group() def cli2(): pass @cli2.command() def barrr(): pass cli = DYMCommandCollection(sources=[cli1, cli2]) result = runner.invoke(cli, ["barr"]) assert result.output == ( "Usage: root [OPTIONS] COMMAND [ARGS]...\n\n" "Error: No such command \"barr\".\n\n" "Did you mean one of these?\n" " barrr\n" " bar\n" ) def test_cutoff_factor(runner): @click.group(cls=DYMGroup, max_suggestions=3, cutoff=1.0) def cli(): pass @cli.command() def foo(): pass @cli.command() def bar(): pass @cli.command() def barrr(): pass # if cutoff factor is 1.0 the match must be perfect. result = runner.invoke(cli, ["barr"]) assert result.output == ( "Usage: cli [OPTIONS] COMMAND [ARGS]...\n\n" "Error: No such command \"barr\".\n" ) def test_max_suggetions(runner): @click.group(cls=DYMGroup, max_suggestions=2, cutoff=0.5) def cli(): pass @cli.command() def foo(): pass @cli.command() def bar(): pass @cli.command() def barrr(): pass @cli.command() def baarr(): pass # if cutoff factor is 1.0 the match must be perfect. result = runner.invoke(cli, ["barr"]) assert result.output == ( "Usage: cli [OPTIONS] COMMAND [ARGS]...\n\n" "Error: No such command \"barr\".\n\n" "Did you mean one of these?\n" " barrr\n" " baarr\n" ) click-didyoumean-0.0.3/tox.ini000066400000000000000000000002171267113726300162560ustar00rootroot00000000000000[tox] envlist = py26,py27,py33,py34,pypy [testenv] passenv = LANG commands = make test deps = pytest click whitelist_externals = make