1
0
mirror of https://github.com/nvbn/thefuck.git synced 2025-11-03 16:42:03 +00:00

Compare commits

..

21 Commits
3.28 ... 3.29

Author SHA1 Message Date
Vladimir Iakovlev
59e1f7b122 Bump to 3.29 2019-05-27 18:29:04 +02:00
Pablo Aguiar
ff2944086d #N/A: Improve how version is fetched for all shells (#920) 2019-05-27 18:24:55 +02:00
Pablo Aguiar
ba949f7fd9 #N/A: Add pyenv_no_such_command rule (#919) 2019-05-27 18:23:45 +02:00
Pablo Aguiar
5efcf1019f #N/A: Improve support to Windows in no_command rule (#918)
Windows “not found” message is quite different from POSIX systems.
2019-05-27 18:23:06 +02:00
Ryan Delaney
70a13406f0 Fix a couple small shellcheck errors (#915)
* Fix shellcheck SC2046

Further reading: https://github.com/koalaman/shellcheck/wiki/Sc2046

* Fix shellcheck 2068

Further reading: https://github.com/koalaman/shellcheck/wiki/Sc2068

* Fix syntax error from bad quoting

I also used a docstring here because the escaping makes it harder for
humans to parse
2019-05-22 20:22:09 +02:00
Jesus Cuesta
201c01fc74 Adding yay AUR manager to Arch Linux's commands since yaourt is unmaintained and has some security issues. (#907) 2019-05-21 20:49:04 +02:00
Pablo Aguiar
78ef9eec88 #902: Use os.pathsep to split PATH env var (#917)
Fix #902
2019-05-21 20:47:47 +02:00
Pablo Aguiar
40ab4eb62d #899: Support -y/--yeah command line args in Fish Shell (#900) 2019-04-24 18:17:52 +02:00
Nick "darkfiberiru" Wolff
55cb3546df Pkg should be recommend on freebsd to install (#905) 2019-04-24 18:17:01 +02:00
Inga Feick
82902fb50d Add rule for pip_install permission fix (#895)
* Add rule for pip_install permission fix

* Fix whitespace

* Switch quotation to single

* remove 2nd else

* E261 indent comment
2019-04-24 18:15:38 +02:00
Inga Feick
828ae537da Docker login (#894)
* Add docker_login rule

* Add docker_login rule

* Whitespace fix

* Fix typo in test case

* Fix typo in test case

* Add test cases
2019-04-04 00:01:14 +02:00
Aiden Song
1208faaabb #N/A: Add rule for git commit that reverts previous commit (#886) 2019-02-25 23:24:16 +01:00
Pablo Santiago Blum de Aguiar
2d81166213 #N/A: Return an ordered list from set of overridden aliases
This way it's ensured that whatever is used as cache key is always
ordered. Sets are unordered collections.
2019-01-17 00:29:22 +01:00
Vladimir Iakovlev
8093f7cab8 Squashed commit of the following:
commit b853385ea9b9409a29a30c7af4d47c9a500cd287
Author: Vladimir Iakovlev <nvbn.rm@gmail.com>
Date:   Tue Jan 15 00:54:01 2019 +0100

    #864: Make the solution for Greek a bit more extensible

commit 073ebceb594ad24972f7765b1f608de44c1cebf2
Merge: b946b7d 141462a
Author: Vladimir Iakovlev <nvbn.rm@gmail.com>
Date:   Tue Jan 15 00:46:09 2019 +0100

    Merge branch 'master' of git://github.com/RealOgre/thefuck into RealOgre-master

commit 141462a6fb
Author: RealOrge <45096491+RealOrge@users.noreply.github.com>
Date:   Thu Dec 13 16:47:43 2018 +0200

    Update switch_lang.py

commit 1f792853f2
Author: RealOrge <45096491+RealOrge@users.noreply.github.com>
Date:   Thu Dec 13 16:39:04 2018 +0200

    Update switch_lang.py

commit e7dede53a1
Author: RealOrge <45096491+RealOrge@users.noreply.github.com>
Date:   Thu Dec 13 15:24:10 2018 +0200

    Update switch_lang.py

commit 4a0a973e62
Author: RealOrge <45096491+RealOrge@users.noreply.github.com>
Date:   Thu Dec 13 15:04:44 2018 +0200

    Update switch_lang.py

commit 80d6b8da4c
Author: RealOrge <45096491+RealOrge@users.noreply.github.com>
Date:   Thu Dec 13 14:25:15 2018 +0200

    Update switch_lang.py

commit 66b13c53b3
Author: RealOrge <45096491+RealOrge@users.noreply.github.com>
Date:   Thu Dec 13 11:44:48 2018 +0200

    Update switch_lang.py
2019-01-15 00:54:48 +01:00
yangkyeongmo
b946b7d319 Comment correction on ui.py (#874) 2019-01-15 00:36:05 +01:00
Vladimir Iakovlev
9354a977dd #N/A: Fix tests with pytest 4 2019-01-15 00:35:28 +01:00
Mickaël Schoentgen
1eb4ccbcc9 Fix 2 DeprecationWarning: invlid escape sequence (#872)
Signed-off-by: Mickaël Schoentgen <contact@tiger-222.fr>
2019-01-06 15:44:09 +01:00
Pablo Santiago Blum de Aguiar
ce5feaebf7 #869: Use fish --version instead of an interactive shell for info()
This prevents initialisation and consequentially a recursive loop.

Fix #869
Ref oh-my-fish/plugin-thefuck#11
2019-01-04 20:54:03 +01:00
Fábio Santos
ac343fb1bd #868: Point out you can use linuxbrew in README
* Point out you can use linuxbrew

* Change text and add link to linuxbrew

* Change brew related URLs to HTTPS
2019-01-04 20:35:27 +01:00
Chris De Pasquale
7bc619385b Fixed incorrect ordering of for_app and sudo_support causing apt_invalid_operation and dnf_no_such_command rules to fail (#861) 2018-12-11 01:01:17 +01:00
Vladimir Iakovlev
d86dd5f179 #N/A: Clear dist/ before uploading releases 2018-11-29 23:57:07 +01:00
36 changed files with 447 additions and 59 deletions

View File

@@ -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` &ndash; fixes `unzip` command that unzipped in the current directory; * `dirty_unzip` &ndash; fixes `unzip` command that unzipped in the current directory;
* `django_south_ghost` &ndash; adds `--delete-ghost-migrations` to failed because ghosts django south migration; * `django_south_ghost` &ndash; adds `--delete-ghost-migrations` to failed because ghosts django south migration;
* `django_south_merge` &ndash; adds `--merge` to inconsistent django south migration; * `django_south_merge` &ndash; adds `--merge` to inconsistent django south migration;
* `docker_login` &ndash; executes a `docker login` and repeats the previous command;
* `docker_not_command` &ndash; fixes wrong docker commands like `docker tags`; * `docker_not_command` &ndash; fixes wrong docker commands like `docker tags`;
* `dry` &ndash; fixes repetitions like `git git push`; * `dry` &ndash; fixes repetitions like `git git push`;
* `fab_command_not_found` &ndash; fix misspelled fabric commands; * `fab_command_not_found` &ndash; fix misspelled fabric commands;
@@ -204,6 +204,7 @@ following rules are enabled by default:
* `git_branch_list` &ndash; catches `git branch list` in place of `git branch` and removes created branch; * `git_branch_list` &ndash; catches `git branch list` in place of `git branch` and removes created branch;
* `git_checkout` &ndash; fixes branch name or creates new branch; * `git_checkout` &ndash; fixes branch name or creates new branch;
* `git_commit_amend` &ndash; offers `git commit --amend` after previous commit; * `git_commit_amend` &ndash; offers `git commit --amend` after previous commit;
* `git_commit_reset` &ndash; offers `git reset HEAD~` after previous commit;
* `git_diff_no_index` &ndash; adds `--no-index` to previous `git diff` on untracked files; * `git_diff_no_index` &ndash; adds `--no-index` to previous `git diff` on untracked files;
* `git_diff_staged` &ndash; adds `--staged` to previous `git diff` with unexpected output; * `git_diff_staged` &ndash; adds `--staged` to previous `git diff` with unexpected output;
* `git_fix_stash` &ndash; fixes `git stash` commands (misspelled subcommand and missing `save`); * `git_fix_stash` &ndash; fixes `git stash` commands (misspelled subcommand and missing `save`);
@@ -264,10 +265,12 @@ following rules are enabled by default:
* `no_command` &ndash; fixes wrong console commands, for example `vom/vim`; * `no_command` &ndash; fixes wrong console commands, for example `vom/vim`;
* `no_such_file` &ndash; creates missing directories with `mv` and `cp` commands; * `no_such_file` &ndash; creates missing directories with `mv` and `cp` commands;
* `open` &ndash; either prepends `http://` to address passed to `open` or create a new file or directory and passes it to `open`; * `open` &ndash; either prepends `http://` to address passed to `open` or create a new file or directory and passes it to `open`;
* `pip_install` &ndash; fixes permission issues with `pip install` commands by adding `--user` or prepending `sudo` if necessary;
* `pip_unknown_command` &ndash; fixes wrong `pip` commands, for example `pip instatl/pip install`; * `pip_unknown_command` &ndash; fixes wrong `pip` commands, for example `pip instatl/pip install`;
* `php_s` &ndash; replaces `-s` by `-S` when trying to run a local php server; * `php_s` &ndash; replaces `-s` by `-S` when trying to run a local php server;
* `port_already_in_use` &ndash; kills process that bound port; * `port_already_in_use` &ndash; kills process that bound port;
* `prove_recursively` &ndash; adds `-r` when called with directory; * `prove_recursively` &ndash; adds `-r` when called with directory;
* `pyenv_no_such_command` &ndash; fixes wrong pyenv commands like `pyenv isntall` or `pyenv list`;
* `python_command` &ndash; prepends `python` when you try to run non-executable/without `./` python script; * `python_command` &ndash; prepends `python` when you try to run non-executable/without `./` python script;
* `python_execute` &ndash; appends missing `.py` when executing Python files; * `python_execute` &ndash; appends missing `.py` when executing Python files;
* `quotation_marks` &ndash; fixes uneven usage of `'` and `"` when containing args'; * `quotation_marks` &ndash; 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` &ndash; fixes wrong brew commands, for example `brew docto/brew doctor`; * `brew_unknown_command` &ndash; fixes wrong brew commands, for example `brew docto/brew doctor`;
* `brew_update_formula` &ndash; turns `brew update <formula>` into `brew upgrade <formula>`; * `brew_update_formula` &ndash; turns `brew update <formula>` into `brew upgrade <formula>`;
* `dnf_no_such_command` &ndash; fixes mistyped DNF commands; * `dnf_no_such_command` &ndash; fixes mistyped DNF commands;
* `pacman` &ndash; installs app with `pacman` if it is not installed (uses `yaourt` if available); * `pacman` &ndash; installs app with `pacman` if it is not installed (uses `yay` or `yaourt` if available);
* `pacman_not_found` &ndash; fixes package name with `pacman` or `yaourt`. * `pacman_not_found` &ndash; 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/

View File

@@ -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)

View File

@@ -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)

View File

@@ -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'],

View File

@@ -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')

View 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'

View 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~'

View File

@@ -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)

View File

@@ -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'])])

View 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'

View 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))

View File

@@ -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']

View File

@@ -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']

View File

@@ -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

View File

@@ -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']

View File

@@ -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']

View File

@@ -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']

View File

@@ -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')])

View File

@@ -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

View File

@@ -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()

View 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)

View 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~'

View File

@@ -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())))

View File

@@ -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)

View 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)

View 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

View File

@@ -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())

View File

@@ -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)

View File

@@ -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:

View File

@@ -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(

View File

@@ -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]

View File

@@ -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]

View File

@@ -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)

View File

@@ -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'

View File

@@ -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]

View File

@@ -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]