1
0
mirror of https://github.com/nvbn/thefuck.git synced 2025-02-22 12:58:33 +00:00

#614: Add --repeat option

This commit is contained in:
Vladimir Iakovlev 2017-03-28 18:09:38 +02:00
parent c3eca8234a
commit cfa831c88d
15 changed files with 97 additions and 29 deletions

View File

@ -33,6 +33,9 @@ class TestBash(object):
def test_and_(self, shell):
assert shell.and_('ls', 'cd') == 'ls && cd'
def test_or_(self, shell):
assert shell.or_('ls', 'cd') == 'ls || cd'
def test_get_aliases(self, shell):
assert shell.get_aliases() == {'fuck': 'eval $(thefuck $(fc -ln -1))',
'l': 'ls -CF',

View File

@ -55,6 +55,9 @@ class TestFish(object):
def test_and_(self, shell):
assert shell.and_('foo', 'bar') == 'foo; and bar'
def test_or_(self, shell):
assert shell.or_('foo', 'bar') == 'foo; or bar'
def test_get_aliases(self, shell):
assert shell.get_aliases() == {'fish_config': 'fish_config',
'fuck': 'fuck',

View File

@ -18,6 +18,9 @@ class TestGeneric(object):
def test_and_(self, shell):
assert shell.and_('ls', 'cd') == 'ls && cd'
def test_or_(self, shell):
assert shell.or_('ls', 'cd') == 'ls || cd'
def test_get_aliases(self, shell):
assert shell.get_aliases() == {}

View File

@ -34,6 +34,9 @@ class TestTcsh(object):
def test_and_(self, shell):
assert shell.and_('ls', 'cd') == 'ls && cd'
def test_or_(self, shell):
assert shell.or_('ls', 'cd') == 'ls || cd'
def test_get_aliases(self, shell):
assert shell.get_aliases() == {'fuck': 'eval $(thefuck $(fc -ln -1))',
'l': 'ls -CF',

View File

@ -32,6 +32,9 @@ class TestZsh(object):
def test_and_(self, shell):
assert shell.and_('ls', 'cd') == 'ls && cd'
def test_or_(self, shell):
assert shell.or_('ls', 'cd') == 'ls || cd'
def test_get_aliases(self, shell):
assert shell.get_aliases() == {
'fuck': 'eval $(thefuck $(fc -ln -1 | tail -n 1))',

View File

@ -3,29 +3,27 @@ from thefuck.argument_parser import Parser
from thefuck.const import ARGUMENT_PLACEHOLDER
def _args(**override):
args = {'alias': None, 'command': [], 'yes': False,
'help': False, 'version': False, 'debug': False,
'force_command': None, 'repeat': False}
args.update(override)
return args
@pytest.mark.parametrize('argv, result', [
(['thefuck'], {'alias': None, 'command': [], 'yes': False,
'help': False, 'version': False, 'debug': False}),
(['thefuck', '-a'],
{'alias': 'fuck', 'command': [], 'yes': False,
'help': False, 'version': False, 'debug': False}),
(['thefuck', '-a', 'fix'],
{'alias': 'fix', 'command': [], 'yes': False,
'help': False, 'version': False, 'debug': False}),
(['thefuck'], _args()),
(['thefuck', '-a'], _args(alias='fuck')),
(['thefuck', '-a', 'fix'], _args(alias='fix')),
(['thefuck', 'git', 'branch', ARGUMENT_PLACEHOLDER, '-y'],
{'alias': None, 'command': ['git', 'branch'], 'yes': True,
'help': False, 'version': False, 'debug': False}),
_args(command=['git', 'branch'], yes=True)),
(['thefuck', 'git', 'branch', '-a', ARGUMENT_PLACEHOLDER, '-y'],
{'alias': None, 'command': ['git', 'branch', '-a'], 'yes': True,
'help': False, 'version': False, 'debug': False}),
(['thefuck', ARGUMENT_PLACEHOLDER, '-v'],
{'alias': None, 'command': [], 'yes': False, 'help': False,
'version': True, 'debug': False}),
(['thefuck', ARGUMENT_PLACEHOLDER, '--help'],
{'alias': None, 'command': [], 'yes': False, 'help': True,
'version': False, 'debug': False}),
_args(command=['git', 'branch', '-a'], yes=True)),
(['thefuck', ARGUMENT_PLACEHOLDER, '-v'], _args(version=True)),
(['thefuck', ARGUMENT_PLACEHOLDER, '--help'], _args(help=True)),
(['thefuck', 'git', 'branch', '-a', ARGUMENT_PLACEHOLDER, '-y', '-d'],
{'alias': None, 'command': ['git', 'branch', '-a'], 'yes': True,
'help': False, 'version': False, 'debug': True})])
_args(command=['git', 'branch', '-a'], yes=True, debug=True)),
(['thefuck', 'git', 'branch', '-a', ARGUMENT_PLACEHOLDER, '-r', '-d'],
_args(command=['git', 'branch', '-a'], repeat=True, debug=True))])
def test_parse(argv, result):
assert vars(Parser().parse(argv)) == result

View File

@ -80,9 +80,10 @@ class TestSettingsFromEnv(object):
def test_settings_from_args(settings):
settings.init(Mock(yes=True, debug=True))
settings.init(Mock(yes=True, debug=True, repeat=True))
assert not settings.require_confirmation
assert settings.debug
assert settings.repeat
class TestInitializeSettingsFile(object):

View File

@ -28,6 +28,20 @@ class TestCorrectedCommand(object):
assert u'{}'.format(CorrectedCommand(u'echo café', None, 100)) == \
u'CorrectedCommand(script=echo café, side_effect=None, priority=100)'
@pytest.mark.parametrize('script, printed, override_settings', [
('git branch', 'git branch', {'repeat': False, 'debug': False}),
('git brunch',
"git brunch || fuck --repeat --force-command 'git brunch'",
{'repeat': True, 'debug': False}),
('git brunch',
"git brunch || fuck --repeat --debug --force-command 'git brunch'",
{'repeat': True, 'debug': True})])
def test_run(self, capsys, settings, script, printed, override_settings):
settings.update(override_settings)
CorrectedCommand(script, None, 1000).run(Command())
out, _ = capsys.readouterr()
assert out[:-1] == printed
class TestRule(object):
def test_from_path(self, mocker):

View File

@ -1,5 +1,5 @@
import sys
from argparse import ArgumentParser
from argparse import ArgumentParser, SUPPRESS
from .const import ARGUMENT_PLACEHOLDER
from .utils import get_alias
@ -20,15 +20,25 @@ class Parser(object):
'-h', '--help',
action='store_true',
help='show this help message and exit')
self._parser.add_argument(
group = self._parser.add_mutually_exclusive_group()
group.add_argument(
'-y', '--yes',
action='store_true',
help='execute fixed command without confirmation')
group.add_argument(
'-r', '--repeat',
action='store_true',
help='repeat on failure')
self._parser.add_argument(
'-d', '--debug',
action='store_true',
help='enable debug output')
self._parser.add_argument('command',
self._parser.add_argument(
'--force-command',
action='store',
help=SUPPRESS)
self._parser.add_argument(
'command',
nargs='*',
help='command that should be fixed')

View File

@ -121,6 +121,8 @@ class Settings(dict):
from_args['require_confirmation'] = not args.yes
if args.debug:
from_args['debug'] = args.debug
if args.repeat:
from_args['repeat'] = args.repeat
return from_args

View File

@ -34,6 +34,7 @@ DEFAULT_SETTINGS = {'rules': DEFAULT_RULES,
'wait_slow_command': 15,
'slow_commands': ['lein', 'react-native', 'gradle',
'./gradlew', 'vagrant'],
'repeat': False,
'env': {'LC_ALL': 'C', 'LANG': 'C', 'GIT_TRACE': '1'}}
ENV_TO_ATTR = {'THEFUCK_RULES': 'rules',
@ -46,7 +47,8 @@ ENV_TO_ATTR = {'THEFUCK_RULES': 'rules',
'THEFUCK_HISTORY_LIMIT': 'history_limit',
'THEFUCK_ALTER_HISTORY': 'alter_history',
'THEFUCK_WAIT_SLOW_COMMAND': 'wait_slow_command',
'THEFUCK_SLOW_COMMANDS': 'slow_commands'}
'THEFUCK_SLOW_COMMANDS': 'slow_commands',
'THEFUCK_REPEAT': 'repeat'}
SETTINGS_HEADER = u"""# The Fuck settings file
#

View File

@ -20,9 +20,11 @@ def fix_command(known_args):
settings.init(known_args)
with logs.debug_time('Total'):
logs.debug(u'Run with settings: {}'.format(pformat(settings)))
raw_command = ([known_args.force_command] if known_args.force_command
else known_args.command)
try:
command = types.Command.from_raw_script(known_args.command)
command = types.Command.from_raw_script(raw_command)
except EmptyCommand:
logs.debug('Empty command, nothing to do')
return

View File

@ -66,6 +66,9 @@ class Fish(Generic):
def and_(self, *commands):
return u'; and '.join(commands)
def or_(self, *commands):
return u'; or '.join(commands)
def how_to_configure(self):
return self._create_shell_configuration(
content=u"eval (thefuck --alias | tr '\n' ';')",

View File

@ -66,6 +66,9 @@ class Generic(object):
def and_(self, *commands):
return u' && '.join(commands)
def or_(self, *commands):
return u' || '.join(commands)
def how_to_configure(self):
return

View File

@ -9,6 +9,7 @@ from .shells import shell
from .conf import settings
from .const import DEFAULT_PRIORITY, ALL_ENABLED
from .exceptions import EmptyCommand
from .utils import get_alias
class Command(object):
@ -276,6 +277,22 @@ class CorrectedCommand(object):
return u'CorrectedCommand(script={}, side_effect={}, priority={})'.format(
self.script, self.side_effect, self.priority)
def _get_script(self):
"""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:
repeat_fuck = '{} --repeat {}--force-command {}'.format(
get_alias(),
'--debug ' if settings.debug else '',
shell.quote(self.script))
return shell.or_(self.script, repeat_fuck)
else:
return self.script
def run(self, old_cmd):
"""Runs command from rule for passed command.
@ -289,4 +306,5 @@ class CorrectedCommand(object):
# This depends on correct setting of PYTHONIOENCODING by the alias:
logs.debug(u'PYTHONIOENCODING: {}'.format(
os.environ.get('PYTHONIOENCODING', '!!not-set!!')))
print(self.script)
print(self._get_script())