1
0
mirror of https://github.com/nvbn/thefuck.git synced 2025-03-14 14:48:49 +00:00

#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.
This commit is contained in:
Pablo Santiago Blum de Aguiar 2019-02-24 02:19:21 -06:00
parent 2d81166213
commit db488d35eb
4 changed files with 61 additions and 41 deletions

View File

@ -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:'

View File

@ -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

View File

@ -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)

View File

@ -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))