mirror of
https://github.com/nvbn/thefuck.git
synced 2025-02-21 20:38:54 +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`;
|
* `man_no_space` – fixes man commands without spaces, for example `mandiff`;
|
||||||
* `mercurial` – fixes wrong `hg` commands;
|
* `mercurial` – fixes wrong `hg` commands;
|
||||||
* `mkdir_p` – adds `-p` when you trying to create directory without parent;
|
* `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_command` – fixes wrong console commands, for example `vom/vim`;
|
||||||
* `no_such_file` – creates missing directories with `mv` and `cp` commands;
|
* `no_such_file` – creates missing directories with `mv` and `cp` commands;
|
||||||
* `open` – prepends `http` to address passed to `open`;
|
* `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'
|
VERSION = '2.8'
|
||||||
|
|
||||||
install_requires = ['psutil', 'colorama', 'six']
|
install_requires = ['psutil', 'colorama', 'six', 'decorator']
|
||||||
extras_require = {':python_version<"3.4"': ['pathlib']}
|
extras_require = {':python_version<"3.4"': ['pathlib']}
|
||||||
|
|
||||||
setup(name='thefuck',
|
setup(name='thefuck',
|
||||||
|
@ -1,6 +1,12 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
from mock import Mock
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def no_memoize(monkeypatch):
|
def no_memoize(monkeypatch):
|
||||||
monkeypatch.setattr('thefuck.utils.memoize.disabled', True)
|
monkeypatch.setattr('thefuck.utils.memoize.disabled', True)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def settings():
|
||||||
|
return Mock(debug=False, no_colors=True)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import pytest
|
import pytest
|
||||||
from mock import Mock
|
|
||||||
from thefuck.rules.lein_not_task import match, get_new_command
|
from thefuck.rules.lein_not_task import match, get_new_command
|
||||||
|
from tests.utils import Command
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
@ -14,10 +14,10 @@ Did you mean this?
|
|||||||
|
|
||||||
|
|
||||||
def test_match(is_not_task):
|
def test_match(is_not_task):
|
||||||
assert match(Mock(script='lein rpl', stderr=is_not_task), None)
|
assert match(Command(script='lein rpl', stderr=is_not_task), None)
|
||||||
assert not match(Mock(script='ls', stderr=is_not_task), None)
|
assert not match(Command(script='ls', stderr=is_not_task), None)
|
||||||
|
|
||||||
|
|
||||||
def test_get_new_command(is_not_task):
|
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']
|
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 thefuck.rules.ls_lah import match, get_new_command
|
||||||
|
from tests.utils import Command
|
||||||
|
|
||||||
|
|
||||||
def test_match():
|
def test_match():
|
||||||
assert match(Mock(script='ls'), None)
|
assert match(Command(script='ls'), None)
|
||||||
assert match(Mock(script='ls file.py'), None)
|
assert match(Command(script='ls file.py'), None)
|
||||||
assert match(Mock(script='ls /opt'), None)
|
assert match(Command(script='ls /opt'), None)
|
||||||
assert not match(Mock(script='ls -lah /opt'), None)
|
assert not match(Command(script='ls -lah /opt'), None)
|
||||||
assert not match(Mock(script='pacman -S binutils'), None)
|
assert not match(Command(script='pacman -S binutils'), None)
|
||||||
assert not match(Mock(script='lsof'), None)
|
assert not match(Command(script='lsof'), None)
|
||||||
|
|
||||||
|
|
||||||
def test_get_new_command():
|
def test_get_new_command():
|
||||||
assert get_new_command(Mock(script='ls file.py'), None) == 'ls -lah file.py'
|
assert get_new_command(Command(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'), 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, 'sudo ls', 'ls', False),
|
||||||
(False, 'ls', 'ls', False)])
|
(False, 'ls', 'ls', False)])
|
||||||
def test_sudo_support(return_value, command, called, result):
|
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
|
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 mock import Mock
|
||||||
from thefuck import corrector, conf, types
|
from thefuck import corrector, conf, types
|
||||||
from tests.utils import Rule, Command, CorrectedCommand
|
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):
|
def test_load_rule(mocker):
|
||||||
@ -75,15 +75,6 @@ class TestGetCorrectedCommands(object):
|
|||||||
== [CorrectedCommand(script='test!', priority=100)]
|
== [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):
|
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),
|
||||||
@ -94,4 +85,4 @@ def test_get_corrected_commands(mocker):
|
|||||||
priority=60)]
|
priority=60)]
|
||||||
mocker.patch('thefuck.corrector.get_rules', return_value=rules)
|
mocker.patch('thefuck.corrector.get_rules', return_value=rules)
|
||||||
assert [cmd.script for cmd in get_corrected_commands(command, None, Mock(debug=False))] \
|
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 thefuck.types import RulesNamesList, Settings, \
|
||||||
from tests.utils import Rule
|
SortedCorrectedCommandsSequence
|
||||||
|
from tests.utils import Rule, CorrectedCommand
|
||||||
|
|
||||||
|
|
||||||
def test_rules_names_list():
|
def test_rules_names_list():
|
||||||
@ -15,3 +16,44 @@ def test_update_settings():
|
|||||||
assert new_settings.key == 'val'
|
assert new_settings.key == 'val'
|
||||||
assert new_settings.unset == 'unset-value'
|
assert new_settings.unset == 'unset-value'
|
||||||
assert settings.key == 'val'
|
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
|
import pytest
|
||||||
from itertools import islice
|
from itertools import islice
|
||||||
from thefuck import ui
|
from thefuck import ui
|
||||||
from thefuck.types import CorrectedCommand
|
from thefuck.types import CorrectedCommand, SortedCorrectedCommandsSequence
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
@ -58,14 +58,18 @@ def test_command_selector():
|
|||||||
|
|
||||||
class TestSelectCommand(object):
|
class TestSelectCommand(object):
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def commands_with_side_effect(self):
|
def commands_with_side_effect(self, settings):
|
||||||
return [CorrectedCommand('ls', lambda *_: None, 100),
|
return SortedCorrectedCommandsSequence(
|
||||||
CorrectedCommand('cd', lambda *_: None, 100)]
|
iter([CorrectedCommand('ls', lambda *_: None, 100),
|
||||||
|
CorrectedCommand('cd', lambda *_: None, 100)]),
|
||||||
|
settings)
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def commands(self):
|
def commands(self, settings):
|
||||||
return [CorrectedCommand('ls', None, 100),
|
return SortedCorrectedCommandsSequence(
|
||||||
CorrectedCommand('cd', None, 100)]
|
iter([CorrectedCommand('ls', None, 100),
|
||||||
|
CorrectedCommand('cd', None, 100)]),
|
||||||
|
settings)
|
||||||
|
|
||||||
def test_without_commands(self, capsys):
|
def test_without_commands(self, capsys):
|
||||||
assert ui.select_command([], Mock(debug=False, no_color=True)) is None
|
assert ui.select_command([], Mock(debug=False, no_color=True)) is None
|
||||||
@ -92,13 +96,6 @@ class TestSelectCommand(object):
|
|||||||
require_confirmation=True)) == commands[0]
|
require_confirmation=True)) == commands[0]
|
||||||
assert capsys.readouterr() == ('', u'\x1b[1K\rls [enter/↑/↓/ctrl+c]\n')
|
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):
|
def test_with_confirmation_abort(self, capsys, patch_getch, commands):
|
||||||
patch_getch([KeyboardInterrupt])
|
patch_getch([KeyboardInterrupt])
|
||||||
assert ui.select_command(commands,
|
assert ui.select_command(commands,
|
||||||
|
@ -2,8 +2,9 @@ import pytest
|
|||||||
from mock import Mock
|
from mock import Mock
|
||||||
from thefuck.utils import wrap_settings,\
|
from thefuck.utils import wrap_settings,\
|
||||||
memoize, get_closest, get_all_executables, replace_argument, \
|
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 thefuck.types import Settings
|
||||||
|
from tests.utils import Command
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('override, old, new', [
|
@pytest.mark.parametrize('override, old, new', [
|
||||||
@ -93,3 +94,25 @@ def test_replace_argument(args, result):
|
|||||||
'service-status', 'service-unbind'])])
|
'service-status', 'service-unbind'])])
|
||||||
def test_get_all_matched_commands(stderr, result):
|
def test_get_all_matched_commands(stderr, result):
|
||||||
assert list(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 . import conf, types, logs
|
||||||
from .utils import eager
|
|
||||||
from imp import load_source
|
from imp import load_source
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from thefuck.types import CorrectedCommand, Rule
|
from thefuck.types import CorrectedCommand, Rule
|
||||||
@ -29,17 +28,16 @@ def get_loaded_rules(rules, settings):
|
|||||||
yield loaded_rule
|
yield loaded_rule
|
||||||
|
|
||||||
|
|
||||||
@eager
|
|
||||||
def get_rules(user_dir, settings):
|
def get_rules(user_dir, settings):
|
||||||
"""Returns all enabled rules."""
|
"""Returns all enabled rules."""
|
||||||
bundled = Path(__file__).parent \
|
bundled = Path(__file__).parent \
|
||||||
.joinpath('rules') \
|
.joinpath('rules') \
|
||||||
.glob('*.py')
|
.glob('*.py')
|
||||||
user = user_dir.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):
|
def get_matched_rules(command, rules, settings):
|
||||||
"""Returns first matched rule for command."""
|
"""Returns first matched rule for command."""
|
||||||
script_only = command.stdout is None and command.stderr is None
|
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)
|
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):
|
def get_corrected_commands(command, user_dir, settings):
|
||||||
rules = get_rules(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)
|
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)
|
corrected_commands = make_corrected_commands(command, matched, settings)
|
||||||
return sorted(remove_duplicates(corrected_commands),
|
return types.SortedCorrectedCommandsSequence(corrected_commands, settings)
|
||||||
key=lambda corrected_command: corrected_command.priority)
|
|
||||||
|
@ -45,15 +45,11 @@ def show_corrected_command(corrected_command, settings):
|
|||||||
reset=color(colorama.Style.RESET_ALL, settings)))
|
reset=color(colorama.Style.RESET_ALL, settings)))
|
||||||
|
|
||||||
|
|
||||||
def confirm_text(corrected_command, multiple_cmds, settings):
|
def confirm_text(corrected_command, settings):
|
||||||
if multiple_cmds:
|
|
||||||
arrows = '{blue}↑{reset}/{blue}↓{reset}/'
|
|
||||||
else:
|
|
||||||
arrows = ''
|
|
||||||
|
|
||||||
sys.stderr.write(
|
sys.stderr.write(
|
||||||
('{clear}{bold}{script}{reset}{side_effect} '
|
('{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,
|
script=corrected_command.script,
|
||||||
side_effect=' (+side effect)' if corrected_command.side_effect else '',
|
side_effect=' (+side effect)' if corrected_command.side_effect else '',
|
||||||
clear='\033[1K\r',
|
clear='\033[1K\r',
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import re
|
import re
|
||||||
|
from thefuck.utils import for_app
|
||||||
|
|
||||||
|
|
||||||
|
@for_app('apt-get')
|
||||||
def match(command, settings):
|
def match(command, settings):
|
||||||
return command.script.startswith('apt-get search')
|
return command.script.startswith('apt-get search')
|
||||||
|
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import re
|
import re
|
||||||
from thefuck.utils import replace_argument
|
from thefuck.utils import replace_argument, for_app
|
||||||
|
|
||||||
|
|
||||||
|
@for_app('cargo')
|
||||||
def match(command, settings):
|
def match(command, settings):
|
||||||
return ('cargo' in command.script
|
return ('No such subcommand' in command.stderr
|
||||||
and 'No such subcommand' in command.stderr
|
|
||||||
and 'Did you mean' in command.stderr)
|
and 'Did you mean' in command.stderr)
|
||||||
|
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ import os
|
|||||||
from difflib import get_close_matches
|
from difflib import get_close_matches
|
||||||
from thefuck.specific.sudo import sudo_support
|
from thefuck.specific.sudo import sudo_support
|
||||||
from thefuck.rules import cd_mkdir
|
from thefuck.rules import cd_mkdir
|
||||||
|
from thefuck.utils import for_app
|
||||||
|
|
||||||
__author__ = "mmussomele"
|
__author__ = "mmussomele"
|
||||||
|
|
||||||
@ -16,6 +17,7 @@ def _get_sub_dirs(parent):
|
|||||||
|
|
||||||
|
|
||||||
@sudo_support
|
@sudo_support
|
||||||
|
@for_app('cd')
|
||||||
def match(command, settings):
|
def match(command, settings):
|
||||||
"""Match function copied from cd_mkdir.py"""
|
"""Match function copied from cd_mkdir.py"""
|
||||||
return (command.script.startswith('cd ')
|
return (command.script.startswith('cd ')
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
import re
|
import re
|
||||||
from thefuck import shells
|
from thefuck import shells
|
||||||
|
from thefuck.utils import for_app
|
||||||
from thefuck.specific.sudo import sudo_support
|
from thefuck.specific.sudo import sudo_support
|
||||||
|
|
||||||
|
|
||||||
@sudo_support
|
@sudo_support
|
||||||
|
@for_app('cd')
|
||||||
def match(command, settings):
|
def match(command, settings):
|
||||||
return (command.script.startswith('cd ')
|
return (('no such file or directory' in command.stderr.lower()
|
||||||
and ('no such file or directory' in command.stderr.lower()
|
or 'cd: can\'t cd to' in command.stderr.lower()))
|
||||||
or 'cd: can\'t cd to' in command.stderr.lower()))
|
|
||||||
|
|
||||||
|
|
||||||
@sudo_support
|
@sudo_support
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import re
|
import re
|
||||||
from thefuck.utils import replace_argument
|
from thefuck.utils import replace_argument, for_app
|
||||||
|
|
||||||
|
|
||||||
|
@for_app('composer')
|
||||||
def match(command, settings):
|
def match(command, settings):
|
||||||
return ('composer' in command.script
|
return (('did you mean this?' in command.stderr.lower()
|
||||||
and ('did you mean this?' in command.stderr.lower()
|
or 'did you mean one of these?' in command.stderr.lower()))
|
||||||
or 'did you mean one of these?' in command.stderr.lower()))
|
|
||||||
|
|
||||||
|
|
||||||
def get_new_command(command, settings):
|
def get_new_command(command, settings):
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
import re
|
import re
|
||||||
from thefuck.specific.sudo import sudo_support
|
from thefuck.specific.sudo import sudo_support
|
||||||
|
from thefuck.utils import for_app
|
||||||
|
|
||||||
|
|
||||||
@sudo_support
|
@sudo_support
|
||||||
|
@for_app('cp')
|
||||||
def match(command, settings):
|
def match(command, settings):
|
||||||
stderr = command.stderr.lower()
|
stderr = command.stderr.lower()
|
||||||
return command.script.startswith('cp ') \
|
return 'omitting directory' in stderr or 'is a directory' in stderr
|
||||||
and ('omitting directory' in stderr or 'is a directory' in stderr)
|
|
||||||
|
|
||||||
|
|
||||||
@sudo_support
|
@sudo_support
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
|
from thefuck.utils import for_app
|
||||||
|
|
||||||
|
|
||||||
|
@for_app(['g++', 'clang++'])
|
||||||
def match(command, settings):
|
def match(command, settings):
|
||||||
return (('g++' in command.script or 'clang++' in command.script) and
|
return ('This file requires compiler and library support for the '
|
||||||
('This file requires compiler and library support for the '
|
'ISO C++ 2011 standard.' in command.stderr or
|
||||||
'ISO C++ 2011 standard.' in command.stderr or
|
'-Wc++11-extensions' in command.stderr)
|
||||||
'-Wc++11-extensions' in command.stderr))
|
|
||||||
|
|
||||||
|
|
||||||
def get_new_command(command, settings):
|
def get_new_command(command, settings):
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
from thefuck import shells
|
|
||||||
import os
|
import os
|
||||||
import tarfile
|
import tarfile
|
||||||
|
from thefuck import shells
|
||||||
|
from thefuck.utils import for_app
|
||||||
|
|
||||||
|
|
||||||
def _is_tar_extract(cmd):
|
def _is_tar_extract(cmd):
|
||||||
@ -20,19 +21,19 @@ def _tar_file(cmd):
|
|||||||
for c in cmd.split():
|
for c in cmd.split():
|
||||||
for ext in tar_extensions:
|
for ext in tar_extensions:
|
||||||
if c.endswith(ext):
|
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):
|
def match(command, settings):
|
||||||
return (command.script.startswith('tar')
|
return ('-C' not in command.script
|
||||||
and '-C' not in command.script
|
|
||||||
and _is_tar_extract(command.script)
|
and _is_tar_extract(command.script)
|
||||||
and _tar_file(command.script) is not None)
|
and _tar_file(command.script) is not None)
|
||||||
|
|
||||||
|
|
||||||
def get_new_command(command, settings):
|
def get_new_command(command, settings):
|
||||||
return shells.and_('mkdir -p {dir}', '{cmd} -C {dir}') \
|
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):
|
def side_effect(old_cmd, command, settings):
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import os
|
import os
|
||||||
import zipfile
|
import zipfile
|
||||||
|
from thefuck.utils import for_app
|
||||||
|
|
||||||
|
|
||||||
def _is_bad_zip(file):
|
def _is_bad_zip(file):
|
||||||
@ -20,9 +21,9 @@ def _zip_file(command):
|
|||||||
return '{}.zip'.format(c)
|
return '{}.zip'.format(c)
|
||||||
|
|
||||||
|
|
||||||
|
@for_app('unzip')
|
||||||
def match(command, settings):
|
def match(command, settings):
|
||||||
return (command.script.startswith('unzip')
|
return ('-d' not in command.script
|
||||||
and '-d' not in command.script
|
|
||||||
and _is_bad_zip(_zip_file(command)))
|
and _is_bad_zip(_zip_file(command)))
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
from itertools import dropwhile, takewhile, islice
|
from itertools import dropwhile, takewhile, islice
|
||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
from thefuck.utils import replace_command
|
from thefuck.utils import replace_command, for_app
|
||||||
from thefuck.specific.sudo import sudo_support
|
from thefuck.specific.sudo import sudo_support
|
||||||
|
|
||||||
|
|
||||||
@sudo_support
|
@sudo_support
|
||||||
|
@for_app('docker')
|
||||||
def match(command, settings):
|
def match(command, settings):
|
||||||
return command.script.startswith('docker') \
|
return 'is not a docker command' in command.stderr
|
||||||
and 'is not a docker command' in command.stderr
|
|
||||||
|
|
||||||
|
|
||||||
def get_docker_commands():
|
def get_docker_commands():
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
from thefuck.utils import for_app
|
||||||
# Appends .go when compiling go files
|
# Appends .go when compiling go files
|
||||||
#
|
#
|
||||||
# Example:
|
# Example:
|
||||||
@ -5,6 +6,7 @@
|
|||||||
# error: go run: no go files listed
|
# error: go run: no go files listed
|
||||||
|
|
||||||
|
|
||||||
|
@for_app('go')
|
||||||
def match(command, settings):
|
def match(command, settings):
|
||||||
return (command.script.startswith('go run ')
|
return (command.script.startswith('go run ')
|
||||||
and not command.script.endswith('.go'))
|
and not command.script.endswith('.go'))
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
|
from thefuck.utils import for_app
|
||||||
|
|
||||||
|
|
||||||
|
@for_app('grep')
|
||||||
def match(command, settings):
|
def match(command, settings):
|
||||||
return (command.script.startswith('grep')
|
return 'is a directory' in command.stderr.lower()
|
||||||
and 'is a directory' in command.stderr.lower())
|
|
||||||
|
|
||||||
|
|
||||||
def get_new_command(command, settings):
|
def get_new_command(command, settings):
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
from thefuck.utils import replace_command
|
from thefuck.utils import replace_command, for_app
|
||||||
|
|
||||||
|
|
||||||
|
@for_app('gulp')
|
||||||
def match(command, script):
|
def match(command, script):
|
||||||
return command.script.startswith('gulp')\
|
return 'is not in your gulpfile' in command.stdout
|
||||||
and 'is not in your gulpfile' in command.stdout
|
|
||||||
|
|
||||||
|
|
||||||
def get_gulp_tasks():
|
def get_gulp_tasks():
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import re
|
import re
|
||||||
from thefuck.utils import replace_command
|
from thefuck.utils import replace_command, for_app
|
||||||
|
|
||||||
|
|
||||||
|
@for_app('heroku')
|
||||||
def match(command, settings):
|
def match(command, settings):
|
||||||
return command.script.startswith('heroku') and \
|
return 'is not a heroku command' in command.stderr and \
|
||||||
'is not a heroku command' in command.stderr and \
|
|
||||||
'Perhaps you meant' in command.stderr
|
'Perhaps you meant' in command.stderr
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,13 +1,16 @@
|
|||||||
# Fixes common java command mistake
|
"""Fixes common java command mistake
|
||||||
#
|
|
||||||
# Example:
|
Example:
|
||||||
# > java foo.java
|
> java foo.java
|
||||||
# Error: Could not find or load main class 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):
|
def match(command, settings):
|
||||||
return (command.script.startswith('java ')
|
return command.script.endswith('.java')
|
||||||
and command.script.endswith('.java'))
|
|
||||||
|
|
||||||
|
|
||||||
def get_new_command(command, settings):
|
def get_new_command(command, settings):
|
||||||
|
@ -1,14 +1,17 @@
|
|||||||
# Appends .java when compiling java files
|
"""Appends .java when compiling java files
|
||||||
#
|
|
||||||
# Example:
|
Example:
|
||||||
# > javac foo
|
> javac foo
|
||||||
# error: Class names, 'foo', are only accepted if annotation
|
error: Class names, 'foo', are only accepted if annotation
|
||||||
# processing is explicitly requested
|
processing is explicitly requested
|
||||||
|
|
||||||
|
"""
|
||||||
|
from thefuck.utils import for_app
|
||||||
|
|
||||||
|
|
||||||
|
@for_app('javac')
|
||||||
def match(command, settings):
|
def match(command, settings):
|
||||||
return (command.script.startswith('javac ')
|
return not command.script.endswith('.java')
|
||||||
and not command.script.endswith('.java'))
|
|
||||||
|
|
||||||
|
|
||||||
def get_new_command(command, settings):
|
def get_new_command(command, settings):
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import re
|
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
|
from thefuck.specific.sudo import sudo_support
|
||||||
|
|
||||||
|
|
||||||
@sudo_support
|
@sudo_support
|
||||||
|
@for_app('lein')
|
||||||
def match(command, settings):
|
def match(command, settings):
|
||||||
return (command.script.startswith('lein')
|
return (command.script.startswith('lein')
|
||||||
and "is not a task. See 'lein help'" in command.stderr
|
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):
|
def match(command, settings):
|
||||||
return (command.script == 'ls'
|
return 'ls -' not in command.script
|
||||||
or command.script.startswith('ls ')
|
|
||||||
and 'ls -' not in command.script)
|
|
||||||
|
|
||||||
|
|
||||||
def get_new_command(command, settings):
|
def get_new_command(command, settings):
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import re
|
import re
|
||||||
from thefuck.utils import get_closest
|
from thefuck.utils import get_closest, for_app
|
||||||
|
|
||||||
|
|
||||||
def extract_possibilities(command):
|
def extract_possibilities(command):
|
||||||
@ -12,14 +12,12 @@ def extract_possibilities(command):
|
|||||||
return possib
|
return possib
|
||||||
|
|
||||||
|
|
||||||
|
@for_app('hg')
|
||||||
def match(command, settings):
|
def match(command, settings):
|
||||||
return (command.script.startswith('hg ')
|
return ('hg: unknown command' in command.stderr
|
||||||
and ('hg: unknown command' in command.stderr
|
and '(did you mean one of ' in command.stderr
|
||||||
and '(did you mean one of ' in command.stderr
|
or "hg: command '" in command.stderr
|
||||||
or "hg: command '" in command.stderr
|
and "' is ambiguous:" in command.stderr)
|
||||||
and "' is ambiguous:" in command.stderr
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def get_new_command(command, settings):
|
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.
|
# The file ~/github.com does not exist.
|
||||||
# Perhaps you meant 'http://github.com'?
|
# 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):
|
def match(command, settings):
|
||||||
return (command.script.startswith(('open', 'xdg-open', 'gnome-open', 'kde-open'))
|
return ('.com' in command.script
|
||||||
and (
|
or '.net' in command.script
|
||||||
'.com' in command.script
|
or '.org' in command.script
|
||||||
or '.net' in command.script
|
or '.ly' in command.script
|
||||||
or '.org' in command.script
|
or '.io' in command.script
|
||||||
or '.ly' in command.script
|
or '.se' in command.script
|
||||||
or '.io' in command.script
|
or '.edu' in command.script
|
||||||
or '.se' in command.script
|
or '.info' in command.script
|
||||||
or '.edu' in command.script
|
or '.me' in command.script
|
||||||
or '.info' in command.script
|
or 'www.' in command.script)
|
||||||
or '.me' in command.script
|
|
||||||
or 'www.' in command.script))
|
|
||||||
|
|
||||||
|
|
||||||
def get_new_command(command, settings):
|
def get_new_command(command, settings):
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
import re
|
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):
|
def match(command, settings):
|
||||||
return ('pip' in command.script and
|
return ('pip' in command.script and
|
||||||
'unknown command' in command.stderr and
|
'unknown command' in command.stderr and
|
||||||
|
@ -3,11 +3,12 @@
|
|||||||
# Example:
|
# Example:
|
||||||
# > python foo
|
# > python foo
|
||||||
# error: python: can't open file 'foo': [Errno 2] No such file or directory
|
# 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):
|
def match(command, settings):
|
||||||
return (command.script.startswith('python ')
|
return not command.script.endswith('.py')
|
||||||
and not command.script.endswith('.py'))
|
|
||||||
|
|
||||||
|
|
||||||
def get_new_command(command, settings):
|
def get_new_command(command, settings):
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import shlex
|
import shlex
|
||||||
from thefuck.utils import quote
|
from thefuck.utils import quote, for_app
|
||||||
|
|
||||||
|
|
||||||
|
@for_app('sed')
|
||||||
def match(command, settings):
|
def match(command, settings):
|
||||||
return ('sed' in command.script
|
return "unterminated `s' command" in command.stderr
|
||||||
and "unterminated `s' command" in command.stderr)
|
|
||||||
|
|
||||||
|
|
||||||
def get_new_command(command, settings):
|
def get_new_command(command, settings):
|
||||||
|
@ -1,10 +1,14 @@
|
|||||||
import re
|
import re
|
||||||
|
from thefuck.utils import for_app
|
||||||
|
|
||||||
|
commands = ('ssh', 'scp')
|
||||||
|
|
||||||
|
|
||||||
|
@for_app(*commands)
|
||||||
def match(command, settings):
|
def match(command, settings):
|
||||||
if not command.script:
|
if not command.script:
|
||||||
return False
|
return False
|
||||||
if not command.script.startswith(('ssh', 'scp')):
|
if not command.script.startswith(commands):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
patterns = (
|
patterns = (
|
||||||
|
@ -2,15 +2,16 @@
|
|||||||
The confusion in systemctl's param order is massive.
|
The confusion in systemctl's param order is massive.
|
||||||
"""
|
"""
|
||||||
from thefuck.specific.sudo import sudo_support
|
from thefuck.specific.sudo import sudo_support
|
||||||
|
from thefuck.utils import for_app
|
||||||
|
|
||||||
|
|
||||||
@sudo_support
|
@sudo_support
|
||||||
|
@for_app('systemctl')
|
||||||
def match(command, settings):
|
def match(command, settings):
|
||||||
# Catches 'Unknown operation 'service'.' when executing systemctl with
|
# Catches 'Unknown operation 'service'.' when executing systemctl with
|
||||||
# misordered arguments
|
# misordered arguments
|
||||||
cmd = command.script.split()
|
cmd = command.script.split()
|
||||||
return ('systemctl' in command.script and
|
return ('Unknown operation \'' in command.stderr and
|
||||||
'Unknown operation \'' in command.stderr and
|
|
||||||
len(cmd) - cmd.index('systemctl') == 3)
|
len(cmd) - cmd.index('systemctl') == 3)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
from thefuck.utils import replace_command
|
|
||||||
import re
|
import re
|
||||||
|
from thefuck.utils import replace_command, for_app
|
||||||
|
|
||||||
|
|
||||||
|
@for_app('tmux')
|
||||||
def match(command, settings):
|
def match(command, settings):
|
||||||
return ('tmux' in command.script
|
return ('ambiguous command:' in command.stderr
|
||||||
and 'ambiguous command:' in command.stderr
|
|
||||||
and 'could be:' in command.stderr)
|
and 'could be:' in command.stderr)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
from thefuck import shells
|
from thefuck import shells
|
||||||
|
from thefuck.utils import for_app
|
||||||
|
|
||||||
|
|
||||||
|
@for_app('tsuru')
|
||||||
def match(command, settings):
|
def match(command, settings):
|
||||||
return (command.script.startswith('tsuru')
|
return ('not authenticated' in command.stderr
|
||||||
and 'not authenticated' in command.stderr
|
|
||||||
and 'session has expired' in command.stderr)
|
and 'session has expired' in command.stderr)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import re
|
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):
|
def match(command, settings):
|
||||||
return (command.script.startswith('tsuru ')
|
return (' is not a tsuru command. See "tsuru help".' in command.stderr
|
||||||
and ' is not a tsuru command. See "tsuru help".' in command.stderr
|
|
||||||
and '\nDid you mean?\n\t' in command.stderr)
|
and '\nDid you mean?\n\t' in command.stderr)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
from thefuck import shells
|
from thefuck import shells
|
||||||
|
from thefuck.utils import for_app
|
||||||
|
|
||||||
|
|
||||||
|
@for_app('vagrant')
|
||||||
def match(command, settings):
|
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):
|
def get_new_command(command, settings):
|
||||||
|
@ -1,37 +1,32 @@
|
|||||||
from functools import wraps
|
|
||||||
import re
|
import re
|
||||||
from shlex import split
|
from shlex import split
|
||||||
|
from decorator import decorator
|
||||||
from ..types import Command
|
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."""
|
"""Resolves git aliases and supports testing for both git and hub."""
|
||||||
@wraps(fn)
|
# supports GitHub's `hub` command
|
||||||
def wrapper(command, settings):
|
# which is recommended to be used with `alias git=hub`
|
||||||
# supports GitHub's `hub` command
|
# but at this point, shell aliases have already been resolved
|
||||||
# which is recommended to be used with `alias git=hub`
|
if not is_app(command, 'git', 'hub'):
|
||||||
# but at this point, shell aliases have already been resolved
|
return False
|
||||||
is_git_cmd = command.script.startswith(('git', 'hub'))
|
|
||||||
|
|
||||||
if not is_git_cmd:
|
# perform git aliases expansion
|
||||||
return False
|
if 'trace: alias expansion:' in command.stderr:
|
||||||
|
search = re.search("trace: alias expansion: ([^ ]*) => ([^\n]*)",
|
||||||
|
command.stderr)
|
||||||
|
alias = search.group(1)
|
||||||
|
|
||||||
# perform git aliases expansion
|
# by default git quotes everything, for example:
|
||||||
if 'trace: alias expansion:' in command.stderr:
|
# 'commit' '--amend'
|
||||||
search = re.search("trace: alias expansion: ([^ ]*) => ([^\n]*)",
|
# which is surprising and does not allow to easily test for
|
||||||
command.stderr)
|
# eg. 'git commit'
|
||||||
alias = search.group(1)
|
expansion = ' '.join(map(quote, split(search.group(2))))
|
||||||
|
new_script = command.script.replace(alias, expansion)
|
||||||
|
|
||||||
# by default git quotes everything, for example:
|
command = Command._replace(command, script=new_script)
|
||||||
# '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)
|
return fn(command, settings)
|
||||||
|
|
||||||
return fn(command, settings)
|
|
||||||
|
|
||||||
return wrapper
|
|
||||||
|
@ -1,24 +1,22 @@
|
|||||||
from functools import wraps
|
|
||||||
import six
|
import six
|
||||||
|
from decorator import decorator
|
||||||
from ..types import Command
|
from ..types import Command
|
||||||
|
|
||||||
|
|
||||||
def sudo_support(fn):
|
@decorator
|
||||||
|
def sudo_support(fn, command, settings):
|
||||||
"""Removes sudo before calling fn and adds it after."""
|
"""Removes sudo before calling fn and adds it after."""
|
||||||
@wraps(fn)
|
if not command.script.startswith('sudo '):
|
||||||
def wrapper(command, settings):
|
return fn(command, settings)
|
||||||
if not command.script.startswith('sudo '):
|
|
||||||
return fn(command, settings)
|
|
||||||
|
|
||||||
result = fn(Command(command.script[5:],
|
result = fn(Command(command.script[5:],
|
||||||
command.stdout,
|
command.stdout,
|
||||||
command.stderr),
|
command.stderr),
|
||||||
settings)
|
settings)
|
||||||
|
|
||||||
if result and isinstance(result, six.string_types):
|
if result and isinstance(result, six.string_types):
|
||||||
return u'sudo {}'.format(result)
|
return u'sudo {}'.format(result)
|
||||||
elif isinstance(result, list):
|
elif isinstance(result, list):
|
||||||
return [u'sudo {}'.format(x) for x in result]
|
return [u'sudo {}'.format(x) for x in result]
|
||||||
else:
|
else:
|
||||||
return result
|
return result
|
||||||
return wrapper
|
|
||||||
|
@ -1,14 +1,34 @@
|
|||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
from traceback import format_stack
|
||||||
|
from .logs import debug
|
||||||
|
|
||||||
Command = namedtuple('Command', ('script', 'stdout', 'stderr'))
|
Command = namedtuple('Command', ('script', 'stdout', 'stderr'))
|
||||||
|
|
||||||
CorrectedCommand = namedtuple('CorrectedCommand', ('script', 'side_effect', 'priority'))
|
|
||||||
|
|
||||||
Rule = namedtuple('Rule', ('name', 'match', 'get_new_command',
|
Rule = namedtuple('Rule', ('name', 'match', 'get_new_command',
|
||||||
'enabled_by_default', 'side_effect',
|
'enabled_by_default', 'side_effect',
|
||||||
'priority', 'requires_output'))
|
'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):
|
class RulesNamesList(list):
|
||||||
"""Wrapper a top of list for storing rules names."""
|
"""Wrapper a top of list for storing rules names."""
|
||||||
@ -18,7 +38,6 @@ class RulesNamesList(list):
|
|||||||
|
|
||||||
|
|
||||||
class Settings(dict):
|
class Settings(dict):
|
||||||
|
|
||||||
def __getattr__(self, item):
|
def __getattr__(self, item):
|
||||||
return self.get(item)
|
return self.get(item)
|
||||||
|
|
||||||
@ -29,3 +48,60 @@ class Settings(dict):
|
|||||||
conf = dict(kwargs)
|
conf = dict(kwargs)
|
||||||
conf.update(self)
|
conf.update(self)
|
||||||
return Settings(conf)
|
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)
|
logs.show_corrected_command(selector.value, settings)
|
||||||
return selector.value
|
return selector.value
|
||||||
|
|
||||||
multiple_cmds = len(corrected_commands) > 1
|
selector.on_change(lambda val: logs.confirm_text(val, settings))
|
||||||
|
|
||||||
selector.on_change(lambda val: logs.confirm_text(val, multiple_cmds, settings))
|
|
||||||
for action in read_actions():
|
for action in read_actions():
|
||||||
if action == SELECT:
|
if action == SELECT:
|
||||||
sys.stderr.write('\n')
|
sys.stderr.write('\n')
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
from difflib import get_close_matches
|
from difflib import get_close_matches
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
from decorator import decorator
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import pickle
|
import pickle
|
||||||
@ -64,12 +65,9 @@ def wrap_settings(params):
|
|||||||
print(settings.apt)
|
print(settings.apt)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def decorator(fn):
|
def _wrap_settings(fn, command, settings):
|
||||||
@wraps(fn)
|
return fn(command, settings.update(**params))
|
||||||
def wrapper(command, settings):
|
return decorator(_wrap_settings)
|
||||||
return fn(command, settings.update(**params))
|
|
||||||
return wrapper
|
|
||||||
return decorator
|
|
||||||
|
|
||||||
|
|
||||||
def get_closest(word, possibilities, n=3, cutoff=0.6, fallback_to_first=True):
|
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)
|
u' {} '.format(from_), u' {} '.format(to), 1)
|
||||||
|
|
||||||
|
|
||||||
def eager(fn):
|
@decorator
|
||||||
@wraps(fn)
|
def eager(fn, *args, **kwargs):
|
||||||
def wrapper(*args, **kwargs):
|
return list(fn(*args, **kwargs))
|
||||||
return list(fn(*args, **kwargs))
|
|
||||||
return wrapper
|
|
||||||
|
|
||||||
|
|
||||||
@eager
|
@eager
|
||||||
@ -133,3 +129,24 @@ def replace_command(command, broken, matched):
|
|||||||
new_cmds = get_close_matches(broken, matched, cutoff=0.1)
|
new_cmds = get_close_matches(broken, matched, cutoff=0.1)
|
||||||
return [replace_argument(command.script, broken, new_cmd.strip())
|
return [replace_argument(command.script, broken, new_cmd.strip())
|
||||||
for new_cmd in new_cmds]
|
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