mirror of
https://github.com/nvbn/thefuck.git
synced 2025-02-07 13:41:21 +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_command` – prepends `python` when you trying to run not executable/without `./` python script;
|
||||||
* `python_execute` – appends missing `.py` when executing Python files;
|
* `python_execute` – appends missing `.py` when executing Python files;
|
||||||
* `quotation_marks` – fixes uneven usage of `'` and `"` when containing args';
|
* `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;
|
* `react_native_command_unrecognized` – fixes unrecognized `react-native` commands;
|
||||||
* `remove_trailing_cedilla` – remove trailling cedillas `ç`, a common typo for european keyboard layouts;
|
* `remove_trailing_cedilla` – remove trailling cedillas `ç`, a common typo for european keyboard layouts;
|
||||||
* `rm_dir` – adds `-rf` when you trying to remove directory;
|
* `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
|
from thefuck.shells import shell
|
||||||
history = shell.get_history()
|
history = shell.get_history()
|
||||||
tf_alias = get_alias()
|
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)
|
return [line for line in _not_corrected(history, tf_alias)
|
||||||
if not line.startswith(tf_alias) and not line == command.script
|
if not line.startswith(tf_alias) and not line == command.script
|
||||||
and line.split(' ')[0] in executables]
|
and line.split(' ')[0] in executables]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user