Skip to content
This repository has been archived by the owner on Jan 12, 2021. It is now read-only.

Commit

Permalink
Merge pull request #247 from dephell/fuzzy-commands
Browse files Browse the repository at this point in the history
Fuzzy command search
  • Loading branch information
orsinium committed Jul 23, 2019
2 parents 04cc863 + bbc1853 commit 8234b88
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 17 deletions.
58 changes: 47 additions & 11 deletions dephell/cli.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
# built-in
from argparse import Action, ArgumentParser
from collections import Counter, defaultdict
from logging import getLogger
from pdb import post_mortem
from sys import argv
from typing import List
from typing import List, Optional, Tuple

# app
from .commands import COMMANDS
Expand Down Expand Up @@ -38,23 +39,58 @@ def format_help(self):

parser = PatchedParser(
description='Manage dependencies, projects, virtual environments.',
usage='dephell COMMAND [OPTIONS]',
)
parser.add_argument('command', choices=COMMANDS.keys(), nargs='?', help='command to execute')


def main(argv: List[str]) -> int:
# get command name
def commands_are_similar(command1: str, command2: str) -> bool:
given = Counter(command1)
guess = Counter(command2)
counter_diff = (given - guess) + (guess - given)
diff = sum(counter_diff.values())
return diff <= 1


def get_command_name_and_size(argv: List[str], commands=COMMANDS) -> Optional[Tuple[str, int]]:
if not argv:
return None

for size, direction in ((1, 1), (2, 1), (2, -1)):
command_name = ' '.join(argv[:size][::direction])
command_args = argv[size:]
if command_name in COMMANDS:
break
if command_name in commands:
return command_name, size

# specified the only one word from command
commands_by_parts = defaultdict(list)
for command_name in commands:
for part in command_name.split():
commands_by_parts[part].append(command_name)
command_names = commands_by_parts[argv[0]]
if len(command_names) == 1:
return command_names[0], 1

# typo in command name
for size in 1, 2:
command_specified = ' '.join(argv[:size])
for command_guess in commands:
if commands_are_similar(command_specified, command_guess):
return command_guess, size

return None


def main(argv: List[str]) -> int:
if not argv or argv[0] in ('--help', 'help', 'commands'):
parser.parse_args(['--help'])

name_and_size = get_command_name_and_size(argv=argv)
if name_and_size:
command_name = name_and_size[0]
command_args = argv[name_and_size[1]:]
else:
args = parser.parse_args(argv[:1])
if args.command is None:
parser.parse_args(['--help'])
command_name = args.command
command_args = argv[1:]
logger.error('ERROR: Unknown command')
parser.parse_args(['--help'])

# get and init command object
command = COMMANDS[command_name]
Expand Down
4 changes: 4 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

Follow [@PythonDepHell](https://twitter.com/PythonDepHell) on Twitter to get updates about new features and releases.

## v.0.7.8 (WIP)

+ Fuzzy command name search ([#247](https://github.com/dephell/dephell/pull/247), [#122](https://github.com/dephell/dephell/issues/122)).

## v.0.7.7

+ Meet [dephell.org](https://dephell.org/) ([#244](https://github.com/dephell/dephell/pull/244)).
Expand Down
38 changes: 32 additions & 6 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,38 @@
# built-in
from pathlib import Path

# external
import pytest

# project
from dephell.cli import main
from dephell.cli import get_command_name_and_size, main


@pytest.mark.parametrize('given, expected', [
('venv shell', 'venv shell'),
('shell venv', 'venv shell'),
('import', 'vendor import'),
('vnv shell', 'venv shell'),
('vnev shell', 'venv shell'),
('venv shll', 'venv shell'),
('venv shel', 'venv shell'),
('venv sl', None),
])
def test_get_command_name_and_size(given, expected):
result = get_command_name_and_size(argv=given.split())
if expected is None:
assert result is None
else:
assert result is not None
assert result[0] == expected


def test_main(tmpdir):
config = tmpdir.join('pyproject.toml')
result = main(['generate config', '--config', str(config), '--project', str(tmpdir)])
def test_main(temp_path: Path):
config = temp_path / 'pyproject.toml'
result = main(['generate config', '--config', str(config), '--project', str(temp_path)])
assert result == 0

assert config.check(file=1, exists=1)
content = config.read()
assert config.exists()
assert config.is_file()
content = config.read_text()
assert '[tool.dephell.example]' in content

0 comments on commit 8234b88

Please sign in to comment.