mirror of
https://github.com/nvbn/thefuck.git
synced 2025-03-14 14:48:49 +00:00
Merge branch 'master' into rule_for_branch_dash_0
This commit is contained in:
commit
18227fda6d
13
README.md
13
README.md
@ -114,8 +114,7 @@ sudo pip3 install thefuck
|
||||
|
||||
On FreeBSD, install *The Fuck* with the following commands:
|
||||
```bash
|
||||
sudo portsnap fetch update
|
||||
cd /usr/ports/misc/thefuck && sudo make install clean
|
||||
pkg install thefuck
|
||||
```
|
||||
|
||||
On ChromeOS, install *The Fuck* using [chromebrew](https://github.com/skycocker/chromebrew) with the following command:
|
||||
@ -190,7 +189,9 @@ following rules are enabled by default:
|
||||
* `dirty_unzip` – fixes `unzip` command that unzipped in the current directory;
|
||||
* `django_south_ghost` – adds `--delete-ghost-migrations` to failed because ghosts django south migration;
|
||||
* `django_south_merge` – adds `--merge` to inconsistent django south migration;
|
||||
* `docker_login` – executes a `docker login` and repeats the previous command;
|
||||
* `docker_not_command` – fixes wrong docker commands like `docker tags`;
|
||||
* `docker_image_being_used_by_container` ‐ removes the container that is using the image before removing the image;
|
||||
* `dry` – fixes repetitions like `git git push`;
|
||||
* `fab_command_not_found` – fix misspelled fabric commands;
|
||||
* `fix_alt_space` – replaces Alt+Space with Space character;
|
||||
@ -266,10 +267,12 @@ following rules are enabled by default:
|
||||
* `no_command` – fixes wrong console commands, for example `vom/vim`;
|
||||
* `no_such_file` – creates missing directories with `mv` and `cp` commands;
|
||||
* `open` – either prepends `http://` to address passed to `open` or create a new file or directory and passes it to `open`;
|
||||
* `pip_install` – fixes permission issues with `pip install` commands by adding `--user` or prepending `sudo` if necessary;
|
||||
* `pip_unknown_command` – fixes wrong `pip` commands, for example `pip instatl/pip install`;
|
||||
* `php_s` – replaces `-s` by `-S` when trying to run a local php server;
|
||||
* `port_already_in_use` – kills process that bound port;
|
||||
* `prove_recursively` – adds `-r` when called with directory;
|
||||
* `pyenv_no_such_command` – fixes wrong pyenv commands like `pyenv isntall` or `pyenv list`;
|
||||
* `python_command` – prepends `python` when you try to run non-executable/without `./` python script;
|
||||
* `python_execute` – appends missing `.py` when executing Python files;
|
||||
* `quotation_marks` – fixes uneven usage of `'` and `"` when containing args';
|
||||
@ -285,6 +288,7 @@ following rules are enabled by default:
|
||||
* `sudo_command_from_user_path` – runs commands from users `$PATH` with `sudo`;
|
||||
* `switch_lang` – switches command from your local layout to en;
|
||||
* `systemctl` – correctly orders parameters of confusing `systemctl`;
|
||||
* `terraform_init.py` – run `terraform init` before plan or apply;
|
||||
* `test.py` – runs `py.test` instead of `test.py`;
|
||||
* `touch` – creates missing directories before "touching";
|
||||
* `tsuru_login` – runs `tsuru login` if not authenticated or session expired;
|
||||
@ -315,8 +319,9 @@ The following rules are enabled by default on specific platforms only:
|
||||
* `brew_unknown_command` – fixes wrong brew commands, for example `brew docto/brew doctor`;
|
||||
* `brew_update_formula` – turns `brew update <formula>` into `brew upgrade <formula>`;
|
||||
* `dnf_no_such_command` – fixes mistyped DNF commands;
|
||||
* `pacman` – installs app with `pacman` if it is not installed (uses `yaourt` if available);
|
||||
* `pacman_not_found` – fixes package name with `pacman` or `yaourt`.
|
||||
* `nixos_cmd_not_found` – installs apps on NixOS;
|
||||
* `pacman` – installs app with `pacman` if it is not installed (uses `yay` or `yaourt` if available);
|
||||
* `pacman_not_found` – fixes package name with `pacman`, `yay` or `yaourt`.
|
||||
|
||||
The following commands are bundled with *The Fuck*, but are not enabled by
|
||||
default:
|
||||
|
@ -32,6 +32,6 @@ call('git push --tags', shell=True)
|
||||
|
||||
env = os.environ
|
||||
env['CONVERT_README'] = 'true'
|
||||
call('rm -rf dist/*')
|
||||
call('rm -rf dist/*', shell=True, env=env)
|
||||
call('python setup.py sdist bdist_wheel', shell=True, env=env)
|
||||
call('twine upload dist/*', shell=True, env=env)
|
||||
|
2
setup.py
2
setup.py
@ -31,7 +31,7 @@ elif (3, 0) < version < (3, 4):
|
||||
' ({}.{} detected).'.format(*version))
|
||||
sys.exit(-1)
|
||||
|
||||
VERSION = '3.28'
|
||||
VERSION = '3.29'
|
||||
|
||||
install_requires = ['psutil', 'colorama', 'six', 'decorator', 'pyte']
|
||||
extras_require = {':python_version<"3.4"': ['pathlib2'],
|
||||
|
27
tests/rules/test_docker_image_being_used_by_container.py
Normal file
27
tests/rules/test_docker_image_being_used_by_container.py
Normal file
@ -0,0 +1,27 @@
|
||||
from thefuck.rules.docker_image_being_used_by_container import match, get_new_command
|
||||
from thefuck.types import Command
|
||||
|
||||
|
||||
def test_match():
|
||||
err_response = """Error response from daemon: conflict: unable to delete cd809b04b6ff (cannot be forced) - image is being used by running container e5e2591040d1"""
|
||||
assert match(Command('docker image rm -f cd809b04b6ff', err_response))
|
||||
|
||||
|
||||
def test_not_match():
|
||||
err_response = 'bash: docker: command not found'
|
||||
assert not match(Command('docker image rm -f cd809b04b6ff', err_response))
|
||||
|
||||
|
||||
def test_not_docker_command():
|
||||
err_response = """Error response from daemon: conflict: unable to delete cd809b04b6ff (cannot be forced) - image is being used by running container e5e2591040d1"""
|
||||
assert not match(Command('git image rm -f cd809b04b6ff', err_response))
|
||||
|
||||
|
||||
def test_get_new_command():
|
||||
err_response = """
|
||||
Error response from daemon: conflict: unable to delete cd809b04b6ff (cannot be forced) - image
|
||||
is being used by running container e5e2591040d1
|
||||
"""
|
||||
result = get_new_command(Command('docker image rm -f cd809b04b6ff', err_response))
|
||||
expected = 'docker container rm -f e5e2591040d1 && docker image rm -f cd809b04b6ff'
|
||||
assert result == expected
|
37
tests/rules/test_docker_login.py
Normal file
37
tests/rules/test_docker_login.py
Normal file
@ -0,0 +1,37 @@
|
||||
from thefuck.rules.docker_login import match, get_new_command
|
||||
from thefuck.types import Command
|
||||
|
||||
|
||||
def test_match():
|
||||
err_response1 = """
|
||||
Sending build context to Docker daemon 118.8kB
|
||||
Step 1/6 : FROM foo/bar:fdb7c6d
|
||||
pull access denied for foo/bar, repository does not exist or may require 'docker login'
|
||||
"""
|
||||
assert match(Command('docker build -t artifactory:9090/foo/bar:fdb7c6d .', err_response1))
|
||||
|
||||
err_response2 = """
|
||||
The push refers to repository [artifactory:9090/foo/bar]
|
||||
push access denied for foo/bar, repository does not exist or may require 'docker login'
|
||||
"""
|
||||
assert match(Command('docker push artifactory:9090/foo/bar:fdb7c6d', err_response2))
|
||||
|
||||
err_response3 = """
|
||||
docker push artifactory:9090/foo/bar:fdb7c6d
|
||||
The push refers to repository [artifactory:9090/foo/bar]
|
||||
9c29c7ad209d: Preparing
|
||||
71f3ad53dfe0: Preparing
|
||||
f58ee068224c: Preparing
|
||||
aeddc924d0f7: Preparing
|
||||
c2040e5d6363: Preparing
|
||||
4d42df4f350f: Preparing
|
||||
35723dab26f9: Preparing
|
||||
71f3ad53dfe0: Pushed
|
||||
cb95fa0faeb1: Layer already exists
|
||||
"""
|
||||
assert not match(Command('docker push artifactory:9090/foo/bar:fdb7c6d', err_response3))
|
||||
|
||||
|
||||
def test_get_new_command():
|
||||
assert get_new_command(Command('docker build -t artifactory:9090/foo/bar:fdb7c6d .', '')) == 'docker login && docker build -t artifactory:9090/foo/bar:fdb7c6d .'
|
||||
assert get_new_command(Command('docker push artifactory:9090/foo/bar:fdb7c6d', '')) == 'docker login && docker push artifactory:9090/foo/bar:fdb7c6d'
|
25
tests/rules/test_nixos_cmd_not_found.py
Normal file
25
tests/rules/test_nixos_cmd_not_found.py
Normal file
@ -0,0 +1,25 @@
|
||||
import pytest
|
||||
from thefuck.rules.nixos_cmd_not_found import match, get_new_command
|
||||
from thefuck.types import Command
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command', [
|
||||
Command('vim', 'nix-env -iA nixos.vim')])
|
||||
def test_match(mocker, command):
|
||||
mocker.patch('thefuck.rules.nixos_cmd_not_found', return_value=None)
|
||||
assert match(command)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command', [
|
||||
Command('vim', ''),
|
||||
Command('', '')])
|
||||
def test_not_match(mocker, command):
|
||||
mocker.patch('thefuck.rules.nixos_cmd_not_found', return_value=None)
|
||||
assert not match(command)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command, new_command', [
|
||||
(Command('vim', 'nix-env -iA nixos.vim'), 'nix-env -iA nixos.vim && vim'),
|
||||
(Command('pacman', 'nix-env -iA nixos.pacman'), 'nix-env -iA nixos.pacman && pacman')])
|
||||
def test_get_new_command(mocker, command, new_command):
|
||||
assert get_new_command(command) == new_command
|
@ -6,7 +6,7 @@ from thefuck.types import Command
|
||||
@pytest.fixture(autouse=True)
|
||||
def get_all_executables(mocker):
|
||||
mocker.patch('thefuck.rules.no_command.get_all_executables',
|
||||
return_value=['vim', 'fsck', 'git', 'go'])
|
||||
return_value=['vim', 'fsck', 'git', 'go', 'python'])
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
@ -20,6 +20,7 @@ def history_without_current(mocker):
|
||||
@pytest.mark.parametrize('script, output', [
|
||||
('vom file.py', 'vom: not found'),
|
||||
('fucck', 'fucck: not found'),
|
||||
('puthon', "'puthon' is not recognized as an internal or external command"),
|
||||
('got commit', 'got: command not found')])
|
||||
def test_match(mocker, script, output):
|
||||
mocker.patch('thefuck.rules.no_command.which', return_value=None)
|
||||
|
@ -11,6 +11,7 @@ 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('yay -S llc', 'error: target not found: llc'),
|
||||
Command('yaourt -S llc', 'error: target not found: llc'),
|
||||
Command('pacman llc', 'error: target not found: llc'),
|
||||
Command('sudo pacman llc', 'error: target not found: llc')])
|
||||
@ -19,6 +20,7 @@ def test_match(command):
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command', [
|
||||
Command('yay -S llc', 'error: target not found: llc'),
|
||||
Command('yaourt -S llc', 'error: target not found: llc'),
|
||||
Command('pacman llc', 'error: target not found: llc'),
|
||||
Command('sudo pacman llc', 'error: target not found: llc')])
|
||||
@ -31,6 +33,7 @@ def test_match_mocked(subp_mock, command):
|
||||
@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('yay -S llc', 'error: target not found: llc'), ['yay -S extra/llvm', 'yay -S extra/llvm35']),
|
||||
(Command('yaourt -S llc', 'error: target not found: llc'), ['yaourt -S extra/llvm', 'yaourt -S extra/llvm35']),
|
||||
(Command('pacman -S llc', 'error: target not found: llc'), ['pacman -S extra/llvm', 'pacman -S extra/llvm35']),
|
||||
(Command('sudo pacman -S llc', 'error: target not found: llc'), ['sudo pacman -S extra/llvm', 'sudo pacman -S extra/llvm35'])])
|
||||
@ -39,6 +42,7 @@ def test_get_new_command(command, fixed):
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command, fixed', [
|
||||
(Command('yay -S llc', 'error: target not found: llc'), ['yay -S extra/llvm', 'yay -S extra/llvm35']),
|
||||
(Command('yaourt -S llc', 'error: target not found: llc'), ['yaourt -S extra/llvm', 'yaourt -S extra/llvm35']),
|
||||
(Command('pacman -S llc', 'error: target not found: llc'), ['pacman -S extra/llvm', 'pacman -S extra/llvm35']),
|
||||
(Command('sudo pacman -S llc', 'error: target not found: llc'), ['sudo pacman -S extra/llvm', 'sudo pacman -S extra/llvm35'])])
|
||||
|
27
tests/rules/test_pip_install.py
Normal file
27
tests/rules/test_pip_install.py
Normal file
@ -0,0 +1,27 @@
|
||||
# -*- coding: UTF-8 -*-
|
||||
from thefuck.rules.pip_install import match, get_new_command
|
||||
from thefuck.types import Command
|
||||
|
||||
|
||||
def test_match():
|
||||
response1 = """
|
||||
Could not install packages due to an EnvironmentError: [Errno 13] Permission denied: '/Library/Python/2.7/site-packages/entrypoints.pyc'
|
||||
Consider using the `--user` option or check the permissions.
|
||||
"""
|
||||
assert match(Command('pip install -r requirements.txt', response1))
|
||||
|
||||
response2 = """
|
||||
Collecting bacon
|
||||
Downloading https://files.pythonhosted.org/packages/b2/81/19fb79139ee71c8bc4e5a444546f318e2b87253b8939ec8a7e10d63b7341/bacon-0.3.1.zip (11.0MB)
|
||||
100% |████████████████████████████████| 11.0MB 3.0MB/s
|
||||
Installing collected packages: bacon
|
||||
Running setup.py install for bacon ... done
|
||||
Successfully installed bacon-0.3.1
|
||||
"""
|
||||
assert not match(Command('pip install bacon', response2))
|
||||
|
||||
|
||||
def test_get_new_command():
|
||||
assert get_new_command(Command('pip install -r requirements.txt', '')) == 'pip install --user -r requirements.txt'
|
||||
assert get_new_command(Command('pip install bacon', '')) == 'pip install --user bacon'
|
||||
assert get_new_command(Command('pip install --user -r requirements.txt', '')) == 'sudo pip install -r requirements.txt'
|
52
tests/rules/test_pyenv_no_such_command.py
Normal file
52
tests/rules/test_pyenv_no_such_command.py
Normal file
@ -0,0 +1,52 @@
|
||||
import pytest
|
||||
|
||||
from thefuck.rules.pyenv_no_such_command import get_new_command, match
|
||||
from thefuck.types import Command
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def output(pyenv_cmd):
|
||||
return "pyenv: no such command `{}'".format(pyenv_cmd)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def Popen(mocker):
|
||||
mock = mocker.patch('thefuck.rules.pyenv_no_such_command.Popen')
|
||||
mock.return_value.stdout.readlines.return_value = (
|
||||
b'--version\nactivate\ncommands\ncompletions\ndeactivate\nexec_\n'
|
||||
b'global\nhelp\nhooks\ninit\ninstall\nlocal\nprefix_\n'
|
||||
b'realpath.dylib\nrehash\nroot\nshell\nshims\nuninstall\nversion_\n'
|
||||
b'version-file\nversion-file-read\nversion-file-write\nversion-name_\n'
|
||||
b'version-origin\nversions\nvirtualenv\nvirtualenv-delete_\n'
|
||||
b'virtualenv-init\nvirtualenv-prefix\nvirtualenvs_\n'
|
||||
b'virtualenvwrapper\nvirtualenvwrapper_lazy\nwhence\nwhich_\n'
|
||||
).split()
|
||||
return mock
|
||||
|
||||
|
||||
@pytest.mark.parametrize('script, pyenv_cmd', [
|
||||
('pyenv globe', 'globe'),
|
||||
('pyenv intall 3.8.0', 'intall'),
|
||||
('pyenv list', 'list'),
|
||||
])
|
||||
def test_match(script, pyenv_cmd, output):
|
||||
assert match(Command(script, output=output))
|
||||
|
||||
|
||||
@pytest.mark.parametrize('script, output', [
|
||||
('pyenv global', 'system'),
|
||||
('pyenv versions', ' 3.7.0\n 3.7.1\n* 3.7.2\n'),
|
||||
('pyenv install --list', ' 3.7.0\n 3.7.1\n 3.7.2\n'),
|
||||
])
|
||||
def test_not_match(script, output):
|
||||
assert not match(Command(script, output=output))
|
||||
|
||||
|
||||
@pytest.mark.parametrize('script, pyenv_cmd, result', [
|
||||
('pyenv globe', 'globe', 'pyenv global'),
|
||||
('pyenv intall 3.8.0', 'intall', 'pyenv install 3.8.0'),
|
||||
('pyenv list', 'list', 'pyenv install --list'),
|
||||
('pyenv remove 3.8.0', 'remove', 'pyenv uninstall 3.8.0'),
|
||||
])
|
||||
def test_get_new_command(script, pyenv_cmd, output, result):
|
||||
assert result in get_new_command(Command(script, output))
|
33
tests/rules/test_terraform_init.py
Normal file
33
tests/rules/test_terraform_init.py
Normal file
@ -0,0 +1,33 @@
|
||||
import pytest
|
||||
from thefuck.rules.terraform_init import match, get_new_command
|
||||
from thefuck.types import Command
|
||||
|
||||
|
||||
@pytest.mark.parametrize('script, output', [
|
||||
('terraform plan', 'Error: Initialization required. '
|
||||
'Please see the error message above.'),
|
||||
('terraform plan', 'This module is not yet installed. Run "terraform init" '
|
||||
'to install all modules required by this configuration.'),
|
||||
('terraform apply', 'Error: Initialization required. '
|
||||
'Please see the error message above.'),
|
||||
('terraform apply', 'This module is not yet installed. Run "terraform init" '
|
||||
'to install all modules required by this configuration.')])
|
||||
def test_match(script, output):
|
||||
assert match(Command(script, output))
|
||||
|
||||
|
||||
@pytest.mark.parametrize('script, output', [
|
||||
('terraform --version', 'Terraform v0.12.2'),
|
||||
('terraform plan', 'No changes. Infrastructure is up-to-date.'),
|
||||
('terraform apply', 'Apply complete! Resources: 0 added, 0 changed, 0 destroyed.'),
|
||||
])
|
||||
def test_not_match(script, output):
|
||||
assert not match(Command(script, output=output))
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command, new_command', [
|
||||
(Command('terraform plan', ''), 'terraform init && terraform plan'),
|
||||
(Command('terraform apply', ''), 'terraform init && terraform apply'),
|
||||
])
|
||||
def test_get_new_command(command, new_command):
|
||||
assert get_new_command(command) == new_command
|
@ -11,6 +11,11 @@ class TestBash(object):
|
||||
def shell(self):
|
||||
return Bash()
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def Popen(self, mocker):
|
||||
mock = mocker.patch('thefuck.shells.bash.Popen')
|
||||
return mock
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def shell_aliases(self):
|
||||
os.environ['TF_SHELL_ALIASES'] = (
|
||||
@ -74,7 +79,12 @@ class TestBash(object):
|
||||
config_exists.return_value = False
|
||||
assert not shell.how_to_configure().can_configure_automatically
|
||||
|
||||
def test_info(self, shell, mocker):
|
||||
patch = mocker.patch('thefuck.shells.bash.Popen')
|
||||
patch.return_value.stdout.read.side_effect = [b'3.5.9']
|
||||
def test_info(self, shell, Popen):
|
||||
Popen.return_value.stdout.read.side_effect = [b'3.5.9']
|
||||
assert shell.info() == 'Bash 3.5.9'
|
||||
|
||||
def test_get_version_error(self, shell, Popen):
|
||||
Popen.return_value.stdout.read.side_effect = OSError
|
||||
with pytest.raises(OSError):
|
||||
shell._get_version()
|
||||
assert Popen.call_args[0][0] == ['bash', '-c', 'echo $BASH_VERSION']
|
||||
|
@ -1,6 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import pytest
|
||||
from thefuck.const import ARGUMENT_PLACEHOLDER
|
||||
from thefuck.shells import Fish
|
||||
|
||||
|
||||
@ -82,6 +83,7 @@ class TestFish(object):
|
||||
assert 'TF_SHELL=fish' in shell.app_alias('fuck')
|
||||
assert 'TF_ALIAS=fuck PYTHONIOENCODING' in shell.app_alias('fuck')
|
||||
assert 'PYTHONIOENCODING=utf-8 thefuck' in shell.app_alias('fuck')
|
||||
assert ARGUMENT_PLACEHOLDER in shell.app_alias('fuck')
|
||||
|
||||
def test_app_alias_alter_history(self, settings, shell):
|
||||
settings.alter_history = True
|
||||
@ -114,7 +116,17 @@ class TestFish(object):
|
||||
config_exists.return_value = False
|
||||
assert not shell.how_to_configure().can_configure_automatically
|
||||
|
||||
def test_info(self, shell, Popen):
|
||||
def test_get_version(self, shell, Popen):
|
||||
Popen.return_value.stdout.read.side_effect = [b'fish, version 3.5.9\n']
|
||||
assert shell.info() == 'Fish Shell 3.5.9'
|
||||
assert shell._get_version() == '3.5.9'
|
||||
assert Popen.call_args[0][0] == ['fish', '--version']
|
||||
|
||||
@pytest.mark.parametrize('side_effect, exception', [
|
||||
([b'\n'], IndexError),
|
||||
(OSError('file not found'), OSError),
|
||||
])
|
||||
def test_get_version_error(self, side_effect, exception, shell, Popen):
|
||||
Popen.return_value.stdout.read.side_effect = side_effect
|
||||
with pytest.raises(exception):
|
||||
shell._get_version()
|
||||
assert Popen.call_args[0][0] == ['fish', '--version']
|
||||
|
@ -43,3 +43,14 @@ class TestGeneric(object):
|
||||
|
||||
def test_how_to_configure(self, shell):
|
||||
assert shell.how_to_configure() is None
|
||||
|
||||
@pytest.mark.parametrize('side_effect, expected_info, warn', [
|
||||
([u'3.5.9'], u'Generic Shell 3.5.9', False),
|
||||
([OSError], u'Generic Shell', True),
|
||||
])
|
||||
def test_info(self, side_effect, expected_info, warn, shell, mocker):
|
||||
warn_mock = mocker.patch('thefuck.shells.generic.warn')
|
||||
shell._get_version = mocker.Mock(side_effect=side_effect)
|
||||
assert shell.info() == expected_info
|
||||
assert warn_mock.called is warn
|
||||
assert shell._get_version.called
|
||||
|
@ -10,6 +10,11 @@ class TestPowershell(object):
|
||||
def shell(self):
|
||||
return Powershell()
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def Popen(self, mocker):
|
||||
mock = mocker.patch('thefuck.shells.powershell.Popen')
|
||||
return mock
|
||||
|
||||
def test_and_(self, shell):
|
||||
assert shell.and_('ls', 'cd') == '(ls) -and (cd)'
|
||||
|
||||
@ -20,3 +25,20 @@ class TestPowershell(object):
|
||||
|
||||
def test_how_to_configure(self, shell):
|
||||
assert not shell.how_to_configure().can_configure_automatically
|
||||
|
||||
@pytest.mark.parametrize('side_effect, expected_version, call_args', [
|
||||
([b'''Major Minor Build Revision
|
||||
----- ----- ----- --------
|
||||
5 1 17763 316 \n'''], 'PowerShell 5.1.17763.316', ['powershell.exe']),
|
||||
([IOError, b'PowerShell 6.1.2\n'], 'PowerShell 6.1.2', ['powershell.exe', 'pwsh'])])
|
||||
def test_info(self, side_effect, expected_version, call_args, shell, Popen):
|
||||
Popen.return_value.stdout.read.side_effect = side_effect
|
||||
assert shell.info() == expected_version
|
||||
assert Popen.call_count == len(call_args)
|
||||
assert all([Popen.call_args_list[i][0][0][0] == call_arg for i, call_arg in enumerate(call_args)])
|
||||
|
||||
def test_get_version_error(self, shell, Popen):
|
||||
Popen.return_value.stdout.read.side_effect = RuntimeError
|
||||
with pytest.raises(RuntimeError):
|
||||
shell._get_version()
|
||||
assert Popen.call_args[0][0] == ['powershell.exe', '$PSVersionTable.PSVersion']
|
||||
|
@ -61,3 +61,17 @@ class TestTcsh(object):
|
||||
config_exists):
|
||||
config_exists.return_value = False
|
||||
assert not shell.how_to_configure().can_configure_automatically
|
||||
|
||||
def test_info(self, shell, Popen):
|
||||
Popen.return_value.stdout.read.side_effect = [
|
||||
b'tcsh 6.20.00 (Astron) 2016-11-24 (unknown-unknown-bsd44) \n']
|
||||
assert shell.info() == 'Tcsh 6.20.00'
|
||||
assert Popen.call_args[0][0] == ['tcsh', '--version']
|
||||
|
||||
@pytest.mark.parametrize('side_effect, exception', [
|
||||
([b'\n'], IndexError), (OSError, OSError)])
|
||||
def test_get_version_error(self, side_effect, exception, shell, Popen):
|
||||
Popen.return_value.stdout.read.side_effect = side_effect
|
||||
with pytest.raises(exception):
|
||||
shell._get_version()
|
||||
assert Popen.call_args[0][0] == ['tcsh', '--version']
|
||||
|
@ -11,6 +11,11 @@ class TestZsh(object):
|
||||
def shell(self):
|
||||
return Zsh()
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def Popen(self, mocker):
|
||||
mock = mocker.patch('thefuck.shells.zsh.Popen')
|
||||
return mock
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def shell_aliases(self):
|
||||
os.environ['TF_SHELL_ALIASES'] = (
|
||||
@ -69,7 +74,12 @@ class TestZsh(object):
|
||||
config_exists.return_value = False
|
||||
assert not shell.how_to_configure().can_configure_automatically
|
||||
|
||||
def test_info(self, shell, mocker):
|
||||
patch = mocker.patch('thefuck.shells.zsh.Popen')
|
||||
patch.return_value.stdout.read.side_effect = [b'3.5.9']
|
||||
def test_info(self, shell, Popen):
|
||||
Popen.return_value.stdout.read.side_effect = [b'3.5.9']
|
||||
assert shell.info() == 'ZSH 3.5.9'
|
||||
|
||||
def test_get_version_error(self, shell, Popen):
|
||||
Popen.return_value.stdout.read.side_effect = OSError
|
||||
with pytest.raises(OSError):
|
||||
shell._get_version()
|
||||
assert Popen.call_args[0][0] == ['zsh', '-c', 'echo $ZSH_VERSION']
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
import pytest
|
||||
import warnings
|
||||
from mock import Mock, patch
|
||||
from mock import Mock, call, patch
|
||||
from thefuck.utils import default_settings, \
|
||||
memoize, get_closest, get_all_executables, replace_argument, \
|
||||
get_all_matched_commands, is_app, for_app, cache, \
|
||||
@ -76,6 +76,24 @@ def test_get_all_executables():
|
||||
assert 'fuck' not in all_callables
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def os_environ_pathsep(monkeypatch, path, pathsep):
|
||||
env = {'PATH': path}
|
||||
monkeypatch.setattr('os.environ', env)
|
||||
monkeypatch.setattr('os.pathsep', pathsep)
|
||||
return env
|
||||
|
||||
|
||||
@pytest.mark.usefixtures('no_memoize', 'os_environ_pathsep')
|
||||
@pytest.mark.parametrize('path, pathsep', [
|
||||
('/foo:/bar:/baz:/foo/bar', ':'),
|
||||
(r'C:\\foo;C:\\bar;C:\\baz;C:\\foo\\bar', ';')])
|
||||
def test_get_all_executables_pathsep(path, pathsep):
|
||||
with patch('thefuck.utils.Path') as Path_mock:
|
||||
get_all_executables()
|
||||
Path_mock.assert_has_calls([call(p) for p in path.split(pathsep)], True)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('args, result', [
|
||||
(('apt-get instol vim', 'instol', 'install'), 'apt-get install vim'),
|
||||
(('git brnch', 'brnch', 'branch'), 'git branch')])
|
||||
|
@ -22,10 +22,13 @@ def main():
|
||||
elif known_args.version:
|
||||
logs.version(get_installation_info().version,
|
||||
sys.version.split()[0], shell.info())
|
||||
elif known_args.command or 'TF_HISTORY' in os.environ:
|
||||
fix_command(known_args)
|
||||
# It's important to check if an alias is being requested before checking if
|
||||
# `TF_HISTORY` is in `os.environ`, otherwise it might mess with subshells.
|
||||
# Check https://github.com/nvbn/thefuck/issues/921 for reference
|
||||
elif known_args.alias:
|
||||
print_alias(known_args)
|
||||
elif known_args.command or 'TF_HISTORY' in os.environ:
|
||||
fix_command(known_args)
|
||||
elif known_args.shell_logger:
|
||||
try:
|
||||
from .shell_logger import shell_logger # noqa: E402
|
||||
|
20
thefuck/rules/docker_image_being_used_by_container.py
Normal file
20
thefuck/rules/docker_image_being_used_by_container.py
Normal file
@ -0,0 +1,20 @@
|
||||
from thefuck.utils import for_app
|
||||
from thefuck.shells import shell
|
||||
|
||||
|
||||
@for_app('docker')
|
||||
def match(command):
|
||||
'''
|
||||
Matches a command's output with docker's output
|
||||
warning you that you need to remove a container before removing an image.
|
||||
'''
|
||||
return 'image is being used by running container' in command.output
|
||||
|
||||
|
||||
def get_new_command(command):
|
||||
'''
|
||||
Prepends docker container rm -f {container ID} to
|
||||
the previous docker image rm {image ID} command
|
||||
'''
|
||||
container_id = command.output.strip().split(' ')
|
||||
return shell.and_('docker container rm -f {}', '{}').format(container_id[-1], command.script)
|
12
thefuck/rules/docker_login.py
Normal file
12
thefuck/rules/docker_login.py
Normal file
@ -0,0 +1,12 @@
|
||||
from thefuck.utils import for_app
|
||||
|
||||
|
||||
@for_app('docker')
|
||||
def match(command):
|
||||
return ('docker' in command.script
|
||||
and "access denied" in command.output
|
||||
and "may require 'docker login'" in command.output)
|
||||
|
||||
|
||||
def get_new_command(command):
|
||||
return 'docker login && {}'.format(command.script)
|
15
thefuck/rules/nixos_cmd_not_found.py
Normal file
15
thefuck/rules/nixos_cmd_not_found.py
Normal file
@ -0,0 +1,15 @@
|
||||
import re
|
||||
from thefuck.specific.nix import nix_available
|
||||
from thefuck.shells import shell
|
||||
|
||||
regex = re.compile(r'nix-env -iA ([^\s]*)')
|
||||
enabled_by_default = nix_available
|
||||
|
||||
|
||||
def match(command):
|
||||
return regex.findall(command.output)
|
||||
|
||||
|
||||
def get_new_command(command):
|
||||
name = regex.findall(command.output)[0]
|
||||
return shell.and_('nix-env -iA {}'.format(name), command.script)
|
@ -6,7 +6,8 @@ from thefuck.specific.sudo import sudo_support
|
||||
@sudo_support
|
||||
def match(command):
|
||||
return (not which(command.script_parts[0])
|
||||
and 'not found' in command.output
|
||||
and ('not found' in command.output
|
||||
or 'is not recognized as' in command.output)
|
||||
and bool(get_close_matches(command.script_parts[0],
|
||||
get_all_executables())))
|
||||
|
||||
|
@ -1,9 +1,9 @@
|
||||
""" Fixes wrong package names with pacman or yaourt.
|
||||
|
||||
For example the `llc` program is in package `llvm` so this:
|
||||
yaourt -S llc
|
||||
yay -S llc
|
||||
should be:
|
||||
yaourt -S llvm
|
||||
yay -S llvm
|
||||
"""
|
||||
|
||||
from thefuck.utils import replace_command
|
||||
@ -12,7 +12,7 @@ from thefuck.specific.archlinux import get_pkgfile, archlinux_env
|
||||
|
||||
def match(command):
|
||||
return (command.script_parts
|
||||
and (command.script_parts[0] in ('pacman', 'yaourt')
|
||||
and (command.script_parts[0] in ('pacman', 'yay', 'yaourt')
|
||||
or command.script_parts[0:2] == ['sudo', 'pacman'])
|
||||
and 'error: target not found:' in command.output)
|
||||
|
||||
|
15
thefuck/rules/pip_install.py
Normal file
15
thefuck/rules/pip_install.py
Normal file
@ -0,0 +1,15 @@
|
||||
from thefuck.utils import for_app
|
||||
from thefuck.specific.sudo import sudo_support
|
||||
|
||||
|
||||
@sudo_support
|
||||
@for_app('pip')
|
||||
def match(command):
|
||||
return ('pip install' in command.script and 'Permission denied' in command.output)
|
||||
|
||||
|
||||
def get_new_command(command):
|
||||
if '--user' not in command.script: # add --user (attempt 1)
|
||||
return command.script.replace(' install ', ' install --user ')
|
||||
|
||||
return 'sudo {}'.format(command.script.replace(' --user', '')) # since --user didn't fix things, let's try sudo (attempt 2)
|
33
thefuck/rules/pyenv_no_such_command.py
Normal file
33
thefuck/rules/pyenv_no_such_command.py
Normal file
@ -0,0 +1,33 @@
|
||||
import re
|
||||
from subprocess import PIPE, Popen
|
||||
|
||||
from thefuck.utils import (cache, for_app, replace_argument, replace_command,
|
||||
which)
|
||||
|
||||
COMMON_TYPOS = {
|
||||
'list': ['versions', 'install --list'],
|
||||
'remove': ['uninstall'],
|
||||
}
|
||||
|
||||
|
||||
@for_app('pyenv')
|
||||
def match(command):
|
||||
return 'pyenv: no such command' in command.output
|
||||
|
||||
|
||||
def get_pyenv_commands():
|
||||
proc = Popen(['pyenv', 'commands'], stdout=PIPE)
|
||||
return [line.decode('utf-8').strip() for line in proc.stdout.readlines()]
|
||||
|
||||
|
||||
if which('pyenv'):
|
||||
get_pyenv_commands = cache(which('pyenv'))(get_pyenv_commands)
|
||||
|
||||
|
||||
@for_app('pyenv')
|
||||
def get_new_command(command):
|
||||
broken = re.findall(r"pyenv: no such command `([^']*)'", command.output)[0]
|
||||
matched = [replace_argument(command.script, broken, common_typo)
|
||||
for common_typo in COMMON_TYPOS.get(broken, [])]
|
||||
matched.extend(replace_command(command, broken, get_pyenv_commands()))
|
||||
return matched
|
13
thefuck/rules/terraform_init.py
Normal file
13
thefuck/rules/terraform_init.py
Normal file
@ -0,0 +1,13 @@
|
||||
from thefuck.shells import shell
|
||||
from thefuck.utils import for_app
|
||||
|
||||
|
||||
@for_app('terraform')
|
||||
def match(command):
|
||||
return ('this module is not yet installed' in command.output.lower() or
|
||||
'initialization required' in command.output.lower()
|
||||
)
|
||||
|
||||
|
||||
def get_new_command(command):
|
||||
return shell.and_('terraform init', command.script)
|
@ -9,6 +9,8 @@ from .generic import Generic
|
||||
|
||||
|
||||
class Bash(Generic):
|
||||
friendly_name = 'Bash'
|
||||
|
||||
def app_alias(self, alias_name):
|
||||
# It is VERY important to have the variables declared WITHIN the function
|
||||
return '''
|
||||
@ -20,8 +22,8 @@ class Bash(Generic):
|
||||
export TF_HISTORY=$(fc -ln -10);
|
||||
export PYTHONIOENCODING=utf-8;
|
||||
TF_CMD=$(
|
||||
thefuck {argument_placeholder} $@
|
||||
) && eval $TF_CMD;
|
||||
thefuck {argument_placeholder} "$@"
|
||||
) && eval "$TF_CMD";
|
||||
unset TF_HISTORY;
|
||||
export PYTHONIOENCODING=$TF_PYTHONIOENCODING;
|
||||
{alter_history}
|
||||
@ -79,13 +81,12 @@ class Bash(Generic):
|
||||
config = 'bash config'
|
||||
|
||||
return self._create_shell_configuration(
|
||||
content=u'eval $(thefuck --alias)',
|
||||
content=u'eval "$(thefuck --alias)"',
|
||||
path=config,
|
||||
reload=u'source {}'.format(config))
|
||||
|
||||
def info(self):
|
||||
"""Returns the name and version of the current shell"""
|
||||
def _get_version(self):
|
||||
"""Returns the version of the current shell"""
|
||||
proc = Popen(['bash', '-c', 'echo $BASH_VERSION'],
|
||||
stdout=PIPE, stderr=DEVNULL)
|
||||
version = proc.stdout.read().decode('utf-8').strip()
|
||||
return u'Bash {}'.format(version)
|
||||
return proc.stdout.read().decode('utf-8').strip()
|
||||
|
@ -5,6 +5,7 @@ import sys
|
||||
import six
|
||||
from .. import logs
|
||||
from ..conf import settings
|
||||
from ..const import ARGUMENT_PLACEHOLDER
|
||||
from ..utils import DEVNULL, cache
|
||||
from .generic import Generic
|
||||
|
||||
@ -37,6 +38,8 @@ def _get_aliases(overridden):
|
||||
|
||||
|
||||
class Fish(Generic):
|
||||
friendly_name = 'Fish Shell'
|
||||
|
||||
def _get_overridden_aliases(self):
|
||||
overridden = os.environ.get('THEFUCK_OVERRIDDEN_ALIASES',
|
||||
os.environ.get('TF_OVERRIDDEN_ALIASES', ''))
|
||||
@ -56,11 +59,11 @@ class Fish(Generic):
|
||||
return ('function {0} -d "Correct your previous console command"\n'
|
||||
' set -l fucked_up_command $history[1]\n'
|
||||
' env TF_SHELL=fish TF_ALIAS={0} PYTHONIOENCODING=utf-8'
|
||||
' thefuck $fucked_up_command | read -l unfucked_command\n'
|
||||
' thefuck $fucked_up_command {2} $argv | read -l unfucked_command\n'
|
||||
' if [ "$unfucked_command" != "" ]\n'
|
||||
' eval $unfucked_command\n{1}'
|
||||
' end\n'
|
||||
'end').format(alias_name, alter_history)
|
||||
'end').format(alias_name, alter_history, ARGUMENT_PLACEHOLDER)
|
||||
|
||||
def get_aliases(self):
|
||||
overridden = self._get_overridden_aliases()
|
||||
@ -103,12 +106,10 @@ class Fish(Generic):
|
||||
path='~/.config/fish/config.fish',
|
||||
reload='fish')
|
||||
|
||||
def info(self):
|
||||
"""Returns the name and version of the current shell"""
|
||||
proc = Popen(['fish', '--version'],
|
||||
stdout=PIPE, stderr=DEVNULL)
|
||||
version = proc.stdout.read().decode('utf-8').split()[-1]
|
||||
return u'Fish Shell {}'.format(version)
|
||||
def _get_version(self):
|
||||
"""Returns the version of the current shell"""
|
||||
proc = Popen(['fish', '--version'], stdout=PIPE, stderr=DEVNULL)
|
||||
return proc.stdout.read().decode('utf-8').split()[-1]
|
||||
|
||||
def put_to_history(self, command):
|
||||
try:
|
||||
|
@ -14,6 +14,8 @@ ShellConfiguration = namedtuple('ShellConfiguration', (
|
||||
|
||||
|
||||
class Generic(object):
|
||||
friendly_name = 'Generic Shell'
|
||||
|
||||
def get_aliases(self):
|
||||
return {}
|
||||
|
||||
@ -34,8 +36,8 @@ class Generic(object):
|
||||
return command_script
|
||||
|
||||
def app_alias(self, alias_name):
|
||||
return "alias {0}='eval $(TF_ALIAS={0} PYTHONIOENCODING=utf-8 " \
|
||||
"thefuck $(fc -ln -1))'".format(alias_name)
|
||||
return """alias {0}='eval "$(TF_ALIAS={0} PYTHONIOENCODING=utf-8 """ \
|
||||
"""thefuck "$(fc -ln -1)")"'""".format(alias_name)
|
||||
|
||||
def instant_mode_alias(self, alias_name):
|
||||
warn("Instant mode not supported by your shell")
|
||||
@ -131,9 +133,18 @@ class Generic(object):
|
||||
'type', 'typeset', 'ulimit', 'umask', 'unalias', 'unset',
|
||||
'until', 'wait', 'while']
|
||||
|
||||
def _get_version(self):
|
||||
"""Returns the version of the current shell"""
|
||||
return ''
|
||||
|
||||
def info(self):
|
||||
"""Returns the name and version of the current shell"""
|
||||
return 'Generic Shell'
|
||||
try:
|
||||
version = self._get_version()
|
||||
except Exception as e:
|
||||
warn(u'Could not determine shell version: {}'.format(e))
|
||||
version = ''
|
||||
return u'{} {}'.format(self.friendly_name, version).rstrip()
|
||||
|
||||
def _create_shell_configuration(self, content, path, reload):
|
||||
return ShellConfiguration(
|
||||
|
@ -1,7 +1,11 @@
|
||||
from subprocess import Popen, PIPE
|
||||
from ..utils import DEVNULL
|
||||
from .generic import Generic, ShellConfiguration
|
||||
|
||||
|
||||
class Powershell(Generic):
|
||||
friendly_name = 'PowerShell'
|
||||
|
||||
def app_alias(self, alias_name):
|
||||
return 'function ' + alias_name + ' {\n' \
|
||||
' $history = (Get-History -Count 1).CommandLine;\n' \
|
||||
@ -24,3 +28,16 @@ class Powershell(Generic):
|
||||
path='$profile',
|
||||
reload='& $profile',
|
||||
can_configure_automatically=False)
|
||||
|
||||
def _get_version(self):
|
||||
"""Returns the version of the current shell"""
|
||||
try:
|
||||
proc = Popen(
|
||||
['powershell.exe', '$PSVersionTable.PSVersion'],
|
||||
stdout=PIPE,
|
||||
stderr=DEVNULL)
|
||||
version = proc.stdout.read().decode('utf-8').rstrip().split('\n')
|
||||
return '.'.join(version[-1].split())
|
||||
except IOError:
|
||||
proc = Popen(['pwsh', '--version'], stdout=PIPE, stderr=DEVNULL)
|
||||
return proc.stdout.read().decode('utf-8').split()[-1]
|
||||
|
@ -6,6 +6,8 @@ from .generic import Generic
|
||||
|
||||
|
||||
class Tcsh(Generic):
|
||||
friendly_name = 'Tcsh'
|
||||
|
||||
def app_alias(self, alias_name):
|
||||
return ("alias {0} 'setenv TF_SHELL tcsh && setenv TF_ALIAS {0} && "
|
||||
"set fucked_cmd=`history -h 2 | head -n 1` && "
|
||||
@ -35,3 +37,8 @@ class Tcsh(Generic):
|
||||
content=u'eval `thefuck --alias`',
|
||||
path='~/.tcshrc',
|
||||
reload='tcsh')
|
||||
|
||||
def _get_version(self):
|
||||
"""Returns the version of the current shell"""
|
||||
proc = Popen(['tcsh', '--version'], stdout=PIPE, stderr=DEVNULL)
|
||||
return proc.stdout.read().decode('utf-8').split()[1]
|
||||
|
@ -10,6 +10,8 @@ from .generic import Generic
|
||||
|
||||
|
||||
class Zsh(Generic):
|
||||
friendly_name = 'ZSH'
|
||||
|
||||
def app_alias(self, alias_name):
|
||||
# It is VERY important to have the variables declared WITHIN the function
|
||||
return '''
|
||||
@ -87,9 +89,8 @@ class Zsh(Generic):
|
||||
path='~/.zshrc',
|
||||
reload='source ~/.zshrc')
|
||||
|
||||
def info(self):
|
||||
"""Returns the name and version of the current shell"""
|
||||
def _get_version(self):
|
||||
"""Returns the version of the current shell"""
|
||||
proc = Popen(['zsh', '-c', 'echo $ZSH_VERSION'],
|
||||
stdout=PIPE, stderr=DEVNULL)
|
||||
version = proc.stdout.read().decode('utf-8').strip()
|
||||
return u'ZSH {}'.format(version)
|
||||
return proc.stdout.read().decode('utf-8').strip()
|
||||
|
@ -32,7 +32,9 @@ def get_pkgfile(command):
|
||||
|
||||
|
||||
def archlinux_env():
|
||||
if utils.which('yaourt'):
|
||||
if utils.which('yay'):
|
||||
pacman = 'yay'
|
||||
elif utils.which('yaourt'):
|
||||
pacman = 'yaourt'
|
||||
elif utils.which('pacman'):
|
||||
pacman = 'sudo pacman'
|
||||
|
3
thefuck/specific/nix.py
Normal file
3
thefuck/specific/nix.py
Normal file
@ -0,0 +1,3 @@
|
||||
from thefuck.utils import which
|
||||
|
||||
nix_available = bool(which('nix'))
|
@ -118,7 +118,7 @@ def get_all_executables():
|
||||
tf_entry_points = ['thefuck', 'fuck']
|
||||
|
||||
bins = [exe.name.decode('utf8') if six.PY2 else exe.name
|
||||
for path in os.environ.get('PATH', '').split(':')
|
||||
for path in os.environ.get('PATH', '').split(os.pathsep)
|
||||
for exe in _safe(lambda: list(Path(path).iterdir()), [])
|
||||
if not _safe(exe.is_dir, True)
|
||||
and exe.name not in tf_entry_points]
|
||||
|
Loading…
x
Reference in New Issue
Block a user