mirror of
https://github.com/nvbn/thefuck.git
synced 2025-11-02 16:12:08 +00:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fbfb4b5e41 | ||
|
|
51c37bc5ab | ||
|
|
78769e4fbc | ||
|
|
3e4c043ccc | ||
|
|
934099fe9e | ||
|
|
464f86eccf | ||
|
|
891fbe7ed1 | ||
|
|
5abab8bd1e | ||
|
|
7ebc8a38af | ||
|
|
f40b63f44b | ||
|
|
4b4e7acc0f | ||
|
|
a8587d3871 | ||
|
|
370c58e679 | ||
|
|
328e65179e | ||
|
|
63bb4da8e1 | ||
|
|
0b5a7a8e2d | ||
|
|
5693bd49f7 | ||
|
|
12f8d017b9 |
@@ -4,6 +4,6 @@ python:
|
||||
- "3.3"
|
||||
- "2.7"
|
||||
install:
|
||||
- python setup.py develop
|
||||
- pip install -r requirements.txt
|
||||
- python setup.py develop
|
||||
script: py.test -v
|
||||
|
||||
11
README.md
11
README.md
@@ -169,12 +169,14 @@ using the matched rule and runs it. Rules enabled by default are as follows:
|
||||
* `go_run` – appends `.go` extension when compiling/running Go programs
|
||||
* `grep_recursive` – adds `-r` when you trying to grep directory;
|
||||
* `has_exists_script` – prepends `./` when script/binary exists;
|
||||
* `history` – tries to replace command with most similar command from history;
|
||||
* `java` – removes `.java` extension when running Java programs;
|
||||
* `javac` – appends missing `.java` when compiling Java files;
|
||||
* `lein_not_task` – fixes wrong `lein` tasks like `lein rpl`;
|
||||
* `ls_lah` – adds -lah to ls;
|
||||
* `man` – change manual section;
|
||||
* `man_no_space` – fixes man commands without spaces, for example `mandiff`;
|
||||
* `mercurial` – fixes wrong `hg` commands;
|
||||
* `mkdir_p` – adds `-p` when you trying to create directory without parent;
|
||||
* `no_command` – fixes wrong console commands, for example `vom/vim`;
|
||||
* `no_such_file` – creates missing directories with `mv` and `cp` commands;
|
||||
@@ -184,7 +186,7 @@ using the matched rule and runs it. Rules enabled by default are as follows:
|
||||
* `python_execute` – appends missing `.py` when executing Python files;
|
||||
* `quotation_marks` – fixes uneven usage of `'` and `"` when containing args'
|
||||
* `rm_dir` – adds `-rf` when you trying to remove directory;
|
||||
* `sed` – adds missing '/' to `sed`'s `s` commands;
|
||||
* `sed_unterminated_s` – adds missing '/' to `sed`'s `s` commands;
|
||||
* `sl_ls` – changes `sl` to `ls`;
|
||||
* `ssh_known_hosts` – removes host from `known_hosts` on warning;
|
||||
* `sudo` – prepends `sudo` to previous command if it failed because of permissions;
|
||||
@@ -251,7 +253,8 @@ The Fuck has a few settings parameters which can be changed in `~/.thefuck/setti
|
||||
* `require_confirmation` – requires confirmation before running new command, by default `False`;
|
||||
* `wait_command` – max amount of time in seconds for getting previous command output;
|
||||
* `no_colors` – disable colored output;
|
||||
* `priority` – dict with rules priorities, rule with lower `priority` will be matched first.
|
||||
* `priority` – dict with rules priorities, rule with lower `priority` will be matched first;
|
||||
* `debug` – enabled debug output, by default `False`;
|
||||
|
||||
Example of `settings.py`:
|
||||
|
||||
@@ -261,6 +264,7 @@ require_confirmation = True
|
||||
wait_command = 10
|
||||
no_colors = False
|
||||
priority = {'sudo': 100, 'no_command': 9999}
|
||||
debug = False
|
||||
```
|
||||
|
||||
Or via environment variables:
|
||||
@@ -270,7 +274,8 @@ Or via environment variables:
|
||||
* `THEFUCK_WAIT_COMMAND` – max amount of time in seconds for getting previous command output;
|
||||
* `THEFUCK_NO_COLORS` – disable colored output, `true/false`;
|
||||
* `THEFUCK_PRIORITY` – priority of the rules, like `no_command=9999:apt_get=100`,
|
||||
rule with lower `priority` will be matched first.
|
||||
rule with lower `priority` will be matched first;
|
||||
* `THEFUCK_DEBUG` – enables debug output, `true/false`.
|
||||
|
||||
For example:
|
||||
|
||||
|
||||
@@ -2,3 +2,4 @@ pytest
|
||||
mock
|
||||
pytest-mock
|
||||
wheel
|
||||
setuptools>=17.1
|
||||
|
||||
7
setup.py
7
setup.py
@@ -11,12 +11,10 @@ elif (3, 0) < sys.version_info < (3, 3):
|
||||
' ({}.{} detected).'.format(*sys.version_info[:2]))
|
||||
sys.exit(-1)
|
||||
|
||||
VERSION = '1.48'
|
||||
VERSION = '1.49.1'
|
||||
|
||||
install_requires = ['psutil', 'colorama', 'six']
|
||||
|
||||
if sys.version_info < (3, 4):
|
||||
install_requires.append('pathlib')
|
||||
extras_require = {':python_version<"3.4"': ['pathlib']}
|
||||
|
||||
setup(name='thefuck',
|
||||
version=VERSION,
|
||||
@@ -30,6 +28,7 @@ setup(name='thefuck',
|
||||
include_package_data=True,
|
||||
zip_safe=False,
|
||||
install_requires=install_requires,
|
||||
extras_require=extras_require,
|
||||
entry_points={'console_scripts': [
|
||||
'thefuck = thefuck.main:main',
|
||||
'thefuck-alias = thefuck.shells:app_alias']})
|
||||
|
||||
6
tests/conftest.py
Normal file
6
tests/conftest.py
Normal file
@@ -0,0 +1,6 @@
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def no_memoize(monkeypatch):
|
||||
monkeypatch.setattr('thefuck.utils.memoize.disabled', True)
|
||||
@@ -1,14 +1,22 @@
|
||||
from mock import Mock
|
||||
import pytest
|
||||
from thefuck.rules.cp_omitting_directory import match, get_new_command
|
||||
from tests.utils import Command
|
||||
|
||||
|
||||
def test_match():
|
||||
assert match(Mock(script='cp dir', stderr="cp: omitting directory 'dir'"),
|
||||
None)
|
||||
assert not match(Mock(script='some dir',
|
||||
stderr="cp: omitting directory 'dir'"), None)
|
||||
assert not match(Mock(script='cp dir', stderr=""), None)
|
||||
@pytest.mark.parametrize('script, stderr', [
|
||||
('cp dir', 'cp: dor: is a directory'),
|
||||
('cp dir', "cp: omitting directory 'dir'")])
|
||||
def test_match(script, stderr):
|
||||
assert match(Command(script, stderr=stderr), None)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('script, stderr', [
|
||||
('some dir', 'cp: dor: is a directory'),
|
||||
('some dir', "cp: omitting directory 'dir'"),
|
||||
('cp dir', '')])
|
||||
def test_not_match(script, stderr):
|
||||
assert not match(Command(script, stderr=stderr), None)
|
||||
|
||||
|
||||
def test_get_new_command():
|
||||
assert get_new_command(Mock(script='cp dir'), None) == 'cp -a dir'
|
||||
assert get_new_command(Command(script='cp dir'), None) == 'cp -a dir'
|
||||
|
||||
35
tests/rules/test_history.py
Normal file
35
tests/rules/test_history.py
Normal file
@@ -0,0 +1,35 @@
|
||||
import pytest
|
||||
from thefuck.rules.history import match, get_new_command
|
||||
from tests.utils import Command
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def history(mocker):
|
||||
return mocker.patch('thefuck.rules.history.get_history',
|
||||
return_value=['ls cat', 'diff x', 'nocommand x'])
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def callables(mocker):
|
||||
return mocker.patch('thefuck.rules.history.get_all_callables',
|
||||
return_value=['diff', 'ls'])
|
||||
|
||||
|
||||
@pytest.mark.usefixtures('history', 'callables', 'no_memoize')
|
||||
@pytest.mark.parametrize('script', ['ls cet', 'daff x'])
|
||||
def test_match(script):
|
||||
assert match(Command(script=script), None)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures('history', 'callables', 'no_memoize')
|
||||
@pytest.mark.parametrize('script', ['apt-get', 'nocommand y'])
|
||||
def test_not_match(script):
|
||||
assert not match(Command(script=script), None)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures('history', 'callables', 'no_memoize')
|
||||
@pytest.mark.parametrize('script, result', [
|
||||
('ls cet', 'ls cat'),
|
||||
('daff x', 'diff x')])
|
||||
def test_get_new_command(script, result):
|
||||
assert get_new_command(Command(script), None) == result
|
||||
@@ -1,36 +1,42 @@
|
||||
from mock import patch, Mock
|
||||
from thefuck.rules.no_command import match, get_new_command, _get_all_callables
|
||||
import pytest
|
||||
from thefuck.rules.no_command import match, get_new_command, get_all_callables
|
||||
from tests.utils import Command
|
||||
|
||||
|
||||
@patch('thefuck.rules.no_command._safe', return_value=[])
|
||||
@patch('thefuck.rules.no_command.get_aliases',
|
||||
return_value=['vim', 'apt-get', 'fsck', 'fuck'])
|
||||
@pytest.fixture(autouse=True)
|
||||
def _safe(mocker):
|
||||
mocker.patch('thefuck.rules.no_command._safe', return_value=[])
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def get_aliases(mocker):
|
||||
mocker.patch('thefuck.rules.no_command.get_aliases',
|
||||
return_value=['vim', 'apt-get', 'fsck', 'fuck'])
|
||||
|
||||
|
||||
@pytest.mark.usefixtures('no_memoize')
|
||||
def test_get_all_callables(*args):
|
||||
all_callables = _get_all_callables()
|
||||
all_callables = get_all_callables()
|
||||
assert 'vim' in all_callables
|
||||
assert 'fsck' in all_callables
|
||||
assert 'fuck' not in all_callables
|
||||
|
||||
|
||||
@patch('thefuck.rules.no_command._safe', return_value=[])
|
||||
@patch('thefuck.rules.no_command.get_aliases',
|
||||
return_value=['vim', 'apt-get', 'fsck', 'fuck'])
|
||||
@pytest.mark.usefixtures('no_memoize')
|
||||
def test_match(*args):
|
||||
assert match(Mock(stderr='vom: not found', script='vom file.py'), None)
|
||||
assert match(Mock(stderr='fucck: not found', script='fucck'), None)
|
||||
assert not match(Mock(stderr='qweqwe: not found', script='qweqwe'), None)
|
||||
assert not match(Mock(stderr='some text', script='vom file.py'), None)
|
||||
assert match(Command(stderr='vom: not found', script='vom file.py'), None)
|
||||
assert match(Command(stderr='fucck: not found', script='fucck'), None)
|
||||
assert not match(Command(stderr='qweqwe: not found', script='qweqwe'), None)
|
||||
assert not match(Command(stderr='some text', script='vom file.py'), None)
|
||||
|
||||
|
||||
@patch('thefuck.rules.no_command._safe', return_value=[])
|
||||
@patch('thefuck.rules.no_command.get_aliases',
|
||||
return_value=['vim', 'apt-get', 'fsck', 'fuck'])
|
||||
@pytest.mark.usefixtures('no_memoize')
|
||||
def test_get_new_command(*args):
|
||||
assert get_new_command(
|
||||
Mock(stderr='vom: not found',
|
||||
script='vom file.py'),
|
||||
Command(stderr='vom: not found',
|
||||
script='vom file.py'),
|
||||
None) == 'vim file.py'
|
||||
assert get_new_command(
|
||||
Mock(stderr='fucck: not found',
|
||||
script='fucck'),
|
||||
None) == 'fsck'
|
||||
Command(stderr='fucck: not found',
|
||||
script='fucck'),
|
||||
Command) == 'fsck'
|
||||
|
||||
@@ -16,4 +16,4 @@ def test_match(tmux_ambiguous):
|
||||
|
||||
def test_get_new_command(tmux_ambiguous):
|
||||
assert get_new_command(Command('tmux list', stderr=tmux_ambiguous), None)\
|
||||
== 'tmux list-buffers'
|
||||
== 'tmux list-keys'
|
||||
|
||||
@@ -5,3 +5,10 @@ from thefuck import logs
|
||||
def test_color():
|
||||
assert logs.color('red', Mock(no_colors=False)) == 'red'
|
||||
assert logs.color('red', Mock(no_colors=True)) == ''
|
||||
|
||||
|
||||
def test_debug(capsys):
|
||||
logs.debug('test', Mock(no_colors=True, debug=True))
|
||||
assert capsys.readouterr() == ('', 'DEBUG: test\n')
|
||||
logs.debug('test', Mock(no_colors=True, debug=False))
|
||||
assert capsys.readouterr() == ('', '')
|
||||
|
||||
@@ -110,7 +110,7 @@ class TestGetMatchedRule(object):
|
||||
def test_when_rule_failed(self, capsys):
|
||||
main.get_matched_rule(
|
||||
Command('ls'), [Rule('test', Mock(side_effect=OSError('Denied')))],
|
||||
Mock(no_colors=True))
|
||||
Mock(no_colors=True, debug=False))
|
||||
assert capsys.readouterr()[1].split('\n')[0] == '[WARN] Rule test:'
|
||||
|
||||
|
||||
@@ -126,7 +126,7 @@ class TestRunRule(object):
|
||||
|
||||
def test_run_rule_with_side_effect(self, capsys):
|
||||
side_effect = Mock()
|
||||
settings = Mock()
|
||||
settings = Mock(debug=False)
|
||||
command = Command()
|
||||
main.run_rule(Rule(get_new_command=lambda *_: 'new-command',
|
||||
side_effect=side_effect),
|
||||
|
||||
@@ -12,6 +12,16 @@ def isfile(mocker):
|
||||
return mocker.patch('os.path.isfile', return_value=True)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@pytest.mark.usefixtures('isfile')
|
||||
def history_lines(mocker):
|
||||
def aux(lines):
|
||||
mock = mocker.patch('io.open')
|
||||
mock.return_value.__enter__\
|
||||
.return_value.__iter__.return_value = lines
|
||||
return aux
|
||||
|
||||
|
||||
class TestGeneric(object):
|
||||
@pytest.fixture
|
||||
def shell(self):
|
||||
@@ -38,6 +48,12 @@ class TestGeneric(object):
|
||||
assert 'thefuck' in shell.app_alias()
|
||||
assert 'TF_ALIAS' in shell.app_alias()
|
||||
|
||||
def test_get_history(self, history_lines, shell):
|
||||
history_lines(['ls', 'rm'])
|
||||
# We don't know what to do in generic shell with history lines,
|
||||
# so just ignore them:
|
||||
assert list(shell.get_history()) == []
|
||||
|
||||
|
||||
@pytest.mark.usefixtures('isfile')
|
||||
class TestBash(object):
|
||||
@@ -85,6 +101,10 @@ class TestBash(object):
|
||||
assert 'thefuck' in shell.app_alias()
|
||||
assert 'TF_ALIAS' in shell.app_alias()
|
||||
|
||||
def test_get_history(self, history_lines, shell):
|
||||
history_lines(['ls', 'rm'])
|
||||
assert list(shell.get_history()) == ['ls', 'rm']
|
||||
|
||||
|
||||
@pytest.mark.usefixtures('isfile')
|
||||
class TestFish(object):
|
||||
@@ -96,18 +116,34 @@ class TestFish(object):
|
||||
def Popen(self, mocker):
|
||||
mock = mocker.patch('thefuck.shells.Popen')
|
||||
mock.return_value.stdout.read.return_value = (
|
||||
b'fish_config\nfuck\nfunced\nfuncsave\ngrep\nhistory\nll\nmath')
|
||||
b'cd\nfish_config\nfuck\nfunced\nfuncsave\ngrep\nhistory\nll\nls\n'
|
||||
b'man\nmath\npopd\npushd\nruby')
|
||||
return mock
|
||||
|
||||
@pytest.fixture
|
||||
def environ(self, monkeypatch):
|
||||
data = {'TF_OVERRIDDEN_ALIASES': 'cd, ls, man, open'}
|
||||
monkeypatch.setattr('thefuck.shells.os.environ', data)
|
||||
return data
|
||||
|
||||
@pytest.mark.usefixture('environ')
|
||||
def test_get_overridden_aliases(self, shell, environ):
|
||||
assert shell._get_overridden_aliases() == ['cd', 'ls', 'man', 'open']
|
||||
|
||||
@pytest.mark.parametrize('before, after', [
|
||||
('cd', 'cd'),
|
||||
('pwd', 'pwd'),
|
||||
('fuck', 'fish -ic "fuck"'),
|
||||
('find', 'find'),
|
||||
('funced', 'fish -ic "funced"'),
|
||||
('grep', 'grep'),
|
||||
('awk', 'awk'),
|
||||
('math "2 + 2"', r'fish -ic "math \"2 + 2\""'),
|
||||
('man', 'man'),
|
||||
('open', 'open'),
|
||||
('vim', 'vim'),
|
||||
('ll', 'fish -ic "ll"')]) # Fish has no aliases but functions
|
||||
('ll', 'fish -ic "ll"'),
|
||||
('ls', 'ls')]) # Fish has no aliases but functions
|
||||
def test_from_shell(self, before, after, shell):
|
||||
assert shell.from_shell(before) == after
|
||||
|
||||
@@ -129,10 +165,12 @@ class TestFish(object):
|
||||
'fuck': 'fuck',
|
||||
'funced': 'funced',
|
||||
'funcsave': 'funcsave',
|
||||
'grep': 'grep',
|
||||
'history': 'history',
|
||||
'll': 'll',
|
||||
'math': 'math'}
|
||||
'math': 'math',
|
||||
'popd': 'popd',
|
||||
'pushd': 'pushd',
|
||||
'ruby': 'ruby'}
|
||||
|
||||
def test_app_alias(self, shell):
|
||||
assert 'function fuck' in shell.app_alias()
|
||||
@@ -187,3 +225,7 @@ class TestZsh(object):
|
||||
assert 'alias fuck' in shell.app_alias()
|
||||
assert 'thefuck' in shell.app_alias()
|
||||
assert 'TF_ALIAS' in shell.app_alias()
|
||||
|
||||
def test_get_history(self, history_lines, shell):
|
||||
history_lines([': 1432613911:0;ls', ': 1432613916:0;rm'])
|
||||
assert list(shell.get_history()) == ['ls', 'rm']
|
||||
|
||||
@@ -34,6 +34,15 @@ def test_memoize():
|
||||
fn.assert_called_once_with()
|
||||
|
||||
|
||||
@pytest.mark.usefixtures('no_memoize')
|
||||
def test_no_memoize():
|
||||
fn = Mock(__name__='fn')
|
||||
memoized = memoize(fn)
|
||||
memoized()
|
||||
memoized()
|
||||
assert fn.call_count == 2
|
||||
|
||||
|
||||
class TestGetClosest(object):
|
||||
|
||||
def test_when_can_match(self):
|
||||
|
||||
@@ -29,13 +29,15 @@ DEFAULT_SETTINGS = {'rules': DEFAULT_RULES,
|
||||
'wait_command': 3,
|
||||
'require_confirmation': False,
|
||||
'no_colors': False,
|
||||
'debug': False,
|
||||
'priority': {}}
|
||||
|
||||
ENV_TO_ATTR = {'THEFUCK_RULES': 'rules',
|
||||
'THEFUCK_WAIT_COMMAND': 'wait_command',
|
||||
'THEFUCK_REQUIRE_CONFIRMATION': 'require_confirmation',
|
||||
'THEFUCK_NO_COLORS': 'no_colors',
|
||||
'THEFUCK_PRIORITY': 'priority'}
|
||||
'THEFUCK_PRIORITY': 'priority',
|
||||
'THEFUCK_DEBUG': 'debug'}
|
||||
|
||||
|
||||
SETTINGS_HEADER = u"""# ~/.thefuck/settings.py: The Fuck settings file
|
||||
@@ -87,7 +89,7 @@ def _val_from_env(env, attr):
|
||||
return dict(_priority_from_env(val))
|
||||
elif attr == 'wait_command':
|
||||
return int(val)
|
||||
elif attr in ('require_confirmation', 'no_colors'):
|
||||
elif attr in ('require_confirmation', 'no_colors', 'debug'):
|
||||
return val.lower() == 'true'
|
||||
else:
|
||||
return val
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from pprint import pformat
|
||||
import sys
|
||||
from traceback import format_exception
|
||||
import colorama
|
||||
@@ -52,3 +53,12 @@ def failed(msg, settings):
|
||||
msg=msg,
|
||||
red=color(colorama.Fore.RED, settings),
|
||||
reset=color(colorama.Style.RESET_ALL, settings)))
|
||||
|
||||
|
||||
def debug(msg, settings):
|
||||
if settings.debug:
|
||||
sys.stderr.write(u'{blue}{bold}DEBUG:{reset} {msg}\n'.format(
|
||||
msg=msg,
|
||||
reset=color(colorama.Style.RESET_ALL, settings),
|
||||
blue=color(colorama.Fore.BLUE, settings),
|
||||
bold=color(colorama.Style.BRIGHT, settings)))
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from imp import load_source
|
||||
from pathlib import Path
|
||||
from os.path import expanduser
|
||||
from pprint import pformat
|
||||
from subprocess import Popen, PIPE
|
||||
import os
|
||||
import sys
|
||||
@@ -79,6 +80,7 @@ def get_command(settings, args):
|
||||
return
|
||||
|
||||
script = shells.from_shell(script)
|
||||
logs.debug('Call: {}'.format(script), settings)
|
||||
result = Popen(script, shell=True, stdout=PIPE, stderr=PIPE,
|
||||
env=dict(os.environ, LANG='C'))
|
||||
if wait_output(settings, result):
|
||||
@@ -90,6 +92,7 @@ def get_matched_rule(command, rules, settings):
|
||||
"""Returns first matched rule for command."""
|
||||
for rule in rules:
|
||||
try:
|
||||
logs.debug(u'Trying rule: {}'.format(rule.name), settings)
|
||||
if rule.match(command, settings):
|
||||
return rule
|
||||
except Exception:
|
||||
@@ -125,12 +128,21 @@ def main():
|
||||
colorama.init()
|
||||
user_dir = setup_user_dir()
|
||||
settings = conf.get_settings(user_dir)
|
||||
logs.debug('Run with settings: {}'.format(pformat(settings)), settings)
|
||||
|
||||
command = get_command(settings, sys.argv)
|
||||
if command:
|
||||
logs.debug('Received stdout: {}'.format(command.stdout), settings)
|
||||
logs.debug('Received stderr: {}'.format(command.stderr), settings)
|
||||
|
||||
rules = get_rules(user_dir, settings)
|
||||
logs.debug(
|
||||
'Loaded rules: {}'.format(', '.join(rule.name for rule in rules)),
|
||||
settings)
|
||||
|
||||
matched_rule = get_matched_rule(command, rules, settings)
|
||||
if matched_rule:
|
||||
logs.debug('Matched rule: {}'.format(matched_rule.name), settings)
|
||||
run_rule(matched_rule, command, settings)
|
||||
return
|
||||
|
||||
|
||||
@@ -4,8 +4,9 @@ from thefuck.utils import sudo_support
|
||||
|
||||
@sudo_support
|
||||
def match(command, settings):
|
||||
stderr = command.stderr.lower()
|
||||
return command.script.startswith('cp ') \
|
||||
and 'cp: omitting directory' in command.stderr.lower()
|
||||
and ('omitting directory' in stderr or 'is a directory' in stderr)
|
||||
|
||||
|
||||
@sudo_support
|
||||
|
||||
24
thefuck/rules/history.py
Normal file
24
thefuck/rules/history.py
Normal file
@@ -0,0 +1,24 @@
|
||||
from difflib import get_close_matches
|
||||
from thefuck.shells import get_history
|
||||
from thefuck.utils import get_closest, memoize
|
||||
from thefuck.rules.no_command import get_all_callables
|
||||
|
||||
|
||||
@memoize
|
||||
def _history_of_exists_without_current(command):
|
||||
callables = get_all_callables()
|
||||
return [line for line in get_history()
|
||||
if line != command.script
|
||||
and line.split(' ')[0] in callables]
|
||||
|
||||
|
||||
def match(command, settings):
|
||||
return len(get_close_matches(command.script,
|
||||
_history_of_exists_without_current(command)))
|
||||
|
||||
|
||||
def get_new_command(command, settings):
|
||||
return get_closest(command.script,
|
||||
_history_of_exists_without_current(command))
|
||||
|
||||
priority = 9999
|
||||
@@ -1,7 +1,7 @@
|
||||
from difflib import get_close_matches
|
||||
import os
|
||||
from pathlib import Path
|
||||
from thefuck.utils import sudo_support
|
||||
from thefuck.utils import sudo_support, memoize
|
||||
from thefuck.shells import thefuck_alias, get_aliases
|
||||
|
||||
|
||||
@@ -12,7 +12,8 @@ def _safe(fn, fallback):
|
||||
return fallback
|
||||
|
||||
|
||||
def _get_all_callables():
|
||||
@memoize
|
||||
def get_all_callables():
|
||||
tf_alias = thefuck_alias()
|
||||
return [exe.name
|
||||
for path in os.environ.get('PATH', '').split(':')
|
||||
@@ -25,14 +26,14 @@ def _get_all_callables():
|
||||
def match(command, settings):
|
||||
return 'not found' in command.stderr and \
|
||||
bool(get_close_matches(command.script.split(' ')[0],
|
||||
_get_all_callables()))
|
||||
get_all_callables()))
|
||||
|
||||
|
||||
@sudo_support
|
||||
def get_new_command(command, settings):
|
||||
old_command = command.script.split(' ')[0]
|
||||
new_command = get_close_matches(old_command,
|
||||
_get_all_callables())[0]
|
||||
get_all_callables())[0]
|
||||
return ' '.join([new_command] + command.script.split(' ')[1:])
|
||||
|
||||
|
||||
|
||||
@@ -13,7 +13,8 @@ patterns = ['permission denied',
|
||||
'must be root',
|
||||
'need to be root',
|
||||
'need root',
|
||||
'only root can do that']
|
||||
'only root can do that',
|
||||
'authentication is required']
|
||||
|
||||
|
||||
def match(command, settings):
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from thefuck.utils import get_closest
|
||||
import re
|
||||
|
||||
|
||||
@@ -8,7 +9,12 @@ def match(command, settings):
|
||||
|
||||
|
||||
def get_new_command(command, settings):
|
||||
cmd = re.match(r"ambiguous command: (.*), could be: ([^, \n]*)",
|
||||
cmd = re.match(r"ambiguous command: (.*), could be: (.*)",
|
||||
command.stderr)
|
||||
|
||||
return command.script.replace(cmd.group(1), cmd.group(2))
|
||||
old_cmd = cmd.group(1)
|
||||
suggestions = [cmd.strip() for cmd in cmd.group(2).split(',')]
|
||||
|
||||
new_cmd = get_closest(old_cmd, suggestions)
|
||||
|
||||
return command.script.replace(old_cmd, new_cmd)
|
||||
|
||||
@@ -7,7 +7,9 @@ from collections import defaultdict
|
||||
from subprocess import Popen, PIPE
|
||||
from time import time
|
||||
import os
|
||||
import io
|
||||
from psutil import Process
|
||||
import six
|
||||
from .utils import DEVNULL, memoize
|
||||
|
||||
|
||||
@@ -48,6 +50,26 @@ class Generic(object):
|
||||
with open(history_file_name, 'a') as history:
|
||||
history.write(self._get_history_line(command_script))
|
||||
|
||||
def _script_from_history(self, line):
|
||||
"""Returns prepared history line.
|
||||
|
||||
Should return a blank line if history line is corrupted or empty.
|
||||
|
||||
"""
|
||||
return ''
|
||||
|
||||
def get_history(self):
|
||||
"""Returns list of history entries."""
|
||||
history_file_name = self._get_history_file_name()
|
||||
if os.path.isfile(history_file_name):
|
||||
with io.open(history_file_name, 'r',
|
||||
encoding='utf-8', errors='ignore') as history:
|
||||
for line in history:
|
||||
prepared = self._script_from_history(line)\
|
||||
.strip()
|
||||
if prepared:
|
||||
yield prepared
|
||||
|
||||
def and_(self, *commands):
|
||||
return u' && '.join(commands)
|
||||
|
||||
@@ -63,7 +85,6 @@ class Bash(Generic):
|
||||
value = value[1:-1]
|
||||
return name, value
|
||||
|
||||
@memoize
|
||||
def get_aliases(self):
|
||||
proc = Popen('bash -ic alias', stdout=PIPE, stderr=DEVNULL,
|
||||
shell=True)
|
||||
@@ -79,12 +100,24 @@ class Bash(Generic):
|
||||
def _get_history_line(self, command_script):
|
||||
return u'{}\n'.format(command_script)
|
||||
|
||||
def _script_from_history(self, line):
|
||||
print(line)
|
||||
return line
|
||||
|
||||
|
||||
class Fish(Generic):
|
||||
|
||||
def _get_overridden_aliases(self):
|
||||
overridden_aliases = os.environ.get('TF_OVERRIDDEN_ALIASES', '').strip()
|
||||
if overridden_aliases:
|
||||
return [alias.strip() for alias in overridden_aliases.split(',')]
|
||||
else:
|
||||
return ['cd', 'grep', 'ls', 'man', 'open']
|
||||
|
||||
def app_alias(self):
|
||||
return ("function fuck -d 'Correct your previous console command'\n"
|
||||
return ("set TF_ALIAS fuck\n"
|
||||
"function fuck -d 'Correct your previous console command'\n"
|
||||
" set -l exit_code $status\n"
|
||||
" set -l TF_ALIAS fuck\n"
|
||||
" set -l eval_script"
|
||||
" (mktemp 2>/dev/null ; or mktemp -t 'thefuck')\n"
|
||||
" set -l fucked_up_commandd $history[1]\n"
|
||||
@@ -96,12 +129,12 @@ class Fish(Generic):
|
||||
" end\n"
|
||||
"end")
|
||||
|
||||
@memoize
|
||||
def get_aliases(self):
|
||||
overridden = self._get_overridden_aliases()
|
||||
proc = Popen('fish -ic functions', stdout=PIPE, stderr=DEVNULL,
|
||||
shell=True)
|
||||
functions = proc.stdout.read().decode('utf-8').strip().split('\n')
|
||||
return {function: function for function in functions}
|
||||
return {func: func for func in functions if func not in overridden}
|
||||
|
||||
def _expand_aliases(self, command_script):
|
||||
aliases = self.get_aliases()
|
||||
@@ -137,7 +170,6 @@ class Zsh(Generic):
|
||||
value = value[1:-1]
|
||||
return name, value
|
||||
|
||||
@memoize
|
||||
def get_aliases(self):
|
||||
proc = Popen('zsh -ic alias', stdout=PIPE, stderr=DEVNULL,
|
||||
shell=True)
|
||||
@@ -153,6 +185,12 @@ class Zsh(Generic):
|
||||
def _get_history_line(self, command_script):
|
||||
return u': {}:0;{}\n'.format(int(time()), command_script)
|
||||
|
||||
def _script_from_history(self, line):
|
||||
if ';' in line:
|
||||
return line.split(';', 1)[1]
|
||||
else:
|
||||
return ''
|
||||
|
||||
|
||||
class Tcsh(Generic):
|
||||
def app_alias(self):
|
||||
@@ -162,7 +200,6 @@ class Tcsh(Generic):
|
||||
name, value = alias.split("\t", 1)
|
||||
return name, value
|
||||
|
||||
@memoize
|
||||
def get_aliases(self):
|
||||
proc = Popen('tcsh -ic alias', stdout=PIPE, stderr=DEVNULL,
|
||||
shell=True)
|
||||
@@ -219,5 +256,11 @@ def and_(*commands):
|
||||
return _get_shell().and_(*commands)
|
||||
|
||||
|
||||
@memoize
|
||||
def get_aliases():
|
||||
return list(_get_shell().get_aliases().keys())
|
||||
|
||||
|
||||
@memoize
|
||||
def get_history():
|
||||
return list(_get_shell().get_history())
|
||||
|
||||
@@ -80,12 +80,13 @@ def memoize(fn):
|
||||
@wraps(fn)
|
||||
def wrapper(*args, **kwargs):
|
||||
key = pickle.dumps((args, kwargs))
|
||||
if key not in memo:
|
||||
if key not in memo or memoize.disabled:
|
||||
memo[key] = fn(*args, **kwargs)
|
||||
|
||||
return memo[key]
|
||||
|
||||
return wrapper
|
||||
memoize.disabled = False
|
||||
|
||||
|
||||
def get_closest(word, possibilities, n=3, cutoff=0.6):
|
||||
|
||||
Reference in New Issue
Block a user