mirror of
https://github.com/nvbn/thefuck.git
synced 2025-01-31 10:11:14 +00:00
#591: Add path_from_history
rule
This commit is contained in:
parent
350be285b8
commit
2379573cf2
@ -234,6 +234,7 @@ using the matched rule and runs it. Rules enabled by default are as follows:
|
||||
* `python_command` – prepends `python` when you trying to run not executable/without `./` python script;
|
||||
* `python_execute` – appends missing `.py` when executing Python files;
|
||||
* `quotation_marks` – fixes uneven usage of `'` and `"` when containing args';
|
||||
* `path_from_history` – replaces not found path with similar absolute path from history;
|
||||
* `react_native_command_unrecognized` – fixes unrecognized `react-native` commands;
|
||||
* `remove_trailing_cedilla` – remove trailling cedillas `ç`, a common typo for european keyboard layouts;
|
||||
* `rm_dir` – adds `-rf` when you trying to remove directory;
|
||||
|
43
tests/rules/test_path_from_history.py
Normal file
43
tests/rules/test_path_from_history.py
Normal file
@ -0,0 +1,43 @@
|
||||
import pytest
|
||||
from thefuck.rules.path_from_history import match, get_new_command
|
||||
from tests.utils import Command
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def history(mocker):
|
||||
return mocker.patch(
|
||||
'thefuck.rules.path_from_history.get_valid_history_without_current',
|
||||
return_value=['cd /opt/java', 'ls ~/work/project/'])
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def path_exists(mocker):
|
||||
path_mock = mocker.patch('thefuck.rules.path_from_history.Path')
|
||||
exists_mock = path_mock.return_value.expanduser.return_value.exists
|
||||
exists_mock.return_value = True
|
||||
return exists_mock
|
||||
|
||||
|
||||
@pytest.mark.parametrize('script, stderr', [
|
||||
('ls project', 'no such file or directory: project'),
|
||||
('cd project', "can't cd to project"),
|
||||
])
|
||||
def test_match(script, stderr):
|
||||
assert match(Command(script, stderr=stderr))
|
||||
|
||||
|
||||
@pytest.mark.parametrize('script, stderr', [
|
||||
('myapp cats', 'no such file or directory: project'),
|
||||
('cd project', ""),
|
||||
])
|
||||
def test_not_match(script, stderr):
|
||||
assert not match(Command(script, stderr=stderr))
|
||||
|
||||
|
||||
@pytest.mark.parametrize('script, stderr, result', [
|
||||
('ls project', 'no such file or directory: project', 'ls ~/work/project'),
|
||||
('cd java', "can't cd to java", 'cd /opt/java'),
|
||||
])
|
||||
def test_get_new_command(script, stderr, result):
|
||||
new_command = get_new_command(Command(script, stderr=stderr))
|
||||
assert new_command[0] == result
|
53
thefuck/rules/path_from_history.py
Normal file
53
thefuck/rules/path_from_history.py
Normal file
@ -0,0 +1,53 @@
|
||||
from collections import Counter
|
||||
import re
|
||||
from thefuck.system import Path
|
||||
from thefuck.utils import (get_valid_history_without_current,
|
||||
memoize, replace_argument)
|
||||
from thefuck.shells import shell
|
||||
|
||||
|
||||
patterns = [r'no such file or directory: (.*)$',
|
||||
r"cannot access '(.*)': No such file or directory",
|
||||
r': (.*): No such file or directory',
|
||||
r"can't cd to (.*)$"]
|
||||
|
||||
|
||||
@memoize
|
||||
def _get_destination(command):
|
||||
for pattern in patterns:
|
||||
found = re.findall(pattern, command.stderr)
|
||||
if found:
|
||||
if found[0] in command.script_parts:
|
||||
return found[0]
|
||||
|
||||
|
||||
def match(command):
|
||||
return bool(_get_destination(command))
|
||||
|
||||
|
||||
def _get_all_absolute_paths_from_history(command):
|
||||
counter = Counter()
|
||||
|
||||
for line in get_valid_history_without_current(command):
|
||||
splitted = shell.split_command(line)
|
||||
|
||||
for param in splitted[1:]:
|
||||
if param.startswith('/') or param.startswith('~'):
|
||||
if param.endswith('/'):
|
||||
param = param[:-1]
|
||||
|
||||
counter[param] += 1
|
||||
|
||||
return (path for path, _ in counter.most_common(None))
|
||||
|
||||
|
||||
def get_new_command(command):
|
||||
destination = _get_destination(command)
|
||||
paths = _get_all_absolute_paths_from_history(command)
|
||||
|
||||
return [replace_argument(command.script, destination, path)
|
||||
for path in paths if path.endswith(destination)
|
||||
and Path(path).expanduser().exists()]
|
||||
|
||||
|
||||
priority = 800
|
@ -268,7 +268,9 @@ def get_valid_history_without_current(command):
|
||||
from thefuck.shells import shell
|
||||
history = shell.get_history()
|
||||
tf_alias = get_alias()
|
||||
executables = set(get_all_executables())
|
||||
executables = set(get_all_executables())\
|
||||
.union(shell.get_builtin_commands())
|
||||
|
||||
return [line for line in _not_corrected(history, tf_alias)
|
||||
if not line.startswith(tf_alias) and not line == command.script
|
||||
and line.split(' ')[0] in executables]
|
||||
|
Loading…
x
Reference in New Issue
Block a user