mirror of
https://github.com/nvbn/thefuck.git
synced 2025-11-01 23:51:59 +00:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
36a0a669b0 | ||
|
|
b16de9c7c2 | ||
|
|
43fead02d3 | ||
|
|
de513cacb1 | ||
|
|
e4b97af73e | ||
|
|
0a40e7f0a9 | ||
|
|
9c649c05a9 | ||
|
|
4fc18cb4e7 | ||
|
|
5d1dd70652 | ||
|
|
65a25d5448 | ||
|
|
4e854a575e | ||
|
|
742200a500 |
@@ -12,6 +12,7 @@ addons:
|
||||
- zsh
|
||||
- fish
|
||||
- tcsh
|
||||
- pandoc
|
||||
env:
|
||||
- FUNCTIONAL=true BARE=true
|
||||
install:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# The Fuck [](https://travis-ci.org/nvbn/thefuck)
|
||||
# The Fuck [](https://travis-ci.org/nvbn/thefuck)
|
||||
|
||||
Magnificent app which corrects your previous console command,
|
||||
inspired by a [@liamosaur](https://twitter.com/liamosaur/)
|
||||
@@ -146,6 +146,7 @@ using the matched rule and runs it. Rules enabled by default are as follows:
|
||||
* `docker_not_command` – fixes wrong docker commands like `docker tags`;
|
||||
* `dry` – fixes repetitions like `git git push`;
|
||||
* `fix_alt_space` – replaces Alt+Space with Space character;
|
||||
* `fix_file` – opens a file with an error in your `$EDITOR`;
|
||||
* `git_add` – fixes *"Did you forget to 'git add'?"*;
|
||||
* `git_branch_delete` – changes `git branch -d` to `git branch -D`;
|
||||
* `git_branch_list` – catches `git branch list` in place of `git branch` and removes created branch;
|
||||
@@ -188,6 +189,7 @@ using the matched rule and runs it. Rules enabled by default are as follows:
|
||||
* `systemctl` – correctly orders parameters of confusing `systemctl`;
|
||||
* `test.py` – runs `py.test` instead of `test.py`;
|
||||
* `tsuru_login` – runs `tsuru login` if not authenticated or session expired;
|
||||
* `tsuru_not_command` – fixes wrong tsuru commands like `tsuru shell`;
|
||||
* `tmux` – fixes `tmux` commands;
|
||||
* `whois` – fixes `whois` command.
|
||||
|
||||
|
||||
2
setup.py
2
setup.py
@@ -20,7 +20,7 @@ elif (3, 0) < version < (3, 3):
|
||||
' ({}.{} detected).'.format(*version))
|
||||
sys.exit(-1)
|
||||
|
||||
VERSION = '2.5.6'
|
||||
VERSION = '2.6'
|
||||
|
||||
install_requires = ['psutil', 'colorama', 'six']
|
||||
extras_require = {':python_version<"3.4"': ['pathlib']}
|
||||
|
||||
188
tests/rules/test_fix_file.py
Normal file
188
tests/rules/test_fix_file.py
Normal file
@@ -0,0 +1,188 @@
|
||||
import pytest
|
||||
import os
|
||||
from thefuck.rules.fix_file import match, get_new_command
|
||||
from tests.utils import Command
|
||||
|
||||
|
||||
# (script, file, line, col (or None), stderr)
|
||||
tests = (
|
||||
('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,
|
||||
"""
|
||||
a.c:3:1: error: expected expression
|
||||
}
|
||||
^
|
||||
"""),
|
||||
|
||||
('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,
|
||||
"""
|
||||
Search pattern not terminated at a.pl line 2.
|
||||
"""),
|
||||
|
||||
('sh a.sh', 'a.sh', 2, None,
|
||||
"""
|
||||
a.sh: line 2: foo: command not found
|
||||
"""),
|
||||
|
||||
('zsh a.sh', 'a.sh', 2, None,
|
||||
"""
|
||||
a.sh:2: command not found: foo
|
||||
"""),
|
||||
|
||||
('bash a.sh', 'a.sh', 2, None,
|
||||
"""
|
||||
a.sh: line 2: foo: command not found
|
||||
"""),
|
||||
|
||||
('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,
|
||||
"""
|
||||
Compiling test v0.1.0 (file:///tmp/fix-error/test)
|
||||
src/lib.rs:3:5: 3:6 error: unexpected token: `+`
|
||||
src/lib.rs:3 +
|
||||
^
|
||||
Could not compile `test`.
|
||||
|
||||
To learn more, run the command again with --verbose.
|
||||
"""),
|
||||
|
||||
('python a.py', 'a.py', 2, None,
|
||||
"""
|
||||
File "a.py", line 2
|
||||
+
|
||||
^
|
||||
SyntaxError: invalid syntax
|
||||
"""),
|
||||
|
||||
('python a.py', 'a.py', 8, None,
|
||||
"""
|
||||
Traceback (most recent call last):
|
||||
File "a.py", line 8, in <module>
|
||||
match("foo")
|
||||
File "a.py", line 5, in match
|
||||
m = re.search(None, command)
|
||||
File "/usr/lib/python3.4/re.py", line 170, in search
|
||||
return _compile(pattern, flags).search(string)
|
||||
File "/usr/lib/python3.4/re.py", line 293, in _compile
|
||||
raise TypeError("first argument must be string or compiled pattern")
|
||||
TypeError: first argument must be string or compiled pattern
|
||||
"""
|
||||
),
|
||||
|
||||
('ruby a.rb', 'a.rb', 3, None,
|
||||
"""
|
||||
a.rb:3: syntax error, unexpected keyword_end
|
||||
"""),
|
||||
|
||||
('lua a.lua', 'a.lua', 2, None,
|
||||
"""
|
||||
lua: a.lua:2: unexpected symbol near '+'
|
||||
"""),
|
||||
|
||||
('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,
|
||||
"""
|
||||
awk: ./a:2: BEGIN { print "Hello, world!" + }
|
||||
awk: ./a:2: ^ syntax error
|
||||
"""),
|
||||
|
||||
('llc a.ll', 'a.ll', 1, None,
|
||||
"""
|
||||
llc: a.ll:1:1: error: expected top-level entity
|
||||
+
|
||||
^
|
||||
"""),
|
||||
|
||||
('go build a.go', 'a.go', 1, None,
|
||||
"""
|
||||
can't load package:
|
||||
a.go:1:1: expected 'package', found '+'
|
||||
"""),
|
||||
|
||||
('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,
|
||||
"""
|
||||
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,
|
||||
"""
|
||||
/Users/pablo/Workspace/barebones/fuck.js:2
|
||||
conole.log(arg); // this should read console.log(arg);
|
||||
^
|
||||
ReferenceError: conole is not defined
|
||||
at /Users/pablo/Workspace/barebones/fuck.js:2:5
|
||||
at Array.forEach (native)
|
||||
at Object.<anonymous> (/Users/pablo/Workspace/barebones/fuck.js:1:85)
|
||||
at Module._compile (module.js:460:26)
|
||||
at Object.Module._extensions..js (module.js:478:10)
|
||||
at Module.load (module.js:355:32)
|
||||
at Function.Module._load (module.js:310:12)
|
||||
at Function.Module.runMain (module.js:501:10)
|
||||
at startup (node.js:129:16)
|
||||
at node.js:814:3
|
||||
"""),
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('test', tests)
|
||||
def test_match(mocker, monkeypatch, test):
|
||||
mocker.patch('os.path.isfile', return_value=True)
|
||||
monkeypatch.setenv('EDITOR', 'dummy_editor')
|
||||
assert match(Command(stderr=test[4]), None)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('test', tests)
|
||||
def test_no_editor(mocker, monkeypatch, test):
|
||||
mocker.patch('os.path.isfile', return_value=True)
|
||||
if 'EDITOR' in os.environ:
|
||||
monkeypatch.delenv('EDITOR')
|
||||
|
||||
assert not match(Command(stderr=test[4]), None)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('test', tests)
|
||||
def test_not_file(mocker, monkeypatch, test):
|
||||
mocker.patch('os.path.isfile', return_value=False)
|
||||
monkeypatch.setenv('EDITOR', 'dummy_editor')
|
||||
|
||||
assert not match(Command(stderr=test[4]), None)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('test', tests)
|
||||
def test_get_new_command(monkeypatch, test):
|
||||
monkeypatch.setenv('EDITOR', 'dummy_editor')
|
||||
assert (get_new_command(Command(script=test[0], stderr=test[4]), None) ==
|
||||
'dummy_editor {} +{} && {}'.format(test[1], test[2], test[0]))
|
||||
90
tests/rules/test_tsuru_not_command.py
Normal file
90
tests/rules/test_tsuru_not_command.py
Normal file
@@ -0,0 +1,90 @@
|
||||
import pytest
|
||||
|
||||
from tests.utils import Command
|
||||
from thefuck.rules.tsuru_not_command import match, get_new_command
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command', [
|
||||
Command('tsuru log', stderr=(
|
||||
'tsuru: "tchururu" is not a tsuru command. See "tsuru help".\n'
|
||||
'\nDid you mean?\n'
|
||||
'\tapp-log\n'
|
||||
'\tlogin\n'
|
||||
'\tlogout\n'
|
||||
)),
|
||||
Command('tsuru app-l', stderr=(
|
||||
'tsuru: "tchururu" is not a tsuru command. See "tsuru help".\n'
|
||||
'\nDid you mean?\n'
|
||||
'\tapp-list\n'
|
||||
'\tapp-log\n'
|
||||
)),
|
||||
Command('tsuru user-list', stderr=(
|
||||
'tsuru: "tchururu" is not a tsuru command. See "tsuru help".\n'
|
||||
'\nDid you mean?\n'
|
||||
'\tteam-user-list\n'
|
||||
)),
|
||||
Command('tsuru targetlist', stderr=(
|
||||
'tsuru: "tchururu" is not a tsuru command. See "tsuru help".\n'
|
||||
'\nDid you mean?\n'
|
||||
'\ttarget-list\n'
|
||||
)),
|
||||
])
|
||||
def test_match(command):
|
||||
assert match(command, None)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command', [
|
||||
Command('tsuru tchururu', stderr=(
|
||||
'tsuru: "tchururu" is not a tsuru command. See "tsuru help".\n'
|
||||
'\nDid you mean?\n'
|
||||
)),
|
||||
Command('tsuru version', stderr='tsuru version 0.16.0.'),
|
||||
Command('tsuru help', stderr=(
|
||||
'tsuru version 0.16.0.\n'
|
||||
'\nUsage: tsuru command [args]\n'
|
||||
)),
|
||||
Command('tsuru platform-list', stderr=(
|
||||
'- java\n'
|
||||
'- logstashgiro\n'
|
||||
'- newnode\n'
|
||||
'- nodejs\n'
|
||||
'- php\n'
|
||||
'- python\n'
|
||||
'- python3\n'
|
||||
'- ruby\n'
|
||||
'- ruby20\n'
|
||||
'- static\n'
|
||||
)),
|
||||
Command('tsuru env-get', stderr='Error: App thefuck not found.'),
|
||||
])
|
||||
def test_not_match(command):
|
||||
assert not match(command, None)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command, new_command', [
|
||||
(Command('tsuru log', stderr=(
|
||||
'tsuru: "log" is not a tsuru command. See "tsuru help".\n'
|
||||
'\nDid you mean?\n'
|
||||
'\tapp-log\n'
|
||||
'\tlogin\n'
|
||||
'\tlogout\n'
|
||||
)), 'tsuru login'),
|
||||
(Command('tsuru app-l', stderr=(
|
||||
'tsuru: "app-l" is not a tsuru command. See "tsuru help".\n'
|
||||
'\nDid you mean?\n'
|
||||
'\tapp-list\n'
|
||||
'\tapp-log\n'
|
||||
)), 'tsuru app-log'),
|
||||
(Command('tsuru user-list', stderr=(
|
||||
'tsuru: "user-list" is not a tsuru command. See "tsuru help".\n'
|
||||
'\nDid you mean?\n'
|
||||
'\tteam-user-list\n'
|
||||
)), 'tsuru team-user-list'),
|
||||
(Command('tsuru targetlist', stderr=(
|
||||
'tsuru: "targetlist" is not a tsuru command. See "tsuru help".\n'
|
||||
'\nDid you mean?\n'
|
||||
'\ttarget-list\n'
|
||||
)), 'tsuru target-list'),
|
||||
])
|
||||
def test_get_new_command(command, new_command):
|
||||
assert get_new_command(command, None) == new_command
|
||||
@@ -1,7 +1,8 @@
|
||||
import pytest
|
||||
from mock import Mock
|
||||
from thefuck.utils import git_support, sudo_support, wrap_settings,\
|
||||
memoize, get_closest, get_all_executables, replace_argument
|
||||
memoize, get_closest, get_all_executables, replace_argument, \
|
||||
get_all_matched_commands
|
||||
from thefuck.types import Settings
|
||||
from tests.utils import Command
|
||||
|
||||
@@ -99,3 +100,32 @@ def test_get_all_callables():
|
||||
(('git brnch', 'brnch', 'branch'), 'git branch')])
|
||||
def test_replace_argument(args, result):
|
||||
assert replace_argument(*args) == result
|
||||
|
||||
|
||||
@pytest.mark.parametrize('stderr, result', [
|
||||
(("git: 'cone' is not a git command. See 'git --help'.\n"
|
||||
'\n'
|
||||
'Did you mean one of these?\n'
|
||||
'\tclone'), ['clone']),
|
||||
(("git: 're' is not a git command. See 'git --help'.\n"
|
||||
'\n'
|
||||
'Did you mean one of these?\n'
|
||||
'\trebase\n'
|
||||
'\treset\n'
|
||||
'\tgrep\n'
|
||||
'\trm'), ['rebase', 'reset', 'grep', 'rm']),
|
||||
(('tsuru: "target" is not a tsuru command. See "tsuru help".\n'
|
||||
'\n'
|
||||
'Did you mean one of these?\n'
|
||||
'\tservice-add\n'
|
||||
'\tservice-bind\n'
|
||||
'\tservice-doc\n'
|
||||
'\tservice-info\n'
|
||||
'\tservice-list\n'
|
||||
'\tservice-remove\n'
|
||||
'\tservice-status\n'
|
||||
'\tservice-unbind'), ['service-add', 'service-bind', 'service-doc',
|
||||
'service-info', 'service-list', 'service-remove',
|
||||
'service-status', 'service-unbind'])])
|
||||
def test_get_all_matched_commands(stderr, result):
|
||||
assert list(get_all_matched_commands(stderr)) == result
|
||||
|
||||
67
thefuck/rules/fix_file.py
Normal file
67
thefuck/rules/fix_file.py
Normal file
@@ -0,0 +1,67 @@
|
||||
import re
|
||||
import os
|
||||
from thefuck.utils import memoize
|
||||
from thefuck import shells
|
||||
|
||||
|
||||
patterns = (
|
||||
# js, node:
|
||||
'^ at {file}:{line}:{col}',
|
||||
# cargo:
|
||||
'^ {file}:{line}:{col}',
|
||||
# python, thefuck:
|
||||
'^ File "{file}", line {line}',
|
||||
# awk:
|
||||
'^awk: {file}:{line}:',
|
||||
# git
|
||||
'^fatal: bad config file line {line} in {file}',
|
||||
# llc:
|
||||
'^llc: {file}:{line}:{col}:',
|
||||
# lua:
|
||||
'^lua: {file}:{line}:',
|
||||
# fish:
|
||||
'^{file} \(line {line}\):',
|
||||
# bash, sh, ssh:
|
||||
'^{file}: line {line}: ',
|
||||
# ghc, make, ruby, zsh:
|
||||
'^{file}:{line}:',
|
||||
# cargo, clang, gcc, go, rustc:
|
||||
'^{file}:{line}:{col}',
|
||||
# perl:
|
||||
'at {file} line {line}',
|
||||
)
|
||||
|
||||
|
||||
# for the sake of readability do not use named groups above
|
||||
def _make_pattern(pattern):
|
||||
pattern = pattern.replace('{file}', '(?P<file>[^:\n]+)')
|
||||
pattern = pattern.replace('{line}', '(?P<line>[0-9]+)')
|
||||
pattern = pattern.replace('{col}', '(?P<col>[0-9]+)')
|
||||
return re.compile(pattern, re.MULTILINE)
|
||||
patterns = [_make_pattern(p) for p in patterns]
|
||||
|
||||
|
||||
@memoize
|
||||
def _search(stderr):
|
||||
for pattern in patterns:
|
||||
m = re.search(pattern, stderr)
|
||||
if m:
|
||||
return m
|
||||
|
||||
|
||||
def match(command, settings):
|
||||
if 'EDITOR' not in os.environ:
|
||||
return False
|
||||
|
||||
m = _search(command.stderr)
|
||||
|
||||
return m and os.path.isfile(m.group('file'))
|
||||
|
||||
|
||||
def get_new_command(command, settings):
|
||||
m = _search(command.stderr)
|
||||
|
||||
# Note: there does not seem to be a standard for columns, so they are just
|
||||
# ignored for now
|
||||
return shells.and_('{} {} +{}'.format(os.environ['EDITOR'], m.group('file'), m.group('line')),
|
||||
command.script)
|
||||
@@ -1,5 +1,6 @@
|
||||
import re
|
||||
from thefuck.utils import get_closest, git_support, replace_argument
|
||||
from thefuck.utils import (get_closest, git_support, replace_argument,
|
||||
get_all_matched_commands)
|
||||
|
||||
|
||||
@git_support
|
||||
@@ -8,20 +9,11 @@ def match(command, settings):
|
||||
and 'Did you mean' in command.stderr)
|
||||
|
||||
|
||||
def _get_all_git_matched_commands(stderr):
|
||||
should_yield = False
|
||||
for line in stderr.split('\n'):
|
||||
if 'Did you mean' in line:
|
||||
should_yield = True
|
||||
elif should_yield and line:
|
||||
yield line.strip()
|
||||
|
||||
|
||||
@git_support
|
||||
def get_new_command(command, settings):
|
||||
broken_cmd = re.findall(r"git: '([^']*)' is not a git command",
|
||||
command.stderr)[0]
|
||||
new_cmd = get_closest(broken_cmd,
|
||||
_get_all_git_matched_commands(command.stderr))
|
||||
get_all_matched_commands(command.stderr))
|
||||
return replace_argument(command.script, broken_cmd, new_cmd)
|
||||
|
||||
|
||||
18
thefuck/rules/tsuru_not_command.py
Normal file
18
thefuck/rules/tsuru_not_command.py
Normal file
@@ -0,0 +1,18 @@
|
||||
import re
|
||||
from thefuck.utils import (get_closest, replace_argument,
|
||||
get_all_matched_commands)
|
||||
|
||||
|
||||
def match(command, settings):
|
||||
return (command.script.startswith('tsuru ')
|
||||
and ' is not a tsuru command. See "tsuru help".' in command.stderr
|
||||
and '\nDid you mean?\n\t' in command.stderr)
|
||||
|
||||
|
||||
def get_new_command(command, settings):
|
||||
broken_cmd = re.findall(r'tsuru: "([^"]*)" is not a tsuru command',
|
||||
command.stderr)[0]
|
||||
new_cmd = get_closest(broken_cmd,
|
||||
get_all_matched_commands(command.stderr))
|
||||
return replace_argument(command.script, broken_cmd, new_cmd)
|
||||
|
||||
@@ -224,6 +224,7 @@ shells = defaultdict(lambda: Generic(), {
|
||||
'tcsh': Tcsh()})
|
||||
|
||||
|
||||
@memoize
|
||||
def _get_shell():
|
||||
try:
|
||||
shell = Process(os.getpid()).parent().name()
|
||||
|
||||
@@ -159,3 +159,12 @@ def replace_argument(script, from_, to):
|
||||
else:
|
||||
return script.replace(
|
||||
u' {} '.format(from_), u' {} '.format(to), 1)
|
||||
|
||||
|
||||
def get_all_matched_commands(stderr, separator='Did you mean'):
|
||||
should_yield = False
|
||||
for line in stderr.split('\n'):
|
||||
if separator in line:
|
||||
should_yield = True
|
||||
elif should_yield and line:
|
||||
yield line.strip()
|
||||
|
||||
Reference in New Issue
Block a user