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

Merge 52e4c418a401fa9d1408198742fb638bc447324a into 201c01fc74d479d9e418d32c92a88b50b27728a7

This commit is contained in:
Payton Garland 2019-05-21 21:11:08 +00:00 committed by GitHub
commit c04678b433
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 142 additions and 42 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

@ -50,6 +50,10 @@ class Parser(object):
'command',
nargs='*',
help='command that should be fixed')
self._parser.add_argument(
'--post-match',
action='store_true', #TODO: Check if store_true is necessary
help=SUPPRESS)
def _add_conflicting_arguments(self):
"""It's too dangerous to use `-y` and `-r` together."""

View File

@ -11,6 +11,7 @@ from ..utils import get_installation_info # noqa: E402
from ..shells import shell # noqa: E402
from .alias import print_alias # noqa: E402
from .fix_command import fix_command # noqa: E402
from .post_match_execute import post_match_execute # noqa: E402
def main():
@ -22,6 +23,8 @@ def main():
elif known_args.version:
logs.version(get_installation_info().version,
sys.version.split()[0], shell.info())
elif known_args.post_match:
post_match_execute(known_args)
elif known_args.command or 'TF_HISTORY' in os.environ:
fix_command(known_args)
elif known_args.alias:

View File

@ -0,0 +1,9 @@
from thefuck.types import Rule
from pathlib import Path
def post_match_execute(known_args):
"""Executes post match funtion. Used when `thefuck` called with `--post-match` argument."""
# check the first flag to see what rule
rule = Rule.from_path(Path(known_args.command[0]))
rule.post_match(known_args.command[1], known_args.command[2])

View File

@ -0,0 +1,64 @@
from urllib import parse
import requests
import sys
import colorama
from thefuck.conf import settings
is_post_match = True
priority = 9500
def match(command):
return bool(command.output)
def get_new_command(command):
return 'Try Stack Overflow'
def post_match(fc,output):
num_of_result = 3;
# make sure the output is not too long
output = output[0:100]
search_query = "{} {}".format(fc, output)
sq_encoded = parse.quote_plus(search_query)
url = "https://api.stackexchange.com/2.2/search/advanced?pagesize={}&order=desc&sort=votes&q={}&accepted=True&site=stackoverflow".format(num_of_result,sq_encoded)
response = requests.get(url)
display_list = []
if response:
response_json = response.json()
for i in response_json["items"]:
display_list.append({"title":i["title"], "link":i["link"], "tags":i["tags"]})
stackoverflow(display_list)
# import pdb; pdb.set_trace(); # BREAKPOINT
return True
def stackoverflow(display_list):
if not display_list:
sys.stdout.write(u"{col}No fuck is found.{reset} \n".format(col=color(colorama.Fore.RED), reset=color(colorama.Style.RESET_ALL)))
for i, msg in enumerate(display_list):
sys.stdout.write(
u'{title_col}{i}. Post Title:{title} \n'
u'Post Tags: {tags} \n'
u'{url_col}URL: {url}\n\n'
.format(
title_col=color(colorama.Fore.GREEN),
url_col=color(colorama.Fore.RED),
title=msg["title"],
tags=msg["tags"],
url=msg["link"],
i=i+1,
reset=color(colorama.Style.RESET_ALL)
))
def color(color_):
"""Utility for ability to disabling colored output."""
if settings.no_colors:
return ''
else:
return color_

View File

@ -21,7 +21,7 @@ class Bash(Generic):
export PYTHONIOENCODING=utf-8;
TF_CMD=$(
thefuck {argument_placeholder} $@
) && eval $TF_CMD;
) && eval "$TF_CMD";
unset TF_HISTORY;
export PYTHONIOENCODING=$TF_PYTHONIOENCODING;
{alter_history}

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