From db488d35eb18763222ca15b349c46c75df3e3c6c Mon Sep 17 00:00:00 2001 From: Pablo Santiago Blum de Aguiar Date: Sun, 24 Feb 2019 02:19:21 -0600 Subject: [PATCH] #N/A: Add support to rules of type post match Rules of this types are those which main functionality is usefull after they had been matched. --- tests/test_types.py | 21 +++++++++-------- tests/test_ui.py | 8 +++---- tests/utils.py | 17 ++++++++------ thefuck/types.py | 56 ++++++++++++++++++++++++++++----------------- 4 files changed, 61 insertions(+), 41 deletions(-) diff --git a/tests/test_types.py b/tests/test_types.py index f946a8b8..91b7845a 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -14,9 +14,9 @@ from thefuck.types import Command class TestCorrectedCommand(object): def test_equality(self): - assert (CorrectedCommand('ls', None, 100) == - CorrectedCommand('ls', None, 200)) - assert (CorrectedCommand('ls', None, 100) != + assert (CorrectedCommand('ls', None, 100,) == + CorrectedCommand('ls', None, 200,)) + assert (CorrectedCommand('ls', None, 100,) != CorrectedCommand('ls', lambda *_: _, 100)) def test_hashable(self): @@ -48,16 +48,19 @@ class TestRule(object): def test_from_path(self, mocker): match = object() get_new_command = object() + post_match = object() load_source = mocker.patch( 'thefuck.types.load_source', return_value=Mock(match=match, get_new_command=get_new_command, + post_match=post_match, enabled_by_default=True, priority=900, - requires_output=True)) + requires_output=True, + is_post_match=False)) rule_path = os.path.join(os.sep, 'rules', 'bash.py') - assert (Rule.from_path(Path(rule_path)) - == Rule('bash', match, get_new_command, priority=900)) + assert (Rule.from_path(Path(rule_path)) == Rule( + 'bash', rule_path, match, get_new_command, post_match, priority=900)) load_source.assert_called_once_with('bash', rule_path) @pytest.mark.parametrize('rules, exclude_rules, rule, is_enabled', [ @@ -77,16 +80,16 @@ class TestRule(object): assert rule.is_enabled == is_enabled def test_isnt_match(self): - assert not Rule('', lambda _: False).is_match( + assert not Rule('', match=lambda _: False).is_match( Command('ls', '')) def test_is_match(self): - rule = Rule('', lambda x: x.script == 'cd ..') + rule = Rule('', match=lambda x: x.script == 'cd ..') assert rule.is_match(Command('cd ..', '')) @pytest.mark.usefixtures('no_colors') def test_isnt_match_when_rule_failed(self, capsys): - rule = Rule('test', Mock(side_effect=OSError('Denied')), + rule = Rule('test', match=Mock(side_effect=OSError('Denied')), requires_output=False) assert not rule.is_match(Command('ls', '')) assert capsys.readouterr()[1].split('\n')[0] == '[WARN] Rule test:' diff --git a/tests/test_ui.py b/tests/test_ui.py index 1e539651..8c3fe8aa 100644 --- a/tests/test_ui.py +++ b/tests/test_ui.py @@ -54,13 +54,13 @@ def test_command_selector(): class TestSelectCommand(object): @pytest.fixture def commands_with_side_effect(self): - return [CorrectedCommand('ls', lambda *_: None, 100), - CorrectedCommand('cd', lambda *_: None, 100)] + return [CorrectedCommand('ls', lambda *_: None, 100, None), + CorrectedCommand('cd', lambda *_: None, 100, None)] @pytest.fixture def commands(self): - return [CorrectedCommand('ls', None, 100), - CorrectedCommand('cd', None, 100)] + return [CorrectedCommand('ls', None, 100, None), + CorrectedCommand('cd', None, 100, None)] def test_without_commands(self, capsys): assert ui.select_command(iter([])) is None diff --git a/tests/utils.py b/tests/utils.py index a3e13531..585c1a09 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -3,18 +3,21 @@ from thefuck.const import DEFAULT_PRIORITY class Rule(types.Rule): - def __init__(self, name='', match=lambda *_: True, + def __init__(self, name='', path='', match=lambda *_: True, get_new_command=lambda *_: '', + post_match=lambda *_: '', enabled_by_default=True, side_effect=None, priority=DEFAULT_PRIORITY, - requires_output=True): - super(Rule, self).__init__(name, match, get_new_command, - enabled_by_default, side_effect, - priority, requires_output) + requires_output=True, + is_post_match=False): + super(Rule, self).__init__(name, path, match, get_new_command, + post_match, enabled_by_default, side_effect, + priority, requires_output, is_post_match) class CorrectedCommand(types.CorrectedCommand): - def __init__(self, script='', side_effect=None, priority=DEFAULT_PRIORITY): + def __init__(self, script='', side_effect=None, + priority=DEFAULT_PRIORITY, rule=None): super(CorrectedCommand, self).__init__( - script, side_effect, priority) + script, side_effect, priority, rule) diff --git a/thefuck/types.py b/thefuck/types.py index 8c5770f4..21e03c01 100644 --- a/thefuck/types.py +++ b/thefuck/types.py @@ -86,9 +86,9 @@ class Command(object): class Rule(object): """Rule for fixing commands.""" - def __init__(self, name, match, get_new_command, + def __init__(self, name, path, match, get_new_command, post_match, enabled_by_default, side_effect, - priority, requires_output): + priority, requires_output, is_post_match): """Initializes rule with given fields. :type name: basestring @@ -101,31 +101,36 @@ class Rule(object): """ self.name = name + self.path = path self.match = match self.get_new_command = get_new_command + self.post_match = post_match self.enabled_by_default = enabled_by_default self.side_effect = side_effect self.priority = priority self.requires_output = requires_output + self.is_post_match = is_post_match def __eq__(self, other): if isinstance(other, Rule): - return ((self.name, self.match, self.get_new_command, - self.enabled_by_default, self.side_effect, - self.priority, self.requires_output) - == (other.name, other.match, other.get_new_command, - other.enabled_by_default, other.side_effect, - other.priority, other.requires_output)) + return ((self.name, self.path, self.match, self.get_new_command, + self.post_match, self.enabled_by_default, + self.side_effect, self.priority, + self.requires_output, self.is_post_match) + == (other.name, other.path, other.match, other.get_new_command, + other.post_match, other.enabled_by_default, + other.side_effect, other.priority, + other.requires_output, other.is_post_match)) else: return False def __repr__(self): - return 'Rule(name={}, match={}, get_new_command={}, ' \ - 'enabled_by_default={}, side_effect={}, ' \ - 'priority={}, requires_output)'.format( - self.name, self.match, self.get_new_command, - self.enabled_by_default, self.side_effect, - self.priority, self.requires_output) + return 'Rule(name={}, path={}, match={}, get_new_command={}, ' \ + 'post_match={}, enabled_by_default={}, side_effect={}, ' \ + 'priority={}, requires_output={}, is_post_match={})'.format( + self.name, self.path, self.match, self.get_new_command, + self.post_match, self.enabled_by_default, self.side_effect, + self.priority, self.requires_output, self.is_post_match) @classmethod def from_path(cls, path): @@ -139,12 +144,14 @@ class Rule(object): with logs.debug_time(u'Importing rule: {};'.format(name)): rule_module = load_source(name, str(path)) priority = getattr(rule_module, 'priority', DEFAULT_PRIORITY) - return cls(name, rule_module.match, + return cls(name, path.as_posix(), rule_module.match, rule_module.get_new_command, + getattr(rule_module, 'post_match', None), getattr(rule_module, 'enabled_by_default', True), getattr(rule_module, 'side_effect', None), settings.priority.get(name, priority), - getattr(rule_module, 'requires_output', True)) + getattr(rule_module, 'requires_output', True), + getattr(rule_module, 'is_post_match', False)) @property def is_enabled(self): @@ -192,13 +199,14 @@ class Rule(object): for n, new_command in enumerate(new_commands): yield CorrectedCommand(script=new_command, side_effect=self.side_effect, - priority=(n + 1) * self.priority) + priority=(n + 1) * self.priority, + rule=self) class CorrectedCommand(object): """Corrected by rule command.""" - def __init__(self, script, side_effect, priority): + def __init__(self, script, side_effect, priority, rule): """Initializes instance with given fields. :type script: basestring @@ -209,6 +217,7 @@ class CorrectedCommand(object): self.script = script self.side_effect = side_effect self.priority = priority + self.rule = rule def __eq__(self, other): """Ignores `priority` field.""" @@ -225,14 +234,19 @@ class CorrectedCommand(object): return u'CorrectedCommand(script={}, side_effect={}, priority={})'.format( self.script, self.side_effect, self.priority) - def _get_script(self): + def _get_script(self, old_cmd): """Returns fixed commands script. If `settings.repeat` is `True`, appends command with second attempt of running fuck in case fixed command fails again. """ - if settings.repeat: + if self.rule and self.rule.is_post_match: + return u'thefuck --post-match {} {} {}'.format( + self.rule.path, + shell.quote(old_cmd.script), + shell.quote(old_cmd.output)) + elif settings.repeat: repeat_fuck = '{} --repeat {}--force-command {}'.format( get_alias(), '--debug ' if settings.debug else '', @@ -255,4 +269,4 @@ class CorrectedCommand(object): logs.debug(u'PYTHONIOENCODING: {}'.format( os.environ.get('PYTHONIOENCODING', '!!not-set!!'))) - print(self._get_script()) + print(self._get_script(old_cmd))