diff --git a/README.md b/README.md index b6e756cf..a6788565 100644 --- a/README.md +++ b/README.md @@ -270,6 +270,7 @@ following rules are enabled by default: * `php_s` – replaces `-s` by `-S` when trying to run a local php server; * `port_already_in_use` – kills process that bound port; * `prove_recursively` – adds `-r` when called with directory; +* `pyenv_no_such_command` – fixes wrong pyenv commands like `pyenv isntall` or `pyenv list`; * `python_command` – prepends `python` when you try to run non-executable/without `./` python script; * `python_execute` – appends missing `.py` when executing Python files; * `quotation_marks` – fixes uneven usage of `'` and `"` when containing args'; diff --git a/tests/rules/test_pyenv_no_such_command.py b/tests/rules/test_pyenv_no_such_command.py new file mode 100644 index 00000000..298620f7 --- /dev/null +++ b/tests/rules/test_pyenv_no_such_command.py @@ -0,0 +1,52 @@ +import pytest + +from thefuck.rules.pyenv_no_such_command import get_new_command, match +from thefuck.types import Command + + +@pytest.fixture +def output(pyenv_cmd): + return "pyenv: no such command `{}'".format(pyenv_cmd) + + +@pytest.fixture(autouse=True) +def Popen(mocker): + mock = mocker.patch('thefuck.rules.pyenv_no_such_command.Popen') + mock.return_value.stdout.readlines.return_value = ( + b'--version\nactivate\ncommands\ncompletions\ndeactivate\nexec_\n' + b'global\nhelp\nhooks\ninit\ninstall\nlocal\nprefix_\n' + b'realpath.dylib\nrehash\nroot\nshell\nshims\nuninstall\nversion_\n' + b'version-file\nversion-file-read\nversion-file-write\nversion-name_\n' + b'version-origin\nversions\nvirtualenv\nvirtualenv-delete_\n' + b'virtualenv-init\nvirtualenv-prefix\nvirtualenvs_\n' + b'virtualenvwrapper\nvirtualenvwrapper_lazy\nwhence\nwhich_\n' + ).split() + return mock + + +@pytest.mark.parametrize('script, pyenv_cmd', [ + ('pyenv globe', 'globe'), + ('pyenv intall 3.8.0', 'intall'), + ('pyenv list', 'list'), +]) +def test_match(script, pyenv_cmd, output): + assert match(Command(script, output=output)) + + +@pytest.mark.parametrize('script, output', [ + ('pyenv global', 'system'), + ('pyenv versions', ' 3.7.0\n 3.7.1\n* 3.7.2\n'), + ('pyenv install --list', ' 3.7.0\n 3.7.1\n 3.7.2\n'), +]) +def test_not_match(script, output): + assert not match(Command(script, output=output)) + + +@pytest.mark.parametrize('script, pyenv_cmd, result', [ + ('pyenv globe', 'globe', 'pyenv global'), + ('pyenv intall 3.8.0', 'intall', 'pyenv install 3.8.0'), + ('pyenv list', 'list', 'pyenv install --list'), + ('pyenv remove 3.8.0', 'remove', 'pyenv uninstall 3.8.0'), +]) +def test_get_new_command(script, pyenv_cmd, output, result): + assert result in get_new_command(Command(script, output)) diff --git a/thefuck/rules/pyenv_no_such_command.py b/thefuck/rules/pyenv_no_such_command.py new file mode 100644 index 00000000..cc9b609e --- /dev/null +++ b/thefuck/rules/pyenv_no_such_command.py @@ -0,0 +1,33 @@ +import re +from subprocess import PIPE, Popen + +from thefuck.utils import (cache, for_app, replace_argument, replace_command, + which) + +COMMON_TYPOS = { + 'list': ['versions', 'install --list'], + 'remove': ['uninstall'], +} + + +@for_app('pyenv') +def match(command): + return 'pyenv: no such command' in command.output + + +def get_pyenv_commands(): + proc = Popen(['pyenv', 'commands'], stdout=PIPE) + return [line.decode('utf-8').strip() for line in proc.stdout.readlines()] + + +if which('pyenv'): + get_pyenv_commands = cache(which('pyenv'))(get_pyenv_commands) + + +@for_app('pyenv') +def get_new_command(command): + broken = re.findall(r"pyenv: no such command `([^']*)'", command.output)[0] + matched = [replace_argument(command.script, broken, common_typo) + for common_typo in COMMON_TYPOS.get(broken, [])] + matched.extend(replace_command(command, broken, get_pyenv_commands())) + return matched