diff --git a/README.md b/README.md index acc222d3..3e84a35a 100644 --- a/README.md +++ b/README.md @@ -249,6 +249,7 @@ using the matched rule and runs it. Rules enabled by default are as follows: * `whois` – fixes `whois` command; * `workon_doesnt_exists` – fixes `virtualenvwrapper` env name os suggests to create new. * `yarn_alias` – fixes aliased `yarn` commands like `yarn ls`; +* `yarn_command_not_found` – fixes misspelled `yarn` commands; Enabled by default only on specific platforms: diff --git a/tests/rules/test_yarn_command_not_found.py b/tests/rules/test_yarn_command_not_found.py new file mode 100644 index 00000000..67b7823b --- /dev/null +++ b/tests/rules/test_yarn_command_not_found.py @@ -0,0 +1,111 @@ +# -*- encoding: utf-8 -*- + +from io import BytesIO +import pytest +from tests.utils import Command +from thefuck.rules.yarn_command_not_found import match, get_new_command + +stderr = ''' +error Command "{}" not found. +'''.format + +yarn_help_stdout = b''' + + Usage: yarn [command] [flags] + + Options: + + -h, --help output usage information + -V, --version output the version number + --verbose output verbose messages on internal operations + --offline trigger an error if any required dependencies are not available in local cache + --prefer-offline use network only if dependencies are not available in local cache + --strict-semver + --json + --ignore-scripts don't run lifecycle scripts + --har save HAR output of network traffic + --ignore-platform ignore platform checks + --ignore-engines ignore engines check + --ignore-optional ignore optional dependencies + --force ignore all caches + --no-bin-links don't generate bin links when setting up packages + --flat only allow one version of a package + --prod, --production [prod] + --no-lockfile don't read or generate a lockfile + --pure-lockfile don't generate a lockfile + --frozen-lockfile don't generate a lockfile and fail if an update is needed + --link-duplicates create hardlinks to the repeated modules in node_modules + --global-folder + --modules-folder rather than installing modules into the node_modules folder relative to the cwd, output them here + --cache-folder specify a custom folder to store the yarn cache + --mutex [:specifier] use a mutex to ensure only one yarn instance is executing + --no-emoji disable emoji in output + --proxy + --https-proxy + --no-progress disable progress bar + --network-concurrency maximum number of concurrent network requests + + Commands: + + - access + - add + - bin + - cache + - check + - clean + - config + - generate-lock-entry + - global + - import + - info + - init + - install + - licenses + - link + - list + - login + - logout + - outdated + - owner + - pack + - publish + - remove + - run + - tag + - team + - unlink + - upgrade + - upgrade-interactive + - version + - versions + - why + + Run `yarn help COMMAND` for more information on specific commands. + Visit https://yarnpkg.com/en/docs/cli/ to learn more about Yarn. +''' + + +@pytest.fixture(autouse=True) +def yarn_help(mocker): + patch = mocker.patch('thefuck.rules.yarn_command_not_found.Popen') + patch.return_value.stdout = BytesIO(yarn_help_stdout) + return patch + + +@pytest.mark.parametrize('command', [ + Command('yarn whyy webpack', stderr=stderr('whyy'))]) +def test_match(command): + assert match(command) + + +@pytest.mark.parametrize('command', [ + Command('npm nuild', stderr=stderr('nuild')), + Command('yarn install')]) +def test_not_match(command): + assert not match(command) + + +@pytest.mark.parametrize('command, result', [ + (Command('yarn whyy webpack', stderr=stderr('whyy')), 'yarn why webpack')]) +def test_get_new_command(command, result): + assert get_new_command(command)[0] == result diff --git a/thefuck/rules/yarn_command_not_found.py b/thefuck/rules/yarn_command_not_found.py new file mode 100644 index 00000000..6d51ff84 --- /dev/null +++ b/thefuck/rules/yarn_command_not_found.py @@ -0,0 +1,31 @@ +import re +from subprocess import Popen, PIPE +from thefuck.utils import for_app, eager, replace_command + +regex = re.compile(r'error Command "(.*)" not found.') + + +@for_app('yarn') +def match(command): + return regex.findall(command.stderr) + + +@eager +def _get_all_tasks(): + proc = Popen(['yarn', '--help'], stdout=PIPE) + should_yield = False + for line in proc.stdout.readlines(): + line = line.decode().strip() + + if 'Commands:' in line: + should_yield = True + continue + + if should_yield and '- ' in line: + yield line.split(' ')[-1] + + +def get_new_command(command): + misspelled_task = regex.findall(command.stderr)[0] + tasks = _get_all_tasks() + return replace_command(command, misspelled_task, tasks)