mirror of
https://github.com/nvbn/thefuck.git
synced 2025-02-21 20:38:54 +00:00
Move rule-related code to Rule
This commit is contained in:
parent
bf80d97062
commit
4a27595e97
@ -2,7 +2,6 @@ import pytest
|
|||||||
import os
|
import os
|
||||||
from thefuck.rules.fix_file import match, get_new_command
|
from thefuck.rules.fix_file import match, get_new_command
|
||||||
from tests.utils import Command
|
from tests.utils import Command
|
||||||
from thefuck.types import Settings
|
|
||||||
|
|
||||||
|
|
||||||
# (script, file, line, col (or None), stdout, stderr)
|
# (script, file, line, col (or None), stdout, stderr)
|
||||||
|
@ -1,42 +1,8 @@
|
|||||||
import pytest
|
import pytest
|
||||||
from pathlib import PosixPath, Path
|
from pathlib import PosixPath
|
||||||
from mock import Mock
|
|
||||||
from thefuck import corrector, conf
|
from thefuck import corrector, conf
|
||||||
from tests.utils import Rule, Command, CorrectedCommand
|
from tests.utils import Rule, Command, CorrectedCommand
|
||||||
from thefuck.corrector import make_corrected_commands, get_corrected_commands,\
|
from thefuck.corrector import get_corrected_commands, organize_commands
|
||||||
is_rule_enabled, organize_commands
|
|
||||||
|
|
||||||
|
|
||||||
def test_load_rule(mocker):
|
|
||||||
match = object()
|
|
||||||
get_new_command = object()
|
|
||||||
load_source = mocker.patch(
|
|
||||||
'thefuck.corrector.load_source',
|
|
||||||
return_value=Mock(match=match,
|
|
||||||
get_new_command=get_new_command,
|
|
||||||
enabled_by_default=True,
|
|
||||||
priority=900,
|
|
||||||
requires_output=True))
|
|
||||||
assert corrector.load_rule(Path('/rules/bash.py')) \
|
|
||||||
== Rule('bash', match, get_new_command, priority=900)
|
|
||||||
load_source.assert_called_once_with('bash', '/rules/bash.py')
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('rules, exclude_rules, rule, is_enabled', [
|
|
||||||
(conf.DEFAULT_RULES, [], Rule('git', enabled_by_default=True), True),
|
|
||||||
(conf.DEFAULT_RULES, [], Rule('git', enabled_by_default=False), False),
|
|
||||||
([], [], Rule('git', enabled_by_default=False), False),
|
|
||||||
([], [], Rule('git', enabled_by_default=True), False),
|
|
||||||
(conf.DEFAULT_RULES + ['git'], [], Rule('git', enabled_by_default=False), True),
|
|
||||||
(['git'], [], Rule('git', enabled_by_default=False), True),
|
|
||||||
(conf.DEFAULT_RULES, ['git'], Rule('git', enabled_by_default=True), False),
|
|
||||||
(conf.DEFAULT_RULES, ['git'], Rule('git', enabled_by_default=False), False),
|
|
||||||
([], ['git'], Rule('git', enabled_by_default=True), False),
|
|
||||||
([], ['git'], Rule('git', enabled_by_default=False), False)])
|
|
||||||
def test_is_rule_enabled(settings, rules, exclude_rules, rule, is_enabled):
|
|
||||||
settings.update(rules=rules,
|
|
||||||
exclude_rules=exclude_rules)
|
|
||||||
assert is_rule_enabled(rule) == is_enabled
|
|
||||||
|
|
||||||
|
|
||||||
class TestGetRules(object):
|
class TestGetRules(object):
|
||||||
@ -49,7 +15,7 @@ class TestGetRules(object):
|
|||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
def load_source(self, monkeypatch):
|
def load_source(self, monkeypatch):
|
||||||
monkeypatch.setattr('thefuck.corrector.load_source',
|
monkeypatch.setattr('thefuck.types.load_source',
|
||||||
lambda x, _: Rule(x))
|
lambda x, _: Rule(x))
|
||||||
|
|
||||||
def _compare_names(self, rules, names):
|
def _compare_names(self, rules, names):
|
||||||
@ -70,37 +36,6 @@ class TestGetRules(object):
|
|||||||
self._compare_names(rules, loaded_rules)
|
self._compare_names(rules, loaded_rules)
|
||||||
|
|
||||||
|
|
||||||
class TestIsRuleMatch(object):
|
|
||||||
def test_no_match(self):
|
|
||||||
assert not corrector.is_rule_match(
|
|
||||||
Command('ls'), Rule('', lambda _: False))
|
|
||||||
|
|
||||||
def test_match(self):
|
|
||||||
rule = Rule('', lambda x: x.script == 'cd ..')
|
|
||||||
assert corrector.is_rule_match(Command('cd ..'), rule)
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures('no_colors')
|
|
||||||
def test_when_rule_failed(self, capsys):
|
|
||||||
rule = Rule('test', Mock(side_effect=OSError('Denied')),
|
|
||||||
requires_output=False)
|
|
||||||
assert not corrector.is_rule_match(Command('ls'), rule)
|
|
||||||
assert capsys.readouterr()[1].split('\n')[0] == '[WARN] Rule test:'
|
|
||||||
|
|
||||||
|
|
||||||
class TestMakeCorrectedCommands(object):
|
|
||||||
def test_with_rule_returns_list(self):
|
|
||||||
rule = Rule(get_new_command=lambda x: [x.script + '!', x.script + '@'],
|
|
||||||
priority=100)
|
|
||||||
assert list(make_corrected_commands(Command(script='test'), rule)) \
|
|
||||||
== [CorrectedCommand(script='test!', priority=100),
|
|
||||||
CorrectedCommand(script='test@', priority=200)]
|
|
||||||
|
|
||||||
def test_with_rule_returns_command(self):
|
|
||||||
rule = Rule(get_new_command=lambda x: x.script + '!',
|
|
||||||
priority=100)
|
|
||||||
assert list(make_corrected_commands(Command(script='test'), rule)) \
|
|
||||||
== [CorrectedCommand(script='test!', priority=100)]
|
|
||||||
|
|
||||||
def test_get_corrected_commands(mocker):
|
def test_get_corrected_commands(mocker):
|
||||||
command = Command('test', 'test', 'test')
|
command = Command('test', 'test', 'test')
|
||||||
rules = [Rule(match=lambda _: False),
|
rules = [Rule(match=lambda _: False),
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
from tests.utils import CorrectedCommand
|
from mock import Mock
|
||||||
|
from pathlib import Path
|
||||||
|
import pytest
|
||||||
|
from tests.utils import CorrectedCommand, Rule, Command
|
||||||
|
from thefuck import conf
|
||||||
|
|
||||||
|
|
||||||
class TestCorrectedCommand(object):
|
class TestCorrectedCommand(object):
|
||||||
@ -12,3 +16,63 @@ class TestCorrectedCommand(object):
|
|||||||
def test_hashable(self):
|
def test_hashable(self):
|
||||||
assert {CorrectedCommand('ls', None, 100),
|
assert {CorrectedCommand('ls', None, 100),
|
||||||
CorrectedCommand('ls', None, 200)} == {CorrectedCommand('ls')}
|
CorrectedCommand('ls', None, 200)} == {CorrectedCommand('ls')}
|
||||||
|
|
||||||
|
|
||||||
|
class TestRule(object):
|
||||||
|
def test_from_path(self, mocker):
|
||||||
|
match = object()
|
||||||
|
get_new_command = object()
|
||||||
|
load_source = mocker.patch(
|
||||||
|
'thefuck.types.load_source',
|
||||||
|
return_value=Mock(match=match,
|
||||||
|
get_new_command=get_new_command,
|
||||||
|
enabled_by_default=True,
|
||||||
|
priority=900,
|
||||||
|
requires_output=True))
|
||||||
|
assert Rule.from_path(Path('/rules/bash.py')) \
|
||||||
|
== Rule('bash', match, get_new_command, priority=900)
|
||||||
|
load_source.assert_called_once_with('bash', '/rules/bash.py')
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('rules, exclude_rules, rule, is_enabled', [
|
||||||
|
(conf.DEFAULT_RULES, [], Rule('git', enabled_by_default=True), True),
|
||||||
|
(conf.DEFAULT_RULES, [], Rule('git', enabled_by_default=False), False),
|
||||||
|
([], [], Rule('git', enabled_by_default=False), False),
|
||||||
|
([], [], Rule('git', enabled_by_default=True), False),
|
||||||
|
(conf.DEFAULT_RULES + ['git'], [], Rule('git', enabled_by_default=False), True),
|
||||||
|
(['git'], [], Rule('git', enabled_by_default=False), True),
|
||||||
|
(conf.DEFAULT_RULES, ['git'], Rule('git', enabled_by_default=True), False),
|
||||||
|
(conf.DEFAULT_RULES, ['git'], Rule('git', enabled_by_default=False), False),
|
||||||
|
([], ['git'], Rule('git', enabled_by_default=True), False),
|
||||||
|
([], ['git'], Rule('git', enabled_by_default=False), False)])
|
||||||
|
def test_is_enabled(self, settings, rules, exclude_rules, rule, is_enabled):
|
||||||
|
settings.update(rules=rules,
|
||||||
|
exclude_rules=exclude_rules)
|
||||||
|
assert rule.is_enabled == is_enabled
|
||||||
|
|
||||||
|
def test_isnt_match(self):
|
||||||
|
assert not Rule('', lambda _: False).is_match(
|
||||||
|
Command('ls'))
|
||||||
|
|
||||||
|
def test_is_match(self):
|
||||||
|
rule = Rule('', 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')),
|
||||||
|
requires_output=False)
|
||||||
|
assert not rule.is_match(Command('ls'))
|
||||||
|
assert capsys.readouterr()[1].split('\n')[0] == '[WARN] Rule test:'
|
||||||
|
|
||||||
|
def test_get_corrected_commands_with_rule_returns_list(self):
|
||||||
|
rule = Rule(get_new_command=lambda x: [x.script + '!', x.script + '@'],
|
||||||
|
priority=100)
|
||||||
|
assert list(rule.get_corrected_commands(Command(script='test'))) \
|
||||||
|
== [CorrectedCommand(script='test!', priority=100),
|
||||||
|
CorrectedCommand(script='test@', priority=200)]
|
||||||
|
|
||||||
|
def test_get_corrected_commands_with_rule_returns_command(self):
|
||||||
|
rule = Rule(get_new_command=lambda x: x.script + '!',
|
||||||
|
priority=100)
|
||||||
|
assert list(rule.get_corrected_commands(Command(script='test'))) \
|
||||||
|
== [CorrectedCommand(script='test!', priority=100)]
|
||||||
|
@ -7,19 +7,22 @@ def Command(script='', stdout='', stderr=''):
|
|||||||
return types.Command(script, stdout, stderr)
|
return types.Command(script, stdout, stderr)
|
||||||
|
|
||||||
|
|
||||||
def Rule(name='', match=lambda *_: True,
|
class Rule(types.Rule):
|
||||||
get_new_command=lambda *_: '',
|
def __init__(self, name='', match=lambda *_: True,
|
||||||
enabled_by_default=True,
|
get_new_command=lambda *_: '',
|
||||||
side_effect=None,
|
enabled_by_default=True,
|
||||||
priority=DEFAULT_PRIORITY,
|
side_effect=None,
|
||||||
requires_output=True):
|
priority=DEFAULT_PRIORITY,
|
||||||
return types.Rule(name, match, get_new_command,
|
requires_output=True):
|
||||||
enabled_by_default, side_effect,
|
super(Rule, self).__init__(name, match, get_new_command,
|
||||||
priority, requires_output)
|
enabled_by_default, side_effect,
|
||||||
|
priority, requires_output)
|
||||||
|
|
||||||
|
|
||||||
def CorrectedCommand(script='', side_effect=None, priority=DEFAULT_PRIORITY):
|
class CorrectedCommand(types.CorrectedCommand):
|
||||||
return types.CorrectedCommand(script, side_effect, priority)
|
def __init__(self, script='', side_effect=None, priority=DEFAULT_PRIORITY):
|
||||||
|
super(CorrectedCommand, self).__init__(
|
||||||
|
script, side_effect, priority)
|
||||||
|
|
||||||
|
|
||||||
root = Path(__file__).parent.parent.resolve()
|
root = Path(__file__).parent.parent.resolve()
|
||||||
|
@ -2,7 +2,15 @@ from imp import load_source
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from six import text_type
|
from six import text_type
|
||||||
from .types import Settings
|
|
||||||
|
|
||||||
|
class Settings(dict):
|
||||||
|
def __getattr__(self, item):
|
||||||
|
return self.get(item)
|
||||||
|
|
||||||
|
def __setattr__(self, key, value):
|
||||||
|
self[key] = value
|
||||||
|
|
||||||
|
|
||||||
ALL_ENABLED = object()
|
ALL_ENABLED = object()
|
||||||
DEFAULT_RULES = [ALL_ENABLED]
|
DEFAULT_RULES = [ALL_ENABLED]
|
||||||
|
@ -1,45 +1,16 @@
|
|||||||
import sys
|
|
||||||
from imp import load_source
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from .conf import settings, DEFAULT_PRIORITY, ALL_ENABLED
|
from .conf import settings
|
||||||
from .types import Rule, CorrectedCommand
|
from .types import Rule
|
||||||
from .utils import compatibility_call
|
|
||||||
from . import logs
|
from . import logs
|
||||||
|
|
||||||
|
|
||||||
def load_rule(rule):
|
def get_loaded_rules(rules_paths):
|
||||||
"""Imports rule module and returns it."""
|
|
||||||
name = rule.name[:-3]
|
|
||||||
with logs.debug_time(u'Importing rule: {};'.format(name)):
|
|
||||||
rule_module = load_source(name, str(rule))
|
|
||||||
priority = getattr(rule_module, 'priority', DEFAULT_PRIORITY)
|
|
||||||
return Rule(name, rule_module.match,
|
|
||||||
rule_module.get_new_command,
|
|
||||||
getattr(rule_module, 'enabled_by_default', True),
|
|
||||||
getattr(rule_module, 'side_effect', None),
|
|
||||||
settings.priority.get(name, priority),
|
|
||||||
getattr(rule_module, 'requires_output', True))
|
|
||||||
|
|
||||||
|
|
||||||
def is_rule_enabled(rule):
|
|
||||||
"""Returns `True` when rule enabled."""
|
|
||||||
if rule.name in settings.exclude_rules:
|
|
||||||
return False
|
|
||||||
elif rule.name in settings.rules:
|
|
||||||
return True
|
|
||||||
elif rule.enabled_by_default and ALL_ENABLED in settings.rules:
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def get_loaded_rules(rules):
|
|
||||||
"""Yields all available rules."""
|
"""Yields all available rules."""
|
||||||
for rule in rules:
|
for path in rules_paths:
|
||||||
if rule.name != '__init__.py':
|
if path.name != '__init__.py':
|
||||||
loaded_rule = load_rule(rule)
|
rule = Rule.from_path(path)
|
||||||
if is_rule_enabled(loaded_rule):
|
if rule.is_enabled:
|
||||||
yield loaded_rule
|
yield rule
|
||||||
|
|
||||||
|
|
||||||
def get_rules():
|
def get_rules():
|
||||||
@ -52,30 +23,6 @@ def get_rules():
|
|||||||
key=lambda rule: rule.priority)
|
key=lambda rule: rule.priority)
|
||||||
|
|
||||||
|
|
||||||
def is_rule_match(command, rule):
|
|
||||||
"""Returns first matched rule for command."""
|
|
||||||
script_only = command.stdout is None and command.stderr is None
|
|
||||||
|
|
||||||
if script_only and rule.requires_output:
|
|
||||||
return False
|
|
||||||
|
|
||||||
try:
|
|
||||||
with logs.debug_time(u'Trying rule: {};'.format(rule.name)):
|
|
||||||
if compatibility_call(rule.match, command):
|
|
||||||
return True
|
|
||||||
except Exception:
|
|
||||||
logs.rule_failed(rule, sys.exc_info())
|
|
||||||
|
|
||||||
|
|
||||||
def make_corrected_commands(command, rule):
|
|
||||||
new_commands = compatibility_call(rule.get_new_command, command)
|
|
||||||
if not isinstance(new_commands, list):
|
|
||||||
new_commands = (new_commands,)
|
|
||||||
for n, new_command in enumerate(new_commands):
|
|
||||||
yield CorrectedCommand(script=new_command,
|
|
||||||
side_effect=rule.side_effect,
|
|
||||||
priority=(n + 1) * rule.priority)
|
|
||||||
|
|
||||||
def organize_commands(corrected_commands):
|
def organize_commands(corrected_commands):
|
||||||
"""Yields sorted commands without duplicates."""
|
"""Yields sorted commands without duplicates."""
|
||||||
try:
|
try:
|
||||||
@ -103,6 +50,6 @@ def organize_commands(corrected_commands):
|
|||||||
def get_corrected_commands(command):
|
def get_corrected_commands(command):
|
||||||
corrected_commands = (
|
corrected_commands = (
|
||||||
corrected for rule in get_rules()
|
corrected for rule in get_rules()
|
||||||
if is_rule_match(command, rule)
|
if rule.is_match(command)
|
||||||
for corrected in make_corrected_commands(command, rule))
|
for corrected in rule.get_corrected_commands(command))
|
||||||
return organize_commands(corrected_commands)
|
return organize_commands(corrected_commands)
|
||||||
|
@ -1,10 +1,91 @@
|
|||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
from imp import load_source
|
||||||
|
import sys
|
||||||
|
from .conf import settings, DEFAULT_PRIORITY, ALL_ENABLED
|
||||||
|
from .utils import compatibility_call
|
||||||
|
from . import logs
|
||||||
|
|
||||||
Command = namedtuple('Command', ('script', 'stdout', 'stderr'))
|
Command = namedtuple('Command', ('script', 'stdout', 'stderr'))
|
||||||
|
|
||||||
Rule = namedtuple('Rule', ('name', 'match', 'get_new_command',
|
|
||||||
'enabled_by_default', 'side_effect',
|
class Rule(object):
|
||||||
'priority', 'requires_output'))
|
def __init__(self, name, match, get_new_command,
|
||||||
|
enabled_by_default, side_effect,
|
||||||
|
priority, requires_output):
|
||||||
|
self.name = name
|
||||||
|
self.match = match
|
||||||
|
self.get_new_command = get_new_command
|
||||||
|
self.enabled_by_default = enabled_by_default
|
||||||
|
self.side_effect = side_effect
|
||||||
|
self.priority = priority
|
||||||
|
self.requires_output = requires_output
|
||||||
|
|
||||||
|
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)
|
||||||
|
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)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_path(cls, path):
|
||||||
|
"""Creates rule instance from path."""
|
||||||
|
name = path.name[:-3]
|
||||||
|
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,
|
||||||
|
rule_module.get_new_command,
|
||||||
|
getattr(rule_module, 'enabled_by_default', True),
|
||||||
|
getattr(rule_module, 'side_effect', None),
|
||||||
|
settings.priority.get(name, priority),
|
||||||
|
getattr(rule_module, 'requires_output', True))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_enabled(self):
|
||||||
|
if self.name in settings.exclude_rules:
|
||||||
|
return False
|
||||||
|
elif self.name in settings.rules:
|
||||||
|
return True
|
||||||
|
elif self.enabled_by_default and ALL_ENABLED in settings.rules:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def is_match(self, command):
|
||||||
|
"""Returns `True` if rule matches the command."""
|
||||||
|
script_only = command.stdout is None and command.stderr is None
|
||||||
|
|
||||||
|
if script_only and self.requires_output:
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
with logs.debug_time(u'Trying rule: {};'.format(self.name)):
|
||||||
|
if compatibility_call(self.match, command):
|
||||||
|
return True
|
||||||
|
except Exception:
|
||||||
|
logs.rule_failed(self, sys.exc_info())
|
||||||
|
|
||||||
|
def get_corrected_commands(self, command):
|
||||||
|
new_commands = compatibility_call(self.get_new_command, command)
|
||||||
|
if not isinstance(new_commands, list):
|
||||||
|
new_commands = (new_commands,)
|
||||||
|
for n, new_command in enumerate(new_commands):
|
||||||
|
yield CorrectedCommand(script=new_command,
|
||||||
|
side_effect=self.side_effect,
|
||||||
|
priority=(n + 1) * self.priority)
|
||||||
|
|
||||||
|
|
||||||
class CorrectedCommand(object):
|
class CorrectedCommand(object):
|
||||||
@ -27,11 +108,3 @@ class CorrectedCommand(object):
|
|||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return 'CorrectedCommand(script={}, side_effect={}, priority={})'.format(
|
return 'CorrectedCommand(script={}, side_effect={}, priority={})'.format(
|
||||||
self.script, self.side_effect, self.priority)
|
self.script, self.side_effect, self.priority)
|
||||||
|
|
||||||
|
|
||||||
class Settings(dict):
|
|
||||||
def __getattr__(self, item):
|
|
||||||
return self.get(item)
|
|
||||||
|
|
||||||
def __setattr__(self, key, value):
|
|
||||||
self[key] = value
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user