diff --git a/README.md b/README.md index 40f23e7f..0d2d7f56 100644 --- a/README.md +++ b/README.md @@ -215,6 +215,7 @@ following rules are enabled by default: * `git_flag_after_filename` – fixes `fatal: bad flag '...' after filename` * `git_help_aliased` – fixes `git help ` commands replacing with the aliased command; * `git_hook_bypass` – adds `--no-verify` flag previous to `git am`, `git commit`, or `git push` command; +* `git_lfs_mistype` – fixes mistyped `git lfs ` commands; * `git_merge` – adds remote to branch names; * `git_merge_unrelated` – adds `--allow-unrelated-histories` when required * `git_not_command` – fixes wrong git commands like `git brnch`; diff --git a/tests/rules/test_fix_file.py b/tests/rules/test_fix_file.py index c4cec0f9..caeb1c6b 100644 --- a/tests/rules/test_fix_file.py +++ b/tests/rules/test_fix_file.py @@ -2,62 +2,54 @@ import pytest import os +from collections import namedtuple from thefuck.rules.fix_file import match, get_new_command from thefuck.types import Command +FixFileTest = namedtuple('FixFileTest', ['script', 'file', 'line', 'col', 'output']) -# (script, file, line, col (or None), output) tests = ( -('gcc a.c', 'a.c', 3, 1, -""" + FixFileTest('gcc a.c', 'a.c', 3, 1, """ a.c: In function 'main': a.c:3:1: error: expected expression before '}' token } ^ """), -('clang a.c', 'a.c', 3, 1, -""" + FixFileTest('clang a.c', 'a.c', 3, 1, """ a.c:3:1: error: expected expression } ^ """), -('perl a.pl', 'a.pl', 3, None, -""" + FixFileTest('perl a.pl', 'a.pl', 3, None, """ syntax error at a.pl line 3, at EOF Execution of a.pl aborted due to compilation errors. """), -('perl a.pl', 'a.pl', 2, None, -""" + FixFileTest('perl a.pl', 'a.pl', 2, None, """ Search pattern not terminated at a.pl line 2. """), -('sh a.sh', 'a.sh', 2, None, -""" + FixFileTest('sh a.sh', 'a.sh', 2, None, """ a.sh: line 2: foo: command not found """), -('zsh a.sh', 'a.sh', 2, None, -""" + FixFileTest('zsh a.sh', 'a.sh', 2, None, """ a.sh:2: command not found: foo """), -('bash a.sh', 'a.sh', 2, None, -""" + FixFileTest('bash a.sh', 'a.sh', 2, None, """ a.sh: line 2: foo: command not found """), -('rustc a.rs', 'a.rs', 2, 5, -""" + FixFileTest('rustc a.rs', 'a.rs', 2, 5, """ a.rs:2:5: 2:6 error: unexpected token: `+` a.rs:2 + ^ """), -('cargo build', 'src/lib.rs', 3, 5, -""" + FixFileTest('cargo build', 'src/lib.rs', 3, 5, """ Compiling test v0.1.0 (file:///tmp/fix-error/test) src/lib.rs:3:5: 3:6 error: unexpected token: `+` src/lib.rs:3 + @@ -67,16 +59,14 @@ Could not compile `test`. To learn more, run the command again with --verbose. """), -('python a.py', 'a.py', 2, None, -""" + FixFileTest('python a.py', 'a.py', 2, None, """ File "a.py", line 2 + ^ SyntaxError: invalid syntax """), -('python a.py', 'a.py', 8, None, -""" + FixFileTest('python a.py', 'a.py', 8, None, """ Traceback (most recent call last): File "a.py", line 8, in match("foo") @@ -89,8 +79,7 @@ Traceback (most recent call last): TypeError: first argument must be string or compiled pattern """), -(u'python café.py', u'café.py', 8, None, -u""" + FixFileTest(u'python café.py', u'café.py', 8, None, u""" Traceback (most recent call last): File "café.py", line 8, in match("foo") @@ -103,57 +92,48 @@ Traceback (most recent call last): TypeError: first argument must be string or compiled pattern """), -('ruby a.rb', 'a.rb', 3, None, -""" + FixFileTest('ruby a.rb', 'a.rb', 3, None, """ a.rb:3: syntax error, unexpected keyword_end """), -('lua a.lua', 'a.lua', 2, None, -""" + FixFileTest('lua a.lua', 'a.lua', 2, None, """ lua: a.lua:2: unexpected symbol near '+' """), -('fish a.sh', '/tmp/fix-error/a.sh', 2, None, -""" + FixFileTest('fish a.sh', '/tmp/fix-error/a.sh', 2, None, """ fish: Unknown command 'foo' /tmp/fix-error/a.sh (line 2): foo ^ """), -('./a', './a', 2, None, -""" + FixFileTest('./a', './a', 2, None, """ awk: ./a:2: BEGIN { print "Hello, world!" + } awk: ./a:2: ^ syntax error """), -('llc a.ll', 'a.ll', 1, 2, -""" + FixFileTest('llc a.ll', 'a.ll', 1, 2, """ llc: a.ll:1:2: error: expected top-level entity + ^ """), -('go build a.go', 'a.go', 1, 2, -""" + FixFileTest('go build a.go', 'a.go', 1, 2, """ can't load package: a.go:1:2: expected 'package', found '+' """), -('make', 'Makefile', 2, None, -""" + FixFileTest('make', 'Makefile', 2, None, """ bidule make: bidule: Command not found Makefile:2: recipe for target 'target' failed make: *** [target] Error 127 """), -('git st', '/home/martin/.config/git/config', 1, None, -""" + FixFileTest('git st', '/home/martin/.config/git/config', 1, None, """ fatal: bad config file line 1 in /home/martin/.config/git/config """), -('node fuck.js asdf qwer', '/Users/pablo/Workspace/barebones/fuck.js', '2', 5, -""" + FixFileTest('node fuck.js asdf qwer', '/Users/pablo/Workspace/barebones/fuck.js', '2', 5, """ /Users/pablo/Workspace/barebones/fuck.js:2 conole.log(arg); // this should read console.log(arg); ^ @@ -170,16 +150,14 @@ ReferenceError: conole is not defined at node.js:814:3 """), -('pep8', './tests/rules/test_systemctl.py', 17, 80, -""" + FixFileTest('pep8', './tests/rules/test_systemctl.py', 17, 80, """ ./tests/rules/test_systemctl.py:17:80: E501 line too long (93 > 79 characters) ./tests/rules/test_systemctl.py:18:80: E501 line too long (103 > 79 characters) ./tests/rules/test_whois.py:20:80: E501 line too long (89 > 79 characters) ./tests/rules/test_whois.py:22:80: E501 line too long (83 > 79 characters) """), -('py.test', '/home/thefuck/tests/rules/test_fix_file.py', 218, None, -""" + FixFileTest('py.test', '/home/thefuck/tests/rules/test_fix_file.py', 218, None, """ monkeypatch = <_pytest.monkeypatch.monkeypatch object at 0x7fdb76a25b38> test = ('fish a.sh', '/tmp/fix-error/a.sh', 2, None, '', "\\nfish: Unknown command 'foo'\\n/tmp/fix-error/a.sh (line 2): foo\\n ^\\n") @@ -191,7 +169,7 @@ E NameError: name 'mocker' is not defined /home/thefuck/tests/rules/test_fix_file.py:218: NameError """), -) # noqa +) @pytest.mark.parametrize('test', tests) @@ -199,7 +177,7 @@ E NameError: name 'mocker' is not defined def test_match(mocker, monkeypatch, test): mocker.patch('os.path.isfile', return_value=True) monkeypatch.setenv('EDITOR', 'dummy_editor') - assert match(Command('', test[4])) + assert match(Command('', test.output)) @pytest.mark.parametrize('test', tests) @@ -209,7 +187,7 @@ def test_no_editor(mocker, monkeypatch, test): if 'EDITOR' in os.environ: monkeypatch.delenv('EDITOR') - assert not match(Command('', test[4])) + assert not match(Command('', test.output)) @pytest.mark.parametrize('test', tests) @@ -218,7 +196,7 @@ def test_not_file(mocker, monkeypatch, test): mocker.patch('os.path.isfile', return_value=False) monkeypatch.setenv('EDITOR', 'dummy_editor') - assert not match(Command('', test[4])) + assert not match(Command('', test.output)) @pytest.mark.parametrize('test', tests) @@ -234,12 +212,12 @@ def test_get_new_command_with_settings(mocker, monkeypatch, test, settings): mocker.patch('os.path.isfile', return_value=True) monkeypatch.setenv('EDITOR', 'dummy_editor') - cmd = Command(test[0], test[4]) + cmd = Command(test.script, test.output) settings.fixcolcmd = '{editor} {file} +{line}:{col}' - if test[3]: + if test.col: assert (get_new_command(cmd) == - u'dummy_editor {} +{}:{} && {}'.format(test[1], test[2], test[3], test[0])) + u'dummy_editor {} +{}:{} && {}'.format(test.file, test.line, test.col, test.script)) else: assert (get_new_command(cmd) == - u'dummy_editor {} +{} && {}'.format(test[1], test[2], test[0])) + u'dummy_editor {} +{} && {}'.format(test.file, test.line, test.script)) diff --git a/tests/rules/test_git_lfs_mistype.py b/tests/rules/test_git_lfs_mistype.py new file mode 100644 index 00000000..1aae66fc --- /dev/null +++ b/tests/rules/test_git_lfs_mistype.py @@ -0,0 +1,29 @@ +import pytest + +from thefuck.rules.git_lfs_mistype import match, get_new_command +from thefuck.types import Command + + +@pytest.fixture +def mistype_response(): + return """ +Error: unknown command "evn" for "git-lfs" + +Did you mean this? + env + ext + +Run 'git-lfs --help' for usage. + """ + + +def test_match(mistype_response): + assert match(Command('git lfs evn', mistype_response)) + err_response = 'bash: git: command not found' + assert not match(Command('git lfs env', err_response)) + assert not match(Command('docker lfs env', mistype_response)) + + +def test_get_new_command(mistype_response): + assert (get_new_command(Command('git lfs evn', mistype_response)) + == ['git lfs env', 'git lfs ext']) diff --git a/thefuck/corrector.py b/thefuck/corrector.py index 222a1359..89d21ebf 100644 --- a/thefuck/corrector.py +++ b/thefuck/corrector.py @@ -71,7 +71,7 @@ def organize_commands(corrected_commands): without_duplicates, key=lambda corrected_command: corrected_command.priority) - logs.debug('Corrected commands: '.format( + logs.debug(u'Corrected commands: {}'.format( ', '.join(u'{}'.format(cmd) for cmd in [first_command] + sorted_commands))) for command in sorted_commands: diff --git a/thefuck/rules/git_lfs_mistype.py b/thefuck/rules/git_lfs_mistype.py new file mode 100644 index 00000000..afa3d5b7 --- /dev/null +++ b/thefuck/rules/git_lfs_mistype.py @@ -0,0 +1,18 @@ +import re +from thefuck.utils import get_all_matched_commands, replace_command +from thefuck.specific.git import git_support + + +@git_support +def match(command): + ''' + Match a mistyped command + ''' + return 'lfs' in command.script and 'Did you mean this?' in command.output + + +@git_support +def get_new_command(command): + broken_cmd = re.findall(r'Error: unknown command "([^"]*)" for "git-lfs"', command.output)[0] + matched = get_all_matched_commands(command.output, ['Did you mean', ' for usage.']) + return replace_command(command, broken_cmd, matched) diff --git a/thefuck/types.py b/thefuck/types.py index 8c5770f4..5afcf2c4 100644 --- a/thefuck/types.py +++ b/thefuck/types.py @@ -122,7 +122,7 @@ class Rule(object): def __repr__(self): return 'Rule(name={}, match={}, get_new_command={}, ' \ 'enabled_by_default={}, side_effect={}, ' \ - 'priority={}, requires_output)'.format( + 'priority={}, requires_output={})'.format( self.name, self.match, self.get_new_command, self.enabled_by_default, self.side_effect, self.priority, self.requires_output)