mirror of
https://github.com/nvbn/thefuck.git
synced 2025-02-20 20:09:07 +00:00
Merge branch 'master' of github.com:nvbn/thefuck into slow
This commit is contained in:
commit
8b62959fe3
@ -181,6 +181,8 @@ using the matched rule and runs it. Rules enabled by default are as follows:
|
||||
* `man_no_space` – fixes man commands without spaces, for example `mandiff`;
|
||||
* `mercurial` – fixes wrong `hg` commands;
|
||||
* `mkdir_p` – adds `-p` when you trying to create directory without parent;
|
||||
* `mvn_no_command` – adds `clean package` to `mvn`;
|
||||
* `mvn_unknown_lifecycle_phase` – fixes miss spelt lifecycle phases with `mvn`;
|
||||
* `no_command` – fixes wrong console commands, for example `vom/vim`;
|
||||
* `no_such_file` – creates missing directories with `mv` and `cp` commands;
|
||||
* `open` – prepends `http` to address passed to `open`;
|
||||
|
2
setup.py
2
setup.py
@ -22,7 +22,7 @@ elif (3, 0) < version < (3, 3):
|
||||
|
||||
VERSION = '2.8'
|
||||
|
||||
install_requires = ['psutil', 'colorama', 'six']
|
||||
install_requires = ['psutil', 'colorama', 'six', 'decorator']
|
||||
extras_require = {':python_version<"3.4"': ['pathlib']}
|
||||
|
||||
setup(name='thefuck',
|
||||
|
@ -1,6 +1,12 @@
|
||||
import pytest
|
||||
from mock import Mock
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def no_memoize(monkeypatch):
|
||||
monkeypatch.setattr('thefuck.utils.memoize.disabled', True)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def settings():
|
||||
return Mock(debug=False, no_colors=True)
|
||||
|
@ -1,6 +1,6 @@
|
||||
import pytest
|
||||
from mock import Mock
|
||||
from thefuck.rules.lein_not_task import match, get_new_command
|
||||
from tests.utils import Command
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@ -14,10 +14,10 @@ Did you mean this?
|
||||
|
||||
|
||||
def test_match(is_not_task):
|
||||
assert match(Mock(script='lein rpl', stderr=is_not_task), None)
|
||||
assert not match(Mock(script='ls', stderr=is_not_task), None)
|
||||
assert match(Command(script='lein rpl', stderr=is_not_task), None)
|
||||
assert not match(Command(script='ls', stderr=is_not_task), None)
|
||||
|
||||
|
||||
def test_get_new_command(is_not_task):
|
||||
assert get_new_command(Mock(script='lein rpl --help', stderr=is_not_task),
|
||||
assert get_new_command(Command(script='lein rpl --help', stderr=is_not_task),
|
||||
None) == ['lein repl --help', 'lein jar --help']
|
||||
|
@ -1,16 +1,16 @@
|
||||
from mock import patch, Mock
|
||||
from thefuck.rules.ls_lah import match, get_new_command
|
||||
from tests.utils import Command
|
||||
|
||||
|
||||
def test_match():
|
||||
assert match(Mock(script='ls'), None)
|
||||
assert match(Mock(script='ls file.py'), None)
|
||||
assert match(Mock(script='ls /opt'), None)
|
||||
assert not match(Mock(script='ls -lah /opt'), None)
|
||||
assert not match(Mock(script='pacman -S binutils'), None)
|
||||
assert not match(Mock(script='lsof'), None)
|
||||
assert match(Command(script='ls'), None)
|
||||
assert match(Command(script='ls file.py'), None)
|
||||
assert match(Command(script='ls /opt'), None)
|
||||
assert not match(Command(script='ls -lah /opt'), None)
|
||||
assert not match(Command(script='pacman -S binutils'), None)
|
||||
assert not match(Command(script='lsof'), None)
|
||||
|
||||
|
||||
def test_get_new_command():
|
||||
assert get_new_command(Mock(script='ls file.py'), None) == 'ls -lah file.py'
|
||||
assert get_new_command(Mock(script='ls'), None) == 'ls -lah'
|
||||
assert get_new_command(Command(script='ls file.py'), None) == 'ls -lah file.py'
|
||||
assert get_new_command(Command(script='ls'), None) == 'ls -lah'
|
||||
|
40
tests/rules/test_mvn_no_command.py
Normal file
40
tests/rules/test_mvn_no_command.py
Normal file
@ -0,0 +1,40 @@
|
||||
import pytest
|
||||
from thefuck.rules.mvn_no_command import match, get_new_command
|
||||
from tests.utils import Command
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command', [
|
||||
Command(script='mvn', stdout='[ERROR] No goals have been specified for this build. You must specify a valid lifecycle phase or a goal in the format <plugin-prefix>:<goal> or <plugin-group-id>:<plugin-artifact-id>[:<plugin-version>]:<goal>. Available lifecycle phases are: validate, initialize, generate-sources, process-sources, generate-resources, process-resources, compile, process-classes, generate-test-sources, process-test-sources, generate-test-resources, process-test-resources, test-compile, process-test-classes, test, prepare-package, package, pre-integration-test, integration-test, post-integration-test, verify, install, deploy, pre-clean, clean, post-clean, pre-site, site, post-site, site-deploy. -> [Help 1]')])
|
||||
def test_match(command):
|
||||
assert match(command, None)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command', [
|
||||
Command(script='mvn clean', stdout="""
|
||||
[INFO] Scanning for projects...[INFO]
|
||||
[INFO] ------------------------------------------------------------------------
|
||||
[INFO] Building test 0.2
|
||||
[INFO] ------------------------------------------------------------------------
|
||||
[INFO]
|
||||
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ test ---
|
||||
[INFO] Deleting /home/mlk/code/test/target
|
||||
[INFO] ------------------------------------------------------------------------
|
||||
[INFO] BUILD SUCCESS
|
||||
[INFO] ------------------------------------------------------------------------
|
||||
[INFO] Total time: 0.477s
|
||||
[INFO] Finished at: Wed Aug 26 13:05:47 BST 2015
|
||||
[INFO] Final Memory: 6M/240M
|
||||
[INFO] ------------------------------------------------------------------------
|
||||
"""),
|
||||
Command(script='mvn --help'),
|
||||
Command(script='mvn -v')
|
||||
])
|
||||
def test_not_match(command):
|
||||
assert not match(command, None)
|
||||
|
||||
@pytest.mark.parametrize('command, new_command', [
|
||||
(Command(script='mvn', stdout='[ERROR] No goals have been specified for this build. You must specify a valid lifecycle phase or a goal in the format <plugin-prefix>:<goal> or <plugin-group-id>:<plugin-artifact-id>[:<plugin-version>]:<goal>. Available lifecycle phases are: validate, initialize, generate-sources, process-sources, generate-resources, process-resources, compile, process-classes, generate-test-sources, process-test-sources, generate-test-resources, process-test-resources, test-compile, process-test-classes, test, prepare-package, package, pre-integration-test, integration-test, post-integration-test, verify, install, deploy, pre-clean, clean, post-clean, pre-site, site, post-site, site-deploy. -> [Help 1]'), ['mvn clean package', 'mvn clean install']),
|
||||
(Command(script='mvn -N', stdout='[ERROR] No goals have been specified for this build. You must specify a valid lifecycle phase or a goal in the format <plugin-prefix>:<goal> or <plugin-group-id>:<plugin-artifact-id>[:<plugin-version>]:<goal>. Available lifecycle phases are: validate, initialize, generate-sources, process-sources, generate-resources, process-resources, compile, process-classes, generate-test-sources, process-test-sources, generate-test-resources, process-test-resources, test-compile, process-test-classes, test, prepare-package, package, pre-integration-test, integration-test, post-integration-test, verify, install, deploy, pre-clean, clean, post-clean, pre-site, site, post-site, site-deploy. -> [Help 1]'), ['mvn -N clean package', 'mvn -N clean install'])])
|
||||
def test_get_new_command(command, new_command):
|
||||
assert get_new_command(command, None) == new_command
|
||||
|
40
tests/rules/test_mvn_unknown_lifecycle_phase.py
Normal file
40
tests/rules/test_mvn_unknown_lifecycle_phase.py
Normal file
@ -0,0 +1,40 @@
|
||||
import pytest
|
||||
from thefuck.rules.mvn_unknown_lifecycle_phase import match, get_new_command
|
||||
from tests.utils import Command
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command', [
|
||||
Command(script='mvn cle', stdout='[ERROR] Unknown lifecycle phase "cle". You must specify a valid lifecycle phase or a goal in the format <plugin-prefix>:<goal> or <plugin-group-id>:<plugin-artifact-id>[:<plugin-version>]:<goal>. Available lifecycle phases are: validate, initialize, generate-sources, process-sources, generate-resources, process-resources, compile, process-classes, generate-test-sources, process-test-sources, generate-test-resources, process-test-resources, test-compile, process-test-classes, test, prepare-package, package, pre-integration-test, integration-test, post-integration-test, verify, install, deploy, pre-clean, clean, post-clean, pre-site, site, post-site, site-deploy. -> [Help 1]')])
|
||||
def test_match(command):
|
||||
assert match(command, None)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command', [
|
||||
Command(script='mvn clean', stdout="""
|
||||
[INFO] Scanning for projects...[INFO]
|
||||
[INFO] ------------------------------------------------------------------------
|
||||
[INFO] Building test 0.2
|
||||
[INFO] ------------------------------------------------------------------------
|
||||
[INFO]
|
||||
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ test ---
|
||||
[INFO] Deleting /home/mlk/code/test/target
|
||||
[INFO] ------------------------------------------------------------------------
|
||||
[INFO] BUILD SUCCESS
|
||||
[INFO] ------------------------------------------------------------------------
|
||||
[INFO] Total time: 0.477s
|
||||
[INFO] Finished at: Wed Aug 26 13:05:47 BST 2015
|
||||
[INFO] Final Memory: 6M/240M
|
||||
[INFO] ------------------------------------------------------------------------
|
||||
"""),
|
||||
Command(script='mvn --help'),
|
||||
Command(script='mvn -v')
|
||||
])
|
||||
def test_not_match(command):
|
||||
assert not match(command, None)
|
||||
|
||||
@pytest.mark.parametrize('command, new_command', [
|
||||
(Command(script='mvn cle', stdout='[ERROR] Unknown lifecycle phase "cle". You must specify a valid lifecycle phase or a goal in the format <plugin-prefix>:<goal> or <plugin-group-id>:<plugin-artifact-id>[:<plugin-version>]:<goal>. Available lifecycle phases are: validate, initialize, generate-sources, process-sources, generate-resources, process-resources, compile, process-classes, generate-test-sources, process-test-sources, generate-test-resources, process-test-resources, test-compile, process-test-classes, test, prepare-package, package, pre-integration-test, integration-test, post-integration-test, verify, install, deploy, pre-clean, clean, post-clean, pre-site, site, post-site, site-deploy. -> [Help 1]'), ['mvn clean', 'mvn compile']),
|
||||
(Command(script='mvn claen package', stdout='[ERROR] Unknown lifecycle phase "claen". You must specify a valid lifecycle phase or a goal in the format <plugin-prefix>:<goal> or <plugin-group-id>:<plugin-artifact-id>[:<plugin-version>]:<goal>. Available lifecycle phases are: validate, initialize, generate-sources, process-sources, generate-resources, process-resources, compile, process-classes, generate-test-sources, process-test-sources, generate-test-resources, process-test-resources, test-compile, process-test-classes, test, prepare-package, package, pre-integration-test, integration-test, post-integration-test, verify, install, deploy, pre-clean, clean, post-clean, pre-site, site, post-site, site-deploy. -> [Help 1]'), ['mvn clean package'])])
|
||||
def test_get_new_command(command, new_command):
|
||||
assert get_new_command(command, None) == new_command
|
||||
|
@ -13,6 +13,8 @@ from tests.utils import Command
|
||||
(False, 'sudo ls', 'ls', False),
|
||||
(False, 'ls', 'ls', False)])
|
||||
def test_sudo_support(return_value, command, called, result):
|
||||
fn = Mock(return_value=return_value, __name__='')
|
||||
def fn(command, settings):
|
||||
assert command == Command(called)
|
||||
return return_value
|
||||
|
||||
assert sudo_support(fn)(Command(command), None) == result
|
||||
fn.assert_called_once_with(Command(called), None)
|
||||
|
@ -3,7 +3,7 @@ from pathlib import PosixPath, Path
|
||||
from mock import Mock
|
||||
from thefuck import corrector, conf, types
|
||||
from tests.utils import Rule, Command, CorrectedCommand
|
||||
from thefuck.corrector import make_corrected_commands, get_corrected_commands, remove_duplicates
|
||||
from thefuck.corrector import make_corrected_commands, get_corrected_commands
|
||||
|
||||
|
||||
def test_load_rule(mocker):
|
||||
@ -75,15 +75,6 @@ class TestGetCorrectedCommands(object):
|
||||
== [CorrectedCommand(script='test!', priority=100)]
|
||||
|
||||
|
||||
def test_remove_duplicates():
|
||||
side_effect = lambda *_: None
|
||||
assert set(remove_duplicates([CorrectedCommand('ls', priority=100),
|
||||
CorrectedCommand('ls', priority=200),
|
||||
CorrectedCommand('ls', side_effect, 300)])) \
|
||||
== {CorrectedCommand('ls', priority=100),
|
||||
CorrectedCommand('ls', side_effect, 300)}
|
||||
|
||||
|
||||
def test_get_corrected_commands(mocker):
|
||||
command = Command('test', 'test', 'test')
|
||||
rules = [Rule(match=lambda *_: False),
|
||||
@ -94,4 +85,4 @@ def test_get_corrected_commands(mocker):
|
||||
priority=60)]
|
||||
mocker.patch('thefuck.corrector.get_rules', return_value=rules)
|
||||
assert [cmd.script for cmd in get_corrected_commands(command, None, Mock(debug=False))] \
|
||||
== ['test@', 'test!', 'test;']
|
||||
== ['test!', 'test@', 'test;']
|
||||
|
@ -1,5 +1,6 @@
|
||||
from thefuck.types import RulesNamesList, Settings
|
||||
from tests.utils import Rule
|
||||
from thefuck.types import RulesNamesList, Settings, \
|
||||
SortedCorrectedCommandsSequence
|
||||
from tests.utils import Rule, CorrectedCommand
|
||||
|
||||
|
||||
def test_rules_names_list():
|
||||
@ -15,3 +16,44 @@ def test_update_settings():
|
||||
assert new_settings.key == 'val'
|
||||
assert new_settings.unset == 'unset-value'
|
||||
assert settings.key == 'val'
|
||||
|
||||
|
||||
class TestSortedCorrectedCommandsSequence(object):
|
||||
def test_realises_generator_only_on_demand(self, settings):
|
||||
should_realise = False
|
||||
|
||||
def gen():
|
||||
nonlocal should_realise
|
||||
yield CorrectedCommand('git commit')
|
||||
yield CorrectedCommand('git branch', priority=200)
|
||||
assert should_realise
|
||||
yield CorrectedCommand('git checkout', priority=100)
|
||||
|
||||
commands = SortedCorrectedCommandsSequence(gen(), settings)
|
||||
assert commands[0] == CorrectedCommand('git commit')
|
||||
should_realise = True
|
||||
assert commands[1] == CorrectedCommand('git checkout', priority=100)
|
||||
assert commands[2] == CorrectedCommand('git branch', priority=200)
|
||||
|
||||
def test_remove_duplicates(self, settings):
|
||||
side_effect = lambda *_: None
|
||||
seq = SortedCorrectedCommandsSequence(
|
||||
iter([CorrectedCommand('ls', priority=100),
|
||||
CorrectedCommand('ls', priority=200),
|
||||
CorrectedCommand('ls', side_effect, 300)]),
|
||||
settings)
|
||||
assert set(seq) == {CorrectedCommand('ls', priority=100),
|
||||
CorrectedCommand('ls', side_effect, 300)}
|
||||
|
||||
|
||||
class TestCorrectedCommand(object):
|
||||
|
||||
def test_equality(self):
|
||||
assert CorrectedCommand('ls', None, 100) == \
|
||||
CorrectedCommand('ls', None, 200)
|
||||
assert CorrectedCommand('ls', None, 100) != \
|
||||
CorrectedCommand('ls', lambda *_: _, 100)
|
||||
|
||||
def test_hashable(self):
|
||||
assert {CorrectedCommand('ls', None, 100),
|
||||
CorrectedCommand('ls', None, 200)} == {CorrectedCommand('ls')}
|
||||
|
@ -4,7 +4,7 @@ from mock import Mock
|
||||
import pytest
|
||||
from itertools import islice
|
||||
from thefuck import ui
|
||||
from thefuck.types import CorrectedCommand
|
||||
from thefuck.types import CorrectedCommand, SortedCorrectedCommandsSequence
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@ -58,14 +58,18 @@ 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)]
|
||||
def commands_with_side_effect(self, settings):
|
||||
return SortedCorrectedCommandsSequence(
|
||||
iter([CorrectedCommand('ls', lambda *_: None, 100),
|
||||
CorrectedCommand('cd', lambda *_: None, 100)]),
|
||||
settings)
|
||||
|
||||
@pytest.fixture
|
||||
def commands(self):
|
||||
return [CorrectedCommand('ls', None, 100),
|
||||
CorrectedCommand('cd', None, 100)]
|
||||
def commands(self, settings):
|
||||
return SortedCorrectedCommandsSequence(
|
||||
iter([CorrectedCommand('ls', None, 100),
|
||||
CorrectedCommand('cd', None, 100)]),
|
||||
settings)
|
||||
|
||||
def test_without_commands(self, capsys):
|
||||
assert ui.select_command([], Mock(debug=False, no_color=True)) is None
|
||||
@ -92,13 +96,6 @@ class TestSelectCommand(object):
|
||||
require_confirmation=True)) == commands[0]
|
||||
assert capsys.readouterr() == ('', u'\x1b[1K\rls [enter/↑/↓/ctrl+c]\n')
|
||||
|
||||
def test_with_confirmation_one_match(self, capsys, patch_getch, commands):
|
||||
patch_getch(['\n'])
|
||||
assert ui.select_command((commands[0],),
|
||||
Mock(debug=False, no_color=True,
|
||||
require_confirmation=True)) == commands[0]
|
||||
assert capsys.readouterr() == ('', u'\x1b[1K\rls [enter/ctrl+c]\n')
|
||||
|
||||
def test_with_confirmation_abort(self, capsys, patch_getch, commands):
|
||||
patch_getch([KeyboardInterrupt])
|
||||
assert ui.select_command(commands,
|
||||
|
@ -2,8 +2,9 @@ import pytest
|
||||
from mock import Mock
|
||||
from thefuck.utils import wrap_settings,\
|
||||
memoize, get_closest, get_all_executables, replace_argument, \
|
||||
get_all_matched_commands
|
||||
get_all_matched_commands, is_app, for_app
|
||||
from thefuck.types import Settings
|
||||
from tests.utils import Command
|
||||
|
||||
|
||||
@pytest.mark.parametrize('override, old, new', [
|
||||
@ -93,3 +94,25 @@ def test_replace_argument(args, result):
|
||||
'service-status', 'service-unbind'])])
|
||||
def test_get_all_matched_commands(stderr, result):
|
||||
assert list(get_all_matched_commands(stderr)) == result
|
||||
|
||||
|
||||
@pytest.mark.usefixtures('no_memoize')
|
||||
@pytest.mark.parametrize('script, names, result', [
|
||||
('git diff', ['git', 'hub'], True),
|
||||
('hub diff', ['git', 'hub'], True),
|
||||
('hg diff', ['git', 'hub'], False)])
|
||||
def test_is_app(script, names, result):
|
||||
assert is_app(Command(script), *names) == result
|
||||
|
||||
|
||||
@pytest.mark.usefixtures('no_memoize')
|
||||
@pytest.mark.parametrize('script, names, result', [
|
||||
('git diff', ['git', 'hub'], True),
|
||||
('hub diff', ['git', 'hub'], True),
|
||||
('hg diff', ['git', 'hub'], False)])
|
||||
def test_for_app(script, names, result):
|
||||
@for_app(*names)
|
||||
def match(command, settings):
|
||||
return True
|
||||
|
||||
assert match(Command(script), None) == result
|
||||
|
@ -1,5 +1,4 @@
|
||||
from . import conf, logs
|
||||
from .utils import eager
|
||||
from . import conf, types, logs
|
||||
from imp import load_source
|
||||
from pathlib import Path
|
||||
from thefuck.types import CorrectedCommand, Rule
|
||||
@ -29,17 +28,16 @@ def get_loaded_rules(rules, settings):
|
||||
yield loaded_rule
|
||||
|
||||
|
||||
@eager
|
||||
def get_rules(user_dir, settings):
|
||||
"""Returns all enabled rules."""
|
||||
bundled = Path(__file__).parent \
|
||||
.joinpath('rules') \
|
||||
.glob('*.py')
|
||||
user = user_dir.joinpath('rules').glob('*.py')
|
||||
return get_loaded_rules(sorted(bundled) + sorted(user), settings)
|
||||
return sorted(get_loaded_rules(sorted(bundled) + sorted(user), settings),
|
||||
key=lambda rule: rule.priority)
|
||||
|
||||
|
||||
@eager
|
||||
def get_matched_rules(command, rules, settings):
|
||||
"""Returns first matched rule for command."""
|
||||
script_only = command.stdout is None and command.stderr is None
|
||||
@ -68,22 +66,8 @@ def make_corrected_commands(command, rules, settings):
|
||||
priority=(n + 1) * rule.priority)
|
||||
|
||||
|
||||
def remove_duplicates(corrected_commands):
|
||||
commands = {(command.script, command.side_effect): command
|
||||
for command in sorted(corrected_commands,
|
||||
key=lambda command: -command.priority)}
|
||||
return commands.values()
|
||||
|
||||
|
||||
def get_corrected_commands(command, user_dir, settings):
|
||||
rules = get_rules(user_dir, settings)
|
||||
logs.debug(
|
||||
u'Loaded rules: {}'.format(', '.join(rule.name for rule in rules)),
|
||||
settings)
|
||||
matched = get_matched_rules(command, rules, settings)
|
||||
logs.debug(
|
||||
u'Matched rules: {}'.format(', '.join(rule.name for rule in matched)),
|
||||
settings)
|
||||
corrected_commands = make_corrected_commands(command, matched, settings)
|
||||
return sorted(remove_duplicates(corrected_commands),
|
||||
key=lambda corrected_command: corrected_command.priority)
|
||||
return types.SortedCorrectedCommandsSequence(corrected_commands, settings)
|
||||
|
@ -45,15 +45,11 @@ def show_corrected_command(corrected_command, settings):
|
||||
reset=color(colorama.Style.RESET_ALL, settings)))
|
||||
|
||||
|
||||
def confirm_text(corrected_command, multiple_cmds, settings):
|
||||
if multiple_cmds:
|
||||
arrows = '{blue}↑{reset}/{blue}↓{reset}/'
|
||||
else:
|
||||
arrows = ''
|
||||
|
||||
def confirm_text(corrected_command, settings):
|
||||
sys.stderr.write(
|
||||
('{clear}{bold}{script}{reset}{side_effect} '
|
||||
'[{green}enter{reset}/' + arrows + '{red}ctrl+c{reset}]').format(
|
||||
'[{green}enter{reset}/{blue}↑{reset}/{blue}↓{reset}'
|
||||
'/{red}ctrl+c{reset}]').format(
|
||||
script=corrected_command.script,
|
||||
side_effect=' (+side effect)' if corrected_command.side_effect else '',
|
||||
clear='\033[1K\r',
|
||||
|
@ -1,6 +1,8 @@
|
||||
import re
|
||||
from thefuck.utils import for_app
|
||||
|
||||
|
||||
@for_app('apt-get')
|
||||
def match(command, settings):
|
||||
return command.script.startswith('apt-get search')
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
import re
|
||||
from thefuck.utils import replace_argument
|
||||
from thefuck.utils import replace_argument, for_app
|
||||
|
||||
|
||||
@for_app('cargo')
|
||||
def match(command, settings):
|
||||
return ('cargo' in command.script
|
||||
and 'No such subcommand' in command.stderr
|
||||
return ('No such subcommand' in command.stderr
|
||||
and 'Did you mean' in command.stderr)
|
||||
|
||||
|
||||
|
@ -4,6 +4,7 @@ import os
|
||||
from difflib import get_close_matches
|
||||
from thefuck.specific.sudo import sudo_support
|
||||
from thefuck.rules import cd_mkdir
|
||||
from thefuck.utils import for_app
|
||||
|
||||
__author__ = "mmussomele"
|
||||
|
||||
@ -16,6 +17,7 @@ def _get_sub_dirs(parent):
|
||||
|
||||
|
||||
@sudo_support
|
||||
@for_app('cd')
|
||||
def match(command, settings):
|
||||
"""Match function copied from cd_mkdir.py"""
|
||||
return (command.script.startswith('cd ')
|
||||
|
@ -1,13 +1,14 @@
|
||||
import re
|
||||
from thefuck import shells
|
||||
from thefuck.utils import for_app
|
||||
from thefuck.specific.sudo import sudo_support
|
||||
|
||||
|
||||
@sudo_support
|
||||
@for_app('cd')
|
||||
def match(command, settings):
|
||||
return (command.script.startswith('cd ')
|
||||
and ('no such file or directory' in command.stderr.lower()
|
||||
or 'cd: can\'t cd to' in command.stderr.lower()))
|
||||
return (('no such file or directory' in command.stderr.lower()
|
||||
or 'cd: can\'t cd to' in command.stderr.lower()))
|
||||
|
||||
|
||||
@sudo_support
|
||||
|
@ -1,11 +1,11 @@
|
||||
import re
|
||||
from thefuck.utils import replace_argument
|
||||
from thefuck.utils import replace_argument, for_app
|
||||
|
||||
|
||||
@for_app('composer')
|
||||
def match(command, settings):
|
||||
return ('composer' in command.script
|
||||
and ('did you mean this?' in command.stderr.lower()
|
||||
or 'did you mean one of these?' in command.stderr.lower()))
|
||||
return (('did you mean this?' in command.stderr.lower()
|
||||
or 'did you mean one of these?' in command.stderr.lower()))
|
||||
|
||||
|
||||
def get_new_command(command, settings):
|
||||
|
@ -1,12 +1,13 @@
|
||||
import re
|
||||
from thefuck.specific.sudo import sudo_support
|
||||
from thefuck.utils import for_app
|
||||
|
||||
|
||||
@sudo_support
|
||||
@for_app('cp')
|
||||
def match(command, settings):
|
||||
stderr = command.stderr.lower()
|
||||
return command.script.startswith('cp ') \
|
||||
and ('omitting directory' in stderr or 'is a directory' in stderr)
|
||||
return 'omitting directory' in stderr or 'is a directory' in stderr
|
||||
|
||||
|
||||
@sudo_support
|
||||
|
@ -1,8 +1,11 @@
|
||||
from thefuck.utils import for_app
|
||||
|
||||
|
||||
@for_app(['g++', 'clang++'])
|
||||
def match(command, settings):
|
||||
return (('g++' in command.script or 'clang++' in command.script) and
|
||||
('This file requires compiler and library support for the '
|
||||
'ISO C++ 2011 standard.' in command.stderr or
|
||||
'-Wc++11-extensions' in command.stderr))
|
||||
return ('This file requires compiler and library support for the '
|
||||
'ISO C++ 2011 standard.' in command.stderr or
|
||||
'-Wc++11-extensions' in command.stderr)
|
||||
|
||||
|
||||
def get_new_command(command, settings):
|
||||
|
@ -1,6 +1,7 @@
|
||||
from thefuck import shells
|
||||
import os
|
||||
import tarfile
|
||||
from thefuck import shells
|
||||
from thefuck.utils import for_app
|
||||
|
||||
|
||||
def _is_tar_extract(cmd):
|
||||
@ -20,19 +21,19 @@ def _tar_file(cmd):
|
||||
for c in cmd.split():
|
||||
for ext in tar_extensions:
|
||||
if c.endswith(ext):
|
||||
return (c, c[0:len(c)-len(ext)])
|
||||
return (c, c[0:len(c) - len(ext)])
|
||||
|
||||
|
||||
@for_app('tar')
|
||||
def match(command, settings):
|
||||
return (command.script.startswith('tar')
|
||||
and '-C' not in command.script
|
||||
return ('-C' not in command.script
|
||||
and _is_tar_extract(command.script)
|
||||
and _tar_file(command.script) is not None)
|
||||
|
||||
|
||||
def get_new_command(command, settings):
|
||||
return shells.and_('mkdir -p {dir}', '{cmd} -C {dir}') \
|
||||
.format(dir=_tar_file(command.script)[1], cmd=command.script)
|
||||
.format(dir=_tar_file(command.script)[1], cmd=command.script)
|
||||
|
||||
|
||||
def side_effect(old_cmd, command, settings):
|
||||
|
@ -1,5 +1,6 @@
|
||||
import os
|
||||
import zipfile
|
||||
from thefuck.utils import for_app
|
||||
|
||||
|
||||
def _is_bad_zip(file):
|
||||
@ -20,9 +21,9 @@ def _zip_file(command):
|
||||
return '{}.zip'.format(c)
|
||||
|
||||
|
||||
@for_app('unzip')
|
||||
def match(command, settings):
|
||||
return (command.script.startswith('unzip')
|
||||
and '-d' not in command.script
|
||||
return ('-d' not in command.script
|
||||
and _is_bad_zip(_zip_file(command)))
|
||||
|
||||
|
||||
|
@ -1,14 +1,14 @@
|
||||
from itertools import dropwhile, takewhile, islice
|
||||
import re
|
||||
import subprocess
|
||||
from thefuck.utils import replace_command
|
||||
from thefuck.utils import replace_command, for_app
|
||||
from thefuck.specific.sudo import sudo_support
|
||||
|
||||
|
||||
@sudo_support
|
||||
@for_app('docker')
|
||||
def match(command, settings):
|
||||
return command.script.startswith('docker') \
|
||||
and 'is not a docker command' in command.stderr
|
||||
return 'is not a docker command' in command.stderr
|
||||
|
||||
|
||||
def get_docker_commands():
|
||||
|
@ -1,3 +1,4 @@
|
||||
from thefuck.utils import for_app
|
||||
# Appends .go when compiling go files
|
||||
#
|
||||
# Example:
|
||||
@ -5,6 +6,7 @@
|
||||
# error: go run: no go files listed
|
||||
|
||||
|
||||
@for_app('go')
|
||||
def match(command, settings):
|
||||
return (command.script.startswith('go run ')
|
||||
and not command.script.endswith('.go'))
|
||||
|
@ -1,6 +1,9 @@
|
||||
from thefuck.utils import for_app
|
||||
|
||||
|
||||
@for_app('grep')
|
||||
def match(command, settings):
|
||||
return (command.script.startswith('grep')
|
||||
and 'is a directory' in command.stderr.lower())
|
||||
return 'is a directory' in command.stderr.lower()
|
||||
|
||||
|
||||
def get_new_command(command, settings):
|
||||
|
@ -1,11 +1,11 @@
|
||||
import re
|
||||
import subprocess
|
||||
from thefuck.utils import replace_command
|
||||
from thefuck.utils import replace_command, for_app
|
||||
|
||||
|
||||
@for_app('gulp')
|
||||
def match(command, script):
|
||||
return command.script.startswith('gulp')\
|
||||
and 'is not in your gulpfile' in command.stdout
|
||||
return 'is not in your gulpfile' in command.stdout
|
||||
|
||||
|
||||
def get_gulp_tasks():
|
||||
|
@ -1,10 +1,10 @@
|
||||
import re
|
||||
from thefuck.utils import replace_command
|
||||
from thefuck.utils import replace_command, for_app
|
||||
|
||||
|
||||
@for_app('heroku')
|
||||
def match(command, settings):
|
||||
return command.script.startswith('heroku') and \
|
||||
'is not a heroku command' in command.stderr and \
|
||||
return 'is not a heroku command' in command.stderr and \
|
||||
'Perhaps you meant' in command.stderr
|
||||
|
||||
|
||||
|
@ -1,13 +1,16 @@
|
||||
# Fixes common java command mistake
|
||||
#
|
||||
# Example:
|
||||
# > java foo.java
|
||||
# Error: Could not find or load main class foo.java
|
||||
"""Fixes common java command mistake
|
||||
|
||||
Example:
|
||||
> java foo.java
|
||||
Error: Could not find or load main class foo.java
|
||||
|
||||
"""
|
||||
from thefuck.utils import for_app
|
||||
|
||||
|
||||
@for_app('java')
|
||||
def match(command, settings):
|
||||
return (command.script.startswith('java ')
|
||||
and command.script.endswith('.java'))
|
||||
return command.script.endswith('.java')
|
||||
|
||||
|
||||
def get_new_command(command, settings):
|
||||
|
@ -1,14 +1,17 @@
|
||||
# Appends .java when compiling java files
|
||||
#
|
||||
# Example:
|
||||
# > javac foo
|
||||
# error: Class names, 'foo', are only accepted if annotation
|
||||
# processing is explicitly requested
|
||||
"""Appends .java when compiling java files
|
||||
|
||||
Example:
|
||||
> javac foo
|
||||
error: Class names, 'foo', are only accepted if annotation
|
||||
processing is explicitly requested
|
||||
|
||||
"""
|
||||
from thefuck.utils import for_app
|
||||
|
||||
|
||||
@for_app('javac')
|
||||
def match(command, settings):
|
||||
return (command.script.startswith('javac ')
|
||||
and not command.script.endswith('.java'))
|
||||
return not command.script.endswith('.java')
|
||||
|
||||
|
||||
def get_new_command(command, settings):
|
||||
|
@ -1,9 +1,10 @@
|
||||
import re
|
||||
from thefuck.utils import replace_command, get_all_matched_commands
|
||||
from thefuck.utils import replace_command, get_all_matched_commands, for_app
|
||||
from thefuck.specific.sudo import sudo_support
|
||||
|
||||
|
||||
@sudo_support
|
||||
@for_app('lein')
|
||||
def match(command, settings):
|
||||
return (command.script.startswith('lein')
|
||||
and "is not a task. See 'lein help'" in command.stderr
|
||||
|
@ -1,7 +1,9 @@
|
||||
from thefuck.utils import for_app
|
||||
|
||||
|
||||
@for_app('ls')
|
||||
def match(command, settings):
|
||||
return (command.script == 'ls'
|
||||
or command.script.startswith('ls ')
|
||||
and 'ls -' not in command.script)
|
||||
return 'ls -' not in command.script
|
||||
|
||||
|
||||
def get_new_command(command, settings):
|
||||
|
@ -1,5 +1,5 @@
|
||||
import re
|
||||
from thefuck.utils import get_closest
|
||||
from thefuck.utils import get_closest, for_app
|
||||
|
||||
|
||||
def extract_possibilities(command):
|
||||
@ -12,14 +12,12 @@ def extract_possibilities(command):
|
||||
return possib
|
||||
|
||||
|
||||
@for_app('hg')
|
||||
def match(command, settings):
|
||||
return (command.script.startswith('hg ')
|
||||
and ('hg: unknown command' in command.stderr
|
||||
and '(did you mean one of ' in command.stderr
|
||||
or "hg: command '" in command.stderr
|
||||
and "' is ambiguous:" in command.stderr
|
||||
)
|
||||
)
|
||||
return ('hg: unknown command' in command.stderr
|
||||
and '(did you mean one of ' in command.stderr
|
||||
or "hg: command '" in command.stderr
|
||||
and "' is ambiguous:" in command.stderr)
|
||||
|
||||
|
||||
def get_new_command(command, settings):
|
||||
|
11
thefuck/rules/mvn_no_command.py
Normal file
11
thefuck/rules/mvn_no_command.py
Normal file
@ -0,0 +1,11 @@
|
||||
from thefuck.utils import for_app
|
||||
|
||||
|
||||
@for_app('mvn')
|
||||
def match(command, settings):
|
||||
return 'No goals have been specified for this build' in command.stdout
|
||||
|
||||
|
||||
def get_new_command(command, settings):
|
||||
return [command.script + ' clean package',
|
||||
command.script + ' clean install']
|
32
thefuck/rules/mvn_unknown_lifecycle_phase.py
Normal file
32
thefuck/rules/mvn_unknown_lifecycle_phase.py
Normal file
@ -0,0 +1,32 @@
|
||||
from thefuck.utils import replace_command, for_app
|
||||
from difflib import get_close_matches
|
||||
import re
|
||||
|
||||
|
||||
def _get_failed_lifecycle(command):
|
||||
return re.search(r'\[ERROR\] Unknown lifecycle phase "(.+)"',
|
||||
command.stdout)
|
||||
|
||||
|
||||
def _getavailable_lifecycles(command):
|
||||
return re.search(
|
||||
r'Available lifecycle phases are: (.+) -> \[Help 1\]', command.stdout)
|
||||
|
||||
|
||||
@for_app('mvn')
|
||||
def match(command, settings):
|
||||
failed_lifecycle = _get_failed_lifecycle(command)
|
||||
available_lifecycles = _getavailable_lifecycles(command)
|
||||
return available_lifecycles and failed_lifecycle
|
||||
|
||||
|
||||
def get_new_command(command, settings):
|
||||
failed_lifecycle = _get_failed_lifecycle(command)
|
||||
available_lifecycles = _getavailable_lifecycles(command)
|
||||
if available_lifecycles and failed_lifecycle:
|
||||
selected_lifecycle = get_close_matches(
|
||||
failed_lifecycle.group(1), available_lifecycles.group(1).split(", "),
|
||||
3, 0.6)
|
||||
return replace_command(command, failed_lifecycle.group(1), selected_lifecycle)
|
||||
else:
|
||||
return []
|
@ -5,21 +5,21 @@
|
||||
# The file ~/github.com does not exist.
|
||||
# Perhaps you meant 'http://github.com'?
|
||||
#
|
||||
from thefuck.utils import for_app
|
||||
|
||||
|
||||
@for_app('open', 'xdg-open', 'gnome-open', 'kde-open')
|
||||
def match(command, settings):
|
||||
return (command.script.startswith(('open', 'xdg-open', 'gnome-open', 'kde-open'))
|
||||
and (
|
||||
'.com' in command.script
|
||||
or '.net' in command.script
|
||||
or '.org' in command.script
|
||||
or '.ly' in command.script
|
||||
or '.io' in command.script
|
||||
or '.se' in command.script
|
||||
or '.edu' in command.script
|
||||
or '.info' in command.script
|
||||
or '.me' in command.script
|
||||
or 'www.' in command.script))
|
||||
return ('.com' in command.script
|
||||
or '.net' in command.script
|
||||
or '.org' in command.script
|
||||
or '.ly' in command.script
|
||||
or '.io' in command.script
|
||||
or '.se' in command.script
|
||||
or '.edu' in command.script
|
||||
or '.info' in command.script
|
||||
or '.me' in command.script
|
||||
or 'www.' in command.script)
|
||||
|
||||
|
||||
def get_new_command(command, settings):
|
||||
|
@ -1,7 +1,10 @@
|
||||
import re
|
||||
from thefuck.utils import replace_argument
|
||||
from thefuck.utils import replace_argument, for_app
|
||||
from thefuck.specific.sudo import sudo_support
|
||||
|
||||
|
||||
@sudo_support
|
||||
@for_app('pip')
|
||||
def match(command, settings):
|
||||
return ('pip' in command.script and
|
||||
'unknown command' in command.stderr and
|
||||
|
@ -3,11 +3,12 @@
|
||||
# Example:
|
||||
# > python foo
|
||||
# error: python: can't open file 'foo': [Errno 2] No such file or directory
|
||||
from thefuck.utils import for_app
|
||||
|
||||
|
||||
@for_app('python')
|
||||
def match(command, settings):
|
||||
return (command.script.startswith('python ')
|
||||
and not command.script.endswith('.py'))
|
||||
return not command.script.endswith('.py')
|
||||
|
||||
|
||||
def get_new_command(command, settings):
|
||||
|
@ -1,10 +1,10 @@
|
||||
import shlex
|
||||
from thefuck.utils import quote
|
||||
from thefuck.utils import quote, for_app
|
||||
|
||||
|
||||
@for_app('sed')
|
||||
def match(command, settings):
|
||||
return ('sed' in command.script
|
||||
and "unterminated `s' command" in command.stderr)
|
||||
return "unterminated `s' command" in command.stderr
|
||||
|
||||
|
||||
def get_new_command(command, settings):
|
||||
|
@ -1,10 +1,14 @@
|
||||
import re
|
||||
from thefuck.utils import for_app
|
||||
|
||||
commands = ('ssh', 'scp')
|
||||
|
||||
|
||||
@for_app(*commands)
|
||||
def match(command, settings):
|
||||
if not command.script:
|
||||
return False
|
||||
if not command.script.startswith(('ssh', 'scp')):
|
||||
if not command.script.startswith(commands):
|
||||
return False
|
||||
|
||||
patterns = (
|
||||
|
@ -2,15 +2,16 @@
|
||||
The confusion in systemctl's param order is massive.
|
||||
"""
|
||||
from thefuck.specific.sudo import sudo_support
|
||||
from thefuck.utils import for_app
|
||||
|
||||
|
||||
@sudo_support
|
||||
@for_app('systemctl')
|
||||
def match(command, settings):
|
||||
# Catches 'Unknown operation 'service'.' when executing systemctl with
|
||||
# misordered arguments
|
||||
cmd = command.script.split()
|
||||
return ('systemctl' in command.script and
|
||||
'Unknown operation \'' in command.stderr and
|
||||
return ('Unknown operation \'' in command.stderr and
|
||||
len(cmd) - cmd.index('systemctl') == 3)
|
||||
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
from thefuck.utils import replace_command
|
||||
import re
|
||||
from thefuck.utils import replace_command, for_app
|
||||
|
||||
|
||||
@for_app('tmux')
|
||||
def match(command, settings):
|
||||
return ('tmux' in command.script
|
||||
and 'ambiguous command:' in command.stderr
|
||||
return ('ambiguous command:' in command.stderr
|
||||
and 'could be:' in command.stderr)
|
||||
|
||||
|
||||
|
@ -1,9 +1,10 @@
|
||||
from thefuck import shells
|
||||
from thefuck.utils import for_app
|
||||
|
||||
|
||||
@for_app('tsuru')
|
||||
def match(command, settings):
|
||||
return (command.script.startswith('tsuru')
|
||||
and 'not authenticated' in command.stderr
|
||||
return ('not authenticated' in command.stderr
|
||||
and 'session has expired' in command.stderr)
|
||||
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
import re
|
||||
from thefuck.utils import get_all_matched_commands, replace_command
|
||||
from thefuck.utils import get_all_matched_commands, replace_command, for_app
|
||||
|
||||
|
||||
@for_app('tsuru')
|
||||
def match(command, settings):
|
||||
return (command.script.startswith('tsuru ')
|
||||
and ' is not a tsuru command. See "tsuru help".' in command.stderr
|
||||
return (' is not a tsuru command. See "tsuru help".' in command.stderr
|
||||
and '\nDid you mean?\n\t' in command.stderr)
|
||||
|
||||
|
||||
|
@ -1,8 +1,10 @@
|
||||
from thefuck import shells
|
||||
from thefuck.utils import for_app
|
||||
|
||||
|
||||
@for_app('vagrant')
|
||||
def match(command, settings):
|
||||
return command.script.startswith('vagrant ') and 'run `vagrant up`' in command.stderr.lower()
|
||||
return 'run `vagrant up`' in command.stderr.lower()
|
||||
|
||||
|
||||
def get_new_command(command, settings):
|
||||
|
@ -1,37 +1,32 @@
|
||||
from functools import wraps
|
||||
import re
|
||||
from shlex import split
|
||||
from decorator import decorator
|
||||
from ..types import Command
|
||||
from ..utils import quote
|
||||
from ..utils import quote, is_app
|
||||
|
||||
|
||||
def git_support(fn):
|
||||
@decorator
|
||||
def git_support(fn, command, settings):
|
||||
"""Resolves git aliases and supports testing for both git and hub."""
|
||||
@wraps(fn)
|
||||
def wrapper(command, settings):
|
||||
# supports GitHub's `hub` command
|
||||
# which is recommended to be used with `alias git=hub`
|
||||
# but at this point, shell aliases have already been resolved
|
||||
is_git_cmd = command.script.startswith(('git', 'hub'))
|
||||
# supports GitHub's `hub` command
|
||||
# which is recommended to be used with `alias git=hub`
|
||||
# but at this point, shell aliases have already been resolved
|
||||
if not is_app(command, 'git', 'hub'):
|
||||
return False
|
||||
|
||||
if not is_git_cmd:
|
||||
return False
|
||||
# perform git aliases expansion
|
||||
if 'trace: alias expansion:' in command.stderr:
|
||||
search = re.search("trace: alias expansion: ([^ ]*) => ([^\n]*)",
|
||||
command.stderr)
|
||||
alias = search.group(1)
|
||||
|
||||
# perform git aliases expansion
|
||||
if 'trace: alias expansion:' in command.stderr:
|
||||
search = re.search("trace: alias expansion: ([^ ]*) => ([^\n]*)",
|
||||
command.stderr)
|
||||
alias = search.group(1)
|
||||
# by default git quotes everything, for example:
|
||||
# 'commit' '--amend'
|
||||
# which is surprising and does not allow to easily test for
|
||||
# eg. 'git commit'
|
||||
expansion = ' '.join(map(quote, split(search.group(2))))
|
||||
new_script = command.script.replace(alias, expansion)
|
||||
|
||||
# by default git quotes everything, for example:
|
||||
# 'commit' '--amend'
|
||||
# which is surprising and does not allow to easily test for
|
||||
# eg. 'git commit'
|
||||
expansion = ' '.join(map(quote, split(search.group(2))))
|
||||
new_script = command.script.replace(alias, expansion)
|
||||
command = Command._replace(command, script=new_script)
|
||||
|
||||
command = Command._replace(command, script=new_script)
|
||||
|
||||
return fn(command, settings)
|
||||
|
||||
return wrapper
|
||||
return fn(command, settings)
|
||||
|
@ -1,24 +1,22 @@
|
||||
from functools import wraps
|
||||
import six
|
||||
from decorator import decorator
|
||||
from ..types import Command
|
||||
|
||||
|
||||
def sudo_support(fn):
|
||||
@decorator
|
||||
def sudo_support(fn, command, settings):
|
||||
"""Removes sudo before calling fn and adds it after."""
|
||||
@wraps(fn)
|
||||
def wrapper(command, settings):
|
||||
if not command.script.startswith('sudo '):
|
||||
return fn(command, settings)
|
||||
if not command.script.startswith('sudo '):
|
||||
return fn(command, settings)
|
||||
|
||||
result = fn(Command(command.script[5:],
|
||||
command.stdout,
|
||||
command.stderr),
|
||||
settings)
|
||||
result = fn(Command(command.script[5:],
|
||||
command.stdout,
|
||||
command.stderr),
|
||||
settings)
|
||||
|
||||
if result and isinstance(result, six.string_types):
|
||||
return u'sudo {}'.format(result)
|
||||
elif isinstance(result, list):
|
||||
return [u'sudo {}'.format(x) for x in result]
|
||||
else:
|
||||
return result
|
||||
return wrapper
|
||||
if result and isinstance(result, six.string_types):
|
||||
return u'sudo {}'.format(result)
|
||||
elif isinstance(result, list):
|
||||
return [u'sudo {}'.format(x) for x in result]
|
||||
else:
|
||||
return result
|
||||
|
@ -1,14 +1,34 @@
|
||||
from collections import namedtuple
|
||||
|
||||
from traceback import format_stack
|
||||
from .logs import debug
|
||||
|
||||
Command = namedtuple('Command', ('script', 'stdout', 'stderr'))
|
||||
|
||||
CorrectedCommand = namedtuple('CorrectedCommand', ('script', 'side_effect', 'priority'))
|
||||
|
||||
Rule = namedtuple('Rule', ('name', 'match', 'get_new_command',
|
||||
'enabled_by_default', 'side_effect',
|
||||
'priority', 'requires_output'))
|
||||
|
||||
class CorrectedCommand(object):
|
||||
def __init__(self, script, side_effect, priority):
|
||||
self.script = script
|
||||
self.side_effect = side_effect
|
||||
self.priority = priority
|
||||
|
||||
def __eq__(self, other):
|
||||
"""Ignores `priority` field."""
|
||||
if isinstance(other, CorrectedCommand):
|
||||
return (other.script, other.side_effect) ==\
|
||||
(self.script, self.side_effect)
|
||||
else:
|
||||
return False
|
||||
|
||||
def __hash__(self):
|
||||
return (self.script, self.side_effect).__hash__()
|
||||
|
||||
def __repr__(self):
|
||||
return 'CorrectedCommand(script={}, side_effect={}, priority={})'.format(
|
||||
self.script, self.side_effect, self.priority)
|
||||
|
||||
|
||||
class RulesNamesList(list):
|
||||
"""Wrapper a top of list for storing rules names."""
|
||||
@ -18,7 +38,6 @@ class RulesNamesList(list):
|
||||
|
||||
|
||||
class Settings(dict):
|
||||
|
||||
def __getattr__(self, item):
|
||||
return self.get(item)
|
||||
|
||||
@ -29,3 +48,60 @@ class Settings(dict):
|
||||
conf = dict(kwargs)
|
||||
conf.update(self)
|
||||
return Settings(conf)
|
||||
|
||||
|
||||
class SortedCorrectedCommandsSequence(object):
|
||||
"""List-like collection/wrapper around generator, that:
|
||||
|
||||
- immediately gives access to the first commands through [];
|
||||
- realises generator and sorts commands on first access to other
|
||||
commands through [], or when len called.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, commands, settings):
|
||||
self._settings = settings
|
||||
self._commands = commands
|
||||
self._cached = self._realise_first()
|
||||
self._realised = False
|
||||
|
||||
def _realise_first(self):
|
||||
try:
|
||||
return [next(self._commands)]
|
||||
except StopIteration:
|
||||
return []
|
||||
|
||||
def _remove_duplicates(self, corrected_commands):
|
||||
"""Removes low-priority duplicates."""
|
||||
commands = {command
|
||||
for command in sorted(corrected_commands,
|
||||
key=lambda command: -command.priority)
|
||||
if command.script != self._cached[0]}
|
||||
return commands
|
||||
|
||||
def _realise(self):
|
||||
"""Realises generator, removes duplicates and sorts commands."""
|
||||
commands = self._remove_duplicates(self._commands)
|
||||
self._cached = [self._cached[0]] + sorted(
|
||||
commands, key=lambda corrected_command: corrected_command.priority)
|
||||
self._realised = True
|
||||
debug('SortedCommandsSequence was realised with: {}, after: {}'.format(
|
||||
self._cached, '\n'.join(format_stack())), self._settings)
|
||||
|
||||
def __getitem__(self, item):
|
||||
if item != 0 and not self._realised:
|
||||
self._realise()
|
||||
return self._cached[item]
|
||||
|
||||
def __bool__(self):
|
||||
return bool(self._cached)
|
||||
|
||||
def __len__(self):
|
||||
if not self._realised:
|
||||
self._realise()
|
||||
return len(self._cached)
|
||||
|
||||
def __iter__(self):
|
||||
if not self._realised:
|
||||
self._realise()
|
||||
return iter(self._cached)
|
||||
|
@ -88,9 +88,7 @@ def select_command(corrected_commands, settings):
|
||||
logs.show_corrected_command(selector.value, settings)
|
||||
return selector.value
|
||||
|
||||
multiple_cmds = len(corrected_commands) > 1
|
||||
|
||||
selector.on_change(lambda val: logs.confirm_text(val, multiple_cmds, settings))
|
||||
selector.on_change(lambda val: logs.confirm_text(val, settings))
|
||||
for action in read_actions():
|
||||
if action == SELECT:
|
||||
sys.stderr.write('\n')
|
||||
|
@ -1,5 +1,6 @@
|
||||
from difflib import get_close_matches
|
||||
from functools import wraps
|
||||
from decorator import decorator
|
||||
|
||||
import os
|
||||
import pickle
|
||||
@ -64,12 +65,9 @@ def wrap_settings(params):
|
||||
print(settings.apt)
|
||||
|
||||
"""
|
||||
def decorator(fn):
|
||||
@wraps(fn)
|
||||
def wrapper(command, settings):
|
||||
return fn(command, settings.update(**params))
|
||||
return wrapper
|
||||
return decorator
|
||||
def _wrap_settings(fn, command, settings):
|
||||
return fn(command, settings.update(**params))
|
||||
return decorator(_wrap_settings)
|
||||
|
||||
|
||||
def get_closest(word, possibilities, n=3, cutoff=0.6, fallback_to_first=True):
|
||||
@ -111,11 +109,9 @@ def replace_argument(script, from_, to):
|
||||
u' {} '.format(from_), u' {} '.format(to), 1)
|
||||
|
||||
|
||||
def eager(fn):
|
||||
@wraps(fn)
|
||||
def wrapper(*args, **kwargs):
|
||||
return list(fn(*args, **kwargs))
|
||||
return wrapper
|
||||
@decorator
|
||||
def eager(fn, *args, **kwargs):
|
||||
return list(fn(*args, **kwargs))
|
||||
|
||||
|
||||
@eager
|
||||
@ -133,3 +129,24 @@ def replace_command(command, broken, matched):
|
||||
new_cmds = get_close_matches(broken, matched, cutoff=0.1)
|
||||
return [replace_argument(command.script, broken, new_cmd.strip())
|
||||
for new_cmd in new_cmds]
|
||||
|
||||
|
||||
@memoize
|
||||
def is_app(command, *app_names):
|
||||
"""Returns `True` if command is call to one of passed app names."""
|
||||
for name in app_names:
|
||||
if command.script == name \
|
||||
or command.script.startswith(u'{} '.format(name)):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def for_app(*app_names):
|
||||
"""Specifies that matching script is for on of app names."""
|
||||
def _for_app(fn, command, settings):
|
||||
if is_app(command, *app_names):
|
||||
return fn(command, settings)
|
||||
else:
|
||||
return False
|
||||
|
||||
return decorator(_for_app)
|
||||
|
Loading…
x
Reference in New Issue
Block a user