1
0
mirror of https://github.com/nvbn/thefuck.git synced 2025-02-23 13:28:39 +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): def test_and_(self, shell):
assert shell.and_('ls', 'cd') == 'ls && cd' 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): def test_get_aliases(self, shell):
assert shell.get_aliases() == {'fuck': 'eval $(thefuck $(fc -ln -1))', assert shell.get_aliases() == {'fuck': 'eval $(thefuck $(fc -ln -1))',
'l': 'ls -CF', 'l': 'ls -CF',

View File

@ -55,6 +55,9 @@ class TestFish(object):
def test_and_(self, shell): def test_and_(self, shell):
assert shell.and_('foo', 'bar') == 'foo; and bar' 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): def test_get_aliases(self, shell):
assert shell.get_aliases() == {'fish_config': 'fish_config', assert shell.get_aliases() == {'fish_config': 'fish_config',
'fuck': 'fuck', 'fuck': 'fuck',

View File

@ -18,6 +18,9 @@ class TestGeneric(object):
def test_and_(self, shell): def test_and_(self, shell):
assert shell.and_('ls', 'cd') == 'ls && cd' 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): def test_get_aliases(self, shell):
assert shell.get_aliases() == {} assert shell.get_aliases() == {}

View File

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

View File

@ -32,6 +32,9 @@ class TestZsh(object):
def test_and_(self, shell): def test_and_(self, shell):
assert shell.and_('ls', 'cd') == 'ls && cd' 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): def test_get_aliases(self, shell):
assert shell.get_aliases() == { assert shell.get_aliases() == {
'fuck': 'eval $(thefuck $(fc -ln -1 | tail -n 1))', '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 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', [ @pytest.mark.parametrize('argv, result', [
(['thefuck'], {'alias': None, 'command': [], 'yes': False, (['thefuck'], _args()),
'help': False, 'version': False, 'debug': False}), (['thefuck', '-a'], _args(alias='fuck')),
(['thefuck', '-a'], (['thefuck', '-a', 'fix'], _args(alias='fix')),
{'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', 'git', 'branch', ARGUMENT_PLACEHOLDER, '-y'], (['thefuck', 'git', 'branch', ARGUMENT_PLACEHOLDER, '-y'],
{'alias': None, 'command': ['git', 'branch'], 'yes': True, _args(command=['git', 'branch'], yes=True)),
'help': False, 'version': False, 'debug': False}),
(['thefuck', 'git', 'branch', '-a', ARGUMENT_PLACEHOLDER, '-y'], (['thefuck', 'git', 'branch', '-a', ARGUMENT_PLACEHOLDER, '-y'],
{'alias': None, 'command': ['git', 'branch', '-a'], 'yes': True, _args(command=['git', 'branch', '-a'], yes=True)),
'help': False, 'version': False, 'debug': False}), (['thefuck', ARGUMENT_PLACEHOLDER, '-v'], _args(version=True)),
(['thefuck', ARGUMENT_PLACEHOLDER, '-v'], (['thefuck', ARGUMENT_PLACEHOLDER, '--help'], _args(help=True)),
{'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}),
(['thefuck', 'git', 'branch', '-a', ARGUMENT_PLACEHOLDER, '-y', '-d'], (['thefuck', 'git', 'branch', '-a', ARGUMENT_PLACEHOLDER, '-y', '-d'],
{'alias': None, 'command': ['git', 'branch', '-a'], 'yes': True, _args(command=['git', 'branch', '-a'], yes=True, debug=True)),
'help': False, 'version': False, 'debug': True})]) (['thefuck', 'git', 'branch', '-a', ARGUMENT_PLACEHOLDER, '-r', '-d'],
_args(command=['git', 'branch', '-a'], repeat=True, debug=True))])
def test_parse(argv, result): def test_parse(argv, result):
assert vars(Parser().parse(argv)) == result assert vars(Parser().parse(argv)) == result

View File

@ -80,9 +80,10 @@ class TestSettingsFromEnv(object):
def test_settings_from_args(settings): 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 not settings.require_confirmation
assert settings.debug assert settings.debug
assert settings.repeat
class TestInitializeSettingsFile(object): class TestInitializeSettingsFile(object):

View File

@ -28,6 +28,20 @@ class TestCorrectedCommand(object):
assert u'{}'.format(CorrectedCommand(u'echo café', None, 100)) == \ assert u'{}'.format(CorrectedCommand(u'echo café', None, 100)) == \
u'CorrectedCommand(script=echo café, side_effect=None, priority=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): class TestRule(object):
def test_from_path(self, mocker): def test_from_path(self, mocker):

View File

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

View File

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

View File

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

View File

@ -20,9 +20,11 @@ def fix_command(known_args):
settings.init(known_args) settings.init(known_args)
with logs.debug_time('Total'): with logs.debug_time('Total'):
logs.debug(u'Run with settings: {}'.format(pformat(settings))) 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: try:
command = types.Command.from_raw_script(known_args.command) command = types.Command.from_raw_script(raw_command)
except EmptyCommand: except EmptyCommand:
logs.debug('Empty command, nothing to do') logs.debug('Empty command, nothing to do')
return return

View File

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

View File

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

View File

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