diff --git a/README.md b/README.md index d7ed5910..07ec8bb0 100644 --- a/README.md +++ b/README.md @@ -199,6 +199,8 @@ enabled_by_default = True def side_effect(command, settings): subprocess.call('chmod 777 .', shell=True) + +priority = 1000 # Lower first ``` [More examples of rules](https://github.com/nvbn/thefuck/tree/master/thefuck/rules), diff --git a/tests/test_main.py b/tests/test_main.py index 681b6164..de750510 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -12,28 +12,43 @@ def test_load_rule(monkeypatch): load_source = Mock() load_source.return_value = Mock(match=match, get_new_command=get_new_command, - enabled_by_default=True) + enabled_by_default=True, + priority=900) monkeypatch.setattr('thefuck.main.load_source', load_source) 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') -@pytest.mark.parametrize('conf_rules, rules', [ - (conf.DEFAULT_RULES, [Rule('bash', 'bash', 'bash'), - Rule('lisp', 'lisp', 'lisp'), - Rule('bash', 'bash', 'bash'), - Rule('lisp', 'lisp', 'lisp')]), - (types.RulesNamesList(['bash']), [Rule('bash', 'bash', 'bash'), - Rule('bash', 'bash', 'bash')])]) -def test_get_rules(monkeypatch, conf_rules, rules): - monkeypatch.setattr( - 'thefuck.main.Path.glob', - lambda *_: [PosixPath('bash.py'), PosixPath('lisp.py')]) - monkeypatch.setattr('thefuck.main.load_source', - lambda x, _: Mock(match=x, get_new_command=x, - enabled_by_default=True)) - assert list(main.get_rules(Path('~'), Mock(rules=conf_rules))) == rules +class TestGetRules(object): + @pytest.fixture(autouse=True) + def glob(self, monkeypatch): + mock = Mock(return_value=[]) + monkeypatch.setattr('thefuck.main.Path.glob', mock) + return mock + + def _compare_names(self, rules, names): + return [r.name for r in rules] == names + + @pytest.mark.parametrize('conf_rules, rules', [ + (conf.DEFAULT_RULES, ['bash', 'lisp', 'bash', 'lisp']), + (types.RulesNamesList(['bash']), ['bash', 'bash'])]) + def test_get(self, monkeypatch, glob, conf_rules, rules): + glob.return_value = [PosixPath('bash.py'), PosixPath('lisp.py')] + monkeypatch.setattr('thefuck.main.load_source', + lambda x, _: Rule(x)) + assert self._compare_names( + main.get_rules(Path('~'), Mock(rules=conf_rules)), rules) + + @pytest.mark.parametrize('unordered, ordered', [ + ([Rule('bash', priority=100), Rule('python', priority=5)], + ['python', 'bash']), + ([Rule('lisp', priority=9999), Rule('c', priority=conf.DEFAULT_PRIORITY)], + ['c', 'lisp'])]) + def test_ordered_by_priority(self, monkeypatch, unordered, ordered): + monkeypatch.setattr('thefuck.main._get_loaded_rules', + lambda *_: unordered) + assert self._compare_names(main.get_rules(Path('~'), Mock()), ordered) class TestGetCommand(object): @@ -64,6 +79,7 @@ class TestGetCommand(object): stdout=PIPE, stderr=PIPE, env={'LANG': 'C'}) + @pytest.mark.parametrize('args, result', [ (['thefuck', 'ls', '-la'], 'ls -la'), (['thefuck', 'ls'], 'ls')]) diff --git a/tests/utils.py b/tests/utils.py index 02e19e7b..4641971d 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -1,4 +1,5 @@ from thefuck import types +from thefuck.conf import DEFAULT_PRIORITY def Command(script='', stdout='', stderr=''): @@ -8,6 +9,8 @@ def Command(script='', stdout='', stderr=''): def Rule(name='', match=lambda *_: True, get_new_command=lambda *_: '', enabled_by_default=True, - side_effect=None): + side_effect=None, + priority=DEFAULT_PRIORITY): return types.Rule(name, match, get_new_command, - enabled_by_default, side_effect) + enabled_by_default, side_effect, + priority) diff --git a/thefuck/conf.py b/thefuck/conf.py index d33b1e34..4bc01e70 100644 --- a/thefuck/conf.py +++ b/thefuck/conf.py @@ -22,6 +22,7 @@ class _DefaultRulesNames(types.RulesNamesList): DEFAULT_RULES = _DefaultRulesNames([]) +DEFAULT_PRIORITY = 1000 DEFAULT_SETTINGS = {'rules': DEFAULT_RULES, diff --git a/thefuck/main.py b/thefuck/main.py index 56e28d1d..434d7ebd 100644 --- a/thefuck/main.py +++ b/thefuck/main.py @@ -26,7 +26,17 @@ def load_rule(rule): return types.Rule(rule.name[:-3], rule_module.match, rule_module.get_new_command, 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): @@ -35,11 +45,8 @@ def get_rules(user_dir, settings): .joinpath('rules') \ .glob('*.py') user = user_dir.joinpath('rules').glob('*.py') - for rule in sorted(list(bundled)) + list(user): - if rule.name != '__init__.py': - loaded_rule = load_rule(rule) - if loaded_rule in settings.rules: - yield loaded_rule + rules = _get_loaded_rules(sorted(bundled) + sorted(user), settings) + return sorted(rules, key=lambda rule: rule.priority) def wait_output(settings, popen): diff --git a/thefuck/types.py b/thefuck/types.py index 221b0e9e..3ca2cf85 100644 --- a/thefuck/types.py +++ b/thefuck/types.py @@ -4,7 +4,8 @@ from collections import namedtuple Command = namedtuple('Command', ('script', 'stdout', 'stderr')) Rule = namedtuple('Rule', ('name', 'match', 'get_new_command', - 'enabled_by_default', 'side_effect')) + 'enabled_by_default', 'side_effect', + 'priority')) class RulesNamesList(list):