diff --git a/README.md b/README.md index 4f974177..e67bff9d 100644 --- a/README.md +++ b/README.md @@ -189,7 +189,7 @@ using the matched rule and runs it. Rules enabled by default are as follows: * `systemctl` – correctly orders parameters of confusing `systemctl`; * `test.py` – runs `py.test` instead of `test.py`; * `tsuru_login` – runs `tsuru login` if not authenticated or session expired; -* `tsuru_not_command` – fixes wrong tsuru commands like `tsuru shell`; +* `tsuru_not_command` – fixes wrong `tsuru` commands like `tsuru shell`; * `tmux` – fixes `tmux` commands; * `whois` – fixes `whois` command. @@ -199,7 +199,7 @@ Enabled by default only on specific platforms: * `brew_install` – fixes formula name for `brew install`; * `brew_unknown_command` – fixes wrong brew commands, for example `brew docto/brew doctor`; * `brew_upgrade` – appends `--all` to `brew upgrade` as per Homebrew's new behaviour; -* `pacman` – installs app with `pacman` or `yaourt` if it is not installed. +* `pacman` – installs app with `pacman` if it is not installed (uses `yaourt` if available). Bundled, but not enabled by default: diff --git a/tests/rules/test_apt_get.py b/tests/rules/test_apt_get.py index 56ad8208..ca2d95d9 100644 --- a/tests/rules/test_apt_get.py +++ b/tests/rules/test_apt_get.py @@ -16,6 +16,8 @@ def test_match(command): @pytest.mark.parametrize('command, return_value', [ (Command(script='vim', stderr='vim: command not found'), + [('vim', 'main'), ('vim-tiny', 'main')]), + (Command(script='sudo 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') @@ -38,7 +40,9 @@ def test_not_match(command): 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')]) + (Command('convert'), 'sudo apt-get install imagemagick && convert'), + (Command('sudo vim'), 'sudo apt-get install vim && sudo vim'), + (Command('sudo convert'), 'sudo apt-get install imagemagick && sudo convert')]) def test_get_new_command(command, new_command): assert get_new_command(command, None) == new_command @@ -47,6 +51,11 @@ def test_get_new_command(command, new_command): (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')]), + (Command('sudo vim'), 'sudo apt-get install vim && sudo vim', + [('vim', 'main'), ('vim-tiny', 'main')]), + (Command('sudo convert'), 'sudo apt-get install imagemagick && sudo convert', [('imagemagick', 'main'), ('graphicsmagick-imagemagick-compat', 'universe')])]) @patch('thefuck.rules.apt_get.CommandNotFound', create=True) @@ -55,5 +64,3 @@ 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_pacman.py b/tests/rules/test_pacman.py index 73413589..0006e8b6 100644 --- a/tests/rules/test_pacman.py +++ b/tests/rules/test_pacman.py @@ -7,6 +7,7 @@ from tests.utils import Command pacman_cmd = getattr(pacman, 'pacman', 'pacman') +PKGFILE_OUTPUT_SUDO = 'core/sudo 1.8.13-13/usr/bin/sudo' PKGFILE_OUTPUT_CONVERT = 'extra/imagemagick 6.9.1.0-1\t/usr/bin/convert' PKGFILE_OUTPUT_VIM = '''extra/gvim 7.4.712-1 \t/usr/bin/vim @@ -28,7 +29,7 @@ def test_match(command): @pytest.mark.parametrize('command, return_value', [ (Command(script='vim', stderr='vim: command not found'), PKGFILE_OUTPUT_VIM), (Command(script='sudo vim', stderr='sudo: vim: command not found'), PKGFILE_OUTPUT_VIM)]) -@patch('thefuck.rules.pacman.subprocess') +@patch('thefuck.archlinux.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 @@ -72,8 +73,9 @@ def test_get_new_command(command, new_command, mocker): (Command('vim'), vim_possibilities, PKGFILE_OUTPUT_VIM), (Command('sudo vim'), sudo_vim_possibilities, PKGFILE_OUTPUT_VIM), (Command('convert'), ['{} -S extra/imagemagick && convert'.format(pacman_cmd)], PKGFILE_OUTPUT_CONVERT), + (Command('sudo'), ['{} -S core/sudo && sudo'.format(pacman_cmd)], PKGFILE_OUTPUT_SUDO), (Command('sudo convert'), ['{} -S extra/imagemagick && sudo convert'.format(pacman_cmd)], PKGFILE_OUTPUT_CONVERT)]) -@patch('thefuck.rules.pacman.subprocess') +@patch('thefuck.archlinux.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 diff --git a/tests/rules/test_pacman_not_found.py b/tests/rules/test_pacman_not_found.py new file mode 100644 index 00000000..d772b23c --- /dev/null +++ b/tests/rules/test_pacman_not_found.py @@ -0,0 +1,48 @@ +import pytest +from mock import patch +from thefuck.rules import pacman_not_found +from thefuck.rules.pacman_not_found import match, get_new_command +from tests.utils import Command + +PKGFILE_OUTPUT_LLC = '''extra/llvm 3.6.0-5 /usr/bin/llc +extra/llvm35 3.5.2-13/usr/bin/llc''' + + +@pytest.mark.skipif(not getattr(pacman_not_found, 'enabled_by_default', True), + reason='Skip if pacman is not available') +@pytest.mark.parametrize('command', [ + Command(script='yaourt -S llc', stderr='error: target not found: llc'), + Command(script='pacman llc', stderr='error: target not found: llc'), + Command(script='sudo pacman llc', stderr='error: target not found: llc')]) +def test_match(command): + assert match(command, None) + + +@pytest.mark.parametrize('command', [ + Command(script='yaourt -S llc', stderr='error: target not found: llc'), + Command(script='pacman llc', stderr='error: target not found: llc'), + Command(script='sudo pacman llc', stderr='error: target not found: llc')]) +@patch('thefuck.archlinux.subprocess') +def test_match_mocked(subp_mock, command): + subp_mock.check_output.return_value = PKGFILE_OUTPUT_LLC + assert match(command, None) + + +@pytest.mark.skipif(not getattr(pacman_not_found, 'enabled_by_default', True), + reason='Skip if pacman is not available') +@pytest.mark.parametrize('command, fixed', [ + (Command(script='yaourt -S llc', stderr='error: target not found: llc'), ['yaourt -S extra/llvm', 'yaourt -S extra/llvm35']), + (Command(script='pacman -S llc', stderr='error: target not found: llc'), ['pacman -S extra/llvm', 'pacman -S extra/llvm35']), + (Command(script='sudo pacman -S llc', stderr='error: target not found: llc'), ['sudo pacman -S extra/llvm', 'sudo pacman -S extra/llvm35'])]) +def test_get_new_command(command, fixed): + assert get_new_command(command, None) == fixed + + +@pytest.mark.parametrize('command, fixed', [ + (Command(script='yaourt -S llc', stderr='error: target not found: llc'), ['yaourt -S extra/llvm', 'yaourt -S extra/llvm35']), + (Command(script='pacman -S llc', stderr='error: target not found: llc'), ['pacman -S extra/llvm', 'pacman -S extra/llvm35']), + (Command(script='sudo pacman -S llc', stderr='error: target not found: llc'), ['sudo pacman -S extra/llvm', 'sudo pacman -S extra/llvm35'])]) +@patch('thefuck.archlinux.subprocess') +def test_get_new_command_mocked(subp_mock, command, fixed): + subp_mock.check_output.return_value = PKGFILE_OUTPUT_LLC + assert get_new_command(command, None) == fixed diff --git a/thefuck/archlinux.py b/thefuck/archlinux.py new file mode 100644 index 00000000..04b97149 --- /dev/null +++ b/thefuck/archlinux.py @@ -0,0 +1,41 @@ +""" This file provide some utility functions for Arch Linux specific rules.""" +import thefuck.utils +import subprocess + + +@thefuck.utils.memoize +def get_pkgfile(command): + """ Gets the packages that provide the given command using `pkgfile`. + + If the command is of the form `sudo foo`, searches for the `foo` command + instead. + """ + try: + command = command.strip() + + if command.startswith('sudo '): + command = command[5:] + + command = command.split(" ")[0] + + packages = subprocess.check_output( + ['pkgfile', '-b', '-v', command], + universal_newlines=True, stderr=thefuck.utils.DEVNULL + ).splitlines() + + return [package.split()[0] for package in packages] + except subprocess.CalledProcessError: + return None + + +def archlinux_env(): + if thefuck.utils.which('yaourt'): + pacman = 'yaourt' + elif thefuck.utils.which('pacman'): + pacman = 'sudo pacman' + else: + return False, None + + enabled_by_default = thefuck.utils.which('pkgfile') + + return enabled_by_default, pacman diff --git a/thefuck/rules/apt_get.py b/thefuck/rules/apt_get.py index 1b727188..7dc306f2 100644 --- a/thefuck/rules/apt_get.py +++ b/thefuck/rules/apt_get.py @@ -1,27 +1,30 @@ from thefuck import shells -from thefuck.utils import sudo_support +from thefuck.utils import memoize try: import CommandNotFound except ImportError: enabled_by_default = False -@sudo_support -def match(command, settings): - if 'not found' in command.stderr: - try: - c = CommandNotFound.CommandNotFound() - pkgs = c.getPackages(command.script.split(" ")[0]) - name, _ = pkgs[0] - return True - except IndexError: - # IndexError is thrown when no matching package is found - return False -@sudo_support +@memoize +def get_package(command): + try: + c = CommandNotFound.CommandNotFound() + cmd = command.split(' ') + pkgs = c.getPackages(cmd[0] if cmd[0] != 'sudo' else cmd[1]) + name, _ = pkgs[0] + return name + except IndexError: + # IndexError is thrown when no matching package is found + return None + + +def match(command, settings): + return 'not found' in command.stderr and get_package(command.script) + + def get_new_command(command, settings): - c = CommandNotFound.CommandNotFound() - pkgs = c.getPackages(command.script.split(" ")[0]) - name, _ = pkgs[0] + name = get_package(command.script) formatme = shells.and_('sudo apt-get install {}', '{}') return formatme.format(name, command.script) diff --git a/thefuck/rules/pacman.py b/thefuck/rules/pacman.py index 2bf64b30..04201cd4 100644 --- a/thefuck/rules/pacman.py +++ b/thefuck/rules/pacman.py @@ -1,46 +1,16 @@ -import subprocess -from thefuck.utils import DEVNULL, which +from thefuck.archlinux import archlinux_env, get_pkgfile from thefuck import shells -from thefuck.utils import memoize - - -@memoize -def __get_pkgfile(command): - try: - command = command.script - - if command.startswith('sudo'): - command = command[5:] - - command = command.split(" ")[0] - - packages = subprocess.check_output( - ['pkgfile', '-b', '-v', command], - universal_newlines=True, stderr=DEVNULL - ).splitlines() - - return [package.split()[0] for package in packages] - except subprocess.CalledProcessError: - return None def match(command, settings): - return 'not found' in command.stderr and __get_pkgfile(command) + return 'not found' in command.stderr and get_pkgfile(command.script) def get_new_command(command, settings): - packages = __get_pkgfile(command) + packages = get_pkgfile(command.script) formatme = shells.and_('{} -S {}', '{}') return [formatme.format(pacman, package, command.script) for package in packages] - -if not which('pkgfile'): - enabled_by_default = False -elif which('yaourt'): - pacman = 'yaourt' -elif which('pacman'): - pacman = 'sudo pacman' -else: - enabled_by_default = False +enabled_by_default, pacman = archlinux_env() diff --git a/thefuck/rules/pacman_not_found.py b/thefuck/rules/pacman_not_found.py new file mode 100644 index 00000000..4ea1b64a --- /dev/null +++ b/thefuck/rules/pacman_not_found.py @@ -0,0 +1,24 @@ +""" Fixes wrong package names with pacman or yaourt. + +For example the `llc` program is in package `llvm` so this: + yaourt -S llc +should be: + yaourt -S llvm +""" + +from thefuck.utils import replace_command +from thefuck.archlinux import archlinux_env, get_pkgfile + + +def match(command, settings): + return (command.script.startswith(('pacman', 'sudo pacman', 'yaourt')) + and 'error: target not found:' in command.stderr) + + +def get_new_command(command, settings): + pgr = command.script.split()[-1] + + return replace_command(command, pgr, get_pkgfile(pgr)) + + +enabled_by_default, _ = archlinux_env() diff --git a/thefuck/utils.py b/thefuck/utils.py index dde7c906..0003e8ba 100644 --- a/thefuck/utils.py +++ b/thefuck/utils.py @@ -1,3 +1,4 @@ +from .types import Command from difflib import get_close_matches from functools import wraps from pathlib import Path @@ -6,7 +7,6 @@ import os import pickle import re import six -from .types import Command DEVNULL = open(os.devnull, 'w')