From 6539c853b4838c7c6ea049604ddb5bcd024ad6ba Mon Sep 17 00:00:00 2001 From: mcarton Date: Sat, 16 May 2015 15:36:27 +0200 Subject: [PATCH 1/7] Add tests for the `whois` rule --- tests/rules/test_whois.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 tests/rules/test_whois.py diff --git a/tests/rules/test_whois.py b/tests/rules/test_whois.py new file mode 100644 index 00000000..b911106e --- /dev/null +++ b/tests/rules/test_whois.py @@ -0,0 +1,19 @@ +import pytest +from thefuck.rules.whois import match, get_new_command +from tests.utils import Command + + +@pytest.mark.parametrize('command', [ + Command(script='whois https://en.wikipedia.org/wiki/Main_Page'), + Command(script='whois https://en.wikipedia.org/'), + Command(script='whois en.wikipedia.org')]) +def test_match(command): + assert match(command, None) + + +@pytest.mark.parametrize('command, new_command', [ + (Command('whois https://en.wikipedia.org/wiki/Main_Page'), 'whois en.wikipedia.org'), + (Command('whois https://en.wikipedia.org/'), 'whois en.wikipedia.org'), + (Command('whois en.wikipedia.org'), 'whois wikipedia.org')]) +def test_get_new_command(command, new_command): + assert get_new_command(command, None) == new_command From bb4b42d2f1b483a263937e7c90253bfb476df0e6 Mon Sep 17 00:00:00 2001 From: mcarton Date: Sat, 16 May 2015 15:37:00 +0200 Subject: [PATCH 2/7] Add the `whois` rule in README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 9cf7237f..1ac70f45 100644 --- a/README.md +++ b/README.md @@ -171,6 +171,7 @@ using the matched rule and runs it. Rules enabled by default are as follows: * `ssh_known_hosts` – removes host from `known_hosts` on warning; * `sudo` – prepends `sudo` to previous command if it failed because of permissions; * `switch_layout` – switches command from your local layout to en; +* `whois` – fixes `whois` command; * `apt_get` – installs app from apt if it not installed; * `brew_install` – fixes formula name for `brew install`; * `composer_not_command` – fixes composer command name. From d854320acc27f95984857673af20a6931a118e60 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Blum de Aguiar Date: Wed, 13 May 2015 16:17:53 -0300 Subject: [PATCH 3/7] refact(shells): add specific `app_alias` methods for Bash and Zsh Signed-off-by: Pablo Santiago Blum de Aguiar --- thefuck/shells.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/thefuck/shells.py b/thefuck/shells.py index 940f196f..50d9145e 100644 --- a/thefuck/shells.py +++ b/thefuck/shells.py @@ -49,6 +49,9 @@ class Generic(object): class Bash(Generic): + def app_alias(self): + return "\nalias fuck='eval $(thefuck $(fc -ln -1)); history -r'\n" + def _parse_alias(self, alias): name, value = alias.replace('alias ', '', 1).split('=', 1) if value[0] == value[-1] == '"' or value[0] == value[-1] == "'": @@ -71,6 +74,9 @@ class Bash(Generic): class Zsh(Generic): + def app_alias(self): + return "\nalias fuck='eval $(thefuck $(fc -ln -1 | tail -n 1)); fc -R'\n" + def _parse_alias(self, alias): name, value = alias.split('=', 1) if value[0] == value[-1] == '"' or value[0] == value[-1] == "'": From 3d0d4be4a9ce51b67e1278b47ca5c593834b3d59 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Blum de Aguiar Date: Tue, 12 May 2015 21:13:51 -0300 Subject: [PATCH 4/7] refact(shells): add `and_` method to assemble expressions involving AND Signed-off-by: Pablo Santiago Blum de Aguiar --- tests/rules/__init__.py | 0 tests/rules/conftest.py | 6 ++++++ thefuck/rules/apt_get.py | 5 ++++- thefuck/rules/cd_mkdir.py | 4 +++- thefuck/rules/git_add.py | 4 +++- thefuck/rules/git_checkout.py | 4 +++- thefuck/rules/git_stash.py | 6 +++++- thefuck/rules/no_such_file.py | 4 +++- thefuck/rules/pacman.py | 4 +++- thefuck/shells.py | 7 +++++++ 10 files changed, 37 insertions(+), 7 deletions(-) create mode 100644 tests/rules/__init__.py create mode 100644 tests/rules/conftest.py diff --git a/tests/rules/__init__.py b/tests/rules/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/rules/conftest.py b/tests/rules/conftest.py new file mode 100644 index 00000000..94152a6c --- /dev/null +++ b/tests/rules/conftest.py @@ -0,0 +1,6 @@ +import pytest + + +@pytest.fixture(autouse=True) +def generic_shell(monkeypatch): + monkeypatch.setattr('thefuck.shells.and_', lambda *x: ' && '.join(x)) diff --git a/thefuck/rules/apt_get.py b/thefuck/rules/apt_get.py index 4d5eca6b..b78bb743 100644 --- a/thefuck/rules/apt_get.py +++ b/thefuck/rules/apt_get.py @@ -1,3 +1,5 @@ +from thefuck import shells + try: import CommandNotFound except ImportError: @@ -20,4 +22,5 @@ def get_new_command(command, settings): c = CommandNotFound.CommandNotFound() pkgs = c.getPackages(command.script.split(" ")[0]) name, _ = pkgs[0] - return "sudo apt-get install {} && {}".format(name, command.script) + formatme = shells.and_('sudo apt-get install {}', '{}') + return formatme.format(name, command.script) diff --git a/thefuck/rules/cd_mkdir.py b/thefuck/rules/cd_mkdir.py index 7aa1d9da..168a2ce0 100644 --- a/thefuck/rules/cd_mkdir.py +++ b/thefuck/rules/cd_mkdir.py @@ -1,4 +1,5 @@ import re +from thefuck import shells from thefuck.utils import sudo_support @@ -11,4 +12,5 @@ def match(command, settings): @sudo_support def get_new_command(command, settings): - return re.sub(r'^cd (.*)', 'mkdir -p \\1 && cd \\1', command.script) + repl = shells.and_('mkdir -p \\1', 'cd \\1') + return re.sub(r'^cd (.*)', repl, command.script) diff --git a/thefuck/rules/git_add.py b/thefuck/rules/git_add.py index 66c7f1dc..bc05d011 100644 --- a/thefuck/rules/git_add.py +++ b/thefuck/rules/git_add.py @@ -1,4 +1,5 @@ import re +from thefuck import shells def match(command, settings): @@ -12,4 +13,5 @@ def get_new_command(command, settings): r"error: pathspec '([^']*)' " "did not match any file\(s\) known to git.", command.stderr)[0] - return 'git add -- {} && {}'.format(missing_file, command.script) + formatme = shells.and_('git add -- {}', '{}') + return formatme.format(missing_file, command.script) diff --git a/thefuck/rules/git_checkout.py b/thefuck/rules/git_checkout.py index 271562b8..6c9d259f 100644 --- a/thefuck/rules/git_checkout.py +++ b/thefuck/rules/git_checkout.py @@ -1,4 +1,5 @@ import re +from thefuck import shells def match(command, settings): @@ -12,4 +13,5 @@ def get_new_command(command, settings): r"error: pathspec '([^']*)' " "did not match any file\(s\) known to git.", command.stderr)[0] - return 'git branch {} && {}'.format(missing_file, command.script) + formatme = shells.and_('git branch {}', '{}') + return formatme.format(missing_file, command.script) diff --git a/thefuck/rules/git_stash.py b/thefuck/rules/git_stash.py index 58bfbb38..9e9034a3 100644 --- a/thefuck/rules/git_stash.py +++ b/thefuck/rules/git_stash.py @@ -1,3 +1,6 @@ +from thefuck import shells + + def match(command, settings): # catches "Please commit or stash them" and "Please, commit your changes or # stash them before you can switch branches." @@ -5,4 +8,5 @@ def match(command, settings): def get_new_command(command, settings): - return 'git stash && ' + command.script + formatme = shells.and_('git stash', '{}') + return formatme.format(command.script) diff --git a/thefuck/rules/no_such_file.py b/thefuck/rules/no_such_file.py index 9a0f3b45..44572f19 100644 --- a/thefuck/rules/no_such_file.py +++ b/thefuck/rules/no_such_file.py @@ -1,4 +1,5 @@ import re +from thefuck import shells patterns = ( @@ -25,4 +26,5 @@ def get_new_command(command, settings): file = file[0] dir = file[0:file.rfind('/')] - return 'mkdir -p {} && {}'.format(dir, command.script) + formatme = shells.and_('mkdir -p {}', '{}') + return formatme.format(dir, command.script) diff --git a/thefuck/rules/pacman.py b/thefuck/rules/pacman.py index 71c6e698..2ec507e2 100644 --- a/thefuck/rules/pacman.py +++ b/thefuck/rules/pacman.py @@ -1,4 +1,5 @@ import subprocess +from thefuck import shells from thefuck.utils import DEVNULL @@ -30,7 +31,8 @@ def match(command, settings): def get_new_command(command, settings): package = __get_pkgfile(command)[0] - return '{} -S {} && {}'.format(pacman, package, command.script) + formatme = shells.and_('{} -S {}', '{}') + return formatme.format(pacman, package, command.script) if not __command_available('pkgfile'): diff --git a/thefuck/shells.py b/thefuck/shells.py index 50d9145e..87715d6b 100644 --- a/thefuck/shells.py +++ b/thefuck/shells.py @@ -47,6 +47,9 @@ class Generic(object): with open(history_file_name, 'a') as history: history.write(self._get_history_line(command_script)) + def and_(self, *commands): + return ' && '.join(commands) + class Bash(Generic): def app_alias(self): @@ -150,3 +153,7 @@ def app_alias(): def put_to_history(command): return _get_shell().put_to_history(command) + + +def and_(*commands): + return _get_shell().and_(*commands) From 179839c32fab0241b2ba9dc4c70d938eabfcea23 Mon Sep 17 00:00:00 2001 From: Pablo Santiago Blum de Aguiar Date: Thu, 14 May 2015 17:34:40 -0300 Subject: [PATCH 5/7] test(rules): test other rules involving `shells.and_()` Signed-off-by: Pablo Santiago Blum de Aguiar --- tests/rules/test_apt_get.py | 59 ++++++++++++++++++++++++++++++++ tests/rules/test_git_add.py | 39 +++++++++++++++++++++ tests/rules/test_git_checkout.py | 37 ++++++++++++++++++++ tests/rules/test_git_stash.py | 39 +++++++++++++++++++++ tests/rules/test_pacman.py | 53 ++++++++++++++++++++++++++++ 5 files changed, 227 insertions(+) create mode 100644 tests/rules/test_apt_get.py create mode 100644 tests/rules/test_git_add.py create mode 100644 tests/rules/test_git_checkout.py create mode 100644 tests/rules/test_git_stash.py create mode 100644 tests/rules/test_pacman.py diff --git a/tests/rules/test_apt_get.py b/tests/rules/test_apt_get.py new file mode 100644 index 00000000..56ad8208 --- /dev/null +++ b/tests/rules/test_apt_get.py @@ -0,0 +1,59 @@ +import pytest +from mock import Mock, patch +from thefuck.rules import apt_get +from thefuck.rules.apt_get import match, get_new_command +from tests.utils import Command + + +# python-commandnotfound is available in ubuntu 14.04+ +@pytest.mark.skipif(not getattr(apt_get, 'enabled_by_default', True), + reason='Skip if python-commandnotfound is not available') +@pytest.mark.parametrize('command', [ + Command(script='vim', stderr='vim: command not found')]) +def test_match(command): + assert match(command, None) + + +@pytest.mark.parametrize('command, return_value', [ + (Command(script='vim', stderr='vim: command not found'), + [('vim', 'main'), ('vim-tiny', 'main')])]) +@patch('thefuck.rules.apt_get.CommandNotFound', create=True) +@patch.multiple(apt_get, create=True, apt_get='apt_get') +def test_match_mocked(cmdnf_mock, command, return_value): + get_packages = Mock(return_value=return_value) + cmdnf_mock.CommandNotFound.return_value = Mock(getPackages=get_packages) + assert match(command, None) + assert cmdnf_mock.CommandNotFound.called + assert get_packages.called + + +@pytest.mark.parametrize('command', [ + Command(script='vim', stderr=''), Command()]) +def test_not_match(command): + assert not match(command, None) + + +# python-commandnotfound is available in ubuntu 14.04+ +@pytest.mark.skipif(not getattr(apt_get, 'enabled_by_default', True), + reason='Skip if python-commandnotfound is not available') +@pytest.mark.parametrize('command, new_command', [ + (Command('vim'), 'sudo apt-get install vim && vim'), + (Command('convert'), 'sudo apt-get install imagemagick && convert')]) +def test_get_new_command(command, new_command): + assert get_new_command(command, None) == new_command + + +@pytest.mark.parametrize('command, new_command, return_value', [ + (Command('vim'), 'sudo apt-get install vim && vim', + [('vim', 'main'), ('vim-tiny', 'main')]), + (Command('convert'), 'sudo apt-get install imagemagick && convert', + [('imagemagick', 'main'), + ('graphicsmagick-imagemagick-compat', 'universe')])]) +@patch('thefuck.rules.apt_get.CommandNotFound', create=True) +@patch.multiple(apt_get, create=True, apt_get='apt_get') +def test_get_new_command_mocked(cmdnf_mock, command, new_command, return_value): + get_packages = Mock(return_value=return_value) + cmdnf_mock.CommandNotFound.return_value = Mock(getPackages=get_packages) + assert get_new_command(command, None) == new_command + assert cmdnf_mock.CommandNotFound.called + assert get_packages.called diff --git a/tests/rules/test_git_add.py b/tests/rules/test_git_add.py new file mode 100644 index 00000000..8bad9bb6 --- /dev/null +++ b/tests/rules/test_git_add.py @@ -0,0 +1,39 @@ +import pytest +from thefuck.rules.git_add import match, get_new_command +from tests.utils import Command + + +@pytest.fixture +def did_not_match(target, did_you_forget=True): + error = ("error: pathspec '{}' did not match any " + "file(s) known to git.".format(target)) + if did_you_forget: + error = ("{}\nDid you forget to 'git add'?'".format(error)) + return error + + +@pytest.mark.parametrize('command', [ + Command(script='git submodule update unknown', + stderr=did_not_match('unknown')), + Command(script='git commit unknown', + stderr=did_not_match('unknown'))]) # Older versions of Git +def test_match(command): + assert match(command, None) + + +@pytest.mark.parametrize('command', [ + Command(script='git submodule update known', stderr=('')), + Command(script='git commit known', stderr=('')), + Command(script='git commit unknown', # Newer versions of Git + stderr=did_not_match('unknown', False))]) +def test_not_match(command): + assert not match(command, None) + + +@pytest.mark.parametrize('command, new_command', [ + (Command('git submodule update unknown', stderr=did_not_match('unknown')), + 'git add -- unknown && git submodule update unknown'), + (Command('git commit unknown', stderr=did_not_match('unknown')), # Old Git + 'git add -- unknown && git commit unknown')]) +def test_get_new_command(command, new_command): + assert get_new_command(command, None) == new_command diff --git a/tests/rules/test_git_checkout.py b/tests/rules/test_git_checkout.py new file mode 100644 index 00000000..a540b62d --- /dev/null +++ b/tests/rules/test_git_checkout.py @@ -0,0 +1,37 @@ +import pytest +from thefuck.rules.git_checkout import match, get_new_command +from tests.utils import Command + + +@pytest.fixture +def did_not_match(target, did_you_forget=False): + error = ("error: pathspec '{}' did not match any " + "file(s) known to git.".format(target)) + if did_you_forget: + error = ("{}\nDid you forget to 'git add'?'".format(error)) + return error + + +@pytest.mark.parametrize('command', [ + Command(script='git checkout unknown', stderr=did_not_match('unknown')), + Command(script='git commit unknown', stderr=did_not_match('unknown'))]) +def test_match(command): + assert match(command, None) + + +@pytest.mark.parametrize('command', [ + Command(script='git submodule update unknown', + stderr=did_not_match('unknown', True)), + Command(script='git checkout known', stderr=('')), + Command(script='git commit known', stderr=(''))]) +def test_not_match(command): + assert not match(command, None) + + +@pytest.mark.parametrize('command, new_command', [ + (Command(script='git checkout unknown', stderr=did_not_match('unknown')), + 'git branch unknown && git checkout unknown'), + (Command('git commit unknown', stderr=did_not_match('unknown')), + 'git branch unknown && git commit unknown')]) +def test_get_new_command(command, new_command): + assert get_new_command(command, None) == new_command diff --git a/tests/rules/test_git_stash.py b/tests/rules/test_git_stash.py new file mode 100644 index 00000000..c62a48aa --- /dev/null +++ b/tests/rules/test_git_stash.py @@ -0,0 +1,39 @@ +import pytest +from thefuck.rules.git_stash import match, get_new_command +from tests.utils import Command + + +@pytest.fixture +def cherry_pick_error(): + return ('error: Your local changes would be overwritten by cherry-pick.\n' + 'hint: Commit your changes or stash them to proceed.\n' + 'fatal: cherry-pick failed') + + +@pytest.fixture +def rebase_error(): + return ('Cannot rebase: Your index contains uncommitted changes.\n' + 'Please commit or stash them.') + + +@pytest.mark.parametrize('command', [ + Command(script='git cherry-pick a1b2c3d', stderr=cherry_pick_error()), + Command(script='git rebase -i HEAD~7', stderr=rebase_error())]) +def test_match(command): + assert match(command, None) + + +@pytest.mark.parametrize('command', [ + Command(script='git cherry-pick a1b2c3d', stderr=('')), + Command(script='git rebase -i HEAD~7', stderr=(''))]) +def test_not_match(command): + assert not match(command, None) + + +@pytest.mark.parametrize('command, new_command', [ + (Command(script='git cherry-pick a1b2c3d', stderr=cherry_pick_error), + 'git stash && git cherry-pick a1b2c3d'), + (Command('git rebase -i HEAD~7', stderr=rebase_error), + 'git stash && git rebase -i HEAD~7')]) +def test_get_new_command(command, new_command): + assert get_new_command(command, None) == new_command diff --git a/tests/rules/test_pacman.py b/tests/rules/test_pacman.py new file mode 100644 index 00000000..8d719cd6 --- /dev/null +++ b/tests/rules/test_pacman.py @@ -0,0 +1,53 @@ +import pytest +from mock import patch +from thefuck.rules import pacman +from thefuck.rules.pacman import match, get_new_command +from tests.utils import Command + + +pacman_cmd = getattr(pacman, 'pacman', 'pacman') + + +@pytest.mark.skipif(not getattr(pacman, 'enabled_by_default', True), + reason='Skip if pacman is not available') +@pytest.mark.parametrize('command', [ + Command(script='vim', stderr='vim: command not found')]) +def test_match(command): + assert match(command, None) + + +@pytest.mark.parametrize('command, return_value', [ + (Command(script='vim', stderr='vim: command not found'), 'vim foo bar')]) +@patch('thefuck.rules.pacman.subprocess') +@patch.multiple(pacman, create=True, pacman=pacman_cmd) +def test_match_mocked(subp_mock, command, return_value): + subp_mock.check_output.return_value = return_value + assert match(command, None) + assert subp_mock.check_output.called + + +@pytest.mark.parametrize('command', [ + Command(script='vim', stderr=''), Command()]) +def test_not_match(command): + assert not match(command, None) + + +@pytest.mark.skipif(not getattr(pacman, 'enabled_by_default', True), + reason='Skip if pacman is not available') +@pytest.mark.parametrize('command, new_command', [ + (Command('vim'), '{} -S vim && vim'.format(pacman_cmd)), + (Command('convert'), '{} -S imagemagick && convert'.format(pacman_cmd))]) +def test_get_new_command(command, new_command, mocker): + assert get_new_command(command, None) == new_command + + +@pytest.mark.parametrize('command, new_command, return_value', [ + (Command('vim'), '{} -S vim && vim'.format(pacman_cmd), 'vim foo bar'), + (Command('convert'), '{} -S imagemagick && convert'.format(pacman_cmd), + 'imagemagick foo bar')]) +@patch('thefuck.rules.pacman.subprocess') +@patch.multiple(pacman, create=True, pacman=pacman_cmd) +def test_get_new_command_mocked(subp_mock, command, new_command, return_value): + subp_mock.check_output.return_value = return_value + assert get_new_command(command, None) == new_command + assert subp_mock.check_output.called From 9ade21bf0ac8a95252d82b649b7ee186762a749f Mon Sep 17 00:00:00 2001 From: Pablo Santiago Blum de Aguiar Date: Fri, 15 May 2015 11:54:38 -0300 Subject: [PATCH 6/7] refact(travis): enable verbose mode for tests on travis Signed-off-by: Pablo Santiago Blum de Aguiar --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index b124bb3a..c14b697a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,4 +6,4 @@ python: install: - python setup.py develop - pip install -r requirements.txt -script: py.test +script: py.test -v From f04c4396eb3cb75cf9f3a9e25ece51d2cc9f50c2 Mon Sep 17 00:00:00 2001 From: mcarton Date: Sat, 16 May 2015 18:57:42 +0200 Subject: [PATCH 7/7] Fix Python 2.7 support --- thefuck/rules/whois.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/thefuck/rules/whois.py b/thefuck/rules/whois.py index f019758e..f53cdde8 100644 --- a/thefuck/rules/whois.py +++ b/thefuck/rules/whois.py @@ -1,4 +1,5 @@ -from urllib.parse import urlparse +# -*- encoding: utf-8 -*- +from six.moves.urllib.parse import urlparse def match(command, settings):