mirror of
https://github.com/nvbn/thefuck.git
synced 2025-11-02 08:02:04 +00:00
Compare commits
43 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f74bbb7a9a | ||
|
|
d6c2c7266d | ||
|
|
51839e65cd | ||
|
|
d5ae3a6b41 | ||
|
|
9f421a17e5 | ||
|
|
9d9820676a | ||
|
|
5ec4909d2f | ||
|
|
c6d2766553 | ||
|
|
c6af8409d9 | ||
|
|
95e7d00aec | ||
|
|
cdccf1881e | ||
|
|
db6053b301 | ||
|
|
183b70c8b8 | ||
|
|
5e0cc8c703 | ||
|
|
1aa2ec1795 | ||
|
|
0c98053f74 | ||
|
|
17b2fba48d | ||
|
|
43886c38ff | ||
|
|
9070748a86 | ||
|
|
61de6f4a51 | ||
|
|
d102af41d9 | ||
|
|
b7002bb9f9 | ||
|
|
18b4f5df6a | ||
|
|
28153db4a8 | ||
|
|
047a1a6072 | ||
|
|
69db5c70e6 | ||
|
|
fa1edd4bae | ||
|
|
333c4b2a3f | ||
|
|
b1f10642fa | ||
|
|
047efd5575 | ||
|
|
f604756cb7 | ||
|
|
a27115bff1 | ||
|
|
5d00b3bc25 | ||
|
|
0cf4f5e8b0 | ||
|
|
41707b80c6 | ||
|
|
3a39deb485 | ||
|
|
d4bc8cebf1 | ||
|
|
6daf687237 | ||
|
|
2207dd2668 | ||
|
|
d1ab08a797 | ||
|
|
51e89a36ef | ||
|
|
39f7cc37eb | ||
|
|
251b69b5a0 |
30
README.md
30
README.md
@@ -1,10 +1,10 @@
|
|||||||
# The Fuck [](https://travis-ci.org/nvbn/thefuck)
|
# The Fuck [![Version][version-badge]][version-link] [![Build Status][travis-badge]][travis-link] [![Windows Build Status][appveyor-badge]][appveyor-link] [![Coverage][coverage-badge]][coverage-link] [![MIT License][license-badge]](LICENSE.md)
|
||||||
|
|
||||||
Magnificent app which corrects your previous console command,
|
Magnificent app which corrects your previous console command,
|
||||||
inspired by a [@liamosaur](https://twitter.com/liamosaur/)
|
inspired by a [@liamosaur](https://twitter.com/liamosaur/)
|
||||||
[tweet](https://twitter.com/liamosaur/status/506975850596536320).
|
[tweet](https://twitter.com/liamosaur/status/506975850596536320).
|
||||||
|
|
||||||
[](https://raw.githubusercontent.com/nvbn/thefuck/master/example.gif)
|
[![gif with examples][examples-link]][examples-link]
|
||||||
|
|
||||||
Few more examples:
|
Few more examples:
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@ Python 3.4.2 (default, Oct 8 2014, 13:08:17)
|
|||||||
git: 'brnch' is not a git command. See 'git --help'.
|
git: 'brnch' is not a git command. See 'git --help'.
|
||||||
|
|
||||||
Did you mean this?
|
Did you mean this?
|
||||||
branch
|
branch
|
||||||
|
|
||||||
➜ fuck
|
➜ fuck
|
||||||
git branch [enter/↑/↓/ctrl+c]
|
git branch [enter/↑/↓/ctrl+c]
|
||||||
@@ -97,7 +97,7 @@ Reading package lists... Done
|
|||||||
## Installation [*experimental*]
|
## Installation [*experimental*]
|
||||||
|
|
||||||
On Ubuntu and OS X you can install `The Fuck` with installation script:
|
On Ubuntu and OS X you can install `The Fuck` with installation script:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
wget -O - https://raw.githubusercontent.com/nvbn/thefuck/master/install.sh | sh - && $0
|
wget -O - https://raw.githubusercontent.com/nvbn/thefuck/master/install.sh | sh - && $0
|
||||||
```
|
```
|
||||||
@@ -107,7 +107,7 @@ wget -O - https://raw.githubusercontent.com/nvbn/thefuck/master/install.sh | sh
|
|||||||
Install `The Fuck` with `pip`:
|
Install `The Fuck` with `pip`:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo pip install thefuck
|
sudo -H pip install thefuck
|
||||||
```
|
```
|
||||||
|
|
||||||
[Or using an OS package manager (OS X, Ubuntu, Arch).](https://github.com/nvbn/thefuck/wiki/Installation)
|
[Or using an OS package manager (OS X, Ubuntu, Arch).](https://github.com/nvbn/thefuck/wiki/Installation)
|
||||||
@@ -144,6 +144,7 @@ using the matched rule and runs it. Rules enabled by default are as follows:
|
|||||||
* `cd_correction` – spellchecks and correct failed cd commands;
|
* `cd_correction` – spellchecks and correct failed cd commands;
|
||||||
* `cd_mkdir` – creates directories before cd'ing into them;
|
* `cd_mkdir` – creates directories before cd'ing into them;
|
||||||
* `cd_parent` – changes `cd..` to `cd ..`;
|
* `cd_parent` – changes `cd..` to `cd ..`;
|
||||||
|
* `chmod_x` – add execution bit;
|
||||||
* `composer_not_command` – fixes composer command name;
|
* `composer_not_command` – fixes composer command name;
|
||||||
* `cp_omitting_directory` – adds `-a` when you `cp` directory;
|
* `cp_omitting_directory` – adds `-a` when you `cp` directory;
|
||||||
* `cpp11` – adds missing `-std=c++11` to `g++` or `clang++`;
|
* `cpp11` – adds missing `-std=c++11` to `g++` or `clang++`;
|
||||||
@@ -155,8 +156,9 @@ using the matched rule and runs it. Rules enabled by default are as follows:
|
|||||||
* `dry` – fixes repetitions like `git git push`;
|
* `dry` – fixes repetitions like `git git push`;
|
||||||
* `fix_alt_space` – replaces Alt+Space with Space character;
|
* `fix_alt_space` – replaces Alt+Space with Space character;
|
||||||
* `fix_file` – opens a file with an error in your `$EDITOR`;
|
* `fix_file` – opens a file with an error in your `$EDITOR`;
|
||||||
* `git_add` – fixes *"Did you forget to 'git add'?"*;
|
* `git_add` – fixes *"pathspec 'foo' did not match any file(s) known to git."*;
|
||||||
* `git_branch_delete` – changes `git branch -d` to `git branch -D`;
|
* `git_branch_delete` – changes `git branch -d` to `git branch -D`;
|
||||||
|
* `git_branch_exists` – offers `git branch -d foo`, `git branch -D foo` or `git checkout foo` when creating a branch that already exists;
|
||||||
* `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_diff_staged` – adds `--staged` to previous `git diff` with unexpected output;
|
* `git_diff_staged` – adds `--staged` to previous `git diff` with unexpected output;
|
||||||
@@ -167,6 +169,7 @@ using the matched rule and runs it. Rules enabled by default are as follows:
|
|||||||
* `git_pull_clone` – clones instead of pulling when the repo does not exist;
|
* `git_pull_clone` – clones instead of pulling when the repo does not exist;
|
||||||
* `git_push` – adds `--set-upstream origin $branch` to previous failed `git push`;
|
* `git_push` – adds `--set-upstream origin $branch` to previous failed `git push`;
|
||||||
* `git_push_pull` – runs `git pull` when `push` was rejected;
|
* `git_push_pull` – runs `git pull` when `push` was rejected;
|
||||||
|
* `git_rm_recursive` – adds `-r` when you try to `rm` a directory;
|
||||||
* `git_remote_seturl_add` – runs `git remote add` when `git remote set_url` on nonexistant remote;
|
* `git_remote_seturl_add` – runs `git remote add` when `git remote set_url` on nonexistant remote;
|
||||||
* `git_stash` – stashes you local modifications before rebasing or switching branch;
|
* `git_stash` – stashes you local modifications before rebasing or switching branch;
|
||||||
* `git_two_dashes` – adds a missing dash to commands like `git commit -amend` or `git rebase -continue`;
|
* `git_two_dashes` – adds a missing dash to commands like `git commit -amend` or `git rebase -continue`;
|
||||||
@@ -219,6 +222,7 @@ Enabled by default only on specific platforms:
|
|||||||
* `apt_invalid_operation` – fixes invalid `apt` and `apt-get` calls, like `apt-get isntall vim`;
|
* `apt_invalid_operation` – fixes invalid `apt` and `apt-get` calls, like `apt-get isntall vim`;
|
||||||
* `brew_install` – fixes formula name for `brew install`;
|
* `brew_install` – fixes formula name for `brew install`;
|
||||||
* `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_upgrade` – appends `--all` to `brew upgrade` as per Homebrew's new behaviour;
|
* `brew_upgrade` – appends `--all` to `brew upgrade` as per Homebrew's new behaviour;
|
||||||
* `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 `yaourt` if available);
|
||||||
* `pacman_not_found` – fixes package name with `pacman` or `yaourt`.
|
* `pacman_not_found` – fixes package name with `pacman` or `yaourt`.
|
||||||
@@ -305,7 +309,7 @@ debug = False
|
|||||||
Or via environment variables:
|
Or via environment variables:
|
||||||
|
|
||||||
* `THEFUCK_RULES` – list of enabled rules, like `DEFAULT_RULES:rm_root` or `sudo:no_command`;
|
* `THEFUCK_RULES` – list of enabled rules, like `DEFAULT_RULES:rm_root` or `sudo:no_command`;
|
||||||
* `THEFUCK_EXCLUDE_RULES` – list of disabled rules, like `git_pull:git_push`;
|
* `THEFUCK_EXCLUDE_RULES` – list of disabled rules, like `git_pull:git_push`;
|
||||||
* `THEFUCK_REQUIRE_CONFIRMATION` – require confirmation before running new command, `true/false`;
|
* `THEFUCK_REQUIRE_CONFIRMATION` – require confirmation before running new command, `true/false`;
|
||||||
* `THEFUCK_WAIT_COMMAND` – max amount of time in seconds for getting previous command output;
|
* `THEFUCK_WAIT_COMMAND` – max amount of time in seconds for getting previous command output;
|
||||||
* `THEFUCK_NO_COLORS` – disable colored output, `true/false`;
|
* `THEFUCK_NO_COLORS` – disable colored output, `true/false`;
|
||||||
@@ -357,3 +361,15 @@ sudo apt-get install pandoc
|
|||||||
|
|
||||||
## License MIT
|
## License MIT
|
||||||
Project License can be found [here](LICENSE.md).
|
Project License can be found [here](LICENSE.md).
|
||||||
|
|
||||||
|
|
||||||
|
[version-badge]: https://img.shields.io/pypi/v/thefuck.svg?label=version
|
||||||
|
[version-link]: https://pypi.python.org/pypi/thefuck/
|
||||||
|
[travis-badge]: https://img.shields.io/travis/nvbn/thefuck.svg
|
||||||
|
[travis-link]: https://travis-ci.org/nvbn/thefuck
|
||||||
|
[appveyor-badge]: https://img.shields.io/appveyor/ci/nvbn/thefuck.svg?label=windows%20build
|
||||||
|
[appveyor-link]: https://ci.appveyor.com/project/nvbn/thefuck
|
||||||
|
[coverage-badge]: https://img.shields.io/coveralls/nvbn/thefuck.svg
|
||||||
|
[coverage-link]: https://coveralls.io/github/nvbn/thefuck
|
||||||
|
[license-badge]: https://img.shields.io/badge/license-MIT-007EC7.svg
|
||||||
|
[examples-link]: https://raw.githubusercontent.com/nvbn/thefuck/master/example.gif
|
||||||
|
|||||||
22
appveyor.yml
Normal file
22
appveyor.yml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
build: false
|
||||||
|
|
||||||
|
environment:
|
||||||
|
matrix:
|
||||||
|
- PYTHON: "C:/Python27"
|
||||||
|
- PYTHON: "C:/Python33"
|
||||||
|
- PYTHON: "C:/Python34"
|
||||||
|
- PYTHON: "C:/Python35"
|
||||||
|
|
||||||
|
init:
|
||||||
|
- "ECHO %PYTHON%"
|
||||||
|
- ps: "ls C:/Python*"
|
||||||
|
|
||||||
|
install:
|
||||||
|
- ps: (new-object net.webclient).DownloadFile('https://bootstrap.pypa.io/get-pip.py', 'C:/get-pip.py')
|
||||||
|
- "%PYTHON%/python.exe C:/get-pip.py"
|
||||||
|
- "%PYTHON%/Scripts/pip.exe install -U setuptools"
|
||||||
|
- "%PYTHON%/python.exe setup.py develop"
|
||||||
|
- "%PYTHON%/Scripts/pip.exe install -U -r requirements.txt"
|
||||||
|
|
||||||
|
test_script:
|
||||||
|
- "%PYTHON%/Scripts/py.test.exe -sv"
|
||||||
2
setup.py
2
setup.py
@@ -26,7 +26,7 @@ elif (3, 0) < version < (3, 3):
|
|||||||
' ({}.{} detected).'.format(*version))
|
' ({}.{} detected).'.format(*version))
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
|
|
||||||
VERSION = '3.5'
|
VERSION = '3.9'
|
||||||
|
|
||||||
install_requires = ['psutil', 'colorama', 'six', 'decorator']
|
install_requires = ['psutil', 'colorama', 'six', 'decorator']
|
||||||
extras_require = {':python_version<"3.4"': ['pathlib'],
|
extras_require = {':python_version<"3.4"': ['pathlib'],
|
||||||
|
|||||||
30
tests/rules/test_brew_update_formula.py
Normal file
30
tests/rules/test_brew_update_formula.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import pytest
|
||||||
|
from tests.utils import Command
|
||||||
|
from thefuck.rules.brew_update_formula import get_new_command, match
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def stderr():
|
||||||
|
return ("Error: This command updates brew itself, and does not take formula"
|
||||||
|
" names.\nUse 'brew upgrade <formula>'.")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def new_command(formula):
|
||||||
|
return 'brew upgrade {}'.format(formula)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('script', ['brew update foo', 'brew update bar zap'])
|
||||||
|
def test_match(stderr, script):
|
||||||
|
assert match(Command(script=script, stderr=stderr))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('script', ['brew upgrade foo', 'brew update'])
|
||||||
|
def test_not_match(script):
|
||||||
|
assert not match(Command(script=script, stderr=''))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('script, formula, ', [
|
||||||
|
('brew update foo', 'foo'), ('brew update bar zap', 'bar zap')])
|
||||||
|
def test_get_new_command(stderr, new_command, script, formula):
|
||||||
|
assert get_new_command(Command(script=script, stderr=stderr)) == new_command
|
||||||
39
tests/rules/test_chmod_x.py
Normal file
39
tests/rules/test_chmod_x.py
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import pytest
|
||||||
|
from tests.utils import Command
|
||||||
|
from thefuck.rules.chmod_x import match, get_new_command
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def file_exists(mocker):
|
||||||
|
return mocker.patch('os.path.exists', return_value=True)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def file_access(mocker):
|
||||||
|
return mocker.patch('os.access', return_value=False)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures('file_exists', 'file_access')
|
||||||
|
@pytest.mark.parametrize('script, stderr', [
|
||||||
|
('./gradlew build', 'gradlew: Permission denied'),
|
||||||
|
('./install.sh --help', 'install.sh: permission denied')])
|
||||||
|
def test_match(script, stderr):
|
||||||
|
assert match(Command(script, stderr=stderr))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('script, stderr, exists, callable', [
|
||||||
|
('./gradlew build', 'gradlew: Permission denied', True, True),
|
||||||
|
('./gradlew build', 'gradlew: Permission denied', False, False),
|
||||||
|
('./gradlew build', 'gradlew: error', True, False),
|
||||||
|
('gradlew build', 'gradlew: Permission denied', True, False)])
|
||||||
|
def test_not_match(file_exists, file_access, script, stderr, exists, callable):
|
||||||
|
file_exists.return_value = exists
|
||||||
|
file_access.return_value = callable
|
||||||
|
assert not match(Command(script, stderr=stderr))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('script, result', [
|
||||||
|
('./gradlew build', 'chmod +x gradlew && ./gradlew build'),
|
||||||
|
('./install.sh --help', 'chmod +x install.sh && ./install.sh --help')])
|
||||||
|
def test_get_new_command(script, result):
|
||||||
|
assert get_new_command(Command(script)) == result
|
||||||
@@ -4,36 +4,28 @@ from tests.utils import Command
|
|||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def did_not_match(target, did_you_forget=True):
|
def stderr(target):
|
||||||
error = ("error: pathspec '{}' did not match any "
|
return ("error: pathspec '{}' did not match any "
|
||||||
"file(s) known to git.".format(target))
|
'file(s) known to git.'.format(target))
|
||||||
if did_you_forget:
|
|
||||||
error = ("{}\nDid you forget to 'git add'?'".format(error))
|
|
||||||
return error
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('command', [
|
@pytest.mark.parametrize('script, target', [
|
||||||
Command(script='git submodule update unknown',
|
('git submodule update unknown', 'unknown'),
|
||||||
stderr=did_not_match('unknown')),
|
('git commit unknown', 'unknown')])
|
||||||
Command(script='git commit unknown',
|
def test_match(stderr, script, target):
|
||||||
stderr=did_not_match('unknown'))]) # Older versions of Git
|
assert match(Command(script=script, stderr=stderr))
|
||||||
def test_match(command):
|
|
||||||
assert match(command)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('command', [
|
@pytest.mark.parametrize('script', [
|
||||||
Command(script='git submodule update known', stderr=('')),
|
'git submodule update known', 'git commit known'])
|
||||||
Command(script='git commit known', stderr=('')),
|
def test_not_match(script):
|
||||||
Command(script='git commit unknown', # Newer versions of Git
|
assert not match(Command(script=script, stderr=''))
|
||||||
stderr=did_not_match('unknown', False))])
|
|
||||||
def test_not_match(command):
|
|
||||||
assert not match(command)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('command, new_command', [
|
@pytest.mark.parametrize('script, target, new_command', [
|
||||||
(Command('git submodule update unknown', stderr=did_not_match('unknown')),
|
('git submodule update unknown', 'unknown',
|
||||||
'git add -- unknown && git submodule update unknown'),
|
'git add -- unknown && git submodule update unknown'),
|
||||||
(Command('git commit unknown', stderr=did_not_match('unknown')), # Old Git
|
('git commit unknown', 'unknown',
|
||||||
'git add -- unknown && git commit unknown')])
|
'git add -- unknown && git commit unknown')])
|
||||||
def test_get_new_command(command, new_command):
|
def test_get_new_command(stderr, script, target, new_command):
|
||||||
assert get_new_command(command) == new_command
|
assert get_new_command(Command(script=script, stderr=stderr)) == new_command
|
||||||
|
|||||||
33
tests/rules/test_git_branch_exists.py
Normal file
33
tests/rules/test_git_branch_exists.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import pytest
|
||||||
|
from thefuck.rules.git_branch_exists import match, get_new_command
|
||||||
|
from tests.utils import Command
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def stderr(branch_name):
|
||||||
|
return "fatal: A branch named '{}' already exists.".format(branch_name)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def new_command(branch_name):
|
||||||
|
return [cmd.format(branch_name) for cmd in [
|
||||||
|
'git branch -d {0} && git branch {0}',
|
||||||
|
'git branch -D {0} && git branch {0}', 'git checkout {0}']]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('script, branch_name', [
|
||||||
|
('git branch foo', 'foo'),
|
||||||
|
('git branch bar', 'bar')])
|
||||||
|
def test_match(stderr, script, branch_name):
|
||||||
|
assert match(Command(script=script, stderr=stderr))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('script', ['git branch foo', 'git branch bar'])
|
||||||
|
def test_not_match(script):
|
||||||
|
assert not match(Command(script=script, stderr=''))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('script, branch_name, ', [
|
||||||
|
('git branch foo', 'foo'), ('git branch bar', 'bar')])
|
||||||
|
def test_get_new_command(stderr, new_command, script, branch_name):
|
||||||
|
assert get_new_command(Command(script=script, stderr=stderr)) == new_command
|
||||||
27
tests/rules/test_git_rm_recursive.py
Normal file
27
tests/rules/test_git_rm_recursive.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import pytest
|
||||||
|
from thefuck.rules.git_rm_recursive import match, get_new_command
|
||||||
|
from tests.utils import Command
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def stderr(target):
|
||||||
|
return "fatal: not removing '{}' recursively without -r".format(target)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('script, target', [
|
||||||
|
('git rm foo', 'foo'),
|
||||||
|
('git rm foo bar', 'foo bar')])
|
||||||
|
def test_match(stderr, script, target):
|
||||||
|
assert match(Command(script=script, stderr=stderr))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('script', ['git rm foo', 'git rm foo bar'])
|
||||||
|
def test_not_match(script):
|
||||||
|
assert not match(Command(script=script, stderr=''))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('script, target, new_command', [
|
||||||
|
('git rm foo', 'foo', 'git rm -r foo'),
|
||||||
|
('git rm foo bar', 'foo bar', 'git rm -r foo bar')])
|
||||||
|
def test_get_new_command(stderr, script, target, new_command):
|
||||||
|
assert get_new_command(Command(script=script, stderr=stderr)) == new_command
|
||||||
@@ -3,38 +3,23 @@ from thefuck.rules.history import match, get_new_command
|
|||||||
from tests.utils import Command
|
from tests.utils import Command
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture(autouse=True)
|
||||||
def history(mocker):
|
def history_without_current(mocker):
|
||||||
return mocker.patch('thefuck.shells.shell.get_history',
|
return mocker.patch(
|
||||||
return_value=['le cat', 'fuck', 'ls cat',
|
'thefuck.rules.history.get_valid_history_without_current',
|
||||||
'diff x', 'nocommand x'])
|
return_value=['ls cat', 'diff x'])
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def alias(mocker):
|
|
||||||
return mocker.patch('thefuck.rules.history.get_alias',
|
|
||||||
return_value='fuck')
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def callables(mocker):
|
|
||||||
return mocker.patch('thefuck.rules.history.get_all_executables',
|
|
||||||
return_value=['diff', 'ls'])
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures('history', 'callables', 'no_memoize', 'alias')
|
|
||||||
@pytest.mark.parametrize('script', ['ls cet', 'daff x'])
|
@pytest.mark.parametrize('script', ['ls cet', 'daff x'])
|
||||||
def test_match(script):
|
def test_match(script):
|
||||||
assert match(Command(script=script))
|
assert match(Command(script=script))
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures('history', 'callables', 'no_memoize', 'alias')
|
|
||||||
@pytest.mark.parametrize('script', ['apt-get', 'nocommand y'])
|
@pytest.mark.parametrize('script', ['apt-get', 'nocommand y'])
|
||||||
def test_not_match(script):
|
def test_not_match(script):
|
||||||
assert not match(Command(script=script))
|
assert not match(Command(script=script))
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures('history', 'callables', 'no_memoize', 'alias')
|
|
||||||
@pytest.mark.parametrize('script, result', [
|
@pytest.mark.parametrize('script, result', [
|
||||||
('ls cet', 'ls cat'),
|
('ls cet', 'ls cat'),
|
||||||
('daff x', 'diff x')])
|
('daff x', 'diff x')])
|
||||||
|
|||||||
@@ -6,22 +6,37 @@ from tests.utils 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', 'apt-get', 'fsck'])
|
return_value=['vim', 'fsck', 'git', 'go'])
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def history_without_current(mocker):
|
||||||
|
return mocker.patch(
|
||||||
|
'thefuck.rules.no_command.get_valid_history_without_current',
|
||||||
|
return_value=['git commit'])
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures('no_memoize')
|
@pytest.mark.usefixtures('no_memoize')
|
||||||
def test_match():
|
@pytest.mark.parametrize('script, stderr', [
|
||||||
assert match(Command(stderr='vom: not found', script='vom file.py'))
|
('vom file.py', 'vom: not found'),
|
||||||
assert match(Command(stderr='fucck: not found', script='fucck'))
|
('fucck', 'fucck: not found'),
|
||||||
assert not match(Command(stderr='qweqwe: not found', script='qweqwe'))
|
('got commit', 'got: command not found')])
|
||||||
assert not match(Command(stderr='some text', script='vom file.py'))
|
def test_match(script, stderr):
|
||||||
|
assert match(Command(script, stderr=stderr))
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures('no_memoize')
|
@pytest.mark.usefixtures('no_memoize')
|
||||||
def test_get_new_command():
|
@pytest.mark.parametrize('script, stderr', [
|
||||||
assert get_new_command(
|
('qweqwe', 'qweqwe: not found'),
|
||||||
Command(stderr='vom: not found',
|
('vom file.py', 'some text')])
|
||||||
script='vom file.py')) == ['vim file.py']
|
def test_not_match(script, stderr):
|
||||||
assert get_new_command(
|
assert not match(Command(script, stderr=stderr))
|
||||||
Command(stderr='fucck: not found',
|
|
||||||
script='fucck')) == ['fsck']
|
|
||||||
|
@pytest.mark.usefixtures('no_memoize')
|
||||||
|
@pytest.mark.parametrize('script, result', [
|
||||||
|
('vom file.py', ['vim file.py']),
|
||||||
|
('fucck', ['fsck']),
|
||||||
|
('got commit', ['git commit', 'go commit'])])
|
||||||
|
def test_get_new_command(script, result):
|
||||||
|
assert get_new_command(Command(script)) == result
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ def test_match(ssh_error):
|
|||||||
assert not match(Command('ssh'))
|
assert not match(Command('ssh'))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif(os.name == 'nt', reason='Skip if testing on Windows')
|
||||||
def test_side_effect(ssh_error):
|
def test_side_effect(ssh_error):
|
||||||
errormsg, path, reset, known_hosts = ssh_error
|
errormsg, path, reset, known_hosts = ssh_error
|
||||||
command = Command('ssh user@host', stderr=errormsg)
|
command = Command('ssh user@host', stderr=errormsg)
|
||||||
|
|||||||
@@ -19,14 +19,18 @@ class TestFish(object):
|
|||||||
return mock
|
return mock
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def environ(self, monkeypatch):
|
def os_environ(self, monkeypatch, key, value):
|
||||||
data = {'TF_OVERRIDDEN_ALIASES': 'cd, ls, man, open'}
|
monkeypatch.setattr('os.environ', {key: value})
|
||||||
monkeypatch.setattr('thefuck.shells.fish.os.environ', data)
|
|
||||||
return data
|
|
||||||
|
|
||||||
@pytest.mark.usefixture('environ')
|
@pytest.mark.parametrize('key, value', [
|
||||||
def test_get_overridden_aliases(self, shell, environ):
|
('TF_OVERRIDDEN_ALIASES', 'cut,git,sed'), # legacy
|
||||||
assert shell._get_overridden_aliases() == ['cd', 'ls', 'man', 'open']
|
('THEFUCK_OVERRIDDEN_ALIASES', 'cut,git,sed'),
|
||||||
|
('THEFUCK_OVERRIDDEN_ALIASES', 'cut, git, sed'),
|
||||||
|
('THEFUCK_OVERRIDDEN_ALIASES', ' cut,\tgit,sed\n'),
|
||||||
|
('THEFUCK_OVERRIDDEN_ALIASES', '\ncut,\n\ngit,\tsed\r')])
|
||||||
|
def test_get_overridden_aliases(self, shell, os_environ):
|
||||||
|
assert shell._get_overridden_aliases() == {'cd', 'cut', 'git', 'grep',
|
||||||
|
'ls', 'man', 'open', 'sed'}
|
||||||
|
|
||||||
@pytest.mark.parametrize('before, after', [
|
@pytest.mark.parametrize('before, after', [
|
||||||
('cd', 'cd'),
|
('cd', 'cd'),
|
||||||
@@ -74,3 +78,12 @@ class TestFish(object):
|
|||||||
history_lines(['- cmd: ls', ' when: 1432613911',
|
history_lines(['- cmd: ls', ' when: 1432613911',
|
||||||
'- cmd: rm', ' when: 1432613916'])
|
'- cmd: rm', ' when: 1432613916'])
|
||||||
assert list(shell.get_history()) == ['ls', 'rm']
|
assert list(shell.get_history()) == ['ls', 'rm']
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('entry, entry_utf8', [
|
||||||
|
('ls', '- cmd: ls\n when: 1430707243\n'),
|
||||||
|
(u'echo café', '- cmd: echo café\n when: 1430707243\n')])
|
||||||
|
def test_put_to_history(self, entry, entry_utf8, builtins_open, mocker, shell):
|
||||||
|
mocker.patch('thefuck.shells.fish.time', return_value=1430707243.3517463)
|
||||||
|
shell.put_to_history(entry)
|
||||||
|
builtins_open.return_value.__enter__.return_value. \
|
||||||
|
write.assert_called_once_with(entry_utf8)
|
||||||
|
|||||||
19
tests/shells/test_powershell.py
Normal file
19
tests/shells/test_powershell.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from thefuck.shells import Powershell
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures('isfile', 'no_memoize', 'no_cache')
|
||||||
|
class TestPowershell(object):
|
||||||
|
@pytest.fixture
|
||||||
|
def shell(self):
|
||||||
|
return Powershell()
|
||||||
|
|
||||||
|
def test_and_(self, shell):
|
||||||
|
assert shell.and_('ls', 'cd') == '(ls) -and (cd)'
|
||||||
|
|
||||||
|
def test_app_alias(self, shell):
|
||||||
|
assert 'function fuck' in shell.app_alias('fuck')
|
||||||
|
assert 'function FUCK' in shell.app_alias('FUCK')
|
||||||
|
assert 'thefuck' in shell.app_alias('fuck')
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from pathlib import PosixPath
|
from pathlib import Path
|
||||||
from thefuck import corrector, const
|
from thefuck import corrector, const
|
||||||
from tests.utils import Rule, Command, CorrectedCommand
|
from tests.utils import Rule, Command, CorrectedCommand
|
||||||
from thefuck.corrector import get_corrected_commands, organize_commands
|
from thefuck.corrector import get_corrected_commands, organize_commands
|
||||||
@@ -30,7 +30,7 @@ class TestGetRules(object):
|
|||||||
(['git.py', 'bash.py'], ['git'], ['git'], [])])
|
(['git.py', 'bash.py'], ['git'], ['git'], [])])
|
||||||
def test_get_rules(self, glob, settings, paths, conf_rules, exclude_rules,
|
def test_get_rules(self, glob, settings, paths, conf_rules, exclude_rules,
|
||||||
loaded_rules):
|
loaded_rules):
|
||||||
glob([PosixPath(path) for path in paths])
|
glob([Path(path) for path in paths])
|
||||||
settings.update(rules=conf_rules,
|
settings.update(rules=conf_rules,
|
||||||
priority={},
|
priority={},
|
||||||
exclude_rules=exclude_rules)
|
exclude_rules=exclude_rules)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import os
|
||||||
from subprocess import PIPE
|
from subprocess import PIPE
|
||||||
from mock import Mock
|
from mock import Mock
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -39,9 +40,10 @@ class TestRule(object):
|
|||||||
enabled_by_default=True,
|
enabled_by_default=True,
|
||||||
priority=900,
|
priority=900,
|
||||||
requires_output=True))
|
requires_output=True))
|
||||||
assert Rule.from_path(Path('/rules/bash.py')) \
|
rule_path = os.path.join(os.sep, 'rules', 'bash.py')
|
||||||
|
assert Rule.from_path(Path(rule_path)) \
|
||||||
== Rule('bash', match, get_new_command, priority=900)
|
== Rule('bash', match, get_new_command, priority=900)
|
||||||
load_source.assert_called_once_with('bash', '/rules/bash.py')
|
load_source.assert_called_once_with('bash', rule_path)
|
||||||
|
|
||||||
@pytest.mark.parametrize('rules, exclude_rules, rule, is_enabled', [
|
@pytest.mark.parametrize('rules, exclude_rules, rule, is_enabled', [
|
||||||
(const.DEFAULT_RULES, [], Rule('git', enabled_by_default=True), True),
|
(const.DEFAULT_RULES, [], Rule('git', enabled_by_default=True), True),
|
||||||
@@ -110,6 +112,7 @@ class TestCommand(object):
|
|||||||
'apt-get search vim', 'stdout', 'stderr')
|
'apt-get search vim', 'stdout', 'stderr')
|
||||||
Popen.assert_called_once_with('apt-get search vim',
|
Popen.assert_called_once_with('apt-get search vim',
|
||||||
shell=True,
|
shell=True,
|
||||||
|
stdin=PIPE,
|
||||||
stdout=PIPE,
|
stdout=PIPE,
|
||||||
stderr=PIPE,
|
stderr=PIPE,
|
||||||
env={})
|
env={})
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ from mock import Mock
|
|||||||
import six
|
import six
|
||||||
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, compatibility_call
|
get_all_matched_commands, is_app, for_app, cache, compatibility_call, \
|
||||||
|
get_valid_history_without_current
|
||||||
from tests.utils import Command
|
from tests.utils import Command
|
||||||
|
|
||||||
|
|
||||||
@@ -229,3 +230,29 @@ class TestCompatibilityCall(object):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
assert compatibility_call(side_effect, Command(), Command())
|
assert compatibility_call(side_effect, Command(), Command())
|
||||||
|
|
||||||
|
|
||||||
|
class TestGetValidHistoryWithoutCurrent(object):
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def history(self, mocker):
|
||||||
|
return mocker.patch('thefuck.shells.shell.get_history',
|
||||||
|
return_value=['le cat', 'fuck', 'ls cat',
|
||||||
|
'diff x', 'nocommand x'])
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def alias(self, mocker):
|
||||||
|
return mocker.patch('thefuck.utils.get_alias',
|
||||||
|
return_value='fuck')
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def callables(self, mocker):
|
||||||
|
return mocker.patch('thefuck.utils.get_all_executables',
|
||||||
|
return_value=['diff', 'ls'])
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('script, result', [
|
||||||
|
('le cat', ['ls cat', 'diff x']),
|
||||||
|
('diff x', ['ls cat']),
|
||||||
|
('fuck', ['ls cat', 'diff x'])])
|
||||||
|
def test_get_valid_history_without_current(self, script, result):
|
||||||
|
command = Command(script=script)
|
||||||
|
assert get_valid_history_without_current(command) == result
|
||||||
|
|||||||
12
thefuck/rules/brew_update_formula.py
Normal file
12
thefuck/rules/brew_update_formula.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
from thefuck.utils import for_app
|
||||||
|
|
||||||
|
|
||||||
|
@for_app('brew', at_least=2)
|
||||||
|
def match(command):
|
||||||
|
return ('update' in command.script
|
||||||
|
and "Error: This command updates brew itself" in command.stderr
|
||||||
|
and "Use 'brew upgrade <formula>'" in command.stderr)
|
||||||
|
|
||||||
|
|
||||||
|
def get_new_command(command):
|
||||||
|
return command.script.replace('update', 'upgrade')
|
||||||
15
thefuck/rules/chmod_x.py
Normal file
15
thefuck/rules/chmod_x.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import os
|
||||||
|
from thefuck.shells import shell
|
||||||
|
|
||||||
|
|
||||||
|
def match(command):
|
||||||
|
return (command.script.startswith('./')
|
||||||
|
and 'permission denied' in command.stderr.lower()
|
||||||
|
and os.path.exists(command.script_parts[0])
|
||||||
|
and not os.access(command.script_parts[0], os.X_OK))
|
||||||
|
|
||||||
|
|
||||||
|
def get_new_command(command):
|
||||||
|
return shell.and_(
|
||||||
|
'chmod +x {}'.format(command.script_parts[0][2:]),
|
||||||
|
command.script)
|
||||||
@@ -5,15 +5,14 @@ from thefuck.specific.git import git_support
|
|||||||
|
|
||||||
@git_support
|
@git_support
|
||||||
def match(command):
|
def match(command):
|
||||||
return ('did not match any file(s) known to git.' in command.stderr
|
return 'did not match any file(s) known to git.' in command.stderr
|
||||||
and "Did you forget to 'git add'?" in command.stderr)
|
|
||||||
|
|
||||||
|
|
||||||
@git_support
|
@git_support
|
||||||
def get_new_command(command):
|
def get_new_command(command):
|
||||||
missing_file = re.findall(
|
missing_file = re.findall(
|
||||||
r"error: pathspec '([^']*)' "
|
r"error: pathspec '([^']*)' "
|
||||||
r"did not match any file\(s\) known to git.", command.stderr)[0]
|
r'did not match any file\(s\) known to git.', command.stderr)[0]
|
||||||
|
|
||||||
formatme = shell.and_('git add -- {}', '{}')
|
formatme = shell.and_('git add -- {}', '{}')
|
||||||
return formatme.format(missing_file, command.script)
|
return formatme.format(missing_file, command.script)
|
||||||
|
|||||||
23
thefuck/rules/git_branch_exists.py
Normal file
23
thefuck/rules/git_branch_exists.py
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import re
|
||||||
|
from thefuck.shells import shell
|
||||||
|
from thefuck.specific.git import git_support
|
||||||
|
from thefuck.utils import eager
|
||||||
|
|
||||||
|
|
||||||
|
@git_support
|
||||||
|
def match(command):
|
||||||
|
return ('branch' in command.script
|
||||||
|
and "fatal: A branch named '" in command.stderr
|
||||||
|
and " already exists." in command.stderr)
|
||||||
|
|
||||||
|
|
||||||
|
@git_support
|
||||||
|
@eager
|
||||||
|
def get_new_command(command):
|
||||||
|
branch_name = re.findall(
|
||||||
|
r"fatal: A branch named '([^']*)' already exists.", command.stderr)[0]
|
||||||
|
new_command_templates = [['git branch -d {0}', 'git branch {0}'],
|
||||||
|
['git branch -D {0}', 'git branch {0}'],
|
||||||
|
['git checkout {0}']]
|
||||||
|
for new_command_template in new_command_templates:
|
||||||
|
yield shell.and_(*new_command_template).format(branch_name)
|
||||||
15
thefuck/rules/git_rm_recursive.py
Normal file
15
thefuck/rules/git_rm_recursive.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
from thefuck.specific.git import git_support
|
||||||
|
|
||||||
|
|
||||||
|
@git_support
|
||||||
|
def match(command):
|
||||||
|
return (' rm ' in command.script
|
||||||
|
and "fatal: not removing '" in command.stderr
|
||||||
|
and "' recursively without -r" in command.stderr)
|
||||||
|
|
||||||
|
|
||||||
|
@git_support
|
||||||
|
def get_new_command(command):
|
||||||
|
index = command.script_parts.index('rm') + 1
|
||||||
|
command.script_parts.insert(index, '-r')
|
||||||
|
return u' '.join(command.script_parts)
|
||||||
@@ -1,37 +1,15 @@
|
|||||||
from difflib import get_close_matches
|
from difflib import get_close_matches
|
||||||
from thefuck.shells import shell
|
from thefuck.utils import get_closest, get_valid_history_without_current
|
||||||
from thefuck.utils import get_closest, memoize, get_all_executables, get_alias
|
|
||||||
|
|
||||||
|
|
||||||
def _not_corrected(history, tf_alias):
|
|
||||||
"""Returns all lines from history except that comes before `fuck`."""
|
|
||||||
previous = None
|
|
||||||
for line in history:
|
|
||||||
if previous is not None and line != tf_alias:
|
|
||||||
yield previous
|
|
||||||
previous = line
|
|
||||||
if history:
|
|
||||||
yield history[-1]
|
|
||||||
|
|
||||||
|
|
||||||
@memoize
|
|
||||||
def _history_of_exists_without_current(command):
|
|
||||||
history = shell.get_history()
|
|
||||||
tf_alias = get_alias()
|
|
||||||
executables = get_all_executables()
|
|
||||||
return [line for line in _not_corrected(history, tf_alias)
|
|
||||||
if not line.startswith(tf_alias) and not line == command.script
|
|
||||||
and line.split(' ')[0] in executables]
|
|
||||||
|
|
||||||
|
|
||||||
def match(command):
|
def match(command):
|
||||||
return len(get_close_matches(command.script,
|
return len(get_close_matches(command.script,
|
||||||
_history_of_exists_without_current(command)))
|
get_valid_history_without_current(command)))
|
||||||
|
|
||||||
|
|
||||||
def get_new_command(command):
|
def get_new_command(command):
|
||||||
return get_closest(command.script,
|
return get_closest(command.script,
|
||||||
_history_of_exists_without_current(command))
|
get_valid_history_without_current(command))
|
||||||
|
|
||||||
|
|
||||||
priority = 9999
|
priority = 9999
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from difflib import get_close_matches
|
from difflib import get_close_matches
|
||||||
from thefuck.utils import get_all_executables
|
from thefuck.utils import get_all_executables, \
|
||||||
|
get_valid_history_without_current, get_closest
|
||||||
from thefuck.specific.sudo import sudo_support
|
from thefuck.specific.sudo import sudo_support
|
||||||
|
|
||||||
|
|
||||||
@@ -11,10 +12,29 @@ def match(command):
|
|||||||
get_all_executables())))
|
get_all_executables())))
|
||||||
|
|
||||||
|
|
||||||
|
def _get_used_executables(command):
|
||||||
|
for script in get_valid_history_without_current(command):
|
||||||
|
yield script.split(' ')[0]
|
||||||
|
|
||||||
|
|
||||||
@sudo_support
|
@sudo_support
|
||||||
def get_new_command(command):
|
def get_new_command(command):
|
||||||
old_command = command.script_parts[0]
|
old_command = command.script_parts[0]
|
||||||
new_cmds = get_close_matches(old_command, get_all_executables(), cutoff=0.1)
|
|
||||||
|
# One from history:
|
||||||
|
already_used = get_closest(
|
||||||
|
old_command, _get_used_executables(command),
|
||||||
|
fallback_to_first=False)
|
||||||
|
if already_used:
|
||||||
|
new_cmds = [already_used]
|
||||||
|
else:
|
||||||
|
new_cmds = []
|
||||||
|
|
||||||
|
# Other from all executables:
|
||||||
|
new_cmds += [cmd for cmd in get_close_matches(old_command,
|
||||||
|
get_all_executables())
|
||||||
|
if cmd not in new_cmds]
|
||||||
|
|
||||||
return [' '.join([new_command] + command.script_parts[1:])
|
return [' '.join([new_command] + command.script_parts[1:])
|
||||||
for new_command in new_cmds]
|
for new_command in new_cmds]
|
||||||
|
|
||||||
|
|||||||
@@ -9,20 +9,36 @@ from .fish import Fish
|
|||||||
from .generic import Generic
|
from .generic import Generic
|
||||||
from .tcsh import Tcsh
|
from .tcsh import Tcsh
|
||||||
from .zsh import Zsh
|
from .zsh import Zsh
|
||||||
|
from .powershell import Powershell
|
||||||
|
|
||||||
shells = {'bash': Bash,
|
shells = {'bash': Bash,
|
||||||
'fish': Fish,
|
'fish': Fish,
|
||||||
'zsh': Zsh,
|
'zsh': Zsh,
|
||||||
'csh': Tcsh,
|
'csh': Tcsh,
|
||||||
'tcsh': Tcsh}
|
'tcsh': Tcsh,
|
||||||
|
'powershell': Powershell}
|
||||||
|
|
||||||
|
|
||||||
def _get_shell():
|
def _get_shell():
|
||||||
try:
|
proc = Process(os.getpid())
|
||||||
shell_name = Process(os.getpid()).parent().name()
|
|
||||||
except TypeError:
|
while proc is not None:
|
||||||
shell_name = Process(os.getpid()).parent.name
|
try:
|
||||||
return shells.get(shell_name, Generic)()
|
name = proc.name()
|
||||||
|
except TypeError:
|
||||||
|
name = proc.name
|
||||||
|
|
||||||
|
name = os.path.splitext(name)[0]
|
||||||
|
|
||||||
|
if name in shells:
|
||||||
|
return shells[name]()
|
||||||
|
|
||||||
|
try:
|
||||||
|
proc = proc.parent()
|
||||||
|
except TypeError:
|
||||||
|
proc = proc.parent
|
||||||
|
|
||||||
|
return Generic()
|
||||||
|
|
||||||
|
|
||||||
shell = _get_shell()
|
shell = _get_shell()
|
||||||
|
|||||||
@@ -1,17 +1,21 @@
|
|||||||
from subprocess import Popen, PIPE
|
from subprocess import Popen, PIPE
|
||||||
from time import time
|
from time import time
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
|
import six
|
||||||
|
from .. import logs
|
||||||
from ..utils import DEVNULL, memoize, cache
|
from ..utils import DEVNULL, memoize, cache
|
||||||
from .generic import Generic
|
from .generic import Generic
|
||||||
|
|
||||||
|
|
||||||
class Fish(Generic):
|
class Fish(Generic):
|
||||||
def _get_overridden_aliases(self):
|
def _get_overridden_aliases(self):
|
||||||
overridden_aliases = os.environ.get('TF_OVERRIDDEN_ALIASES', '').strip()
|
overridden = os.environ.get('THEFUCK_OVERRIDDEN_ALIASES',
|
||||||
if overridden_aliases:
|
os.environ.get('TF_OVERRIDDEN_ALIASES', ''))
|
||||||
return [alias.strip() for alias in overridden_aliases.split(',')]
|
default = {'cd', 'grep', 'ls', 'man', 'open'}
|
||||||
else:
|
for alias in overridden.split(','):
|
||||||
return ['cd', 'grep', 'ls', 'man', 'open']
|
default.add(alias.strip())
|
||||||
|
return default
|
||||||
|
|
||||||
def app_alias(self, fuck):
|
def app_alias(self, fuck):
|
||||||
# It is VERY important to have the variables declared WITHIN the alias
|
# It is VERY important to have the variables declared WITHIN the alias
|
||||||
@@ -64,3 +68,20 @@ class Fish(Generic):
|
|||||||
def how_to_configure(self):
|
def how_to_configure(self):
|
||||||
return (r"eval (thefuck --alias | tr '\n' ';')",
|
return (r"eval (thefuck --alias | tr '\n' ';')",
|
||||||
'~/.config/fish/config.fish')
|
'~/.config/fish/config.fish')
|
||||||
|
|
||||||
|
def put_to_history(self, command):
|
||||||
|
try:
|
||||||
|
return self._put_to_history(command)
|
||||||
|
except IOError:
|
||||||
|
logs.exception("Can't update history", sys.exc_info())
|
||||||
|
|
||||||
|
def _put_to_history(self, command_script):
|
||||||
|
"""Puts command script to shell history."""
|
||||||
|
history_file_name = self._get_history_file_name()
|
||||||
|
if os.path.isfile(history_file_name):
|
||||||
|
with open(history_file_name, 'a') as history:
|
||||||
|
entry = self._get_history_line(command_script)
|
||||||
|
if six.PY2:
|
||||||
|
history.write(entry.encode('utf-8'))
|
||||||
|
else:
|
||||||
|
history.write(entry)
|
||||||
|
|||||||
@@ -81,3 +81,11 @@ class Generic(object):
|
|||||||
|
|
||||||
def _script_from_history(self, line):
|
def _script_from_history(self, line):
|
||||||
return line
|
return line
|
||||||
|
|
||||||
|
def put_to_history(self, command):
|
||||||
|
"""Adds fixed command to shell history.
|
||||||
|
|
||||||
|
In most of shells we change history on shell-level, but not
|
||||||
|
all shells support it (Fish).
|
||||||
|
|
||||||
|
"""
|
||||||
|
|||||||
18
thefuck/shells/powershell.py
Normal file
18
thefuck/shells/powershell.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
from .generic import Generic
|
||||||
|
|
||||||
|
|
||||||
|
class Powershell(Generic):
|
||||||
|
def app_alias(self, fuck):
|
||||||
|
return 'function ' + fuck + ' { \n' \
|
||||||
|
' $fuck = $(thefuck (Get-History -Count 1).CommandLine)\n' \
|
||||||
|
' if (-not [string]::IsNullOrWhiteSpace($fuck)) {\n' \
|
||||||
|
' if ($fuck.StartsWith("echo")) { $fuck = $fuck.Substring(5) }\n' \
|
||||||
|
' else { iex "$fuck" }\n' \
|
||||||
|
' }\n' \
|
||||||
|
'}\n'
|
||||||
|
|
||||||
|
def and_(self, *commands):
|
||||||
|
return u' -and '.join('({0})'.format(c) for c in commands)
|
||||||
|
|
||||||
|
def how_to_configure(self):
|
||||||
|
return 'iex "thefuck --alias"', '$profile'
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import os
|
||||||
import sys
|
import sys
|
||||||
import msvcrt
|
import msvcrt
|
||||||
import win_unicode_console
|
import win_unicode_console
|
||||||
@@ -16,10 +17,11 @@ def get_key():
|
|||||||
ch = msvcrt.getch() # second call returns the actual key code
|
ch = msvcrt.getch() # second call returns the actual key code
|
||||||
|
|
||||||
if ch == b'\x03':
|
if ch == b'\x03':
|
||||||
raise const.KEY_CTRL_C
|
return const.KEY_CTRL_C
|
||||||
if ch == b'H':
|
if ch == b'H':
|
||||||
return const.KEY_UP
|
return const.KEY_UP
|
||||||
if ch == b'P':
|
if ch == b'P':
|
||||||
return const.KEY_DOWN
|
return const.KEY_DOWN
|
||||||
|
|
||||||
return ch.decode(sys.stdout.encoding)
|
encoding = sys.stdout.encoding or os.environ.get('PYTHONIOENCODING', 'utf-8')
|
||||||
|
return ch.decode(encoding)
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ class Command(object):
|
|||||||
env.update(settings.env)
|
env.update(settings.env)
|
||||||
|
|
||||||
with logs.debug_time(u'Call: {}; with env: {};'.format(script, env)):
|
with logs.debug_time(u'Call: {}; with env: {};'.format(script, env)):
|
||||||
result = Popen(script, shell=True, stdout=PIPE, stderr=PIPE, env=env)
|
result = Popen(script, shell=True, stdin=PIPE, stdout=PIPE, stderr=PIPE, env=env)
|
||||||
if cls._wait_output(result):
|
if cls._wait_output(result):
|
||||||
stdout = result.stdout.read().decode('utf-8')
|
stdout = result.stdout.read().decode('utf-8')
|
||||||
stderr = result.stderr.read().decode('utf-8')
|
stderr = result.stderr.read().decode('utf-8')
|
||||||
@@ -280,6 +280,8 @@ class CorrectedCommand(object):
|
|||||||
"""
|
"""
|
||||||
if self.side_effect:
|
if self.side_effect:
|
||||||
compatibility_call(self.side_effect, old_cmd, self.script)
|
compatibility_call(self.side_effect, old_cmd, self.script)
|
||||||
|
if settings.alter_history:
|
||||||
|
shell.put_to_history(self.script)
|
||||||
# This depends on correct setting of PYTHONIOENCODING by the alias:
|
# This depends on correct setting of PYTHONIOENCODING by the alias:
|
||||||
logs.debug(u'PYTHONIOENCODING: {}'.format(
|
logs.debug(u'PYTHONIOENCODING: {}'.format(
|
||||||
os.environ.get('PYTHONIOENCODING', '!!not-set!!')))
|
os.environ.get('PYTHONIOENCODING', '!!not-set!!')))
|
||||||
|
|||||||
@@ -269,3 +269,24 @@ def get_installation_info():
|
|||||||
|
|
||||||
def get_alias():
|
def get_alias():
|
||||||
return os.environ.get('TF_ALIAS', 'fuck')
|
return os.environ.get('TF_ALIAS', 'fuck')
|
||||||
|
|
||||||
|
|
||||||
|
@memoize
|
||||||
|
def get_valid_history_without_current(command):
|
||||||
|
def _not_corrected(history, tf_alias):
|
||||||
|
"""Returns all lines from history except that comes before `fuck`."""
|
||||||
|
previous = None
|
||||||
|
for line in history:
|
||||||
|
if previous is not None and line != tf_alias:
|
||||||
|
yield previous
|
||||||
|
previous = line
|
||||||
|
if history:
|
||||||
|
yield history[-1]
|
||||||
|
|
||||||
|
from thefuck.shells import shell
|
||||||
|
history = shell.get_history()
|
||||||
|
tf_alias = get_alias()
|
||||||
|
executables = get_all_executables()
|
||||||
|
return [line for line in _not_corrected(history, tf_alias)
|
||||||
|
if not line.startswith(tf_alias) and not line == command.script
|
||||||
|
and line.split(' ')[0] in executables]
|
||||||
|
|||||||
Reference in New Issue
Block a user