mirror of
https://github.com/nvbn/thefuck.git
synced 2025-11-03 00:22:10 +00:00
Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f082ba829f | ||
|
|
112e20d7c5 | ||
|
|
95007220fb | ||
|
|
56f636f3d8 | ||
|
|
932a7c5db5 | ||
|
|
1bed4d4e8d | ||
|
|
e0bba379ff | ||
|
|
045959ec47 | ||
|
|
65aeea857e | ||
|
|
793e883073 | ||
|
|
a395ac568c | ||
|
|
29e70e14a0 | ||
|
|
0cdd23edcf | ||
|
|
36d80859a4 | ||
|
|
2b12b4bfce | ||
|
|
91c1fe414a | ||
|
|
f3d377114e | ||
|
|
05f594b918 | ||
|
|
5bf1424613 | ||
|
|
fc3fcf028a | ||
|
|
5864faadef | ||
|
|
608d48e408 | ||
|
|
9380eb1f56 | ||
|
|
fb069b74d7 | ||
|
|
6624ecb3b8 |
16
README.md
16
README.md
@@ -144,13 +144,18 @@ using matched rule and run it. Rules enabled by default:
|
|||||||
* `cd_parent` – changes `cd..` to `cd ..`;
|
* `cd_parent` – changes `cd..` to `cd ..`;
|
||||||
* `cd_mkdir` – creates directories before cd'ing into them;
|
* `cd_mkdir` – creates directories before cd'ing into them;
|
||||||
* `cp_omitting_directory` – adds `-a` when you `cp` directory;
|
* `cp_omitting_directory` – adds `-a` when you `cp` directory;
|
||||||
|
* `dry` – fix repetitions like "git git push";
|
||||||
* `fix_alt_space` – replaces Alt+Space with Space character;
|
* `fix_alt_space` – replaces Alt+Space with Space character;
|
||||||
|
* `git_add` – fix *"Did you forget to 'git add'?"*;
|
||||||
|
* `git_checkout` – creates the branch before checking-out;
|
||||||
* `git_no_command` – fixes wrong git commands like `git brnch`;
|
* `git_no_command` – fixes wrong git commands like `git brnch`;
|
||||||
* `git_push` – adds `--set-upstream origin $branch` to previous failed `git push`;
|
* `git_push` – adds `--set-upstream origin $branch` to previous failed `git push`;
|
||||||
* `has_exists_script` – prepends `./` when script/binary exists;
|
* `has_exists_script` – prepends `./` when script/binary exists;
|
||||||
* `lein_not_task` – fixes wrong `lein` tasks like `lein rpl`;
|
* `lein_not_task` – fixes wrong `lein` tasks like `lein rpl`;
|
||||||
* `mkdir_p` – adds `-p` when you trying to create directory without parent;
|
* `mkdir_p` – adds `-p` when you trying to create directory without parent;
|
||||||
* `no_command` – fixes wrong console commands, for example `vom/vim`;
|
* `no_command` – fixes wrong console commands, for example `vom/vim`;
|
||||||
|
* `man_no_space` – fixes man commands without spaces, for example `mandiff`;
|
||||||
|
* `pacman` – installs app with `pacman` or `yaourt` if it is not installed;
|
||||||
* `pip_unknown_command` – fixes wrong pip commands, for example `pip instatl/pip install`;
|
* `pip_unknown_command` – fixes wrong pip commands, for example `pip instatl/pip install`;
|
||||||
* `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;
|
||||||
* `sl_ls` – changes `sl` to `ls`;
|
* `sl_ls` – changes `sl` to `ls`;
|
||||||
@@ -197,6 +202,8 @@ enabled_by_default = True
|
|||||||
|
|
||||||
def side_effect(command, settings):
|
def side_effect(command, settings):
|
||||||
subprocess.call('chmod 777 .', shell=True)
|
subprocess.call('chmod 777 .', shell=True)
|
||||||
|
|
||||||
|
priority = 1000 # Lower first
|
||||||
```
|
```
|
||||||
|
|
||||||
[More examples of rules](https://github.com/nvbn/thefuck/tree/master/thefuck/rules),
|
[More examples of rules](https://github.com/nvbn/thefuck/tree/master/thefuck/rules),
|
||||||
@@ -209,7 +216,8 @@ The Fuck has a few settings parameters, they can be changed in `~/.thefuck/setti
|
|||||||
* `rules` – list of enabled rules, by default `thefuck.conf.DEFAULT_RULES`;
|
* `rules` – list of enabled rules, by default `thefuck.conf.DEFAULT_RULES`;
|
||||||
* `require_confirmation` – require confirmation before running new command, by default `False`;
|
* `require_confirmation` – require confirmation before running new command, by default `False`;
|
||||||
* `wait_command` – max amount of time in seconds for getting previous command output;
|
* `wait_command` – max amount of time in seconds for getting previous command output;
|
||||||
* `no_colors` – disable colored output.
|
* `no_colors` – disable colored output;
|
||||||
|
* `priority` – dict with rules priorities, rule with lower `priority` will be matched first.
|
||||||
|
|
||||||
Example of `settings.py`:
|
Example of `settings.py`:
|
||||||
|
|
||||||
@@ -218,6 +226,7 @@ rules = ['sudo', 'no_command']
|
|||||||
require_confirmation = True
|
require_confirmation = True
|
||||||
wait_command = 10
|
wait_command = 10
|
||||||
no_colors = False
|
no_colors = False
|
||||||
|
priority = {'sudo': 100, 'no_command': 9999}
|
||||||
```
|
```
|
||||||
|
|
||||||
Or via environment variables:
|
Or via environment variables:
|
||||||
@@ -225,7 +234,9 @@ Or via environment variables:
|
|||||||
* `THEFUCK_RULES` – list of enabled rules, like `DEFAULT_RULES:rm_root` or `sudo:no_command`;
|
* `THEFUCK_RULES` – list of enabled rules, like `DEFAULT_RULES:rm_root` or `sudo:no_command`;
|
||||||
* `THEFUCK_REQUIRE_CONFIRMATION` – require confirmation before running new command, `true/false`;
|
* `THEFUCK_REQUIRE_CONFIRMATION` – require confirmation before running new command, `true/false`;
|
||||||
* `THEFUCK_WAIT_COMMAND` – max amount of time in seconds for getting previous command output;
|
* `THEFUCK_WAIT_COMMAND` – max amount of time in seconds for getting previous command output;
|
||||||
* `THEFUCK_NO_COLORS` – disable colored output, `true/false`.
|
* `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.
|
||||||
|
|
||||||
For example:
|
For example:
|
||||||
|
|
||||||
@@ -234,6 +245,7 @@ export THEFUCK_RULES='sudo:no_command'
|
|||||||
export THEFUCK_REQUIRE_CONFIRMATION='true'
|
export THEFUCK_REQUIRE_CONFIRMATION='true'
|
||||||
export THEFUCK_WAIT_COMMAND=10
|
export THEFUCK_WAIT_COMMAND=10
|
||||||
export THEFUCK_NO_COLORS='false'
|
export THEFUCK_NO_COLORS='false'
|
||||||
|
export THEFUCK_PRIORITY='no_command=9999:apt_get=100'
|
||||||
```
|
```
|
||||||
|
|
||||||
## Developing
|
## Developing
|
||||||
|
|||||||
@@ -28,4 +28,4 @@ call('git commit -am "Bump to {}"'.format(version), shell=True)
|
|||||||
call('git tag {}'.format(version), shell=True)
|
call('git tag {}'.format(version), shell=True)
|
||||||
call('git push', shell=True)
|
call('git push', shell=True)
|
||||||
call('git push --tags', shell=True)
|
call('git push --tags', shell=True)
|
||||||
call('python setup.py sdist upload', shell=True)
|
call('python setup.py sdist bdist_wheel upload', shell=True)
|
||||||
|
|||||||
@@ -1,2 +1,4 @@
|
|||||||
pytest
|
pytest
|
||||||
mock
|
mock
|
||||||
|
pytest-mock
|
||||||
|
wheel
|
||||||
|
|||||||
5
setup.py
5
setup.py
@@ -1,7 +1,7 @@
|
|||||||
from setuptools import setup, find_packages
|
from setuptools import setup, find_packages
|
||||||
|
|
||||||
|
|
||||||
VERSION = '1.35'
|
VERSION = '1.38'
|
||||||
|
|
||||||
|
|
||||||
setup(name='thefuck',
|
setup(name='thefuck',
|
||||||
@@ -17,4 +17,5 @@ setup(name='thefuck',
|
|||||||
zip_safe=False,
|
zip_safe=False,
|
||||||
install_requires=['pathlib', 'psutil', 'colorama', 'six'],
|
install_requires=['pathlib', 'psutil', 'colorama', 'six'],
|
||||||
entry_points={'console_scripts': [
|
entry_points={'console_scripts': [
|
||||||
'thefuck = thefuck.main:main', 'thefuck-alias = thefuck.main:alias']})
|
'thefuck = thefuck.main:main',
|
||||||
|
'thefuck-alias = thefuck.shells:app_alias']})
|
||||||
|
|||||||
17
tests/rules/test_dry.py
Normal file
17
tests/rules/test_dry.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import pytest
|
||||||
|
from thefuck.rules.dry import match, get_new_command
|
||||||
|
from tests.utils import Command
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('command', [
|
||||||
|
Command(script='cd cd foo'),
|
||||||
|
Command(script='git git push origin/master')])
|
||||||
|
def test_match(command):
|
||||||
|
assert match(command, None)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('command, new_command', [
|
||||||
|
(Command('cd cd foo'), 'cd foo'),
|
||||||
|
(Command('git git push origin/master'), 'git push origin/master')])
|
||||||
|
def test_get_new_command(command, new_command):
|
||||||
|
assert get_new_command(command, None) == new_command
|
||||||
12
tests/rules/test_man_no_space.py
Normal file
12
tests/rules/test_man_no_space.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
from thefuck.rules.man_no_space import match, get_new_command
|
||||||
|
from tests.utils import Command
|
||||||
|
|
||||||
|
|
||||||
|
def test_match():
|
||||||
|
assert match(Command('mandiff', stderr='mandiff: command not found'), None)
|
||||||
|
assert not match(Command(), None)
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_new_command():
|
||||||
|
assert get_new_command(
|
||||||
|
Command('mandiff'), None) == 'man diff'
|
||||||
@@ -14,10 +14,8 @@ def test_default(enabled, rules, result):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def load_source(monkeypatch):
|
def load_source(mocker):
|
||||||
mock = Mock()
|
return mocker.patch('thefuck.conf.load_source')
|
||||||
monkeypatch.setattr('thefuck.conf.load_source', mock)
|
|
||||||
return mock
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
@@ -40,12 +38,14 @@ class TestSettingsFromFile(object):
|
|||||||
load_source.return_value = Mock(rules=['test'],
|
load_source.return_value = Mock(rules=['test'],
|
||||||
wait_command=10,
|
wait_command=10,
|
||||||
require_confirmation=True,
|
require_confirmation=True,
|
||||||
no_colors=True)
|
no_colors=True,
|
||||||
|
priority={'vim': 100})
|
||||||
settings = conf.get_settings(Mock())
|
settings = conf.get_settings(Mock())
|
||||||
assert settings.rules == ['test']
|
assert settings.rules == ['test']
|
||||||
assert settings.wait_command == 10
|
assert settings.wait_command == 10
|
||||||
assert settings.require_confirmation is True
|
assert settings.require_confirmation is True
|
||||||
assert settings.no_colors is True
|
assert settings.no_colors is True
|
||||||
|
assert settings.priority == {'vim': 100}
|
||||||
|
|
||||||
def test_from_file_with_DEFAULT(self, load_source):
|
def test_from_file_with_DEFAULT(self, load_source):
|
||||||
load_source.return_value = Mock(rules=conf.DEFAULT_RULES + ['test'],
|
load_source.return_value = Mock(rules=conf.DEFAULT_RULES + ['test'],
|
||||||
@@ -62,12 +62,14 @@ class TestSettingsFromEnv(object):
|
|||||||
environ.update({'THEFUCK_RULES': 'bash:lisp',
|
environ.update({'THEFUCK_RULES': 'bash:lisp',
|
||||||
'THEFUCK_WAIT_COMMAND': '55',
|
'THEFUCK_WAIT_COMMAND': '55',
|
||||||
'THEFUCK_REQUIRE_CONFIRMATION': 'true',
|
'THEFUCK_REQUIRE_CONFIRMATION': 'true',
|
||||||
'THEFUCK_NO_COLORS': 'false'})
|
'THEFUCK_NO_COLORS': 'false',
|
||||||
|
'THEFUCK_PRIORITY': 'bash=10:lisp=wrong:vim=15'})
|
||||||
settings = conf.get_settings(Mock())
|
settings = conf.get_settings(Mock())
|
||||||
assert settings.rules == ['bash', 'lisp']
|
assert settings.rules == ['bash', 'lisp']
|
||||||
assert settings.wait_command == 55
|
assert settings.wait_command == 55
|
||||||
assert settings.require_confirmation is True
|
assert settings.require_confirmation is True
|
||||||
assert settings.no_colors is False
|
assert settings.no_colors is False
|
||||||
|
assert settings.priority == {'bash': 10, 'vim': 15}
|
||||||
|
|
||||||
def test_from_env_with_DEFAULT(self, environ):
|
def test_from_env_with_DEFAULT(self, environ):
|
||||||
environ.update({'THEFUCK_RULES': 'DEFAULT_RULES:bash:lisp'})
|
environ.update({'THEFUCK_RULES': 'DEFAULT_RULES:bash:lisp'})
|
||||||
|
|||||||
@@ -6,34 +6,55 @@ from thefuck import main, conf, types
|
|||||||
from tests.utils import Rule, Command
|
from tests.utils import Rule, Command
|
||||||
|
|
||||||
|
|
||||||
def test_load_rule(monkeypatch):
|
def test_load_rule(mocker):
|
||||||
match = object()
|
match = object()
|
||||||
get_new_command = object()
|
get_new_command = object()
|
||||||
load_source = Mock()
|
load_source = mocker.patch(
|
||||||
load_source.return_value = Mock(match=match,
|
'thefuck.main.load_source',
|
||||||
get_new_command=get_new_command,
|
return_value=Mock(match=match,
|
||||||
enabled_by_default=True)
|
get_new_command=get_new_command,
|
||||||
monkeypatch.setattr('thefuck.main.load_source', load_source)
|
enabled_by_default=True,
|
||||||
|
priority=900))
|
||||||
assert main.load_rule(Path('/rules/bash.py')) \
|
assert main.load_rule(Path('/rules/bash.py')) \
|
||||||
== Rule('bash', match, get_new_command)
|
== Rule('bash', match, get_new_command, priority=900)
|
||||||
load_source.assert_called_once_with('bash', '/rules/bash.py')
|
load_source.assert_called_once_with('bash', '/rules/bash.py')
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('conf_rules, rules', [
|
class TestGetRules(object):
|
||||||
(conf.DEFAULT_RULES, [Rule('bash', 'bash', 'bash'),
|
@pytest.fixture(autouse=True)
|
||||||
Rule('lisp', 'lisp', 'lisp'),
|
def glob(self, mocker):
|
||||||
Rule('bash', 'bash', 'bash'),
|
return mocker.patch('thefuck.main.Path.glob', return_value=[])
|
||||||
Rule('lisp', 'lisp', 'lisp')]),
|
|
||||||
(types.RulesNamesList(['bash']), [Rule('bash', 'bash', 'bash'),
|
def _compare_names(self, rules, names):
|
||||||
Rule('bash', 'bash', 'bash')])])
|
return [r.name for r in rules] == names
|
||||||
def test_get_rules(monkeypatch, conf_rules, rules):
|
|
||||||
monkeypatch.setattr(
|
@pytest.mark.parametrize('conf_rules, rules', [
|
||||||
'thefuck.main.Path.glob',
|
(conf.DEFAULT_RULES, ['bash', 'lisp', 'bash', 'lisp']),
|
||||||
lambda *_: [PosixPath('bash.py'), PosixPath('lisp.py')])
|
(types.RulesNamesList(['bash']), ['bash', 'bash'])])
|
||||||
monkeypatch.setattr('thefuck.main.load_source',
|
def test_get(self, monkeypatch, glob, conf_rules, rules):
|
||||||
lambda x, _: Mock(match=x, get_new_command=x,
|
glob.return_value = [PosixPath('bash.py'), PosixPath('lisp.py')]
|
||||||
enabled_by_default=True))
|
monkeypatch.setattr('thefuck.main.load_source',
|
||||||
assert list(main.get_rules(Path('~'), Mock(rules=conf_rules))) == rules
|
lambda x, _: Rule(x))
|
||||||
|
assert self._compare_names(
|
||||||
|
main.get_rules(Path('~'), Mock(rules=conf_rules, priority={})),
|
||||||
|
rules)
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('priority, unordered, ordered', [
|
||||||
|
({},
|
||||||
|
[Rule('bash', priority=100), Rule('python', priority=5)],
|
||||||
|
['python', 'bash']),
|
||||||
|
({},
|
||||||
|
[Rule('lisp', priority=9999), Rule('c', priority=conf.DEFAULT_PRIORITY)],
|
||||||
|
['c', 'lisp']),
|
||||||
|
({'python': 9999},
|
||||||
|
[Rule('bash', priority=100), Rule('python', priority=5)],
|
||||||
|
['bash', 'python'])])
|
||||||
|
def test_ordered_by_priority(self, monkeypatch, priority, unordered, ordered):
|
||||||
|
monkeypatch.setattr('thefuck.main._get_loaded_rules',
|
||||||
|
lambda *_: unordered)
|
||||||
|
assert self._compare_names(
|
||||||
|
main.get_rules(Path('~'), Mock(priority=priority)),
|
||||||
|
ordered)
|
||||||
|
|
||||||
|
|
||||||
class TestGetCommand(object):
|
class TestGetCommand(object):
|
||||||
@@ -64,6 +85,7 @@ class TestGetCommand(object):
|
|||||||
stdout=PIPE,
|
stdout=PIPE,
|
||||||
stderr=PIPE,
|
stderr=PIPE,
|
||||||
env={'LANG': 'C'})
|
env={'LANG': 'C'})
|
||||||
|
|
||||||
@pytest.mark.parametrize('args, result', [
|
@pytest.mark.parametrize('args, result', [
|
||||||
(['thefuck', 'ls', '-la'], 'ls -la'),
|
(['thefuck', 'ls', '-la'], 'ls -la'),
|
||||||
(['thefuck', 'ls'], 'ls')])
|
(['thefuck', 'ls'], 'ls')])
|
||||||
@@ -94,10 +116,8 @@ class TestGetMatchedRule(object):
|
|||||||
|
|
||||||
class TestRunRule(object):
|
class TestRunRule(object):
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
def confirm(self, monkeypatch):
|
def confirm(self, mocker):
|
||||||
mock = Mock(return_value=True)
|
return mocker.patch('thefuck.main.confirm', return_value=True)
|
||||||
monkeypatch.setattr('thefuck.main.confirm', mock)
|
|
||||||
return mock
|
|
||||||
|
|
||||||
def test_run_rule(self, capsys):
|
def test_run_rule(self, capsys):
|
||||||
main.run_rule(Rule(get_new_command=lambda *_: 'new-command'),
|
main.run_rule(Rule(get_new_command=lambda *_: 'new-command'),
|
||||||
@@ -123,10 +143,8 @@ class TestRunRule(object):
|
|||||||
|
|
||||||
class TestConfirm(object):
|
class TestConfirm(object):
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def stdin(self, monkeypatch):
|
def stdin(self, mocker):
|
||||||
mock = Mock(return_value='\n')
|
return mocker.patch('sys.stdin.read', return_value='\n')
|
||||||
monkeypatch.setattr('sys.stdin.read', mock)
|
|
||||||
return mock
|
|
||||||
|
|
||||||
def test_when_not_required(self, capsys):
|
def test_when_not_required(self, capsys):
|
||||||
assert main.confirm('command', None, Mock(require_confirmation=False))
|
assert main.confirm('command', None, Mock(require_confirmation=False))
|
||||||
|
|||||||
@@ -1,20 +1,15 @@
|
|||||||
import pytest
|
import pytest
|
||||||
from mock import Mock, MagicMock
|
|
||||||
from thefuck import shells
|
from thefuck import shells
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def builtins_open(monkeypatch):
|
def builtins_open(mocker):
|
||||||
mock = MagicMock()
|
return mocker.patch('six.moves.builtins.open')
|
||||||
monkeypatch.setattr('six.moves.builtins.open', mock)
|
|
||||||
return mock
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def isfile(monkeypatch):
|
def isfile(mocker):
|
||||||
mock = Mock(return_value=True)
|
return mocker.patch('os.path.isfile', return_value=True)
|
||||||
monkeypatch.setattr('os.path.isfile', mock)
|
|
||||||
return mock
|
|
||||||
|
|
||||||
|
|
||||||
class TestGeneric(object):
|
class TestGeneric(object):
|
||||||
@@ -32,9 +27,8 @@ class TestGeneric(object):
|
|||||||
@pytest.mark.usefixtures('isfile')
|
@pytest.mark.usefixtures('isfile')
|
||||||
class TestBash(object):
|
class TestBash(object):
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
def Popen(self, monkeypatch):
|
def Popen(self, mocker):
|
||||||
mock = Mock()
|
mock = mocker.patch('thefuck.shells.Popen')
|
||||||
monkeypatch.setattr('thefuck.shells.Popen', mock)
|
|
||||||
mock.return_value.stdout.read.return_value = (
|
mock.return_value.stdout.read.return_value = (
|
||||||
b'alias l=\'ls -CF\'\n'
|
b'alias l=\'ls -CF\'\n'
|
||||||
b'alias la=\'ls -A\'\n'
|
b'alias la=\'ls -A\'\n'
|
||||||
@@ -52,16 +46,15 @@ class TestBash(object):
|
|||||||
|
|
||||||
def test_put_to_history(self, builtins_open):
|
def test_put_to_history(self, builtins_open):
|
||||||
shells.Bash().put_to_history('ls')
|
shells.Bash().put_to_history('ls')
|
||||||
builtins_open.return_value.__enter__.return_value.\
|
builtins_open.return_value.__enter__.return_value. \
|
||||||
write.assert_called_once_with('ls\n')
|
write.assert_called_once_with('ls\n')
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures('isfile')
|
@pytest.mark.usefixtures('isfile')
|
||||||
class TestZsh(object):
|
class TestZsh(object):
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
def Popen(self, monkeypatch):
|
def Popen(self, mocker):
|
||||||
mock = Mock()
|
mock = mocker.patch('thefuck.shells.Popen')
|
||||||
monkeypatch.setattr('thefuck.shells.Popen', mock)
|
|
||||||
mock.return_value.stdout.read.return_value = (
|
mock.return_value.stdout.read.return_value = (
|
||||||
b'l=\'ls -CF\'\n'
|
b'l=\'ls -CF\'\n'
|
||||||
b'la=\'ls -A\'\n'
|
b'la=\'ls -A\'\n'
|
||||||
@@ -77,9 +70,9 @@ class TestZsh(object):
|
|||||||
def test_to_shell(self):
|
def test_to_shell(self):
|
||||||
assert shells.Zsh().to_shell('pwd') == 'pwd'
|
assert shells.Zsh().to_shell('pwd') == 'pwd'
|
||||||
|
|
||||||
def test_put_to_history(self, builtins_open, monkeypatch):
|
def test_put_to_history(self, builtins_open, mocker):
|
||||||
monkeypatch.setattr('thefuck.shells.time',
|
mocker.patch('thefuck.shells.time',
|
||||||
lambda: 1430707243.3517463)
|
return_value=1430707243.3517463)
|
||||||
shells.Zsh().put_to_history('ls')
|
shells.Zsh().put_to_history('ls')
|
||||||
builtins_open.return_value.__enter__.return_value. \
|
builtins_open.return_value.__enter__.return_value. \
|
||||||
write.assert_called_once_with(': 1430707243:0;ls\n')
|
write.assert_called_once_with(': 1430707243:0;ls\n')
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
from thefuck import types
|
from thefuck import types
|
||||||
|
from thefuck.conf import DEFAULT_PRIORITY
|
||||||
|
|
||||||
|
|
||||||
def Command(script='', stdout='', stderr=''):
|
def Command(script='', stdout='', stderr=''):
|
||||||
@@ -8,6 +9,8 @@ def Command(script='', stdout='', stderr=''):
|
|||||||
def Rule(name='', match=lambda *_: True,
|
def Rule(name='', match=lambda *_: True,
|
||||||
get_new_command=lambda *_: '',
|
get_new_command=lambda *_: '',
|
||||||
enabled_by_default=True,
|
enabled_by_default=True,
|
||||||
side_effect=None):
|
side_effect=None,
|
||||||
|
priority=DEFAULT_PRIORITY):
|
||||||
return types.Rule(name, match, get_new_command,
|
return types.Rule(name, match, get_new_command,
|
||||||
enabled_by_default, side_effect)
|
enabled_by_default, side_effect,
|
||||||
|
priority)
|
||||||
|
|||||||
@@ -22,17 +22,20 @@ class _DefaultRulesNames(types.RulesNamesList):
|
|||||||
|
|
||||||
|
|
||||||
DEFAULT_RULES = _DefaultRulesNames([])
|
DEFAULT_RULES = _DefaultRulesNames([])
|
||||||
|
DEFAULT_PRIORITY = 1000
|
||||||
|
|
||||||
|
|
||||||
DEFAULT_SETTINGS = {'rules': DEFAULT_RULES,
|
DEFAULT_SETTINGS = {'rules': DEFAULT_RULES,
|
||||||
'wait_command': 3,
|
'wait_command': 3,
|
||||||
'require_confirmation': False,
|
'require_confirmation': False,
|
||||||
'no_colors': False}
|
'no_colors': False,
|
||||||
|
'priority': {}}
|
||||||
|
|
||||||
ENV_TO_ATTR = {'THEFUCK_RULES': 'rules',
|
ENV_TO_ATTR = {'THEFUCK_RULES': 'rules',
|
||||||
'THEFUCK_WAIT_COMMAND': 'wait_command',
|
'THEFUCK_WAIT_COMMAND': 'wait_command',
|
||||||
'THEFUCK_REQUIRE_CONFIRMATION': 'require_confirmation',
|
'THEFUCK_REQUIRE_CONFIRMATION': 'require_confirmation',
|
||||||
'THEFUCK_NO_COLORS': 'no_colors'}
|
'THEFUCK_NO_COLORS': 'no_colors',
|
||||||
|
'THEFUCK_PRIORITY': 'priority'}
|
||||||
|
|
||||||
|
|
||||||
SETTINGS_HEADER = u"""# ~/.thefuck/settings.py: The Fuck settings file
|
SETTINGS_HEADER = u"""# ~/.thefuck/settings.py: The Fuck settings file
|
||||||
@@ -65,16 +68,29 @@ def _rules_from_env(val):
|
|||||||
return val
|
return val
|
||||||
|
|
||||||
|
|
||||||
|
def _priority_from_env(val):
|
||||||
|
"""Gets priority pairs from env."""
|
||||||
|
for part in val.split(':'):
|
||||||
|
try:
|
||||||
|
rule, priority = part.split('=')
|
||||||
|
yield rule, int(priority)
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
|
||||||
def _val_from_env(env, attr):
|
def _val_from_env(env, attr):
|
||||||
"""Transforms env-strings to python."""
|
"""Transforms env-strings to python."""
|
||||||
val = os.environ[env]
|
val = os.environ[env]
|
||||||
if attr == 'rules':
|
if attr == 'rules':
|
||||||
val = _rules_from_env(val)
|
return _rules_from_env(val)
|
||||||
|
elif attr == 'priority':
|
||||||
|
return dict(_priority_from_env(val))
|
||||||
elif attr == 'wait_command':
|
elif attr == 'wait_command':
|
||||||
val = int(val)
|
return int(val)
|
||||||
elif attr in ('require_confirmation', 'no_colors'):
|
elif attr in ('require_confirmation', 'no_colors'):
|
||||||
val = val.lower() == 'true'
|
return val.lower() == 'true'
|
||||||
return val
|
else:
|
||||||
|
return val
|
||||||
|
|
||||||
|
|
||||||
def _settings_from_env():
|
def _settings_from_env():
|
||||||
|
|||||||
@@ -26,7 +26,17 @@ def load_rule(rule):
|
|||||||
return types.Rule(rule.name[:-3], rule_module.match,
|
return types.Rule(rule.name[:-3], rule_module.match,
|
||||||
rule_module.get_new_command,
|
rule_module.get_new_command,
|
||||||
getattr(rule_module, 'enabled_by_default', True),
|
getattr(rule_module, 'enabled_by_default', True),
|
||||||
getattr(rule_module, 'side_effect', None))
|
getattr(rule_module, 'side_effect', None),
|
||||||
|
getattr(rule_module, 'priority', conf.DEFAULT_PRIORITY))
|
||||||
|
|
||||||
|
|
||||||
|
def _get_loaded_rules(rules, settings):
|
||||||
|
"""Yields all available rules."""
|
||||||
|
for rule in rules:
|
||||||
|
if rule.name != '__init__.py':
|
||||||
|
loaded_rule = load_rule(rule)
|
||||||
|
if loaded_rule in settings.rules:
|
||||||
|
yield loaded_rule
|
||||||
|
|
||||||
|
|
||||||
def get_rules(user_dir, settings):
|
def get_rules(user_dir, settings):
|
||||||
@@ -35,11 +45,9 @@ def get_rules(user_dir, settings):
|
|||||||
.joinpath('rules') \
|
.joinpath('rules') \
|
||||||
.glob('*.py')
|
.glob('*.py')
|
||||||
user = user_dir.joinpath('rules').glob('*.py')
|
user = user_dir.joinpath('rules').glob('*.py')
|
||||||
for rule in sorted(list(bundled)) + list(user):
|
rules = _get_loaded_rules(sorted(bundled) + sorted(user), settings)
|
||||||
if rule.name != '__init__.py':
|
return sorted(rules, key=lambda rule: settings.priority.get(
|
||||||
loaded_rule = load_rule(rule)
|
rule.name, rule.priority))
|
||||||
if loaded_rule in settings.rules:
|
|
||||||
yield loaded_rule
|
|
||||||
|
|
||||||
|
|
||||||
def wait_output(settings, popen):
|
def wait_output(settings, popen):
|
||||||
@@ -113,10 +121,6 @@ def run_rule(rule, command, settings):
|
|||||||
print(new_command)
|
print(new_command)
|
||||||
|
|
||||||
|
|
||||||
def alias():
|
|
||||||
print(shells.app_alias())
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
colorama.init()
|
colorama.init()
|
||||||
user_dir = setup_user_dir()
|
user_dir = setup_user_dir()
|
||||||
|
|||||||
12
thefuck/rules/dry.py
Normal file
12
thefuck/rules/dry.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
def match(command, settings):
|
||||||
|
split_command = command.script.split()
|
||||||
|
|
||||||
|
return len(split_command) >= 2 and split_command[0] == split_command[1]
|
||||||
|
|
||||||
|
|
||||||
|
def get_new_command(command, settings):
|
||||||
|
return command.script[command.script.find(' ')+1:]
|
||||||
|
|
||||||
|
# it should be rare enough to actually have to type twice the same word, so
|
||||||
|
# this rule can have a higher priority to come before things like "cd cd foo"
|
||||||
|
priority = 900
|
||||||
15
thefuck/rules/git_add.py
Normal file
15
thefuck/rules/git_add.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
def match(command, settings):
|
||||||
|
return ('git' in command.script
|
||||||
|
and 'did not match any file(s) known to git.' in command.stderr
|
||||||
|
and "Did you forget to 'git add'?" in command.stderr)
|
||||||
|
|
||||||
|
|
||||||
|
def get_new_command(command, settings):
|
||||||
|
missing_file = re.findall(
|
||||||
|
r"error: pathspec '([^']*)' "
|
||||||
|
"did not match any file\(s\) known to git.", command.stderr)[0]
|
||||||
|
|
||||||
|
return 'git add -- {} && {}'.format(missing_file, command.script)
|
||||||
15
thefuck/rules/git_checkout.py
Normal file
15
thefuck/rules/git_checkout.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
def match(command, settings):
|
||||||
|
return ('git' in command.script
|
||||||
|
and 'did not match any file(s) known to git.' in command.stderr
|
||||||
|
and "Did you forget to 'git add'?" not in command.stderr)
|
||||||
|
|
||||||
|
|
||||||
|
def get_new_command(command, settings):
|
||||||
|
missing_file = re.findall(
|
||||||
|
r"error: pathspec '([^']*)' "
|
||||||
|
"did not match any file\(s\) known to git.", command.stderr)[0]
|
||||||
|
|
||||||
|
return 'git branch {} && {}'.format(missing_file, command.script)
|
||||||
9
thefuck/rules/man_no_space.py
Normal file
9
thefuck/rules/man_no_space.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
def match(command, settings):
|
||||||
|
return (command.script.startswith(u'man')
|
||||||
|
and u'command not found' in command.stderr.lower())
|
||||||
|
|
||||||
|
|
||||||
|
def get_new_command(command, settings):
|
||||||
|
return u'man {}'.format(command.script[3:])
|
||||||
|
|
||||||
|
priority = 2000
|
||||||
@@ -31,3 +31,6 @@ def get_new_command(command, settings):
|
|||||||
new_command = get_close_matches(old_command,
|
new_command = get_close_matches(old_command,
|
||||||
_get_all_bins())[0]
|
_get_all_bins())[0]
|
||||||
return ' '.join([new_command] + command.script.split(' ')[1:])
|
return ' '.join([new_command] + command.script.split(' ')[1:])
|
||||||
|
|
||||||
|
|
||||||
|
priority = 3000
|
||||||
|
|||||||
43
thefuck/rules/pacman.py
Normal file
43
thefuck/rules/pacman.py
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import subprocess
|
||||||
|
from thefuck.utils import DEVNULL
|
||||||
|
|
||||||
|
|
||||||
|
def __command_available(command):
|
||||||
|
try:
|
||||||
|
subprocess.check_output([command], stderr=DEVNULL)
|
||||||
|
return True
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
# command exists but is not happy to be called without any argument
|
||||||
|
return True
|
||||||
|
except OSError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def __get_pkgfile(command):
|
||||||
|
try:
|
||||||
|
return subprocess.check_output(
|
||||||
|
['pkgfile', '-b', '-v', command.script.split(" ")[0]],
|
||||||
|
universal_newlines=True, stderr=subprocess.DEVNULL
|
||||||
|
).split()
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def match(command, settings):
|
||||||
|
return 'not found' in command.stderr and __get_pkgfile(command)
|
||||||
|
|
||||||
|
|
||||||
|
def get_new_command(command, settings):
|
||||||
|
package = __get_pkgfile(command)[0]
|
||||||
|
|
||||||
|
return '{} -S {} && {}'.format(pacman, package, command.script)
|
||||||
|
|
||||||
|
|
||||||
|
if not __command_available('pkgfile'):
|
||||||
|
enabled_by_default = False
|
||||||
|
elif __command_available('yaourt'):
|
||||||
|
pacman = 'yaourt'
|
||||||
|
elif __command_available('pacman'):
|
||||||
|
pacman = 'sudo pacman'
|
||||||
|
else:
|
||||||
|
enabled_by_default = False
|
||||||
@@ -8,9 +8,7 @@ from subprocess import Popen, PIPE
|
|||||||
from time import time
|
from time import time
|
||||||
import os
|
import os
|
||||||
from psutil import Process
|
from psutil import Process
|
||||||
|
from .utils import DEVNULL
|
||||||
|
|
||||||
FNULL = open(os.devnull, 'w')
|
|
||||||
|
|
||||||
|
|
||||||
class Generic(object):
|
class Generic(object):
|
||||||
@@ -58,7 +56,7 @@ class Bash(Generic):
|
|||||||
return name, value
|
return name, value
|
||||||
|
|
||||||
def _get_aliases(self):
|
def _get_aliases(self):
|
||||||
proc = Popen('bash -ic alias', stdout=PIPE, stderr=FNULL, shell=True)
|
proc = Popen('bash -ic alias', stdout=PIPE, stderr=DEVNULL, shell=True)
|
||||||
return dict(
|
return dict(
|
||||||
self._parse_alias(alias)
|
self._parse_alias(alias)
|
||||||
for alias in proc.stdout.read().decode('utf-8').split('\n')
|
for alias in proc.stdout.read().decode('utf-8').split('\n')
|
||||||
@@ -80,7 +78,7 @@ class Zsh(Generic):
|
|||||||
return name, value
|
return name, value
|
||||||
|
|
||||||
def _get_aliases(self):
|
def _get_aliases(self):
|
||||||
proc = Popen('zsh -ic alias', stdout=PIPE, stderr=FNULL, shell=True)
|
proc = Popen('zsh -ic alias', stdout=PIPE, stderr=DEVNULL, shell=True)
|
||||||
return dict(
|
return dict(
|
||||||
self._parse_alias(alias)
|
self._parse_alias(alias)
|
||||||
for alias in proc.stdout.read().decode('utf-8').split('\n')
|
for alias in proc.stdout.read().decode('utf-8').split('\n')
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ from collections import namedtuple
|
|||||||
Command = namedtuple('Command', ('script', 'stdout', 'stderr'))
|
Command = namedtuple('Command', ('script', 'stdout', 'stderr'))
|
||||||
|
|
||||||
Rule = namedtuple('Rule', ('name', 'match', 'get_new_command',
|
Rule = namedtuple('Rule', ('name', 'match', 'get_new_command',
|
||||||
'enabled_by_default', 'side_effect'))
|
'enabled_by_default', 'side_effect',
|
||||||
|
'priority'))
|
||||||
|
|
||||||
|
|
||||||
class RulesNamesList(list):
|
class RulesNamesList(list):
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ import six
|
|||||||
from .types import Command
|
from .types import Command
|
||||||
|
|
||||||
|
|
||||||
|
DEVNULL = open(os.devnull, 'w')
|
||||||
|
|
||||||
|
|
||||||
def which(program):
|
def which(program):
|
||||||
"""Returns `program` path or `None`."""
|
"""Returns `program` path or `None`."""
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user