mirror of
https://github.com/nvbn/thefuck.git
synced 2025-11-03 16:42:03 +00:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
59e1f7b122 | ||
|
|
ff2944086d | ||
|
|
ba949f7fd9 | ||
|
|
5efcf1019f | ||
|
|
70a13406f0 | ||
|
|
201c01fc74 | ||
|
|
78ef9eec88 | ||
|
|
40ab4eb62d | ||
|
|
55cb3546df | ||
|
|
82902fb50d | ||
|
|
828ae537da | ||
|
|
1208faaabb | ||
|
|
2d81166213 | ||
|
|
8093f7cab8 | ||
|
|
b946b7d319 | ||
|
|
9354a977dd | ||
|
|
1eb4ccbcc9 | ||
|
|
ce5feaebf7 | ||
|
|
ac343fb1bd | ||
|
|
7bc619385b | ||
|
|
d86dd5f179 |
16
README.md
16
README.md
@@ -99,7 +99,7 @@ Reading package lists... Done
|
|||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
On OS X, you can install *The Fuck* via [Homebrew][homebrew]:
|
On OS X, you can install *The Fuck* via [Homebrew][homebrew] (or via [Linuxbrew][linuxbrew] on Linux):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
brew install thefuck
|
brew install thefuck
|
||||||
@@ -114,8 +114,7 @@ sudo pip3 install thefuck
|
|||||||
|
|
||||||
On FreeBSD, install *The Fuck* with the following commands:
|
On FreeBSD, install *The Fuck* with the following commands:
|
||||||
```bash
|
```bash
|
||||||
sudo portsnap fetch update
|
pkg install thefuck
|
||||||
cd /usr/ports/misc/thefuck && sudo make install clean
|
|
||||||
```
|
```
|
||||||
|
|
||||||
On ChromeOS, install *The Fuck* using [chromebrew](https://github.com/skycocker/chromebrew) with the following command:
|
On ChromeOS, install *The Fuck* using [chromebrew](https://github.com/skycocker/chromebrew) with the following command:
|
||||||
@@ -190,6 +189,7 @@ following rules are enabled by default:
|
|||||||
* `dirty_unzip` – fixes `unzip` command that unzipped in the current directory;
|
* `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_ghost` – adds `--delete-ghost-migrations` to failed because ghosts django south migration;
|
||||||
* `django_south_merge` – adds `--merge` to inconsistent 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_not_command` – fixes wrong docker commands like `docker tags`;
|
||||||
* `dry` – fixes repetitions like `git git push`;
|
* `dry` – fixes repetitions like `git git push`;
|
||||||
* `fab_command_not_found` – fix misspelled fabric commands;
|
* `fab_command_not_found` – fix misspelled fabric commands;
|
||||||
@@ -204,6 +204,7 @@ following rules are enabled by default:
|
|||||||
* `git_branch_list` – catches `git branch list` in place of `git branch` and removes created branch;
|
* `git_branch_list` – catches `git branch list` in place of `git branch` and removes created branch;
|
||||||
* `git_checkout` – fixes branch name or creates new branch;
|
* `git_checkout` – fixes branch name or creates new branch;
|
||||||
* `git_commit_amend` – offers `git commit --amend` after previous commit;
|
* `git_commit_amend` – offers `git commit --amend` after previous commit;
|
||||||
|
* `git_commit_reset` – offers `git reset HEAD~` after previous commit;
|
||||||
* `git_diff_no_index` – adds `--no-index` to previous `git diff` on untracked files;
|
* `git_diff_no_index` – adds `--no-index` to previous `git diff` on untracked files;
|
||||||
* `git_diff_staged` – adds `--staged` to previous `git diff` with unexpected output;
|
* `git_diff_staged` – adds `--staged` to previous `git diff` with unexpected output;
|
||||||
* `git_fix_stash` – fixes `git stash` commands (misspelled subcommand and missing `save`);
|
* `git_fix_stash` – fixes `git stash` commands (misspelled subcommand and missing `save`);
|
||||||
@@ -264,10 +265,12 @@ following rules are enabled by default:
|
|||||||
* `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` – either prepends `http://` to address passed to `open` or create a new file or directory and passes it to `open`;
|
* `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`;
|
* `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;
|
* `php_s` – replaces `-s` by `-S` when trying to run a local php server;
|
||||||
* `port_already_in_use` – kills process that bound port;
|
* `port_already_in_use` – kills process that bound port;
|
||||||
* `prove_recursively` – adds `-r` when called with directory;
|
* `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_command` – prepends `python` when you try to run non-executable/without `./` python script;
|
||||||
* `python_execute` – appends missing `.py` when executing Python files;
|
* `python_execute` – appends missing `.py` when executing Python files;
|
||||||
* `quotation_marks` – fixes uneven usage of `'` and `"` when containing args';
|
* `quotation_marks` – fixes uneven usage of `'` and `"` when containing args';
|
||||||
@@ -313,8 +316,8 @@ 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_unknown_command` – fixes wrong brew commands, for example `brew docto/brew doctor`;
|
||||||
* `brew_update_formula` – turns `brew update <formula>` into `brew upgrade <formula>`;
|
* `brew_update_formula` – turns `brew update <formula>` into `brew upgrade <formula>`;
|
||||||
* `dnf_no_such_command` – fixes mistyped DNF commands;
|
* `dnf_no_such_command` – fixes mistyped DNF commands;
|
||||||
* `pacman` – installs app with `pacman` if it is not installed (uses `yaourt` if available);
|
* `pacman` – installs app with `pacman` if it is not installed (uses `yay` or `yaourt` if available);
|
||||||
* `pacman_not_found` – fixes package name with `pacman` or `yaourt`.
|
* `pacman_not_found` – fixes package name with `pacman`, `yay` or `yaourt`.
|
||||||
|
|
||||||
The following commands are bundled with *The Fuck*, but are not enabled by
|
The following commands are bundled with *The Fuck*, but are not enabled by
|
||||||
default:
|
default:
|
||||||
@@ -495,4 +498,5 @@ Project License can be found [here](LICENSE.md).
|
|||||||
[license-badge]: https://img.shields.io/badge/license-MIT-007EC7.svg
|
[license-badge]: https://img.shields.io/badge/license-MIT-007EC7.svg
|
||||||
[examples-link]: https://raw.githubusercontent.com/nvbn/thefuck/master/example.gif
|
[examples-link]: https://raw.githubusercontent.com/nvbn/thefuck/master/example.gif
|
||||||
[instant-mode-gif-link]: https://raw.githubusercontent.com/nvbn/thefuck/master/example_instant_mode.gif
|
[instant-mode-gif-link]: https://raw.githubusercontent.com/nvbn/thefuck/master/example_instant_mode.gif
|
||||||
[homebrew]: http://brew.sh/
|
[homebrew]: https://brew.sh/
|
||||||
|
[linuxbrew]: https://linuxbrew.sh/
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ http://github.com/ninjaaron/fast-entry_points
|
|||||||
'''
|
'''
|
||||||
from setuptools.command import easy_install
|
from setuptools.command import easy_install
|
||||||
import re
|
import re
|
||||||
TEMPLATE = '''\
|
TEMPLATE = r'''\
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# EASY-INSTALL-ENTRY-SCRIPT: '{3}','{4}','{5}'
|
# EASY-INSTALL-ENTRY-SCRIPT: '{3}','{4}','{5}'
|
||||||
__requires__ = '{3}'
|
__requires__ = '{3}'
|
||||||
@@ -83,7 +83,7 @@ def main():
|
|||||||
import shutil
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
dests = sys.argv[1:] or ['.']
|
dests = sys.argv[1:] or ['.']
|
||||||
filename = re.sub('\.pyc$', '.py', __file__)
|
filename = re.sub(r'\.pyc$', '.py', __file__)
|
||||||
|
|
||||||
for dst in dests:
|
for dst in dests:
|
||||||
shutil.copy(filename, dst)
|
shutil.copy(filename, dst)
|
||||||
|
|||||||
@@ -32,5 +32,6 @@ call('git push --tags', shell=True)
|
|||||||
|
|
||||||
env = os.environ
|
env = os.environ
|
||||||
env['CONVERT_README'] = 'true'
|
env['CONVERT_README'] = 'true'
|
||||||
|
call('rm -rf dist/*')
|
||||||
call('python setup.py sdist bdist_wheel', shell=True, env=env)
|
call('python setup.py sdist bdist_wheel', shell=True, env=env)
|
||||||
call('twine upload dist/*', 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))
|
' ({}.{} detected).'.format(*version))
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
|
|
||||||
VERSION = '3.28'
|
VERSION = '3.29'
|
||||||
|
|
||||||
install_requires = ['psutil', 'colorama', 'six', 'decorator', 'pyte']
|
install_requires = ['psutil', 'colorama', 'six', 'decorator', 'pyte']
|
||||||
extras_require = {':python_version<"3.4"': ['pathlib2'],
|
extras_require = {':python_version<"3.4"': ['pathlib2'],
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ def no_cache(monkeypatch):
|
|||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
def functional(request):
|
def functional(request):
|
||||||
if request.node.get_marker('functional') \
|
if request.node.get_closest_marker('functional') \
|
||||||
and not request.config.getoption('enable_functional'):
|
and not request.config.getoption('enable_functional'):
|
||||||
pytest.skip('functional tests are disabled')
|
pytest.skip('functional tests are disabled')
|
||||||
|
|
||||||
|
|||||||
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_git_commit_reset.py
Normal file
25
tests/rules/test_git_commit_reset.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import pytest
|
||||||
|
from thefuck.rules.git_commit_reset import match, get_new_command
|
||||||
|
from thefuck.types import Command
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('script, output', [
|
||||||
|
('git commit -m "test"', 'test output'),
|
||||||
|
('git commit', '')])
|
||||||
|
def test_match(output, script):
|
||||||
|
assert match(Command(script, output))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('script', [
|
||||||
|
'git branch foo',
|
||||||
|
'git checkout feature/test_commit',
|
||||||
|
'git push'])
|
||||||
|
def test_not_match(script):
|
||||||
|
assert not match(Command(script, ''))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('script', [
|
||||||
|
('git commit -m "test commit"'),
|
||||||
|
('git commit')])
|
||||||
|
def test_get_new_command(script):
|
||||||
|
assert get_new_command(Command(script, '')) == 'git reset HEAD~'
|
||||||
@@ -6,7 +6,7 @@ from thefuck.types import Command
|
|||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
def get_all_executables(mocker):
|
def get_all_executables(mocker):
|
||||||
mocker.patch('thefuck.rules.no_command.get_all_executables',
|
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)
|
@pytest.fixture(autouse=True)
|
||||||
@@ -20,6 +20,7 @@ def history_without_current(mocker):
|
|||||||
@pytest.mark.parametrize('script, output', [
|
@pytest.mark.parametrize('script, output', [
|
||||||
('vom file.py', 'vom: not found'),
|
('vom file.py', 'vom: not found'),
|
||||||
('fucck', 'fucck: not found'),
|
('fucck', 'fucck: not found'),
|
||||||
|
('puthon', "'puthon' is not recognized as an internal or external command"),
|
||||||
('got commit', 'got: command not found')])
|
('got commit', 'got: command not found')])
|
||||||
def test_match(mocker, script, output):
|
def test_match(mocker, script, output):
|
||||||
mocker.patch('thefuck.rules.no_command.which', return_value=None)
|
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),
|
@pytest.mark.skipif(not getattr(pacman_not_found, 'enabled_by_default', True),
|
||||||
reason='Skip if pacman is not available')
|
reason='Skip if pacman is not available')
|
||||||
@pytest.mark.parametrize('command', [
|
@pytest.mark.parametrize('command', [
|
||||||
|
Command('yay -S llc', 'error: target not found: llc'),
|
||||||
Command('yaourt -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('pacman llc', 'error: target not found: llc'),
|
||||||
Command('sudo 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', [
|
@pytest.mark.parametrize('command', [
|
||||||
|
Command('yay -S llc', 'error: target not found: llc'),
|
||||||
Command('yaourt -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('pacman llc', 'error: target not found: llc'),
|
||||||
Command('sudo 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),
|
@pytest.mark.skipif(not getattr(pacman_not_found, 'enabled_by_default', True),
|
||||||
reason='Skip if pacman is not available')
|
reason='Skip if pacman is not available')
|
||||||
@pytest.mark.parametrize('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('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('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'])])
|
(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', [
|
@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('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('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'])])
|
(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))
|
||||||
@@ -11,6 +11,11 @@ class TestBash(object):
|
|||||||
def shell(self):
|
def shell(self):
|
||||||
return Bash()
|
return Bash()
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def Popen(self, mocker):
|
||||||
|
mock = mocker.patch('thefuck.shells.bash.Popen')
|
||||||
|
return mock
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
def shell_aliases(self):
|
def shell_aliases(self):
|
||||||
os.environ['TF_SHELL_ALIASES'] = (
|
os.environ['TF_SHELL_ALIASES'] = (
|
||||||
@@ -74,7 +79,12 @@ class TestBash(object):
|
|||||||
config_exists.return_value = False
|
config_exists.return_value = False
|
||||||
assert not shell.how_to_configure().can_configure_automatically
|
assert not shell.how_to_configure().can_configure_automatically
|
||||||
|
|
||||||
def test_info(self, shell, mocker):
|
def test_info(self, shell, Popen):
|
||||||
patch = mocker.patch('thefuck.shells.bash.Popen')
|
Popen.return_value.stdout.read.side_effect = [b'3.5.9']
|
||||||
patch.return_value.stdout.read.side_effect = [b'3.5.9']
|
|
||||||
assert shell.info() == 'Bash 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 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from thefuck.const import ARGUMENT_PLACEHOLDER
|
||||||
from thefuck.shells import Fish
|
from thefuck.shells import Fish
|
||||||
|
|
||||||
|
|
||||||
@@ -28,8 +29,9 @@ class TestFish(object):
|
|||||||
('THEFUCK_OVERRIDDEN_ALIASES', '\ncut,\n\ngit,\tsed\r')])
|
('THEFUCK_OVERRIDDEN_ALIASES', '\ncut,\n\ngit,\tsed\r')])
|
||||||
def test_get_overridden_aliases(self, shell, os_environ, key, value):
|
def test_get_overridden_aliases(self, shell, os_environ, key, value):
|
||||||
os_environ[key] = value
|
os_environ[key] = value
|
||||||
assert shell._get_overridden_aliases() == {'cd', 'cut', 'git', 'grep',
|
overridden = shell._get_overridden_aliases()
|
||||||
'ls', 'man', 'open', 'sed'}
|
assert set(overridden) == {'cd', 'cut', 'git', 'grep',
|
||||||
|
'ls', 'man', 'open', 'sed'}
|
||||||
|
|
||||||
@pytest.mark.parametrize('before, after', [
|
@pytest.mark.parametrize('before, after', [
|
||||||
('cd', 'cd'),
|
('cd', 'cd'),
|
||||||
@@ -81,6 +83,7 @@ class TestFish(object):
|
|||||||
assert 'TF_SHELL=fish' in shell.app_alias('fuck')
|
assert 'TF_SHELL=fish' in shell.app_alias('fuck')
|
||||||
assert 'TF_ALIAS=fuck PYTHONIOENCODING' 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 '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):
|
def test_app_alias_alter_history(self, settings, shell):
|
||||||
settings.alter_history = True
|
settings.alter_history = True
|
||||||
@@ -113,6 +116,17 @@ class TestFish(object):
|
|||||||
config_exists.return_value = False
|
config_exists.return_value = False
|
||||||
assert not shell.how_to_configure().can_configure_automatically
|
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'3.5.9']
|
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):
|
def test_how_to_configure(self, shell):
|
||||||
assert shell.how_to_configure() is None
|
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):
|
def shell(self):
|
||||||
return Powershell()
|
return Powershell()
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def Popen(self, mocker):
|
||||||
|
mock = mocker.patch('thefuck.shells.powershell.Popen')
|
||||||
|
return mock
|
||||||
|
|
||||||
def test_and_(self, shell):
|
def test_and_(self, shell):
|
||||||
assert shell.and_('ls', 'cd') == '(ls) -and (cd)'
|
assert shell.and_('ls', 'cd') == '(ls) -and (cd)'
|
||||||
|
|
||||||
@@ -20,3 +25,20 @@ class TestPowershell(object):
|
|||||||
|
|
||||||
def test_how_to_configure(self, shell):
|
def test_how_to_configure(self, shell):
|
||||||
assert not shell.how_to_configure().can_configure_automatically
|
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):
|
||||||
config_exists.return_value = False
|
config_exists.return_value = False
|
||||||
assert not shell.how_to_configure().can_configure_automatically
|
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):
|
def shell(self):
|
||||||
return Zsh()
|
return Zsh()
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def Popen(self, mocker):
|
||||||
|
mock = mocker.patch('thefuck.shells.zsh.Popen')
|
||||||
|
return mock
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
def shell_aliases(self):
|
def shell_aliases(self):
|
||||||
os.environ['TF_SHELL_ALIASES'] = (
|
os.environ['TF_SHELL_ALIASES'] = (
|
||||||
@@ -69,7 +74,12 @@ class TestZsh(object):
|
|||||||
config_exists.return_value = False
|
config_exists.return_value = False
|
||||||
assert not shell.how_to_configure().can_configure_automatically
|
assert not shell.how_to_configure().can_configure_automatically
|
||||||
|
|
||||||
def test_info(self, shell, mocker):
|
def test_info(self, shell, Popen):
|
||||||
patch = mocker.patch('thefuck.shells.zsh.Popen')
|
Popen.return_value.stdout.read.side_effect = [b'3.5.9']
|
||||||
patch.return_value.stdout.read.side_effect = [b'3.5.9']
|
|
||||||
assert shell.info() == 'ZSH 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 pytest
|
||||||
import warnings
|
import warnings
|
||||||
from mock import Mock, patch
|
from mock import Mock, call, patch
|
||||||
from thefuck.utils import default_settings, \
|
from thefuck.utils import default_settings, \
|
||||||
memoize, get_closest, get_all_executables, replace_argument, \
|
memoize, get_closest, get_all_executables, replace_argument, \
|
||||||
get_all_matched_commands, is_app, for_app, cache, \
|
get_all_matched_commands, is_app, for_app, cache, \
|
||||||
@@ -76,6 +76,24 @@ def test_get_all_executables():
|
|||||||
assert 'fuck' not in all_callables
|
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', [
|
@pytest.mark.parametrize('args, result', [
|
||||||
(('apt-get instol vim', 'instol', 'install'), 'apt-get install vim'),
|
(('apt-get instol vim', 'instol', 'install'), 'apt-get install vim'),
|
||||||
(('git brnch', 'brnch', 'branch'), 'git branch')])
|
(('git brnch', 'brnch', 'branch'), 'git branch')])
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ from thefuck.utils import for_app, eager, replace_command
|
|||||||
enabled_by_default = apt_available
|
enabled_by_default = apt_available
|
||||||
|
|
||||||
|
|
||||||
@for_app('apt', 'apt-get', 'apt-cache')
|
|
||||||
@sudo_support
|
@sudo_support
|
||||||
|
@for_app('apt', 'apt-get', 'apt-cache')
|
||||||
def match(command):
|
def match(command):
|
||||||
return 'E: Invalid operation' in command.output
|
return 'E: Invalid operation' in command.output
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ from thefuck.specific.dnf import dnf_available
|
|||||||
regex = re.compile(r'No such command: (.*)\.')
|
regex = re.compile(r'No such command: (.*)\.')
|
||||||
|
|
||||||
|
|
||||||
@for_app('dnf')
|
|
||||||
@sudo_support
|
@sudo_support
|
||||||
|
@for_app('dnf')
|
||||||
def match(command):
|
def match(command):
|
||||||
return 'no such command' in command.output.lower()
|
return 'no such command' in command.output.lower()
|
||||||
|
|
||||||
|
|||||||
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)
|
||||||
11
thefuck/rules/git_commit_reset.py
Normal file
11
thefuck/rules/git_commit_reset.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
from thefuck.specific.git import git_support
|
||||||
|
|
||||||
|
|
||||||
|
@git_support
|
||||||
|
def match(command):
|
||||||
|
return ('commit' in command.script_parts)
|
||||||
|
|
||||||
|
|
||||||
|
@git_support
|
||||||
|
def get_new_command(command):
|
||||||
|
return 'git reset HEAD~'
|
||||||
@@ -6,7 +6,8 @@ from thefuck.specific.sudo import sudo_support
|
|||||||
@sudo_support
|
@sudo_support
|
||||||
def match(command):
|
def match(command):
|
||||||
return (not which(command.script_parts[0])
|
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],
|
and bool(get_close_matches(command.script_parts[0],
|
||||||
get_all_executables())))
|
get_all_executables())))
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
""" Fixes wrong package names with pacman or yaourt.
|
""" Fixes wrong package names with pacman or yaourt.
|
||||||
|
|
||||||
For example the `llc` program is in package `llvm` so this:
|
For example the `llc` program is in package `llvm` so this:
|
||||||
yaourt -S llc
|
yay -S llc
|
||||||
should be:
|
should be:
|
||||||
yaourt -S llvm
|
yay -S llvm
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from thefuck.utils import replace_command
|
from thefuck.utils import replace_command
|
||||||
@@ -12,7 +12,7 @@ from thefuck.specific.archlinux import get_pkgfile, archlinux_env
|
|||||||
|
|
||||||
def match(command):
|
def match(command):
|
||||||
return (command.script_parts
|
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'])
|
or command.script_parts[0:2] == ['sudo', 'pacman'])
|
||||||
and 'error: target not found:' in command.output)
|
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
|
||||||
@@ -2,22 +2,42 @@
|
|||||||
from thefuck.utils import memoize, get_alias
|
from thefuck.utils import memoize, get_alias
|
||||||
|
|
||||||
target_layout = '''qwertyuiop[]asdfghjkl;'zxcvbnm,./QWERTYUIOP{}ASDFGHJKL:"ZXCVBNM<>?'''
|
target_layout = '''qwertyuiop[]asdfghjkl;'zxcvbnm,./QWERTYUIOP{}ASDFGHJKL:"ZXCVBNM<>?'''
|
||||||
|
# any new keyboard layout must be appended
|
||||||
|
|
||||||
|
greek = u''';ςερτυθιοπ[]ασδφγηξκλ΄ζχψωβνμ,./:΅ΕΡΤΥΘΙΟΠ{}ΑΣΔΦΓΗΞΚΛ¨"ΖΧΨΩΒΝΜ<>?'''
|
||||||
|
|
||||||
source_layouts = [u'''йцукенгшщзхъфывапролджэячсмитьбю.ЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭЯЧСМИТЬБЮ,''',
|
source_layouts = [u'''йцукенгшщзхъфывапролджэячсмитьбю.ЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭЯЧСМИТЬБЮ,''',
|
||||||
u'''йцукенгшщзхїфівапролджєячсмитьбю.ЙЦУКЕНГШЩЗХЇФІВАПРОЛДЖЄЯЧСМИТЬБЮ,''',
|
u'''йцукенгшщзхїфівапролджєячсмитьбю.ЙЦУКЕНГШЩЗХЇФІВАПРОЛДЖЄЯЧСМИТЬБЮ,''',
|
||||||
u'''ضصثقفغعهخحجچشسیبلاتنمکگظطزرذدپو./ًٌٍَُِّْ][}{ؤئيإأآة»«:؛كٓژٰٔء><؟''',
|
u'''ضصثقفغعهخحجچشسیبلاتنمکگظطزرذدپو./ًٌٍَُِّْ][}{ؤئيإأآة»«:؛كٓژٰٔء><؟''',
|
||||||
u''';ςερτυθιοπ[]ασδφγηξκλ΄ζχψωβνμ,./:΅ΕΡΤΥΘΙΟΠ{}ΑΣΔΦΓΗΞΚΛ¨"ΖΧΨΩΒΝΜ<>?''',
|
u'''/'קראטוןםפ][שדגכעיחלךף,זסבהנמצתץ.QWERTYUIOP{}ASDFGHJKL:"ZXCVBNM<>?''',
|
||||||
u'''/'קראטוןםפ][שדגכעיחלךף,זסבהנמצתץ.QWERTYUIOP{}ASDFGHJKL:"ZXCVBNM<>?''']
|
greek]
|
||||||
|
|
||||||
|
|
||||||
|
source_to_target = {
|
||||||
|
greek: {u';': "q", u'ς': "w", u'ε': "e", u'ρ': "r", u'τ': "t", u'υ': "y",
|
||||||
|
u'θ': "u", u'ι': "i", u'ο': "o", u'π': "p", u'[': "[", u']': "]",
|
||||||
|
u'α': "a", u'σ': "s", u'δ': "d", u'φ': "f", u'γ': "g", u'η': "h",
|
||||||
|
u'ξ': "j", u'κ': "k", u'λ': "l", u'΄': "'", u'ζ': "z", u'χ': "x",
|
||||||
|
u'ψ': "c", u'ω': "v", u'β': "b", u'ν': "n", u'μ': "m", u',': ",",
|
||||||
|
u'.': ".", u'/': "/", u':': "Q", u'΅': "W", u'Ε': "E", u'Ρ': "R",
|
||||||
|
u'Τ': "T", u'Υ': "Y", u'Θ': "U", u'Ι': "I", u'Ο': "O", u'Π': "P",
|
||||||
|
u'{': "{", u'}': "}", u'Α': "A", u'Σ': "S", u'Δ': "D", u'Φ': "F",
|
||||||
|
u'Γ': "G", u'Η': "H", u'Ξ': "J", u'Κ': "K", u'Λ': "L", u'¨': ":",
|
||||||
|
u'"': '"', u'Ζ': "Z", u'Χ': "X", u'Ψ': "C", u'Ω': "V", u'Β': "B",
|
||||||
|
u'Ν': "N", u'Μ': "M", u'<': "<", u'>': ">", u'?': "?", u'ά': "a",
|
||||||
|
u'έ': "e", u'ύ': "y", u'ί': "i", u'ό': "o", u'ή': 'h', u'ώ': u"v",
|
||||||
|
u'Ά': "A", u'Έ': "E", u'Ύ': "Y", u'Ί': "I", u'Ό': "O", u'Ή': "H",
|
||||||
|
u'Ώ': "V"},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@memoize
|
@memoize
|
||||||
def _get_matched_layout(command):
|
def _get_matched_layout(command):
|
||||||
# don't use command.split_script here because a layout mismatch will likely
|
# don't use command.split_script here because a layout mismatch will likely
|
||||||
# result in a non-splitable sript as per shlex
|
# result in a non-splitable script as per shlex
|
||||||
cmd = command.script.split(' ')
|
cmd = command.script.split(' ')
|
||||||
for source_layout in source_layouts:
|
for source_layout in source_layouts:
|
||||||
is_all_match = True
|
is_all_match = True
|
||||||
|
|
||||||
for cmd_part in cmd:
|
for cmd_part in cmd:
|
||||||
if not all([ch in source_layout or ch in '-_' for ch in cmd_part]):
|
if not all([ch in source_layout or ch in '-_' for ch in cmd_part]):
|
||||||
is_all_match = False
|
is_all_match = False
|
||||||
@@ -35,12 +55,18 @@ def _switch(ch, layout):
|
|||||||
|
|
||||||
|
|
||||||
def _switch_command(command, layout):
|
def _switch_command(command, layout):
|
||||||
|
# Layouts with different amount of characters than English
|
||||||
|
if layout in source_to_target:
|
||||||
|
return ''.join(source_to_target[layout].get(ch, ch)
|
||||||
|
for ch in command.script)
|
||||||
|
|
||||||
return ''.join(_switch(ch, layout) for ch in command.script)
|
return ''.join(_switch(ch, layout) for ch in command.script)
|
||||||
|
|
||||||
|
|
||||||
def match(command):
|
def match(command):
|
||||||
if 'not found' not in command.output:
|
if 'not found' not in command.output:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
matched_layout = _get_matched_layout(command)
|
matched_layout = _get_matched_layout(command)
|
||||||
return (matched_layout and
|
return (matched_layout and
|
||||||
_switch_command(command, matched_layout) != get_alias())
|
_switch_command(command, matched_layout) != get_alias())
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ from .generic import Generic
|
|||||||
|
|
||||||
|
|
||||||
class Bash(Generic):
|
class Bash(Generic):
|
||||||
|
friendly_name = 'Bash'
|
||||||
|
|
||||||
def app_alias(self, alias_name):
|
def app_alias(self, alias_name):
|
||||||
# It is VERY important to have the variables declared WITHIN the function
|
# It is VERY important to have the variables declared WITHIN the function
|
||||||
return '''
|
return '''
|
||||||
@@ -20,8 +22,8 @@ class Bash(Generic):
|
|||||||
export TF_HISTORY=$(fc -ln -10);
|
export TF_HISTORY=$(fc -ln -10);
|
||||||
export PYTHONIOENCODING=utf-8;
|
export PYTHONIOENCODING=utf-8;
|
||||||
TF_CMD=$(
|
TF_CMD=$(
|
||||||
thefuck {argument_placeholder} $@
|
thefuck {argument_placeholder} "$@"
|
||||||
) && eval $TF_CMD;
|
) && eval "$TF_CMD";
|
||||||
unset TF_HISTORY;
|
unset TF_HISTORY;
|
||||||
export PYTHONIOENCODING=$TF_PYTHONIOENCODING;
|
export PYTHONIOENCODING=$TF_PYTHONIOENCODING;
|
||||||
{alter_history}
|
{alter_history}
|
||||||
@@ -79,13 +81,12 @@ class Bash(Generic):
|
|||||||
config = 'bash config'
|
config = 'bash config'
|
||||||
|
|
||||||
return self._create_shell_configuration(
|
return self._create_shell_configuration(
|
||||||
content=u'eval $(thefuck --alias)',
|
content=u'eval "$(thefuck --alias)"',
|
||||||
path=config,
|
path=config,
|
||||||
reload=u'source {}'.format(config))
|
reload=u'source {}'.format(config))
|
||||||
|
|
||||||
def info(self):
|
def _get_version(self):
|
||||||
"""Returns the name and version of the current shell"""
|
"""Returns the version of the current shell"""
|
||||||
proc = Popen(['bash', '-c', 'echo $BASH_VERSION'],
|
proc = Popen(['bash', '-c', 'echo $BASH_VERSION'],
|
||||||
stdout=PIPE, stderr=DEVNULL)
|
stdout=PIPE, stderr=DEVNULL)
|
||||||
version = proc.stdout.read().decode('utf-8').strip()
|
return proc.stdout.read().decode('utf-8').strip()
|
||||||
return u'Bash {}'.format(version)
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import sys
|
|||||||
import six
|
import six
|
||||||
from .. import logs
|
from .. import logs
|
||||||
from ..conf import settings
|
from ..conf import settings
|
||||||
|
from ..const import ARGUMENT_PLACEHOLDER
|
||||||
from ..utils import DEVNULL, cache
|
from ..utils import DEVNULL, cache
|
||||||
from .generic import Generic
|
from .generic import Generic
|
||||||
|
|
||||||
@@ -37,13 +38,15 @@ def _get_aliases(overridden):
|
|||||||
|
|
||||||
|
|
||||||
class Fish(Generic):
|
class Fish(Generic):
|
||||||
|
friendly_name = 'Fish Shell'
|
||||||
|
|
||||||
def _get_overridden_aliases(self):
|
def _get_overridden_aliases(self):
|
||||||
overridden = os.environ.get('THEFUCK_OVERRIDDEN_ALIASES',
|
overridden = os.environ.get('THEFUCK_OVERRIDDEN_ALIASES',
|
||||||
os.environ.get('TF_OVERRIDDEN_ALIASES', ''))
|
os.environ.get('TF_OVERRIDDEN_ALIASES', ''))
|
||||||
default = {'cd', 'grep', 'ls', 'man', 'open'}
|
default = {'cd', 'grep', 'ls', 'man', 'open'}
|
||||||
for alias in overridden.split(','):
|
for alias in overridden.split(','):
|
||||||
default.add(alias.strip())
|
default.add(alias.strip())
|
||||||
return default
|
return sorted(default)
|
||||||
|
|
||||||
def app_alias(self, alias_name):
|
def app_alias(self, alias_name):
|
||||||
if settings.alter_history:
|
if settings.alter_history:
|
||||||
@@ -56,11 +59,11 @@ class Fish(Generic):
|
|||||||
return ('function {0} -d "Correct your previous console command"\n'
|
return ('function {0} -d "Correct your previous console command"\n'
|
||||||
' set -l fucked_up_command $history[1]\n'
|
' set -l fucked_up_command $history[1]\n'
|
||||||
' env TF_SHELL=fish TF_ALIAS={0} PYTHONIOENCODING=utf-8'
|
' 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'
|
' if [ "$unfucked_command" != "" ]\n'
|
||||||
' eval $unfucked_command\n{1}'
|
' eval $unfucked_command\n{1}'
|
||||||
' end\n'
|
' end\n'
|
||||||
'end').format(alias_name, alter_history)
|
'end').format(alias_name, alter_history, ARGUMENT_PLACEHOLDER)
|
||||||
|
|
||||||
def get_aliases(self):
|
def get_aliases(self):
|
||||||
overridden = self._get_overridden_aliases()
|
overridden = self._get_overridden_aliases()
|
||||||
@@ -103,12 +106,10 @@ class Fish(Generic):
|
|||||||
path='~/.config/fish/config.fish',
|
path='~/.config/fish/config.fish',
|
||||||
reload='fish')
|
reload='fish')
|
||||||
|
|
||||||
def info(self):
|
def _get_version(self):
|
||||||
"""Returns the name and version of the current shell"""
|
"""Returns the version of the current shell"""
|
||||||
proc = Popen(['fish', '-c', 'echo $FISH_VERSION'],
|
proc = Popen(['fish', '--version'], stdout=PIPE, stderr=DEVNULL)
|
||||||
stdout=PIPE, stderr=DEVNULL)
|
return proc.stdout.read().decode('utf-8').split()[-1]
|
||||||
version = proc.stdout.read().decode('utf-8').strip()
|
|
||||||
return u'Fish Shell {}'.format(version)
|
|
||||||
|
|
||||||
def put_to_history(self, command):
|
def put_to_history(self, command):
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ ShellConfiguration = namedtuple('ShellConfiguration', (
|
|||||||
|
|
||||||
|
|
||||||
class Generic(object):
|
class Generic(object):
|
||||||
|
friendly_name = 'Generic Shell'
|
||||||
|
|
||||||
def get_aliases(self):
|
def get_aliases(self):
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
@@ -34,8 +36,8 @@ class Generic(object):
|
|||||||
return command_script
|
return command_script
|
||||||
|
|
||||||
def app_alias(self, alias_name):
|
def app_alias(self, alias_name):
|
||||||
return "alias {0}='eval $(TF_ALIAS={0} PYTHONIOENCODING=utf-8 " \
|
return """alias {0}='eval "$(TF_ALIAS={0} PYTHONIOENCODING=utf-8 """ \
|
||||||
"thefuck $(fc -ln -1))'".format(alias_name)
|
"""thefuck "$(fc -ln -1)")"'""".format(alias_name)
|
||||||
|
|
||||||
def instant_mode_alias(self, alias_name):
|
def instant_mode_alias(self, alias_name):
|
||||||
warn("Instant mode not supported by your shell")
|
warn("Instant mode not supported by your shell")
|
||||||
@@ -131,9 +133,18 @@ class Generic(object):
|
|||||||
'type', 'typeset', 'ulimit', 'umask', 'unalias', 'unset',
|
'type', 'typeset', 'ulimit', 'umask', 'unalias', 'unset',
|
||||||
'until', 'wait', 'while']
|
'until', 'wait', 'while']
|
||||||
|
|
||||||
|
def _get_version(self):
|
||||||
|
"""Returns the version of the current shell"""
|
||||||
|
return ''
|
||||||
|
|
||||||
def info(self):
|
def info(self):
|
||||||
"""Returns the name and version of the current shell"""
|
"""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):
|
def _create_shell_configuration(self, content, path, reload):
|
||||||
return ShellConfiguration(
|
return ShellConfiguration(
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
|
from subprocess import Popen, PIPE
|
||||||
|
from ..utils import DEVNULL
|
||||||
from .generic import Generic, ShellConfiguration
|
from .generic import Generic, ShellConfiguration
|
||||||
|
|
||||||
|
|
||||||
class Powershell(Generic):
|
class Powershell(Generic):
|
||||||
|
friendly_name = 'PowerShell'
|
||||||
|
|
||||||
def app_alias(self, alias_name):
|
def app_alias(self, alias_name):
|
||||||
return 'function ' + alias_name + ' {\n' \
|
return 'function ' + alias_name + ' {\n' \
|
||||||
' $history = (Get-History -Count 1).CommandLine;\n' \
|
' $history = (Get-History -Count 1).CommandLine;\n' \
|
||||||
@@ -24,3 +28,16 @@ class Powershell(Generic):
|
|||||||
path='$profile',
|
path='$profile',
|
||||||
reload='& $profile',
|
reload='& $profile',
|
||||||
can_configure_automatically=False)
|
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):
|
class Tcsh(Generic):
|
||||||
|
friendly_name = 'Tcsh'
|
||||||
|
|
||||||
def app_alias(self, alias_name):
|
def app_alias(self, alias_name):
|
||||||
return ("alias {0} 'setenv TF_SHELL tcsh && setenv TF_ALIAS {0} && "
|
return ("alias {0} 'setenv TF_SHELL tcsh && setenv TF_ALIAS {0} && "
|
||||||
"set fucked_cmd=`history -h 2 | head -n 1` && "
|
"set fucked_cmd=`history -h 2 | head -n 1` && "
|
||||||
@@ -35,3 +37,8 @@ class Tcsh(Generic):
|
|||||||
content=u'eval `thefuck --alias`',
|
content=u'eval `thefuck --alias`',
|
||||||
path='~/.tcshrc',
|
path='~/.tcshrc',
|
||||||
reload='tcsh')
|
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):
|
class Zsh(Generic):
|
||||||
|
friendly_name = 'ZSH'
|
||||||
|
|
||||||
def app_alias(self, alias_name):
|
def app_alias(self, alias_name):
|
||||||
# It is VERY important to have the variables declared WITHIN the function
|
# It is VERY important to have the variables declared WITHIN the function
|
||||||
return '''
|
return '''
|
||||||
@@ -87,9 +89,8 @@ class Zsh(Generic):
|
|||||||
path='~/.zshrc',
|
path='~/.zshrc',
|
||||||
reload='source ~/.zshrc')
|
reload='source ~/.zshrc')
|
||||||
|
|
||||||
def info(self):
|
def _get_version(self):
|
||||||
"""Returns the name and version of the current shell"""
|
"""Returns the version of the current shell"""
|
||||||
proc = Popen(['zsh', '-c', 'echo $ZSH_VERSION'],
|
proc = Popen(['zsh', '-c', 'echo $ZSH_VERSION'],
|
||||||
stdout=PIPE, stderr=DEVNULL)
|
stdout=PIPE, stderr=DEVNULL)
|
||||||
version = proc.stdout.read().decode('utf-8').strip()
|
return proc.stdout.read().decode('utf-8').strip()
|
||||||
return u'ZSH {}'.format(version)
|
|
||||||
|
|||||||
@@ -32,7 +32,9 @@ def get_pkgfile(command):
|
|||||||
|
|
||||||
|
|
||||||
def archlinux_env():
|
def archlinux_env():
|
||||||
if utils.which('yaourt'):
|
if utils.which('yay'):
|
||||||
|
pacman = 'yay'
|
||||||
|
elif utils.which('yaourt'):
|
||||||
pacman = 'yaourt'
|
pacman = 'yaourt'
|
||||||
elif utils.which('pacman'):
|
elif utils.which('pacman'):
|
||||||
pacman = 'sudo pacman'
|
pacman = 'sudo pacman'
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ class CommandSelector(object):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def value(self):
|
def value(self):
|
||||||
""":rtype hefuck.types.CorrectedCommand"""
|
""":rtype thefuck.types.CorrectedCommand"""
|
||||||
return self._commands[self._index]
|
return self._commands[self._index]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ def get_all_executables():
|
|||||||
tf_entry_points = ['thefuck', 'fuck']
|
tf_entry_points = ['thefuck', 'fuck']
|
||||||
|
|
||||||
bins = [exe.name.decode('utf8') if six.PY2 else exe.name
|
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()), [])
|
for exe in _safe(lambda: list(Path(path).iterdir()), [])
|
||||||
if not _safe(exe.is_dir, True)
|
if not _safe(exe.is_dir, True)
|
||||||
and exe.name not in tf_entry_points]
|
and exe.name not in tf_entry_points]
|
||||||
|
|||||||
Reference in New Issue
Block a user