mirror of
https://github.com/nvbn/thefuck.git
synced 2025-11-01 23:51:59 +00:00
Compare commits
107 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 | ||
|
|
8b1f078e27 | ||
|
|
a47e84fa6b | ||
|
|
bcab700215 | ||
|
|
4fb99fd7a8 | ||
|
|
bb5f6bb705 | ||
|
|
d92765d5df | ||
|
|
d8de5cfd20 | ||
|
|
d73b14ce4b | ||
|
|
04b83cf7e8 | ||
|
|
019f0abf67 | ||
|
|
c8d3dcd1bf | ||
|
|
4f5b382df4 | ||
|
|
77ad68b04b | ||
|
|
6b0311181d | ||
|
|
48808f93ac | ||
|
|
7ce4307c87 | ||
|
|
f7f0660114 | ||
|
|
46f2351907 | ||
|
|
e9de01fa41 | ||
|
|
9b260eb239 | ||
|
|
f20d4dbf85 | ||
|
|
4af7dc2748 | ||
|
|
c8e9606c7d | ||
|
|
d71b9c2e62 | ||
|
|
60497b9d04 | ||
|
|
619af2638a | ||
|
|
a1f15bfe5f | ||
|
|
ce959b2a8b | ||
|
|
3cc4940842 | ||
|
|
470c0ef699 | ||
|
|
4f95b3365a | ||
|
|
bbfd53d718 | ||
|
|
28d078708b | ||
|
|
778d5f3e6e | ||
|
|
c5acee54ea | ||
|
|
c3e9c1bfc1 | ||
|
|
6c25b33b9e | ||
|
|
052f415d94 | ||
|
|
3438d6dde7 | ||
|
|
af0fe66a9f | ||
|
|
fe07fcaa62 | ||
|
|
b5dc7aab6d | ||
|
|
a2ec5aa3ff | ||
|
|
0b2bda9e85 | ||
|
|
68c882cf1a | ||
|
|
abe8fb84c6 | ||
|
|
86b17eb570 | ||
|
|
dcc0ce7ff3 | ||
|
|
fee874cddc | ||
|
|
2ce1c6bf90 | ||
|
|
9b129fad08 | ||
|
|
20adefbe7d | ||
|
|
52ef1fa47d | ||
|
|
94f8652175 | ||
|
|
abe287a52b | ||
|
|
60e19a054a | ||
|
|
45add1e3d6 | ||
|
|
934870f650 | ||
|
|
738cc0c9a8 | ||
|
|
cb6f964a30 | ||
|
|
9c9a7343de | ||
|
|
3803452980 | ||
|
|
a19833d0c7 | ||
|
|
084b907ac0 |
@@ -17,8 +17,9 @@ addons:
|
||||
- pandoc
|
||||
- git
|
||||
install:
|
||||
- pip install coveralls
|
||||
- pip install -r requirements.txt
|
||||
- pip install -U pip
|
||||
- pip install -U coveralls
|
||||
- pip install -Ur requirements.txt
|
||||
- python setup.py develop
|
||||
- rm -rf build
|
||||
script:
|
||||
|
||||
37
README.md
37
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,
|
||||
inspired by a [@liamosaur](https://twitter.com/liamosaur/)
|
||||
[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:
|
||||
|
||||
@@ -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'.
|
||||
|
||||
Did you mean this?
|
||||
branch
|
||||
branch
|
||||
|
||||
➜ fuck
|
||||
git branch [enter/↑/↓/ctrl+c]
|
||||
@@ -97,7 +97,7 @@ Reading package lists... Done
|
||||
## Installation [*experimental*]
|
||||
|
||||
On Ubuntu and OS X you can install `The Fuck` with installation script:
|
||||
|
||||
|
||||
```bash
|
||||
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`:
|
||||
|
||||
```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)
|
||||
@@ -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_mkdir` – creates directories before cd'ing into them;
|
||||
* `cd_parent` – changes `cd..` to `cd ..`;
|
||||
* `chmod_x` – add execution bit;
|
||||
* `composer_not_command` – fixes composer command name;
|
||||
* `cp_omitting_directory` – adds `-a` when you `cp` directory;
|
||||
* `cpp11` – adds missing `-std=c++11` to `g++` or `clang++`;
|
||||
@@ -155,20 +156,25 @@ using the matched rule and runs it. Rules enabled by default are as follows:
|
||||
* `dry` – fixes repetitions like `git git push`;
|
||||
* `fix_alt_space` – replaces Alt+Space with Space character;
|
||||
* `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_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_checkout` – fixes branch name or creates new branch;
|
||||
* `git_diff_staged` – adds `--staged` to previous `git diff` with unexpected output;
|
||||
* `git_fix_stash` – fixes `git stash` commands (misspelled subcommand and missing `save`);
|
||||
* `git_help_aliased` – fixes `git help <alias>` commands replacing <alias> with the aliased command;
|
||||
* `git_not_command` – fixes wrong git commands like `git brnch`;
|
||||
* `git_pull` – sets upstream before executing previous `git pull`;
|
||||
* `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_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_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`;
|
||||
* `go_run` – appends `.go` extension when compiling/running Go programs
|
||||
* `go_run` – appends `.go` extension when compiling/running Go programs;
|
||||
* `grep_arguments_order` – fixes grep arguments order for situations like `grep -lir . test`;
|
||||
* `grep_recursive` – adds `-r` when you trying to `grep` directory;
|
||||
* `gulp_not_task` – fixes misspelled `gulp` tasks;
|
||||
* `has_exists_script` – prepends `./` when script/binary exists;
|
||||
@@ -177,6 +183,7 @@ using the matched rule and runs it. Rules enabled by default are as follows:
|
||||
* `java` – removes `.java` extension when running Java programs;
|
||||
* `javac` – appends missing `.java` when compiling Java files;
|
||||
* `lein_not_task` – fixes wrong `lein` tasks like `lein rpl`;
|
||||
* `ln_no_hard_link` – catches hard link creation on directories, suggest symbolic link;
|
||||
* `ls_lah` – adds `-lah` to `ls`;
|
||||
* `man` – changes manual section;
|
||||
* `man_no_space` – fixes man commands without spaces, for example `mandiff`;
|
||||
@@ -184,6 +191,7 @@ using the matched rule and runs it. Rules enabled by default are as follows:
|
||||
* `mkdir_p` – adds `-p` when you trying to create directory without parent;
|
||||
* `mvn_no_command` – adds `clean package` to `mvn`;
|
||||
* `mvn_unknown_lifecycle_phase` – fixes misspelled lifecycle phases with `mvn`;
|
||||
* `npm_wrong_command` – fixes wrong npm commands like `npm urgrade`;
|
||||
* `no_command` – fixes wrong console commands, for example `vom/vim`;
|
||||
* `no_such_file` – creates missing directories with `mv` and `cp` commands;
|
||||
* `open` – prepends `http` to address passed to `open`;
|
||||
@@ -214,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`;
|
||||
* `brew_install` – fixes formula name for `brew install`;
|
||||
* `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;
|
||||
* `pacman` – installs app with `pacman` if it is not installed (uses `yaourt` if available);
|
||||
* `pacman_not_found` – fixes package name with `pacman` or `yaourt`.
|
||||
@@ -300,7 +309,7 @@ debug = False
|
||||
Or via environment variables:
|
||||
|
||||
* `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_WAIT_COMMAND` – max amount of time in seconds for getting previous command output;
|
||||
* `THEFUCK_NO_COLORS` – disable colored output, `true/false`;
|
||||
@@ -352,3 +361,15 @@ sudo apt-get install pandoc
|
||||
|
||||
## License MIT
|
||||
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"
|
||||
@@ -15,7 +15,7 @@ install_thefuck () {
|
||||
sudo apt-get update -yy
|
||||
sudo apt-get install -yy python-pip python-dev command-not-found python-gdbm
|
||||
|
||||
if [[ -n $(apt-cache search python-commandnotfound) ]]; then
|
||||
if [ -n "$(apt-cache search python-commandnotfound)" ]; then
|
||||
# In case of different python versions:
|
||||
sudo apt-get install -yy python-commandnotfound
|
||||
fi
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
pip
|
||||
pytest
|
||||
mock
|
||||
pytest-mock
|
||||
|
||||
10
setup.py
10
setup.py
@@ -1,8 +1,14 @@
|
||||
#!/usr/bin/env python
|
||||
from setuptools import setup, find_packages
|
||||
import pip
|
||||
import sys
|
||||
import os
|
||||
|
||||
if int(pip.__version__.split('.')[0]) < 6:
|
||||
print('pip older than 6.0 not supported, please upgrade pip with:\n\n'
|
||||
' pip install -U pip')
|
||||
sys.exit(-1)
|
||||
|
||||
if os.environ.get('CONVERT_README'):
|
||||
import pypandoc
|
||||
|
||||
@@ -20,7 +26,7 @@ elif (3, 0) < version < (3, 3):
|
||||
' ({}.{} detected).'.format(*version))
|
||||
sys.exit(-1)
|
||||
|
||||
VERSION = '3.3'
|
||||
VERSION = '3.9'
|
||||
|
||||
install_requires = ['psutil', 'colorama', 'six', 'decorator']
|
||||
extras_require = {':python_version<"3.4"': ['pathlib'],
|
||||
@@ -35,7 +41,7 @@ setup(name='thefuck',
|
||||
url='https://github.com/nvbn/thefuck',
|
||||
license='MIT',
|
||||
packages=find_packages(exclude=['ez_setup', 'examples',
|
||||
'tests', 'release']),
|
||||
'tests', 'tests.*', 'release']),
|
||||
include_package_data=True,
|
||||
zip_safe=False,
|
||||
install_requires=install_requires,
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
from pathlib import Path
|
||||
import pytest
|
||||
from thefuck import conf
|
||||
from thefuck import shells
|
||||
from thefuck import conf, const
|
||||
|
||||
shells.shell = shells.Generic()
|
||||
|
||||
|
||||
def pytest_addoption(parser):
|
||||
@@ -19,7 +22,7 @@ def no_memoize(monkeypatch):
|
||||
def settings(request):
|
||||
def _reset_settings():
|
||||
conf.settings.clear()
|
||||
conf.settings.update(conf.DEFAULT_SETTINGS)
|
||||
conf.settings.update(const.DEFAULT_SETTINGS)
|
||||
|
||||
request.addfinalizer(_reset_settings)
|
||||
conf.settings.user_dir = Path('~/.thefuck')
|
||||
@@ -46,3 +49,14 @@ def functional(request):
|
||||
@pytest.fixture
|
||||
def source_root():
|
||||
return Path(__file__).parent.parent.resolve()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def set_shell(monkeypatch, request):
|
||||
def _set(cls):
|
||||
shell = cls()
|
||||
monkeypatch.setattr('thefuck.shells.shell', shell)
|
||||
request.addfinalizer()
|
||||
return shell
|
||||
|
||||
return _set
|
||||
|
||||
@@ -49,7 +49,7 @@ def test_select_command_with_arrows(proc, TIMEOUT):
|
||||
|
||||
|
||||
@pytest.mark.functional
|
||||
@pytest.mark.once_without_docker
|
||||
@pytest.mark.skip_without_docker
|
||||
def test_refuse_with_confirmation(proc, TIMEOUT):
|
||||
refuse_with_confirmation(proc, TIMEOUT)
|
||||
history_not_changed(proc, TIMEOUT)
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def generic_shell(monkeypatch):
|
||||
monkeypatch.setattr('thefuck.shells.and_', lambda *x: u' && '.join(x))
|
||||
@@ -29,12 +29,27 @@ def test_match_mocked(cmdnf_mock, command, return_value):
|
||||
assert get_packages.called
|
||||
|
||||
|
||||
# python-commandnotfound is available in ubuntu 14.04+
|
||||
@pytest.mark.skipif(not getattr(apt_get, 'enabled_by_default', True),
|
||||
reason='Skip if python-commandnotfound is not available')
|
||||
@pytest.mark.parametrize('command', [
|
||||
Command(script='a_bad_cmd', stderr='a_bad_cmd: command not found'),
|
||||
Command(script='vim', stderr=''), Command()])
|
||||
def test_not_match(command):
|
||||
assert not match(command)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command, return_value', [
|
||||
(Command(script='a_bad_cmd', stderr='a_bad_cmd: command not found'), []),
|
||||
(Command(script='vim', stderr=''), []), (Command(), [])])
|
||||
@patch('thefuck.rules.apt_get.CommandNotFound', create=True)
|
||||
@patch.multiple(apt_get, create=True, apt_get='apt_get')
|
||||
def test_not_match_mocked(cmdnf_mock, command, return_value):
|
||||
get_packages = Mock(return_value=return_value)
|
||||
cmdnf_mock.CommandNotFound.return_value = Mock(getPackages=get_packages)
|
||||
assert not match(command)
|
||||
|
||||
|
||||
# python-commandnotfound is available in ubuntu 14.04+
|
||||
@pytest.mark.skipif(not getattr(apt_get, 'enabled_by_default', True),
|
||||
reason='Skip if python-commandnotfound is not available')
|
||||
|
||||
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
|
||||
def did_not_match(target, did_you_forget=True):
|
||||
error = ("error: pathspec '{}' did not match any "
|
||||
"file(s) known to git.".format(target))
|
||||
if did_you_forget:
|
||||
error = ("{}\nDid you forget to 'git add'?'".format(error))
|
||||
return error
|
||||
def stderr(target):
|
||||
return ("error: pathspec '{}' did not match any "
|
||||
'file(s) known to git.'.format(target))
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command', [
|
||||
Command(script='git submodule update unknown',
|
||||
stderr=did_not_match('unknown')),
|
||||
Command(script='git commit unknown',
|
||||
stderr=did_not_match('unknown'))]) # Older versions of Git
|
||||
def test_match(command):
|
||||
assert match(command)
|
||||
@pytest.mark.parametrize('script, target', [
|
||||
('git submodule update unknown', 'unknown'),
|
||||
('git commit unknown', 'unknown')])
|
||||
def test_match(stderr, script, target):
|
||||
assert match(Command(script=script, stderr=stderr))
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command', [
|
||||
Command(script='git submodule update known', stderr=('')),
|
||||
Command(script='git commit known', stderr=('')),
|
||||
Command(script='git commit unknown', # Newer versions of Git
|
||||
stderr=did_not_match('unknown', False))])
|
||||
def test_not_match(command):
|
||||
assert not match(command)
|
||||
@pytest.mark.parametrize('script', [
|
||||
'git submodule update known', 'git commit known'])
|
||||
def test_not_match(script):
|
||||
assert not match(Command(script=script, stderr=''))
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command, new_command', [
|
||||
(Command('git submodule update unknown', stderr=did_not_match('unknown')),
|
||||
@pytest.mark.parametrize('script, target, new_command', [
|
||||
('git submodule update unknown', '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')])
|
||||
def test_get_new_command(command, new_command):
|
||||
assert get_new_command(command) == new_command
|
||||
def test_get_new_command(stderr, script, target, 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
|
||||
@@ -1,5 +1,5 @@
|
||||
from thefuck import shells
|
||||
from thefuck.rules.git_branch_list import match, get_new_command
|
||||
from thefuck.shells import shell
|
||||
from tests.utils import Command
|
||||
|
||||
|
||||
@@ -16,4 +16,4 @@ def test_not_match():
|
||||
|
||||
def test_get_new_command():
|
||||
assert (get_new_command(Command('git branch list')) ==
|
||||
shells.and_('git branch --delete list', 'git branch'))
|
||||
shell.and_('git branch --delete list', 'git branch'))
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import pytest
|
||||
from thefuck.rules.git_checkout import match, get_new_command
|
||||
from io import BytesIO
|
||||
from thefuck.rules.git_checkout import match, get_branches, get_new_command
|
||||
from tests.utils import Command
|
||||
|
||||
|
||||
@@ -13,8 +14,10 @@ def did_not_match(target, did_you_forget=False):
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def get_branches(mocker):
|
||||
return mocker.patch('thefuck.rules.git_checkout.get_branches')
|
||||
def git_branch(mocker, branches):
|
||||
mock = mocker.patch('subprocess.Popen')
|
||||
mock.return_value.stdout = BytesIO(branches)
|
||||
return mock
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command', [
|
||||
@@ -33,21 +36,34 @@ def test_not_match(command):
|
||||
assert not match(command)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('branches, branch_list', [
|
||||
(b'', []),
|
||||
(b'* master', ['master']),
|
||||
(b' remotes/origin/master', ['master']),
|
||||
(b' just-another-branch', ['just-another-branch']),
|
||||
(b'* master\n just-another-branch', ['master', 'just-another-branch']),
|
||||
(b'* master\n remotes/origin/master\n just-another-branch',
|
||||
['master', 'master', 'just-another-branch'])])
|
||||
def test_get_branches(branches, branch_list, git_branch):
|
||||
git_branch(branches)
|
||||
assert list(get_branches()) == branch_list
|
||||
|
||||
|
||||
@pytest.mark.parametrize('branches, command, new_command', [
|
||||
([],
|
||||
(b'',
|
||||
Command(script='git checkout unknown', stderr=did_not_match('unknown')),
|
||||
'git branch unknown && git checkout unknown'),
|
||||
([],
|
||||
(b'',
|
||||
Command('git commit unknown', stderr=did_not_match('unknown')),
|
||||
'git branch unknown && git commit unknown'),
|
||||
(['test-random-branch-123'],
|
||||
(b' test-random-branch-123',
|
||||
Command(script='git checkout tst-rdm-brnch-123',
|
||||
stderr=did_not_match('tst-rdm-brnch-123')),
|
||||
'git checkout test-random-branch-123'),
|
||||
(['test-random-branch-123'],
|
||||
(b' test-random-branch-123',
|
||||
Command(script='git commit tst-rdm-brnch-123',
|
||||
stderr=did_not_match('tst-rdm-brnch-123')),
|
||||
'git commit test-random-branch-123')])
|
||||
def test_get_new_command(branches, command, new_command, get_branches):
|
||||
get_branches.return_value = branches
|
||||
def test_get_new_command(branches, command, new_command, git_branch):
|
||||
git_branch(branches)
|
||||
assert get_new_command(command) == new_command
|
||||
|
||||
24
tests/rules/test_git_help_aliased.py
Normal file
24
tests/rules/test_git_help_aliased.py
Normal file
@@ -0,0 +1,24 @@
|
||||
import pytest
|
||||
from thefuck.rules.git_help_aliased import match, get_new_command
|
||||
from tests.utils import Command
|
||||
|
||||
|
||||
@pytest.mark.parametrize('script, stdout', [
|
||||
('git help st', "`git st' is aliased to `status'"),
|
||||
('git help ds', "`git ds' is aliased to `diff --staged'")])
|
||||
def test_match(script, stdout):
|
||||
assert match(Command(script=script, stdout=stdout))
|
||||
|
||||
|
||||
@pytest.mark.parametrize('script, stdout', [
|
||||
('git help status', "GIT-STATUS(1)...Git Manual...GIT-STATUS(1)"),
|
||||
('git help diff', "GIT-DIFF(1)...Git Manual...GIT-DIFF(1)")])
|
||||
def test_not_match(script, stdout):
|
||||
assert not match(Command(script=script, stdout=stdout))
|
||||
|
||||
|
||||
@pytest.mark.parametrize('script, stdout, new_command', [
|
||||
('git help st', "`git st' is aliased to `status'", 'git help status'),
|
||||
('git help ds', "`git ds' is aliased to `diff --staged'", 'git help diff')])
|
||||
def test_get_new_command(script, stdout, new_command):
|
||||
assert get_new_command(Command(script=script, stdout=stdout)) == new_command
|
||||
26
tests/rules/test_git_remote_seturl_add.py
Normal file
26
tests/rules/test_git_remote_seturl_add.py
Normal file
@@ -0,0 +1,26 @@
|
||||
import pytest
|
||||
from thefuck.rules.git_remote_seturl_add import match, get_new_command
|
||||
from tests.utils import Command
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command', [
|
||||
Command(script='git remote set-url origin url', stderr="fatal: No such remote")])
|
||||
def test_match(command):
|
||||
assert match(command)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command', [
|
||||
Command('git remote set-url origin url', stderr=""),
|
||||
Command('git remote add origin url'),
|
||||
Command('git remote remove origin'),
|
||||
Command('git remote prune origin'),
|
||||
Command('git remote set-branches origin branch')
|
||||
])
|
||||
def test_not_match(command):
|
||||
assert not match(command)
|
||||
|
||||
@pytest.mark.parametrize('command, new_command', [
|
||||
(Command('git remote set-url origin git@github.com:nvbn/thefuck.git'),
|
||||
'git remote add origin git@github.com:nvbn/thefuck.git')])
|
||||
def test_get_new_command(command, new_command):
|
||||
assert get_new_command(command) == 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
|
||||
40
tests/rules/test_grep_arguments_order.py
Normal file
40
tests/rules/test_grep_arguments_order.py
Normal file
@@ -0,0 +1,40 @@
|
||||
import pytest
|
||||
from thefuck.rules.grep_arguments_order import get_new_command, match
|
||||
from tests.utils import Command
|
||||
|
||||
stderr = 'grep: {}: No such file or directory'.format
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def os_path(monkeypatch):
|
||||
monkeypatch.setattr('os.path.isfile', lambda x: not x.startswith('-'))
|
||||
|
||||
|
||||
@pytest.mark.parametrize('script, file', [
|
||||
('grep test.py test', 'test'),
|
||||
('grep -lir . test', 'test'),
|
||||
('egrep test.py test', 'test'),
|
||||
('egrep -lir . test', 'test')])
|
||||
def test_match(script, file):
|
||||
assert match(Command(script, stderr=stderr(file)))
|
||||
|
||||
|
||||
@pytest.mark.parametrize('script, stderr', [
|
||||
('cat test.py', stderr('test')),
|
||||
('grep test test.py', ''),
|
||||
('grep -lir test .', ''),
|
||||
('egrep test test.py', ''),
|
||||
('egrep -lir test .', '')])
|
||||
def test_not_match(script, stderr):
|
||||
assert not match(Command(script, stderr=stderr))
|
||||
|
||||
|
||||
@pytest.mark.parametrize('script, stderr, result', [
|
||||
('grep test.py test', stderr('test'), 'grep test test.py'),
|
||||
('grep -lir . test', stderr('test'), 'grep -lir test .'),
|
||||
('grep . test -lir', stderr('test'), 'grep test -lir .'),
|
||||
('egrep test.py test', stderr('test'), 'egrep test test.py'),
|
||||
('egrep -lir . test', stderr('test'), 'egrep -lir test .'),
|
||||
('egrep . test -lir', stderr('test'), 'egrep test -lir .')])
|
||||
def test_get_new_command(script, stderr, result):
|
||||
assert get_new_command(Command(script, stderr=stderr)) == result
|
||||
@@ -1,4 +1,5 @@
|
||||
import pytest
|
||||
from io import BytesIO
|
||||
from tests.utils import Command
|
||||
from thefuck.rules.gulp_not_task import match, get_new_command
|
||||
|
||||
@@ -22,7 +23,7 @@ def test_not_march(script, stdout):
|
||||
|
||||
|
||||
def test_get_new_command(mocker):
|
||||
mocker.patch('thefuck.rules.gulp_not_task.get_gulp_tasks', return_value=[
|
||||
'serve', 'build', 'default'])
|
||||
mock = mocker.patch('subprocess.Popen')
|
||||
mock.return_value.stdout = BytesIO(b'serve \nbuild \ndefault \n')
|
||||
command = Command('gulp srve', stdout('srve'))
|
||||
assert get_new_command(command) == ['gulp serve', 'gulp default']
|
||||
|
||||
@@ -3,38 +3,23 @@ from thefuck.rules.history import match, get_new_command
|
||||
from tests.utils import Command
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def history(mocker):
|
||||
return mocker.patch('thefuck.rules.history.get_history',
|
||||
return_value=['le cat', 'fuck', 'ls cat',
|
||||
'diff x', 'nocommand x'])
|
||||
@pytest.fixture(autouse=True)
|
||||
def history_without_current(mocker):
|
||||
return mocker.patch(
|
||||
'thefuck.rules.history.get_valid_history_without_current',
|
||||
return_value=['ls cat', 'diff x'])
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def alias(mocker):
|
||||
return mocker.patch('thefuck.rules.history.thefuck_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'])
|
||||
def test_match(script):
|
||||
assert match(Command(script=script))
|
||||
|
||||
|
||||
@pytest.mark.usefixtures('history', 'callables', 'no_memoize', 'alias')
|
||||
@pytest.mark.parametrize('script', ['apt-get', 'nocommand y'])
|
||||
def test_not_match(script):
|
||||
assert not match(Command(script=script))
|
||||
|
||||
|
||||
@pytest.mark.usefixtures('history', 'callables', 'no_memoize', 'alias')
|
||||
@pytest.mark.parametrize('script, result', [
|
||||
('ls cet', 'ls cat'),
|
||||
('daff x', 'diff x')])
|
||||
|
||||
37
tests/rules/test_ln_no_hard_link.py
Normal file
37
tests/rules/test_ln_no_hard_link.py
Normal file
@@ -0,0 +1,37 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import pytest
|
||||
from thefuck.rules.ln_no_hard_link import match, get_new_command
|
||||
from tests.utils import Command
|
||||
|
||||
error = "hard link not allowed for directory"
|
||||
|
||||
|
||||
@pytest.mark.parametrize('script, stderr', [
|
||||
("ln barDir barLink", "ln: ‘barDir’: {}"),
|
||||
("sudo ln a b", "ln: ‘a’: {}"),
|
||||
("sudo ln -nbi a b", "ln: ‘a’: {}")])
|
||||
def test_match(script, stderr):
|
||||
command = Command(script, stderr=stderr.format(error))
|
||||
assert match(command)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('script, stderr', [
|
||||
('', ''),
|
||||
("ln a b", "... hard link"),
|
||||
("sudo ln a b", "... hard link"),
|
||||
("a b", error)])
|
||||
def test_assert_not_match(script, stderr):
|
||||
command = Command(script, stderr=stderr)
|
||||
assert not match(command)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('script, result', [
|
||||
("ln barDir barLink", "ln -s barDir barLink"),
|
||||
("sudo ln barDir barLink", "sudo ln -s barDir barLink"),
|
||||
("sudo ln -nbi a b", "sudo ln -s -nbi a b"),
|
||||
("ln -nbi a b && ls", "ln -s -nbi a b && ls"),
|
||||
("ln a ln", "ln -s a ln"),
|
||||
("sudo ln a ln", "sudo ln -s a ln")])
|
||||
def test_get_new_command(script, result):
|
||||
command = Command(script)
|
||||
assert get_new_command(command) == result
|
||||
@@ -6,22 +6,37 @@ from tests.utils import Command
|
||||
@pytest.fixture(autouse=True)
|
||||
def get_all_executables(mocker):
|
||||
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')
|
||||
def test_match():
|
||||
assert match(Command(stderr='vom: not found', script='vom file.py'))
|
||||
assert match(Command(stderr='fucck: not found', script='fucck'))
|
||||
assert not match(Command(stderr='qweqwe: not found', script='qweqwe'))
|
||||
assert not match(Command(stderr='some text', script='vom file.py'))
|
||||
@pytest.mark.parametrize('script, stderr', [
|
||||
('vom file.py', 'vom: not found'),
|
||||
('fucck', 'fucck: not found'),
|
||||
('got commit', 'got: command not found')])
|
||||
def test_match(script, stderr):
|
||||
assert match(Command(script, stderr=stderr))
|
||||
|
||||
|
||||
@pytest.mark.usefixtures('no_memoize')
|
||||
def test_get_new_command():
|
||||
assert get_new_command(
|
||||
Command(stderr='vom: not found',
|
||||
script='vom file.py')) == ['vim file.py']
|
||||
assert get_new_command(
|
||||
Command(stderr='fucck: not found',
|
||||
script='fucck')) == ['fsck']
|
||||
@pytest.mark.parametrize('script, stderr', [
|
||||
('qweqwe', 'qweqwe: not found'),
|
||||
('vom file.py', 'some text')])
|
||||
def test_not_match(script, stderr):
|
||||
assert not match(Command(script, stderr=stderr))
|
||||
|
||||
|
||||
@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
|
||||
|
||||
58
tests/rules/test_npm_wrong_command.py
Normal file
58
tests/rules/test_npm_wrong_command.py
Normal file
@@ -0,0 +1,58 @@
|
||||
import pytest
|
||||
from thefuck.rules.npm_wrong_command import match, get_new_command
|
||||
from tests.utils import Command
|
||||
|
||||
stdout = '''
|
||||
Usage: npm <command>
|
||||
|
||||
where <command> is one of:
|
||||
access, add-user, adduser, apihelp, author, bin, bugs, c,
|
||||
cache, completion, config, ddp, dedupe, deprecate, dist-tag,
|
||||
dist-tags, docs, edit, explore, faq, find, find-dupes, get,
|
||||
help, help-search, home, i, info, init, install, issues, la,
|
||||
link, list, ll, ln, login, logout, ls, outdated, owner,
|
||||
pack, ping, prefix, prune, publish, r, rb, rebuild, remove,
|
||||
repo, restart, rm, root, run-script, s, se, search, set,
|
||||
show, shrinkwrap, star, stars, start, stop, t, tag, team,
|
||||
test, tst, un, uninstall, unlink, unpublish, unstar, up,
|
||||
update, upgrade, v, verison, version, view, whoami
|
||||
|
||||
npm <cmd> -h quick help on <cmd>
|
||||
npm -l display full usage info
|
||||
npm faq commonly asked questions
|
||||
npm help <term> search for help on <term>
|
||||
npm help npm involved overview
|
||||
|
||||
Specify configs in the ini-formatted file:
|
||||
/home/nvbn/.npmrc
|
||||
or on the command line via: npm <command> --key value
|
||||
Config info can be viewed via: npm help config
|
||||
|
||||
npm@2.14.7 /opt/node/lib/node_modules/npm
|
||||
'''
|
||||
|
||||
|
||||
@pytest.mark.parametrize('script', [
|
||||
'npm urgrdae',
|
||||
'npm urgrade -g',
|
||||
'npm -f urgrade -g',
|
||||
'npm urg'])
|
||||
def test_match(script):
|
||||
assert match(Command(script, stdout))
|
||||
|
||||
|
||||
@pytest.mark.parametrize('script, stdout', [
|
||||
('npm urgrade', ''),
|
||||
('npm', stdout),
|
||||
('test urgrade', stdout),
|
||||
('npm -e', stdout)])
|
||||
def test_not_match(script, stdout):
|
||||
assert not match(Command(script, stdout))
|
||||
|
||||
|
||||
@pytest.mark.parametrize('script, result', [
|
||||
('npm urgrade', 'npm upgrade'),
|
||||
('npm -g isntall gulp', 'npm -g install gulp'),
|
||||
('npm isntall -g gulp', 'npm install -g gulp')])
|
||||
def test_get_new_command(script, result):
|
||||
assert get_new_command(Command(script, stdout)) == result
|
||||
@@ -52,6 +52,7 @@ def test_match(ssh_error):
|
||||
assert not match(Command('ssh'))
|
||||
|
||||
|
||||
@pytest.mark.skipif(os.name == 'nt', reason='Skip if testing on Windows')
|
||||
def test_side_effect(ssh_error):
|
||||
errormsg, path, reset, known_hosts = ssh_error
|
||||
command = Command('ssh user@host', stderr=errormsg)
|
||||
|
||||
0
tests/shells/__init__.py
Normal file
0
tests/shells/__init__.py
Normal file
22
tests/shells/conftest.py
Normal file
22
tests/shells/conftest.py
Normal file
@@ -0,0 +1,22 @@
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def builtins_open(mocker):
|
||||
return mocker.patch('six.moves.builtins.open')
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def isfile(mocker):
|
||||
return mocker.patch('os.path.isfile', return_value=True)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@pytest.mark.usefixtures('isfile')
|
||||
def history_lines(mocker):
|
||||
def aux(lines):
|
||||
mock = mocker.patch('io.open')
|
||||
mock.return_value.__enter__ \
|
||||
.return_value.readlines.return_value = lines
|
||||
|
||||
return aux
|
||||
58
tests/shells/test_bash.py
Normal file
58
tests/shells/test_bash.py
Normal file
@@ -0,0 +1,58 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import pytest
|
||||
from thefuck.shells import Bash
|
||||
|
||||
|
||||
@pytest.mark.usefixtures('isfile', 'no_memoize', 'no_cache')
|
||||
class TestBash(object):
|
||||
@pytest.fixture
|
||||
def shell(self):
|
||||
return Bash()
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def shell_aliases(self):
|
||||
os.environ['TF_SHELL_ALIASES'] = (
|
||||
'alias fuck=\'eval $(thefuck $(fc -ln -1))\'\n'
|
||||
'alias l=\'ls -CF\'\n'
|
||||
'alias la=\'ls -A\'\n'
|
||||
'alias ll=\'ls -alF\'')
|
||||
|
||||
@pytest.mark.parametrize('before, after', [
|
||||
('pwd', 'pwd'),
|
||||
('fuck', 'eval $(thefuck $(fc -ln -1))'),
|
||||
('awk', 'awk'),
|
||||
('ll', 'ls -alF')])
|
||||
def test_from_shell(self, before, after, shell):
|
||||
assert shell.from_shell(before) == after
|
||||
|
||||
def test_to_shell(self, shell):
|
||||
assert shell.to_shell('pwd') == 'pwd'
|
||||
|
||||
def test_and_(self, shell):
|
||||
assert shell.and_('ls', 'cd') == 'ls && cd'
|
||||
|
||||
def test_get_aliases(self, shell):
|
||||
assert shell.get_aliases() == {'fuck': 'eval $(thefuck $(fc -ln -1))',
|
||||
'l': 'ls -CF',
|
||||
'la': 'ls -A',
|
||||
'll': 'ls -alF'}
|
||||
|
||||
def test_app_alias(self, shell):
|
||||
assert 'alias fuck' in shell.app_alias('fuck')
|
||||
assert 'alias FUCK' in shell.app_alias('FUCK')
|
||||
assert 'thefuck' in shell.app_alias('fuck')
|
||||
assert 'TF_ALIAS=fuck' in shell.app_alias('fuck')
|
||||
assert 'PYTHONIOENCODING=utf-8' in shell.app_alias('fuck')
|
||||
|
||||
def test_app_alias_variables_correctly_set(self, shell):
|
||||
alias = shell.app_alias('fuck')
|
||||
assert "alias fuck='TF_CMD=$(TF_ALIAS" in alias
|
||||
assert '$(TF_ALIAS=fuck PYTHONIOENCODING' in alias
|
||||
assert 'PYTHONIOENCODING=utf-8 TF_SHELL_ALIASES' in alias
|
||||
assert 'ALIASES=$(alias) thefuck' in alias
|
||||
|
||||
def test_get_history(self, history_lines, shell):
|
||||
history_lines(['ls', 'rm'])
|
||||
assert list(shell.get_history()) == ['ls', 'rm']
|
||||
89
tests/shells/test_fish.py
Normal file
89
tests/shells/test_fish.py
Normal file
@@ -0,0 +1,89 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import pytest
|
||||
from thefuck.shells import Fish
|
||||
|
||||
|
||||
@pytest.mark.usefixtures('isfile', 'no_memoize', 'no_cache')
|
||||
class TestFish(object):
|
||||
@pytest.fixture
|
||||
def shell(self):
|
||||
return Fish()
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def Popen(self, mocker):
|
||||
mock = mocker.patch('thefuck.shells.fish.Popen')
|
||||
mock.return_value.stdout.read.return_value = (
|
||||
b'cd\nfish_config\nfuck\nfunced\nfuncsave\ngrep\nhistory\nll\nls\n'
|
||||
b'man\nmath\npopd\npushd\nruby')
|
||||
return mock
|
||||
|
||||
@pytest.fixture
|
||||
def os_environ(self, monkeypatch, key, value):
|
||||
monkeypatch.setattr('os.environ', {key: value})
|
||||
|
||||
@pytest.mark.parametrize('key, value', [
|
||||
('TF_OVERRIDDEN_ALIASES', 'cut,git,sed'), # legacy
|
||||
('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', [
|
||||
('cd', 'cd'),
|
||||
('pwd', 'pwd'),
|
||||
('fuck', 'fish -ic "fuck"'),
|
||||
('find', 'find'),
|
||||
('funced', 'fish -ic "funced"'),
|
||||
('grep', 'grep'),
|
||||
('awk', 'awk'),
|
||||
('math "2 + 2"', r'fish -ic "math \"2 + 2\""'),
|
||||
('man', 'man'),
|
||||
('open', 'open'),
|
||||
('vim', 'vim'),
|
||||
('ll', 'fish -ic "ll"'),
|
||||
('ls', 'ls')]) # Fish has no aliases but functions
|
||||
def test_from_shell(self, before, after, shell):
|
||||
assert shell.from_shell(before) == after
|
||||
|
||||
def test_to_shell(self, shell):
|
||||
assert shell.to_shell('pwd') == 'pwd'
|
||||
|
||||
def test_and_(self, shell):
|
||||
assert shell.and_('foo', 'bar') == 'foo; and bar'
|
||||
|
||||
def test_get_aliases(self, shell):
|
||||
assert shell.get_aliases() == {'fish_config': 'fish_config',
|
||||
'fuck': 'fuck',
|
||||
'funced': 'funced',
|
||||
'funcsave': 'funcsave',
|
||||
'history': 'history',
|
||||
'll': 'll',
|
||||
'math': 'math',
|
||||
'popd': 'popd',
|
||||
'pushd': 'pushd',
|
||||
'ruby': 'ruby'}
|
||||
|
||||
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')
|
||||
assert 'TF_ALIAS=fuck PYTHONIOENCODING' in shell.app_alias('fuck')
|
||||
assert 'PYTHONIOENCODING=utf-8 thefuck' in shell.app_alias('fuck')
|
||||
|
||||
def test_get_history(self, history_lines, shell):
|
||||
history_lines(['- cmd: ls', ' when: 1432613911',
|
||||
'- cmd: rm', ' when: 1432613916'])
|
||||
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)
|
||||
39
tests/shells/test_generic.py
Normal file
39
tests/shells/test_generic.py
Normal file
@@ -0,0 +1,39 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import pytest
|
||||
from thefuck.shells import Generic
|
||||
|
||||
|
||||
class TestGeneric(object):
|
||||
@pytest.fixture
|
||||
def shell(self):
|
||||
return Generic()
|
||||
|
||||
def test_from_shell(self, shell):
|
||||
assert shell.from_shell('pwd') == 'pwd'
|
||||
|
||||
def test_to_shell(self, shell):
|
||||
assert shell.to_shell('pwd') == 'pwd'
|
||||
|
||||
def test_and_(self, shell):
|
||||
assert shell.and_('ls', 'cd') == 'ls && cd'
|
||||
|
||||
def test_get_aliases(self, shell):
|
||||
assert shell.get_aliases() == {}
|
||||
|
||||
def test_app_alias(self, shell):
|
||||
assert 'alias fuck' in shell.app_alias('fuck')
|
||||
assert 'alias FUCK' in shell.app_alias('FUCK')
|
||||
assert 'thefuck' 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')
|
||||
|
||||
def test_get_history(self, history_lines, shell):
|
||||
history_lines(['ls', 'rm'])
|
||||
# We don't know what to do in generic shell with history lines,
|
||||
# so just ignore them:
|
||||
assert list(shell.get_history()) == []
|
||||
|
||||
def test_split_command(self, shell):
|
||||
assert shell.split_command('ls') == ['ls']
|
||||
assert shell.split_command(u'echo café') == [u'echo', u'café']
|
||||
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')
|
||||
50
tests/shells/test_tcsh.py
Normal file
50
tests/shells/test_tcsh.py
Normal file
@@ -0,0 +1,50 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import pytest
|
||||
from thefuck.shells.tcsh import Tcsh
|
||||
|
||||
|
||||
@pytest.mark.usefixtures('isfile', 'no_memoize', 'no_cache')
|
||||
class TestTcsh(object):
|
||||
@pytest.fixture
|
||||
def shell(self):
|
||||
return Tcsh()
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def Popen(self, mocker):
|
||||
mock = mocker.patch('thefuck.shells.tcsh.Popen')
|
||||
mock.return_value.stdout.read.return_value = (
|
||||
b'fuck\teval $(thefuck $(fc -ln -1))\n'
|
||||
b'l\tls -CF\n'
|
||||
b'la\tls -A\n'
|
||||
b'll\tls -alF')
|
||||
return mock
|
||||
|
||||
@pytest.mark.parametrize('before, after', [
|
||||
('pwd', 'pwd'),
|
||||
('fuck', 'eval $(thefuck $(fc -ln -1))'),
|
||||
('awk', 'awk'),
|
||||
('ll', 'ls -alF')])
|
||||
def test_from_shell(self, before, after, shell):
|
||||
assert shell.from_shell(before) == after
|
||||
|
||||
def test_to_shell(self, shell):
|
||||
assert shell.to_shell('pwd') == 'pwd'
|
||||
|
||||
def test_and_(self, shell):
|
||||
assert shell.and_('ls', 'cd') == 'ls && cd'
|
||||
|
||||
def test_get_aliases(self, shell):
|
||||
assert shell.get_aliases() == {'fuck': 'eval $(thefuck $(fc -ln -1))',
|
||||
'l': 'ls -CF',
|
||||
'la': 'ls -A',
|
||||
'll': 'ls -alF'}
|
||||
|
||||
def test_app_alias(self, shell):
|
||||
assert 'alias fuck' in shell.app_alias('fuck')
|
||||
assert 'alias FUCK' in shell.app_alias('FUCK')
|
||||
assert 'thefuck' in shell.app_alias('fuck')
|
||||
|
||||
def test_get_history(self, history_lines, shell):
|
||||
history_lines(['ls', 'rm'])
|
||||
assert list(shell.get_history()) == ['ls', 'rm']
|
||||
57
tests/shells/test_zsh.py
Normal file
57
tests/shells/test_zsh.py
Normal file
@@ -0,0 +1,57 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import pytest
|
||||
from thefuck.shells.zsh import Zsh
|
||||
|
||||
|
||||
@pytest.mark.usefixtures('isfile', 'no_memoize', 'no_cache')
|
||||
class TestZsh(object):
|
||||
@pytest.fixture
|
||||
def shell(self):
|
||||
return Zsh()
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def shell_aliases(self):
|
||||
os.environ['TF_SHELL_ALIASES'] = (
|
||||
'fuck=\'eval $(thefuck $(fc -ln -1 | tail -n 1))\'\n'
|
||||
'l=\'ls -CF\'\n'
|
||||
'la=\'ls -A\'\n'
|
||||
'll=\'ls -alF\'')
|
||||
|
||||
@pytest.mark.parametrize('before, after', [
|
||||
('fuck', 'eval $(thefuck $(fc -ln -1 | tail -n 1))'),
|
||||
('pwd', 'pwd'),
|
||||
('ll', 'ls -alF')])
|
||||
def test_from_shell(self, before, after, shell):
|
||||
assert shell.from_shell(before) == after
|
||||
|
||||
def test_to_shell(self, shell):
|
||||
assert shell.to_shell('pwd') == 'pwd'
|
||||
|
||||
def test_and_(self, shell):
|
||||
assert shell.and_('ls', 'cd') == 'ls && cd'
|
||||
|
||||
def test_get_aliases(self, shell):
|
||||
assert shell.get_aliases() == {
|
||||
'fuck': 'eval $(thefuck $(fc -ln -1 | tail -n 1))',
|
||||
'l': 'ls -CF',
|
||||
'la': 'ls -A',
|
||||
'll': 'ls -alF'}
|
||||
|
||||
def test_app_alias(self, shell):
|
||||
assert 'alias fuck' in shell.app_alias('fuck')
|
||||
assert 'alias FUCK' in shell.app_alias('FUCK')
|
||||
assert 'thefuck' in shell.app_alias('fuck')
|
||||
assert 'PYTHONIOENCODING' in shell.app_alias('fuck')
|
||||
|
||||
def test_app_alias_variables_correctly_set(self, shell):
|
||||
alias = shell.app_alias('fuck')
|
||||
assert "alias fuck='TF_CMD=$(TF_ALIAS" in alias
|
||||
assert '$(TF_ALIAS=fuck PYTHONIOENCODING' in alias
|
||||
assert 'PYTHONIOENCODING=utf-8 TF_SHELL_ALIASES' in alias
|
||||
assert 'ALIASES=$(alias) thefuck' in alias
|
||||
|
||||
def test_get_history(self, history_lines, shell):
|
||||
history_lines([': 1432613911:0;ls', ': 1432613916:0;rm'])
|
||||
assert list(shell.get_history()) == ['ls', 'rm']
|
||||
@@ -1,7 +1,7 @@
|
||||
import pytest
|
||||
import six
|
||||
from mock import Mock
|
||||
from thefuck import conf
|
||||
from thefuck import const
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -20,7 +20,7 @@ def environ(monkeypatch):
|
||||
def test_settings_defaults(load_source, settings):
|
||||
load_source.return_value = object()
|
||||
settings.init()
|
||||
for key, val in conf.DEFAULT_SETTINGS.items():
|
||||
for key, val in const.DEFAULT_SETTINGS.items():
|
||||
assert getattr(settings, key) == val
|
||||
|
||||
|
||||
@@ -42,13 +42,13 @@ class TestSettingsFromFile(object):
|
||||
assert settings.exclude_rules == ['git']
|
||||
|
||||
def test_from_file_with_DEFAULT(self, load_source, settings):
|
||||
load_source.return_value = Mock(rules=conf.DEFAULT_RULES + ['test'],
|
||||
load_source.return_value = Mock(rules=const.DEFAULT_RULES + ['test'],
|
||||
wait_command=10,
|
||||
exclude_rules=[],
|
||||
require_confirmation=True,
|
||||
no_colors=True)
|
||||
settings.init()
|
||||
assert settings.rules == conf.DEFAULT_RULES + ['test']
|
||||
assert settings.rules == const.DEFAULT_RULES + ['test']
|
||||
|
||||
|
||||
@pytest.mark.usefixture('load_source')
|
||||
@@ -71,7 +71,7 @@ class TestSettingsFromEnv(object):
|
||||
def test_from_env_with_DEFAULT(self, environ, settings):
|
||||
environ.update({'THEFUCK_RULES': 'DEFAULT_RULES:bash:lisp'})
|
||||
settings.init()
|
||||
assert settings.rules == conf.DEFAULT_RULES + ['bash', 'lisp']
|
||||
assert settings.rules == const.DEFAULT_RULES + ['bash', 'lisp']
|
||||
|
||||
|
||||
class TestInitializeSettingsFile(object):
|
||||
@@ -93,7 +93,7 @@ class TestInitializeSettingsFile(object):
|
||||
settings_file_contents = settings_file.getvalue()
|
||||
assert settings_path_mock.is_file.call_count == 1
|
||||
assert settings_path_mock.open.call_count == 1
|
||||
assert conf.SETTINGS_HEADER in settings_file_contents
|
||||
for setting in conf.DEFAULT_SETTINGS.items():
|
||||
assert const.SETTINGS_HEADER in settings_file_contents
|
||||
for setting in const.DEFAULT_SETTINGS.items():
|
||||
assert '# {} = {}\n'.format(*setting) in settings_file_contents
|
||||
settings_file.close()
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import pytest
|
||||
from pathlib import PosixPath
|
||||
from thefuck import corrector, conf
|
||||
from pathlib import Path
|
||||
from thefuck import corrector, const
|
||||
from tests.utils import Rule, Command, CorrectedCommand
|
||||
from thefuck.corrector import get_corrected_commands, organize_commands
|
||||
|
||||
@@ -24,13 +24,13 @@ class TestGetRules(object):
|
||||
assert {r.name for r in rules} == set(names)
|
||||
|
||||
@pytest.mark.parametrize('paths, conf_rules, exclude_rules, loaded_rules', [
|
||||
(['git.py', 'bash.py'], conf.DEFAULT_RULES, [], ['git', 'bash']),
|
||||
(['git.py', 'bash.py'], const.DEFAULT_RULES, [], ['git', 'bash']),
|
||||
(['git.py', 'bash.py'], ['git'], [], ['git']),
|
||||
(['git.py', 'bash.py'], conf.DEFAULT_RULES, ['git'], ['bash']),
|
||||
(['git.py', 'bash.py'], const.DEFAULT_RULES, ['git'], ['bash']),
|
||||
(['git.py', 'bash.py'], ['git'], ['git'], [])])
|
||||
def test_get_rules(self, glob, settings, paths, conf_rules, exclude_rules,
|
||||
loaded_rules):
|
||||
glob([PosixPath(path) for path in paths])
|
||||
glob([Path(path) for path in paths])
|
||||
settings.update(rules=conf_rules,
|
||||
priority={},
|
||||
exclude_rules=exclude_rules)
|
||||
|
||||
@@ -1,260 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import pytest
|
||||
from thefuck import shells
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def builtins_open(mocker):
|
||||
return mocker.patch('six.moves.builtins.open')
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def isfile(mocker):
|
||||
return mocker.patch('os.path.isfile', return_value=True)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@pytest.mark.usefixtures('isfile')
|
||||
def history_lines(mocker):
|
||||
def aux(lines):
|
||||
mock = mocker.patch('io.open')
|
||||
mock.return_value.__enter__\
|
||||
.return_value.readlines.return_value = lines
|
||||
return aux
|
||||
|
||||
|
||||
class TestGeneric(object):
|
||||
@pytest.fixture
|
||||
def shell(self):
|
||||
return shells.Generic()
|
||||
|
||||
def test_from_shell(self, shell):
|
||||
assert shell.from_shell('pwd') == 'pwd'
|
||||
|
||||
def test_to_shell(self, shell):
|
||||
assert shell.to_shell('pwd') == 'pwd'
|
||||
|
||||
def test_put_to_history(self, builtins_open, shell):
|
||||
assert shell.put_to_history('ls') is None
|
||||
assert shell.put_to_history(u'echo café') is None
|
||||
assert builtins_open.call_count == 0
|
||||
|
||||
def test_and_(self, shell):
|
||||
assert shell.and_('ls', 'cd') == 'ls && cd'
|
||||
|
||||
def test_get_aliases(self, shell):
|
||||
assert shell.get_aliases() == {}
|
||||
|
||||
def test_app_alias(self, shell):
|
||||
assert 'alias fuck' in shell.app_alias('fuck')
|
||||
assert 'alias FUCK' in shell.app_alias('FUCK')
|
||||
assert 'thefuck' in shell.app_alias('fuck')
|
||||
assert 'TF_ALIAS' in shell.app_alias('fuck')
|
||||
assert 'PYTHONIOENCODING=utf-8' in shell.app_alias('fuck')
|
||||
|
||||
def test_get_history(self, history_lines, shell):
|
||||
history_lines(['ls', 'rm'])
|
||||
# We don't know what to do in generic shell with history lines,
|
||||
# so just ignore them:
|
||||
assert list(shell.get_history()) == []
|
||||
|
||||
def test_split_command(self, shell):
|
||||
assert shell.split_command('ls') == ['ls']
|
||||
assert shell.split_command(u'echo café') == [u'echo', u'café']
|
||||
|
||||
|
||||
@pytest.mark.usefixtures('isfile')
|
||||
class TestBash(object):
|
||||
@pytest.fixture
|
||||
def shell(self):
|
||||
return shells.Bash()
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def Popen(self, mocker):
|
||||
mock = mocker.patch('thefuck.shells.Popen')
|
||||
mock.return_value.stdout.read.return_value = (
|
||||
b'alias fuck=\'eval $(thefuck $(fc -ln -1))\'\n'
|
||||
b'alias l=\'ls -CF\'\n'
|
||||
b'alias la=\'ls -A\'\n'
|
||||
b'alias ll=\'ls -alF\'')
|
||||
return mock
|
||||
|
||||
@pytest.mark.parametrize('before, after', [
|
||||
('pwd', 'pwd'),
|
||||
('fuck', 'eval $(thefuck $(fc -ln -1))'),
|
||||
('awk', 'awk'),
|
||||
('ll', 'ls -alF')])
|
||||
def test_from_shell(self, before, after, shell):
|
||||
assert shell.from_shell(before) == after
|
||||
|
||||
def test_to_shell(self, shell):
|
||||
assert shell.to_shell('pwd') == 'pwd'
|
||||
|
||||
@pytest.mark.parametrize('entry, entry_utf8', [
|
||||
('ls', 'ls\n'),
|
||||
(u'echo café', 'echo café\n')])
|
||||
def test_put_to_history(self, entry, entry_utf8, builtins_open, shell):
|
||||
shell.put_to_history(entry)
|
||||
builtins_open.return_value.__enter__.return_value. \
|
||||
write.assert_called_once_with(entry_utf8)
|
||||
|
||||
def test_and_(self, shell):
|
||||
assert shell.and_('ls', 'cd') == 'ls && cd'
|
||||
|
||||
def test_get_aliases(self, shell):
|
||||
assert shell.get_aliases() == {'fuck': 'eval $(thefuck $(fc -ln -1))',
|
||||
'l': 'ls -CF',
|
||||
'la': 'ls -A',
|
||||
'll': 'ls -alF'}
|
||||
|
||||
def test_app_alias(self, shell):
|
||||
assert 'alias fuck' in shell.app_alias('fuck')
|
||||
assert 'alias FUCK' in shell.app_alias('FUCK')
|
||||
assert 'thefuck' in shell.app_alias('fuck')
|
||||
assert 'TF_ALIAS' in shell.app_alias('fuck')
|
||||
assert 'PYTHONIOENCODING=utf-8' in shell.app_alias('fuck')
|
||||
|
||||
def test_get_history(self, history_lines, shell):
|
||||
history_lines(['ls', 'rm'])
|
||||
assert list(shell.get_history()) == ['ls', 'rm']
|
||||
|
||||
|
||||
@pytest.mark.usefixtures('isfile')
|
||||
class TestFish(object):
|
||||
@pytest.fixture
|
||||
def shell(self):
|
||||
return shells.Fish()
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def Popen(self, mocker):
|
||||
mock = mocker.patch('thefuck.shells.Popen')
|
||||
mock.return_value.stdout.read.return_value = (
|
||||
b'cd\nfish_config\nfuck\nfunced\nfuncsave\ngrep\nhistory\nll\nls\n'
|
||||
b'man\nmath\npopd\npushd\nruby')
|
||||
return mock
|
||||
|
||||
@pytest.fixture
|
||||
def environ(self, monkeypatch):
|
||||
data = {'TF_OVERRIDDEN_ALIASES': 'cd, ls, man, open'}
|
||||
monkeypatch.setattr('thefuck.shells.os.environ', data)
|
||||
return data
|
||||
|
||||
@pytest.mark.usefixture('environ')
|
||||
def test_get_overridden_aliases(self, shell, environ):
|
||||
assert shell._get_overridden_aliases() == ['cd', 'ls', 'man', 'open']
|
||||
|
||||
@pytest.mark.parametrize('before, after', [
|
||||
('cd', 'cd'),
|
||||
('pwd', 'pwd'),
|
||||
('fuck', 'fish -ic "fuck"'),
|
||||
('find', 'find'),
|
||||
('funced', 'fish -ic "funced"'),
|
||||
('grep', 'grep'),
|
||||
('awk', 'awk'),
|
||||
('math "2 + 2"', r'fish -ic "math \"2 + 2\""'),
|
||||
('man', 'man'),
|
||||
('open', 'open'),
|
||||
('vim', 'vim'),
|
||||
('ll', 'fish -ic "ll"'),
|
||||
('ls', 'ls')]) # Fish has no aliases but functions
|
||||
def test_from_shell(self, before, after, shell):
|
||||
assert shell.from_shell(before) == after
|
||||
|
||||
def test_to_shell(self, shell):
|
||||
assert shell.to_shell('pwd') == 'pwd'
|
||||
|
||||
@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.time',
|
||||
return_value=1430707243.3517463)
|
||||
shell.put_to_history(entry)
|
||||
builtins_open.return_value.__enter__.return_value. \
|
||||
write.assert_called_once_with(entry_utf8)
|
||||
|
||||
def test_and_(self, shell):
|
||||
assert shell.and_('foo', 'bar') == 'foo; and bar'
|
||||
|
||||
def test_get_aliases(self, shell):
|
||||
assert shell.get_aliases() == {'fish_config': 'fish_config',
|
||||
'fuck': 'fuck',
|
||||
'funced': 'funced',
|
||||
'funcsave': 'funcsave',
|
||||
'history': 'history',
|
||||
'll': 'll',
|
||||
'math': 'math',
|
||||
'popd': 'popd',
|
||||
'pushd': 'pushd',
|
||||
'ruby': 'ruby'}
|
||||
|
||||
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')
|
||||
assert 'TF_ALIAS' in shell.app_alias('fuck')
|
||||
assert 'PYTHONIOENCODING=utf-8' in shell.app_alias('fuck')
|
||||
|
||||
def test_get_history(self, history_lines, shell):
|
||||
history_lines(['- cmd: ls', ' when: 1432613911',
|
||||
'- cmd: rm', ' when: 1432613916'])
|
||||
assert list(shell.get_history()) == ['ls', 'rm']
|
||||
|
||||
|
||||
@pytest.mark.usefixtures('isfile')
|
||||
class TestZsh(object):
|
||||
@pytest.fixture
|
||||
def shell(self):
|
||||
return shells.Zsh()
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def Popen(self, mocker):
|
||||
mock = mocker.patch('thefuck.shells.Popen')
|
||||
mock.return_value.stdout.read.return_value = (
|
||||
b'fuck=\'eval $(thefuck $(fc -ln -1 | tail -n 1))\'\n'
|
||||
b'l=\'ls -CF\'\n'
|
||||
b'la=\'ls -A\'\n'
|
||||
b'll=\'ls -alF\'')
|
||||
return mock
|
||||
|
||||
@pytest.mark.parametrize('before, after', [
|
||||
('fuck', 'eval $(thefuck $(fc -ln -1 | tail -n 1))'),
|
||||
('pwd', 'pwd'),
|
||||
('ll', 'ls -alF')])
|
||||
def test_from_shell(self, before, after, shell):
|
||||
assert shell.from_shell(before) == after
|
||||
|
||||
def test_to_shell(self, shell):
|
||||
assert shell.to_shell('pwd') == 'pwd'
|
||||
|
||||
@pytest.mark.parametrize('entry, entry_utf8', [
|
||||
('ls', ': 1430707243:0;ls\n'),
|
||||
(u'echo café', ': 1430707243:0;echo café\n')])
|
||||
def test_put_to_history(self, entry, entry_utf8, builtins_open, mocker, shell):
|
||||
mocker.patch('thefuck.shells.time',
|
||||
return_value=1430707243.3517463)
|
||||
shell.put_to_history(entry)
|
||||
builtins_open.return_value.__enter__.return_value. \
|
||||
write.assert_called_once_with(entry_utf8)
|
||||
|
||||
def test_and_(self, shell):
|
||||
assert shell.and_('ls', 'cd') == 'ls && cd'
|
||||
|
||||
def test_get_aliases(self, shell):
|
||||
assert shell.get_aliases() == {
|
||||
'fuck': 'eval $(thefuck $(fc -ln -1 | tail -n 1))',
|
||||
'l': 'ls -CF',
|
||||
'la': 'ls -A',
|
||||
'll': 'ls -alF'}
|
||||
|
||||
def test_app_alias(self, shell):
|
||||
assert 'alias fuck' in shell.app_alias('fuck')
|
||||
assert 'alias FUCK' in shell.app_alias('FUCK')
|
||||
assert 'thefuck' in shell.app_alias('fuck')
|
||||
assert 'TF_ALIAS' in shell.app_alias('fuck')
|
||||
assert 'PYTHONIOENCODING=utf-8' in shell.app_alias('fuck')
|
||||
|
||||
def test_get_history(self, history_lines, shell):
|
||||
history_lines([': 1432613911:0;ls', ': 1432613916:0;rm'])
|
||||
assert list(shell.get_history()) == ['ls', 'rm']
|
||||
@@ -1,11 +1,12 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
from subprocess import PIPE
|
||||
from mock import Mock
|
||||
from pathlib import Path
|
||||
import pytest
|
||||
from tests.utils import CorrectedCommand, Rule, Command
|
||||
from thefuck import conf
|
||||
from thefuck import const
|
||||
from thefuck.exceptions import EmptyCommand
|
||||
|
||||
|
||||
@@ -39,19 +40,20 @@ class TestRule(object):
|
||||
enabled_by_default=True,
|
||||
priority=900,
|
||||
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)
|
||||
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', [
|
||||
(conf.DEFAULT_RULES, [], Rule('git', enabled_by_default=True), True),
|
||||
(conf.DEFAULT_RULES, [], Rule('git', enabled_by_default=False), False),
|
||||
(const.DEFAULT_RULES, [], Rule('git', enabled_by_default=True), True),
|
||||
(const.DEFAULT_RULES, [], Rule('git', enabled_by_default=False), False),
|
||||
([], [], Rule('git', enabled_by_default=False), False),
|
||||
([], [], Rule('git', enabled_by_default=True), False),
|
||||
(conf.DEFAULT_RULES + ['git'], [], Rule('git', enabled_by_default=False), True),
|
||||
(const.DEFAULT_RULES + ['git'], [], Rule('git', enabled_by_default=False), True),
|
||||
(['git'], [], Rule('git', enabled_by_default=False), True),
|
||||
(conf.DEFAULT_RULES, ['git'], Rule('git', enabled_by_default=True), False),
|
||||
(conf.DEFAULT_RULES, ['git'], Rule('git', enabled_by_default=False), False),
|
||||
(const.DEFAULT_RULES, ['git'], Rule('git', enabled_by_default=True), False),
|
||||
(const.DEFAULT_RULES, ['git'], Rule('git', enabled_by_default=False), False),
|
||||
([], ['git'], Rule('git', enabled_by_default=True), False),
|
||||
([], ['git'], Rule('git', enabled_by_default=False), False)])
|
||||
def test_is_enabled(self, settings, rules, exclude_rules, rule, is_enabled):
|
||||
@@ -103,11 +105,6 @@ class TestCommand(object):
|
||||
monkeypatch.setattr('thefuck.types.Command._wait_output',
|
||||
staticmethod(lambda *_: True))
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def generic_shell(self, monkeypatch):
|
||||
monkeypatch.setattr('thefuck.shells.from_shell', lambda x: x)
|
||||
monkeypatch.setattr('thefuck.shells.to_shell', lambda x: x)
|
||||
|
||||
def test_from_script_calls(self, Popen, settings):
|
||||
settings.env = {}
|
||||
assert Command.from_raw_script(
|
||||
@@ -115,6 +112,7 @@ class TestCommand(object):
|
||||
'apt-get search vim', 'stdout', 'stderr')
|
||||
Popen.assert_called_once_with('apt-get search vim',
|
||||
shell=True,
|
||||
stdin=PIPE,
|
||||
stdout=PIPE,
|
||||
stderr=PIPE,
|
||||
env={})
|
||||
|
||||
@@ -31,7 +31,9 @@ def test_read_actions(patch_get_key):
|
||||
# Ctrl+C:
|
||||
const.KEY_CTRL_C])
|
||||
assert list(islice(ui.read_actions(), 5)) \
|
||||
== [ui.SELECT, ui.SELECT, ui.PREVIOUS, ui.NEXT, ui.ABORT]
|
||||
== [const.ACTION_SELECT, const.ACTION_SELECT,
|
||||
const.ACTION_PREVIOUS, const.ACTION_NEXT,
|
||||
const.ACTION_ABORT]
|
||||
|
||||
|
||||
def test_command_selector():
|
||||
|
||||
@@ -3,7 +3,8 @@ from mock import Mock
|
||||
import six
|
||||
from thefuck.utils import default_settings, \
|
||||
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
|
||||
|
||||
|
||||
@@ -50,7 +51,7 @@ class TestGetClosest(object):
|
||||
|
||||
@pytest.fixture
|
||||
def get_aliases(mocker):
|
||||
mocker.patch('thefuck.shells.get_aliases',
|
||||
mocker.patch('thefuck.shells.shell.get_aliases',
|
||||
return_value=['vim', 'apt-get', 'fsck', 'fuck'])
|
||||
|
||||
|
||||
@@ -162,10 +163,10 @@ class TestCache(object):
|
||||
|
||||
@pytest.fixture
|
||||
def key(self):
|
||||
if six.PY3:
|
||||
return 'tests.test_utils.<function TestCache.fn.<locals>.fn '
|
||||
else:
|
||||
if six.PY2:
|
||||
return 'tests.test_utils.<function fn '
|
||||
else:
|
||||
return 'tests.test_utils.<function TestCache.fn.<locals>.fn '
|
||||
|
||||
def test_with_blank_cache(self, shelve, fn, key):
|
||||
assert shelve == {}
|
||||
@@ -229,3 +230,29 @@ class TestCompatibilityCall(object):
|
||||
return True
|
||||
|
||||
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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from thefuck import types
|
||||
from thefuck.conf import DEFAULT_PRIORITY
|
||||
from thefuck.const import DEFAULT_PRIORITY
|
||||
|
||||
|
||||
class Command(types.Command):
|
||||
|
||||
@@ -3,44 +3,7 @@ import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from six import text_type
|
||||
|
||||
|
||||
ALL_ENABLED = object()
|
||||
DEFAULT_RULES = [ALL_ENABLED]
|
||||
DEFAULT_PRIORITY = 1000
|
||||
|
||||
DEFAULT_SETTINGS = {'rules': DEFAULT_RULES,
|
||||
'exclude_rules': [],
|
||||
'wait_command': 3,
|
||||
'require_confirmation': True,
|
||||
'no_colors': False,
|
||||
'debug': False,
|
||||
'priority': {},
|
||||
'history_limit': None,
|
||||
'alter_history': True,
|
||||
'env': {'LC_ALL': 'C', 'LANG': 'C', 'GIT_TRACE': '1'}}
|
||||
|
||||
ENV_TO_ATTR = {'THEFUCK_RULES': 'rules',
|
||||
'THEFUCK_EXCLUDE_RULES': 'exclude_rules',
|
||||
'THEFUCK_WAIT_COMMAND': 'wait_command',
|
||||
'THEFUCK_REQUIRE_CONFIRMATION': 'require_confirmation',
|
||||
'THEFUCK_NO_COLORS': 'no_colors',
|
||||
'THEFUCK_DEBUG': 'debug',
|
||||
'THEFUCK_PRIORITY': 'priority',
|
||||
'THEFUCK_HISTORY_LIMIT': 'history_limit',
|
||||
'THEFUCK_ALTER_HISTORY': 'alter_history'}
|
||||
|
||||
SETTINGS_HEADER = u"""# The Fuck settings file
|
||||
#
|
||||
# The rules are defined as in the example bellow:
|
||||
#
|
||||
# rules = ['cd_parent', 'git_push', 'python_command', 'sudo']
|
||||
#
|
||||
# The default values are as follows. Uncomment and change to fit your needs.
|
||||
# See https://github.com/nvbn/thefuck#settings for more information.
|
||||
#
|
||||
|
||||
"""
|
||||
from . import const
|
||||
|
||||
|
||||
class Settings(dict):
|
||||
@@ -71,8 +34,8 @@ class Settings(dict):
|
||||
settings_path = self.user_dir.joinpath('settings.py')
|
||||
if not settings_path.is_file():
|
||||
with settings_path.open(mode='w') as settings_file:
|
||||
settings_file.write(SETTINGS_HEADER)
|
||||
for setting in DEFAULT_SETTINGS.items():
|
||||
settings_file.write(const.SETTINGS_HEADER)
|
||||
for setting in const.DEFAULT_SETTINGS.items():
|
||||
settings_file.write(u'# {} = {}\n'.format(*setting))
|
||||
|
||||
def _get_user_dir_path(self):
|
||||
@@ -100,14 +63,14 @@ class Settings(dict):
|
||||
settings = load_source(
|
||||
'settings', text_type(self.user_dir.joinpath('settings.py')))
|
||||
return {key: getattr(settings, key)
|
||||
for key in DEFAULT_SETTINGS.keys()
|
||||
for key in const.DEFAULT_SETTINGS.keys()
|
||||
if hasattr(settings, key)}
|
||||
|
||||
def _rules_from_env(self, val):
|
||||
"""Transforms rules list from env-string to python."""
|
||||
val = val.split(':')
|
||||
if 'DEFAULT_RULES' in val:
|
||||
val = DEFAULT_RULES + [rule for rule in val if rule != 'DEFAULT_RULES']
|
||||
val = const.DEFAULT_RULES + [rule for rule in val if rule != 'DEFAULT_RULES']
|
||||
return val
|
||||
|
||||
def _priority_from_env(self, val):
|
||||
@@ -139,8 +102,8 @@ class Settings(dict):
|
||||
def _settings_from_env(self):
|
||||
"""Loads settings from env."""
|
||||
return {attr: self._val_from_env(env, attr)
|
||||
for env, attr in ENV_TO_ATTR.items()
|
||||
for env, attr in const.ENV_TO_ATTR.items()
|
||||
if env in os.environ}
|
||||
|
||||
|
||||
settings = Settings(DEFAULT_SETTINGS)
|
||||
settings = Settings(const.DEFAULT_SETTINGS)
|
||||
|
||||
@@ -12,3 +12,45 @@ class _GenConst(object):
|
||||
KEY_UP = _GenConst('↑')
|
||||
KEY_DOWN = _GenConst('↓')
|
||||
KEY_CTRL_C = _GenConst('Ctrl+C')
|
||||
|
||||
ACTION_SELECT = _GenConst('select')
|
||||
ACTION_ABORT = _GenConst('abort')
|
||||
ACTION_PREVIOUS = _GenConst('previous')
|
||||
ACTION_NEXT = _GenConst('next')
|
||||
|
||||
ALL_ENABLED = _GenConst('All rules enabled')
|
||||
DEFAULT_RULES = [ALL_ENABLED]
|
||||
DEFAULT_PRIORITY = 1000
|
||||
|
||||
DEFAULT_SETTINGS = {'rules': DEFAULT_RULES,
|
||||
'exclude_rules': [],
|
||||
'wait_command': 3,
|
||||
'require_confirmation': True,
|
||||
'no_colors': False,
|
||||
'debug': False,
|
||||
'priority': {},
|
||||
'history_limit': None,
|
||||
'alter_history': True,
|
||||
'env': {'LC_ALL': 'C', 'LANG': 'C', 'GIT_TRACE': '1'}}
|
||||
|
||||
ENV_TO_ATTR = {'THEFUCK_RULES': 'rules',
|
||||
'THEFUCK_EXCLUDE_RULES': 'exclude_rules',
|
||||
'THEFUCK_WAIT_COMMAND': 'wait_command',
|
||||
'THEFUCK_REQUIRE_CONFIRMATION': 'require_confirmation',
|
||||
'THEFUCK_NO_COLORS': 'no_colors',
|
||||
'THEFUCK_DEBUG': 'debug',
|
||||
'THEFUCK_PRIORITY': 'priority',
|
||||
'THEFUCK_HISTORY_LIMIT': 'history_limit',
|
||||
'THEFUCK_ALTER_HISTORY': 'alter_history'}
|
||||
|
||||
SETTINGS_HEADER = u"""# The Fuck settings file
|
||||
#
|
||||
# The rules are defined as in the example bellow:
|
||||
#
|
||||
# rules = ['cd_parent', 'git_push', 'python_command', 'sudo']
|
||||
#
|
||||
# The default values are as follows. Uncomment and change to fit your needs.
|
||||
# See https://github.com/nvbn/thefuck#settings for more information.
|
||||
#
|
||||
|
||||
"""
|
||||
|
||||
@@ -7,11 +7,12 @@ from argparse import ArgumentParser
|
||||
from warnings import warn
|
||||
from pprint import pformat
|
||||
import sys
|
||||
from . import logs, types, shells
|
||||
from . import logs, types
|
||||
from .shells import shell
|
||||
from .conf import settings
|
||||
from .corrector import get_corrected_commands
|
||||
from .exceptions import EmptyCommand
|
||||
from .utils import get_installation_info
|
||||
from .utils import get_installation_info, get_alias
|
||||
from .ui import select_command
|
||||
|
||||
|
||||
@@ -42,10 +43,10 @@ def print_alias(entry_point=True):
|
||||
else:
|
||||
position = 2
|
||||
|
||||
alias = shells.thefuck_alias()
|
||||
alias = get_alias()
|
||||
if len(sys.argv) > position:
|
||||
alias = sys.argv[position]
|
||||
print(shells.app_alias(alias))
|
||||
print(shell.app_alias(alias))
|
||||
|
||||
|
||||
def how_to_configure_alias():
|
||||
@@ -55,17 +56,17 @@ def how_to_configure_alias():
|
||||
|
||||
"""
|
||||
settings.init()
|
||||
logs.how_to_configure_alias(shells.how_to_configure())
|
||||
logs.how_to_configure_alias(shell.how_to_configure())
|
||||
|
||||
|
||||
def main():
|
||||
parser = ArgumentParser(prog='thefuck')
|
||||
version = get_installation_info().version
|
||||
parser.add_argument(
|
||||
'-v', '--version',
|
||||
action='version',
|
||||
version='The Fuck {} using Python {}'.format(
|
||||
version, sys.version.split()[0]))
|
||||
'-v', '--version',
|
||||
action='version',
|
||||
version='The Fuck {} using Python {}'.format(
|
||||
version, sys.version.split()[0]))
|
||||
parser.add_argument('-a', '--alias',
|
||||
action='store_true',
|
||||
help='[custom-alias-name] prints alias for current shell')
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
from thefuck import shells
|
||||
from thefuck.specific.apt import apt_available
|
||||
from thefuck.utils import memoize
|
||||
from thefuck.shells import shell
|
||||
|
||||
try:
|
||||
import CommandNotFound
|
||||
enabled_by_default = apt_available
|
||||
except ImportError:
|
||||
enabled_by_default = False
|
||||
|
||||
@@ -26,5 +28,5 @@ def match(command):
|
||||
|
||||
def get_new_command(command):
|
||||
name = get_package(command.script)
|
||||
formatme = shells.and_('sudo apt-get install {}', '{}')
|
||||
formatme = shell.and_('sudo apt-get install {}', '{}')
|
||||
return formatme.format(name, command.script)
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import re
|
||||
from thefuck.specific.apt import apt_available
|
||||
from thefuck.utils import for_app
|
||||
|
||||
enabled_by_default = apt_available
|
||||
|
||||
|
||||
@for_app('apt-get')
|
||||
def match(command):
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import subprocess
|
||||
from thefuck.specific.apt import apt_available
|
||||
from thefuck.specific.sudo import sudo_support
|
||||
from thefuck.utils import for_app, eager, replace_command
|
||||
|
||||
enabled_by_default = apt_available
|
||||
|
||||
|
||||
@for_app('apt', 'apt-get', 'apt-cache')
|
||||
@sudo_support
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import os
|
||||
import re
|
||||
from thefuck.utils import get_closest, replace_argument, which
|
||||
from thefuck.specific.brew import get_brew_path_prefix
|
||||
from thefuck.utils import get_closest, replace_argument
|
||||
from thefuck.specific.brew import get_brew_path_prefix, brew_available
|
||||
|
||||
|
||||
enabled_by_default = bool(which('brew'))
|
||||
enabled_by_default = brew_available
|
||||
|
||||
|
||||
def _get_formulas():
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import os
|
||||
import re
|
||||
from thefuck.utils import get_closest, replace_command
|
||||
from thefuck.specific.brew import get_brew_path_prefix
|
||||
from thefuck.specific.brew import get_brew_path_prefix, brew_available
|
||||
|
||||
BREW_CMD_PATH = '/Library/Homebrew/cmd'
|
||||
TAP_PATH = '/Library/Taps'
|
||||
TAP_CMD_PATH = '/%s/%s/cmd'
|
||||
|
||||
enabled_by_default = brew_available
|
||||
|
||||
|
||||
def _get_brew_commands(brew_path_prefix):
|
||||
"""To get brew default commands on local environment"""
|
||||
@@ -53,7 +55,7 @@ def _brew_commands():
|
||||
if brew_path_prefix:
|
||||
try:
|
||||
return _get_brew_commands(brew_path_prefix) \
|
||||
+ _get_brew_tap_specific_commands(brew_path_prefix)
|
||||
+ _get_brew_tap_specific_commands(brew_path_prefix)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
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')
|
||||
@@ -4,6 +4,9 @@
|
||||
# > brew upgrade
|
||||
# Warning: brew upgrade with no arguments will change behaviour soon!
|
||||
# It currently upgrades all formula but this will soon change to require '--all'.
|
||||
from thefuck.specific.brew import brew_available
|
||||
|
||||
enabled_by_default = brew_available
|
||||
|
||||
|
||||
def match(command):
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import re
|
||||
from thefuck import shells
|
||||
from thefuck.utils import for_app
|
||||
from thefuck.specific.sudo import sudo_support
|
||||
from thefuck.shells import shell
|
||||
|
||||
|
||||
@sudo_support
|
||||
@@ -14,5 +14,5 @@ def match(command):
|
||||
|
||||
@sudo_support
|
||||
def get_new_command(command):
|
||||
repl = shells.and_('mkdir -p \\1', 'cd \\1')
|
||||
repl = shell.and_('mkdir -p \\1', 'cd \\1')
|
||||
return re.sub(r'^cd (.*)', repl, command.script)
|
||||
|
||||
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)
|
||||
@@ -1,7 +1,7 @@
|
||||
import tarfile
|
||||
import os
|
||||
from thefuck import shells
|
||||
from thefuck.utils import for_app
|
||||
from thefuck.shells import shell
|
||||
|
||||
|
||||
tar_extensions = ('.tar', '.tar.Z', '.tar.bz2', '.tar.gz', '.tar.lz',
|
||||
@@ -33,8 +33,8 @@ def match(command):
|
||||
|
||||
|
||||
def get_new_command(command):
|
||||
dir = shells.quote(_tar_file(command.script_parts)[1])
|
||||
return shells.and_('mkdir -p {dir}', '{cmd} -C {dir}') \
|
||||
dir = shell.quote(_tar_file(command.script_parts)[1])
|
||||
return shell.and_('mkdir -p {dir}', '{cmd} -C {dir}') \
|
||||
.format(dir=dir, cmd=command.script)
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import os
|
||||
import zipfile
|
||||
from thefuck.utils import for_app
|
||||
from thefuck.shells import quote
|
||||
from thefuck.shells import shell
|
||||
|
||||
|
||||
def _is_bad_zip(file):
|
||||
@@ -38,7 +38,8 @@ def match(command):
|
||||
|
||||
|
||||
def get_new_command(command):
|
||||
return u'{} -d {}'.format(command.script, quote(_zip_file(command)[:-4]))
|
||||
return u'{} -d {}'.format(
|
||||
command.script, shell.quote(_zip_file(command)[:-4]))
|
||||
|
||||
|
||||
def side_effect(old_cmd, command):
|
||||
|
||||
@@ -2,7 +2,7 @@ import re
|
||||
import os
|
||||
from thefuck.utils import memoize, default_settings
|
||||
from thefuck.conf import settings
|
||||
from thefuck import shells
|
||||
from thefuck.shells import shell
|
||||
|
||||
|
||||
# order is important: only the first match is considered
|
||||
@@ -75,4 +75,4 @@ def get_new_command(command):
|
||||
file=m.group('file'),
|
||||
line=m.group('line'))
|
||||
|
||||
return shells.and_(editor_call, command.script)
|
||||
return shell.and_(editor_call, command.script)
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
import re
|
||||
from thefuck import shells
|
||||
from thefuck.shells import shell
|
||||
from thefuck.specific.git import git_support
|
||||
|
||||
|
||||
@git_support
|
||||
def match(command):
|
||||
return ('did not match any file(s) known to git.' in command.stderr
|
||||
and "Did you forget to 'git add'?" in command.stderr)
|
||||
return 'did not match any file(s) known to git.' in command.stderr
|
||||
|
||||
|
||||
@git_support
|
||||
def get_new_command(command):
|
||||
missing_file = re.findall(
|
||||
r"error: pathspec '([^']*)' "
|
||||
r"did not match any file\(s\) known to git.", command.stderr)[0]
|
||||
r"error: pathspec '([^']*)' "
|
||||
r'did not match any file\(s\) known to git.', command.stderr)[0]
|
||||
|
||||
formatme = shells.and_('git add -- {}', '{}')
|
||||
formatme = shell.and_('git add -- {}', '{}')
|
||||
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)
|
||||
@@ -1,4 +1,4 @@
|
||||
from thefuck import shells
|
||||
from thefuck.shells import shell
|
||||
from thefuck.specific.git import git_support
|
||||
|
||||
|
||||
@@ -11,4 +11,4 @@ def match(command):
|
||||
|
||||
@git_support
|
||||
def get_new_command(command):
|
||||
return shells.and_('git branch --delete list', 'git branch')
|
||||
return shell.and_('git branch --delete list', 'git branch')
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import re
|
||||
import subprocess
|
||||
from thefuck import shells, utils
|
||||
from thefuck import utils
|
||||
from thefuck.utils import replace_argument
|
||||
from thefuck.specific.git import git_support
|
||||
from thefuck.shells import shell
|
||||
|
||||
|
||||
@git_support
|
||||
@@ -34,5 +35,5 @@ def get_new_command(command):
|
||||
if closest_branch:
|
||||
return replace_argument(command.script, missing_file, closest_branch)
|
||||
else:
|
||||
return shells.and_('git branch {}', '{}').format(
|
||||
return shell.and_('git branch {}', '{}').format(
|
||||
missing_file, command.script)
|
||||
|
||||
12
thefuck/rules/git_help_aliased.py
Normal file
12
thefuck/rules/git_help_aliased.py
Normal file
@@ -0,0 +1,12 @@
|
||||
from thefuck.specific.git import git_support
|
||||
|
||||
|
||||
@git_support
|
||||
def match(command):
|
||||
return 'help' in command.script and ' is aliased to ' in command.stdout
|
||||
|
||||
|
||||
@git_support
|
||||
def get_new_command(command):
|
||||
aliased = command.stdout.split('`', 2)[2].split("'", 1)[0].split(' ', 1)[0]
|
||||
return 'git help {}'.format(aliased)
|
||||
@@ -1,4 +1,4 @@
|
||||
from thefuck import shells
|
||||
from thefuck.shells import shell
|
||||
from thefuck.specific.git import git_support
|
||||
|
||||
|
||||
@@ -14,4 +14,4 @@ def get_new_command(command):
|
||||
branch = line.split(' ')[-1]
|
||||
set_upstream = line.replace('<remote>', 'origin')\
|
||||
.replace('<branch>', branch)
|
||||
return shells.and_(set_upstream, command.script)
|
||||
return shell.and_(set_upstream, command.script)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from thefuck import shells
|
||||
from thefuck.shells import shell
|
||||
from thefuck.utils import replace_argument
|
||||
from thefuck.specific.git import git_support
|
||||
|
||||
@@ -13,5 +13,5 @@ def match(command):
|
||||
|
||||
@git_support
|
||||
def get_new_command(command):
|
||||
return shells.and_(replace_argument(command.script, 'push', 'pull'),
|
||||
command.script)
|
||||
return shell.and_(replace_argument(command.script, 'push', 'pull'),
|
||||
command.script)
|
||||
|
||||
14
thefuck/rules/git_remote_seturl_add.py
Normal file
14
thefuck/rules/git_remote_seturl_add.py
Normal file
@@ -0,0 +1,14 @@
|
||||
from thefuck.utils import replace_argument
|
||||
from thefuck.specific.git import git_support
|
||||
|
||||
|
||||
@git_support
|
||||
def match(command):
|
||||
return ('set-url' in command.script and 'fatal: No such remote' in command.stderr)
|
||||
|
||||
|
||||
def get_new_command(command):
|
||||
return replace_argument(command.script, 'set-url', 'add')
|
||||
|
||||
|
||||
enabled_by_default = True
|
||||
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,4 +1,4 @@
|
||||
from thefuck import shells
|
||||
from thefuck.shells import shell
|
||||
from thefuck.specific.git import git_support
|
||||
|
||||
|
||||
@@ -11,5 +11,5 @@ def match(command):
|
||||
|
||||
@git_support
|
||||
def get_new_command(command):
|
||||
formatme = shells.and_('git stash', '{}')
|
||||
formatme = shell.and_('git stash', '{}')
|
||||
return formatme.format(command.script)
|
||||
|
||||
23
thefuck/rules/grep_arguments_order.py
Normal file
23
thefuck/rules/grep_arguments_order.py
Normal file
@@ -0,0 +1,23 @@
|
||||
import os
|
||||
from thefuck.utils import for_app
|
||||
|
||||
|
||||
def _get_actual_file(parts):
|
||||
for part in parts[1:]:
|
||||
if os.path.isfile(part) or os.path.isdir(part):
|
||||
return part
|
||||
|
||||
|
||||
@for_app('grep', 'egrep')
|
||||
def match(command):
|
||||
return ': No such file or directory' in command.stderr \
|
||||
and _get_actual_file(command.script_parts)
|
||||
|
||||
|
||||
def get_new_command(command):
|
||||
actual_file = _get_actual_file(command.script_parts)
|
||||
parts = command.script_parts[::]
|
||||
# Moves file to the end of the script:
|
||||
parts.remove(actual_file)
|
||||
parts.append(actual_file)
|
||||
return ' '.join(parts)
|
||||
@@ -1,37 +1,15 @@
|
||||
from difflib import get_close_matches
|
||||
from thefuck.shells import get_history, thefuck_alias
|
||||
from thefuck.utils import get_closest, memoize, get_all_executables
|
||||
|
||||
|
||||
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 = get_history()
|
||||
tf_alias = thefuck_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]
|
||||
from thefuck.utils import get_closest, get_valid_history_without_current
|
||||
|
||||
|
||||
def match(command):
|
||||
return len(get_close_matches(command.script,
|
||||
_history_of_exists_without_current(command)))
|
||||
get_valid_history_without_current(command)))
|
||||
|
||||
|
||||
def get_new_command(command):
|
||||
return get_closest(command.script,
|
||||
_history_of_exists_without_current(command))
|
||||
get_valid_history_without_current(command))
|
||||
|
||||
|
||||
priority = 9999
|
||||
|
||||
23
thefuck/rules/ln_no_hard_link.py
Normal file
23
thefuck/rules/ln_no_hard_link.py
Normal file
@@ -0,0 +1,23 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Suggest creating symbolic link if hard link is not allowed.
|
||||
|
||||
Example:
|
||||
> ln barDir barLink
|
||||
ln: ‘barDir’: hard link not allowed for directory
|
||||
|
||||
--> ln -s barDir barLink
|
||||
"""
|
||||
|
||||
import re
|
||||
from thefuck.specific.sudo import sudo_support
|
||||
|
||||
|
||||
@sudo_support
|
||||
def match(command):
|
||||
return (command.stderr.endswith("hard link not allowed for directory") and
|
||||
command.script.startswith("ln "))
|
||||
|
||||
|
||||
@sudo_support
|
||||
def get_new_command(command):
|
||||
return re.sub(r'^ln ', 'ln -s ', command.script)
|
||||
@@ -1,5 +1,6 @@
|
||||
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
|
||||
|
||||
|
||||
@@ -11,10 +12,29 @@ def match(command):
|
||||
get_all_executables())))
|
||||
|
||||
|
||||
def _get_used_executables(command):
|
||||
for script in get_valid_history_without_current(command):
|
||||
yield script.split(' ')[0]
|
||||
|
||||
|
||||
@sudo_support
|
||||
def get_new_command(command):
|
||||
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:])
|
||||
for new_command in new_cmds]
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import re
|
||||
from thefuck import shells
|
||||
from thefuck.shells import shell
|
||||
|
||||
|
||||
patterns = (
|
||||
@@ -26,5 +26,5 @@ def get_new_command(command):
|
||||
file = file[0]
|
||||
dir = file[0:file.rfind('/')]
|
||||
|
||||
formatme = shells.and_('mkdir -p {}', '{}')
|
||||
formatme = shell.and_('mkdir -p {}', '{}')
|
||||
return formatme.format(dir, command.script)
|
||||
|
||||
39
thefuck/rules/npm_wrong_command.py
Normal file
39
thefuck/rules/npm_wrong_command.py
Normal file
@@ -0,0 +1,39 @@
|
||||
from thefuck.utils import replace_argument, for_app, eager, get_closest
|
||||
from thefuck.specific.sudo import sudo_support
|
||||
|
||||
|
||||
def _get_wrong_command(script_parts):
|
||||
commands = [part for part in script_parts[1:] if not part.startswith('-')]
|
||||
if commands:
|
||||
return commands[0]
|
||||
|
||||
|
||||
@sudo_support
|
||||
@for_app('npm')
|
||||
def match(command):
|
||||
return (command.script_parts[0] == 'npm' and
|
||||
'where <command> is one of:' in command.stdout and
|
||||
_get_wrong_command(command.script_parts))
|
||||
|
||||
|
||||
@eager
|
||||
def _get_available_commands(stdout):
|
||||
commands_listing = False
|
||||
for line in stdout.split('\n'):
|
||||
if line.startswith('where <command> is one of:'):
|
||||
commands_listing = True
|
||||
elif commands_listing:
|
||||
if not line:
|
||||
break
|
||||
|
||||
for command in line.split(', '):
|
||||
stripped = command.strip()
|
||||
if stripped:
|
||||
yield stripped
|
||||
|
||||
|
||||
def get_new_command(command):
|
||||
npm_commands = _get_available_commands(command.stdout)
|
||||
wrong_command = _get_wrong_command(command.script_parts)
|
||||
fixed = get_closest(wrong_command, npm_commands)
|
||||
return replace_argument(command.script, wrong_command, fixed)
|
||||
@@ -1,5 +1,5 @@
|
||||
from thefuck.specific.archlinux import get_pkgfile, archlinux_env
|
||||
from thefuck import shells
|
||||
from thefuck.shells import shell
|
||||
|
||||
|
||||
def match(command):
|
||||
@@ -9,7 +9,7 @@ def match(command):
|
||||
def get_new_command(command):
|
||||
packages = get_pkgfile(command.script)
|
||||
|
||||
formatme = shells.and_('{} -S {}', '{}')
|
||||
formatme = shell.and_('{} -S {}', '{}')
|
||||
return [formatme.format(pacman, package, command.script)
|
||||
for package in packages]
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import shlex
|
||||
from thefuck.shells import quote
|
||||
from thefuck.shells import shell
|
||||
from thefuck.utils import for_app
|
||||
|
||||
|
||||
@@ -15,4 +15,4 @@ def get_new_command(command):
|
||||
if e.startswith(('s/', '-es/')) and e[-1] != '/':
|
||||
script[i] += '/'
|
||||
|
||||
return ' '.join(map(quote, script))
|
||||
return ' '.join(map(shell.quote, script))
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
patterns = ['permission denied',
|
||||
'EACCES',
|
||||
'pkg: Insufficient privileges',
|
||||
'eacces',
|
||||
'pkg: insufficient privileges',
|
||||
'you cannot perform this operation unless you are root',
|
||||
'non-root users cannot',
|
||||
'Operation not permitted',
|
||||
'operation not permitted',
|
||||
'root privilege',
|
||||
'This command has to be run under the root user.',
|
||||
'This operation requires root.',
|
||||
'this command has to be run under the root user.',
|
||||
'this operation requires root.',
|
||||
'requested operation requires superuser privilege',
|
||||
'must be run as root',
|
||||
'must run as root',
|
||||
@@ -14,10 +14,12 @@ patterns = ['permission denied',
|
||||
'must be root',
|
||||
'need to be root',
|
||||
'need root',
|
||||
'needs to be run as root',
|
||||
'only root can ',
|
||||
'You don\'t have access to the history DB.',
|
||||
'you don\'t have access to the history db.',
|
||||
'authentication is required',
|
||||
'eDSPermissionError']
|
||||
'edspermissionerror',
|
||||
'you don\'t have write permissions']
|
||||
|
||||
|
||||
def match(command):
|
||||
@@ -25,8 +27,8 @@ def match(command):
|
||||
return False
|
||||
|
||||
for pattern in patterns:
|
||||
if pattern.lower() in command.stderr.lower()\
|
||||
or pattern.lower() in command.stdout.lower():
|
||||
if pattern in command.stderr.lower()\
|
||||
or pattern in command.stdout.lower():
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
from thefuck.shells import thefuck_alias
|
||||
from thefuck.utils import memoize
|
||||
from thefuck.utils import memoize, get_alias
|
||||
|
||||
target_layout = '''qwertyuiop[]asdfghjkl;'zxcvbnm,./QWERTYUIOP{}ASDFGHJKL:"ZXCVBNM<>?'''
|
||||
|
||||
@@ -35,7 +34,7 @@ def match(command):
|
||||
return False
|
||||
matched_layout = _get_matched_layout(command)
|
||||
return matched_layout and \
|
||||
_switch_command(command, matched_layout) != thefuck_alias()
|
||||
_switch_command(command, matched_layout) != get_alias()
|
||||
|
||||
|
||||
def get_new_command(command):
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import re
|
||||
from thefuck import shells
|
||||
from thefuck.shells import shell
|
||||
from thefuck.utils import for_app
|
||||
|
||||
|
||||
@@ -10,4 +10,4 @@ def match(command):
|
||||
|
||||
def get_new_command(command):
|
||||
path = re.findall(r"touch: cannot touch '(.+)/.+':", command.stderr)[0]
|
||||
return shells.and_(u'mkdir -p {}'.format(path), command.script)
|
||||
return shell.and_(u'mkdir -p {}'.format(path), command.script)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from thefuck import shells
|
||||
from thefuck.shells import shell
|
||||
from thefuck.utils import for_app
|
||||
|
||||
|
||||
@@ -9,4 +9,4 @@ def match(command):
|
||||
|
||||
|
||||
def get_new_command(command):
|
||||
return shells.and_('tsuru login', command.script)
|
||||
return shell.and_('tsuru login', command.script)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from thefuck import shells
|
||||
from thefuck.shells import shell
|
||||
from thefuck.utils import for_app
|
||||
|
||||
|
||||
@@ -13,8 +13,8 @@ def get_new_command(command):
|
||||
if len(cmds) >= 3:
|
||||
machine = cmds[2]
|
||||
|
||||
startAllInstances = shells.and_("vagrant up", command.script)
|
||||
startAllInstances = shell.and_("vagrant up", command.script)
|
||||
if machine is None:
|
||||
return startAllInstances
|
||||
else:
|
||||
return [shells.and_("vagrant up " + machine, command.script), startAllInstances]
|
||||
return [shell.and_("vagrant up " + machine, command.script), startAllInstances]
|
||||
|
||||
@@ -1,334 +0,0 @@
|
||||
"""Module with shell specific actions, each shell class should
|
||||
implement `from_shell`, `to_shell`, `app_alias`, `put_to_history` and
|
||||
`get_aliases` methods.
|
||||
|
||||
"""
|
||||
from collections import defaultdict
|
||||
from psutil import Process
|
||||
from subprocess import Popen, PIPE
|
||||
from time import time
|
||||
import io
|
||||
import os
|
||||
import shlex
|
||||
import sys
|
||||
import six
|
||||
from .utils import DEVNULL, memoize, cache
|
||||
from .conf import settings
|
||||
from . import logs
|
||||
|
||||
|
||||
class Generic(object):
|
||||
def get_aliases(self):
|
||||
return {}
|
||||
|
||||
def _expand_aliases(self, command_script):
|
||||
aliases = self.get_aliases()
|
||||
binary = command_script.split(' ')[0]
|
||||
if binary in aliases:
|
||||
return command_script.replace(binary, aliases[binary], 1)
|
||||
else:
|
||||
return command_script
|
||||
|
||||
def from_shell(self, command_script):
|
||||
"""Prepares command before running in app."""
|
||||
return self._expand_aliases(command_script)
|
||||
|
||||
def to_shell(self, command_script):
|
||||
"""Prepares command for running in shell."""
|
||||
return command_script
|
||||
|
||||
def app_alias(self, fuck):
|
||||
return "alias {0}='TF_ALIAS={0} PYTHONIOENCODING=utf-8 " \
|
||||
"eval $(thefuck $(fc -ln -1))'".format(fuck)
|
||||
|
||||
def _get_history_file_name(self):
|
||||
return ''
|
||||
|
||||
def _get_history_line(self, command_script):
|
||||
return ''
|
||||
|
||||
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)
|
||||
|
||||
def get_history(self):
|
||||
"""Returns list of history entries."""
|
||||
history_file_name = self._get_history_file_name()
|
||||
if os.path.isfile(history_file_name):
|
||||
with io.open(history_file_name, 'r',
|
||||
encoding='utf-8', errors='ignore') as history_file:
|
||||
|
||||
lines = history_file.readlines()
|
||||
if settings.history_limit:
|
||||
lines = lines[-settings.history_limit:]
|
||||
|
||||
for line in lines:
|
||||
prepared = self._script_from_history(line) \
|
||||
.strip()
|
||||
if prepared:
|
||||
yield prepared
|
||||
|
||||
def and_(self, *commands):
|
||||
return u' && '.join(commands)
|
||||
|
||||
def how_to_configure(self):
|
||||
return
|
||||
|
||||
def split_command(self, command):
|
||||
"""Split the command using shell-like syntax."""
|
||||
if six.PY2:
|
||||
return [s.decode('utf8') for s in shlex.split(command.encode('utf8'))]
|
||||
return shlex.split(command)
|
||||
|
||||
def quote(self, s):
|
||||
"""Return a shell-escaped version of the string s."""
|
||||
|
||||
if six.PY2:
|
||||
from pipes import quote
|
||||
else:
|
||||
from shlex import quote
|
||||
|
||||
return quote(s)
|
||||
|
||||
def _script_from_history(self, line):
|
||||
return line
|
||||
|
||||
|
||||
class Bash(Generic):
|
||||
def app_alias(self, fuck):
|
||||
return "TF_ALIAS={0} alias {0}='PYTHONIOENCODING=utf-8 " \
|
||||
"eval $(thefuck $(fc -ln -1));" \
|
||||
" history -r'".format(fuck)
|
||||
|
||||
def _parse_alias(self, alias):
|
||||
name, value = alias.replace('alias ', '', 1).split('=', 1)
|
||||
if value[0] == value[-1] == '"' or value[0] == value[-1] == "'":
|
||||
value = value[1:-1]
|
||||
return name, value
|
||||
|
||||
@memoize
|
||||
@cache('.bashrc', '.bash_profile')
|
||||
def get_aliases(self):
|
||||
proc = Popen(['bash', '-ic', 'alias'], stdout=PIPE, stderr=DEVNULL)
|
||||
return dict(
|
||||
self._parse_alias(alias)
|
||||
for alias in proc.stdout.read().decode('utf-8').split('\n')
|
||||
if alias and '=' in alias)
|
||||
|
||||
def _get_history_file_name(self):
|
||||
return os.environ.get("HISTFILE",
|
||||
os.path.expanduser('~/.bash_history'))
|
||||
|
||||
def _get_history_line(self, command_script):
|
||||
return u'{}\n'.format(command_script)
|
||||
|
||||
def how_to_configure(self):
|
||||
if os.path.join(os.path.expanduser('~'), '.bashrc'):
|
||||
config = '~/.bashrc'
|
||||
elif os.path.join(os.path.expanduser('~'), '.bash_profile'):
|
||||
config = '~/.bashrc'
|
||||
else:
|
||||
config = 'bash config'
|
||||
return 'eval $(thefuck --alias)', config
|
||||
|
||||
|
||||
class Fish(Generic):
|
||||
def _get_overridden_aliases(self):
|
||||
overridden_aliases = os.environ.get('TF_OVERRIDDEN_ALIASES', '').strip()
|
||||
if overridden_aliases:
|
||||
return [alias.strip() for alias in overridden_aliases.split(',')]
|
||||
else:
|
||||
return ['cd', 'grep', 'ls', 'man', 'open']
|
||||
|
||||
def app_alias(self, fuck):
|
||||
return ('function {0} -d "Correct your previous console command"\n'
|
||||
' set -l exit_code $status\n'
|
||||
' set -l fucked_up_command $history[1]\n'
|
||||
' env TF_ALIAS={0} PYTHONIOENCODING=utf-8'
|
||||
' thefuck $fucked_up_command | read -l unfucked_command\n'
|
||||
' if [ "$unfucked_command" != "" ]\n'
|
||||
' eval $unfucked_command\n'
|
||||
' if test $exit_code -ne 0\n'
|
||||
' history --delete $fucked_up_command\n'
|
||||
' history --merge ^ /dev/null\n'
|
||||
' return 0\n'
|
||||
' end\n'
|
||||
' end\n'
|
||||
'end').format(fuck)
|
||||
|
||||
@memoize
|
||||
def get_aliases(self):
|
||||
overridden = self._get_overridden_aliases()
|
||||
proc = Popen(['fish', '-ic', 'functions'], stdout=PIPE, stderr=DEVNULL)
|
||||
functions = proc.stdout.read().decode('utf-8').strip().split('\n')
|
||||
return {func: func for func in functions if func not in overridden}
|
||||
|
||||
def _expand_aliases(self, command_script):
|
||||
aliases = self.get_aliases()
|
||||
binary = command_script.split(' ')[0]
|
||||
if binary in aliases:
|
||||
return u'fish -ic "{}"'.format(command_script.replace('"', r'\"'))
|
||||
else:
|
||||
return command_script
|
||||
|
||||
def from_shell(self, command_script):
|
||||
"""Prepares command before running in app."""
|
||||
return self._expand_aliases(command_script)
|
||||
|
||||
def _get_history_file_name(self):
|
||||
return os.path.expanduser('~/.config/fish/fish_history')
|
||||
|
||||
def _get_history_line(self, command_script):
|
||||
return u'- cmd: {}\n when: {}\n'.format(command_script, int(time()))
|
||||
|
||||
def _script_from_history(self, line):
|
||||
if '- cmd: ' in line:
|
||||
return line.split('- cmd: ', 1)[1]
|
||||
else:
|
||||
return ''
|
||||
|
||||
def and_(self, *commands):
|
||||
return u'; and '.join(commands)
|
||||
|
||||
def how_to_configure(self):
|
||||
return 'eval thefuck --alias', '~/.config/fish/config.fish'
|
||||
|
||||
|
||||
class Zsh(Generic):
|
||||
def app_alias(self, fuck):
|
||||
return "TF_ALIAS={0}" \
|
||||
" alias {0}='PYTHONIOENCODING=utf-8 " \
|
||||
"eval $(thefuck $(fc -ln -1 | tail -n 1));" \
|
||||
" fc -R'".format(fuck)
|
||||
|
||||
def _parse_alias(self, alias):
|
||||
name, value = alias.split('=', 1)
|
||||
if value[0] == value[-1] == '"' or value[0] == value[-1] == "'":
|
||||
value = value[1:-1]
|
||||
return name, value
|
||||
|
||||
@memoize
|
||||
@cache('.zshrc')
|
||||
def get_aliases(self):
|
||||
proc = Popen(['zsh', '-ic', 'alias'], stdout=PIPE, stderr=DEVNULL)
|
||||
return dict(
|
||||
self._parse_alias(alias)
|
||||
for alias in proc.stdout.read().decode('utf-8').split('\n')
|
||||
if alias and '=' in alias)
|
||||
|
||||
def _get_history_file_name(self):
|
||||
return os.environ.get("HISTFILE",
|
||||
os.path.expanduser('~/.zsh_history'))
|
||||
|
||||
def _get_history_line(self, command_script):
|
||||
return u': {}:0;{}\n'.format(int(time()), command_script)
|
||||
|
||||
def _script_from_history(self, line):
|
||||
if ';' in line:
|
||||
return line.split(';', 1)[1]
|
||||
else:
|
||||
return ''
|
||||
|
||||
def how_to_configure(self):
|
||||
return 'eval $(thefuck --alias)', '~/.zshrc'
|
||||
|
||||
|
||||
class Tcsh(Generic):
|
||||
def app_alias(self, fuck):
|
||||
return ("alias {0} 'setenv TF_ALIAS {0} && "
|
||||
"set fucked_cmd=`history -h 2 | head -n 1` && "
|
||||
"eval `thefuck ${{fucked_cmd}}`'").format(fuck)
|
||||
|
||||
def _parse_alias(self, alias):
|
||||
name, value = alias.split("\t", 1)
|
||||
return name, value
|
||||
|
||||
@memoize
|
||||
def get_aliases(self):
|
||||
proc = Popen(['tcsh', '-ic', 'alias'], stdout=PIPE, stderr=DEVNULL)
|
||||
return dict(
|
||||
self._parse_alias(alias)
|
||||
for alias in proc.stdout.read().decode('utf-8').split('\n')
|
||||
if alias and '\t' in alias)
|
||||
|
||||
def _get_history_file_name(self):
|
||||
return os.environ.get("HISTFILE",
|
||||
os.path.expanduser('~/.history'))
|
||||
|
||||
def _get_history_line(self, command_script):
|
||||
return u'#+{}\n{}\n'.format(int(time()), command_script)
|
||||
|
||||
def how_to_configure(self):
|
||||
return 'eval `thefuck --alias`', '~/.tcshrc'
|
||||
|
||||
|
||||
shells = defaultdict(Generic, {
|
||||
'bash': Bash(),
|
||||
'fish': Fish(),
|
||||
'zsh': Zsh(),
|
||||
'csh': Tcsh(),
|
||||
'tcsh': Tcsh()})
|
||||
|
||||
|
||||
@memoize
|
||||
def _get_shell():
|
||||
try:
|
||||
shell = Process(os.getpid()).parent().name()
|
||||
except TypeError:
|
||||
shell = Process(os.getpid()).parent.name
|
||||
return shells[shell]
|
||||
|
||||
|
||||
def from_shell(command):
|
||||
return _get_shell().from_shell(command)
|
||||
|
||||
|
||||
def to_shell(command):
|
||||
return _get_shell().to_shell(command)
|
||||
|
||||
|
||||
def app_alias(alias):
|
||||
return _get_shell().app_alias(alias)
|
||||
|
||||
|
||||
def thefuck_alias():
|
||||
return os.environ.get('TF_ALIAS', 'fuck')
|
||||
|
||||
|
||||
def put_to_history(command):
|
||||
try:
|
||||
return _get_shell().put_to_history(command)
|
||||
except IOError:
|
||||
logs.exception("Can't update history", sys.exc_info())
|
||||
|
||||
|
||||
def and_(*commands):
|
||||
return _get_shell().and_(*commands)
|
||||
|
||||
|
||||
def get_aliases():
|
||||
return list(_get_shell().get_aliases().keys())
|
||||
|
||||
|
||||
def split_command(command):
|
||||
return _get_shell().split_command(command)
|
||||
|
||||
|
||||
def quote(s):
|
||||
return _get_shell().quote(s)
|
||||
|
||||
|
||||
@memoize
|
||||
def get_history():
|
||||
return list(_get_shell().get_history())
|
||||
|
||||
|
||||
def how_to_configure():
|
||||
return _get_shell().how_to_configure()
|
||||
44
thefuck/shells/__init__.py
Normal file
44
thefuck/shells/__init__.py
Normal file
@@ -0,0 +1,44 @@
|
||||
"""Package with shell specific actions, each shell class should
|
||||
implement `from_shell`, `to_shell`, `app_alias`, `put_to_history` and
|
||||
`get_aliases` methods.
|
||||
"""
|
||||
import os
|
||||
from psutil import Process
|
||||
from .bash import Bash
|
||||
from .fish import Fish
|
||||
from .generic import Generic
|
||||
from .tcsh import Tcsh
|
||||
from .zsh import Zsh
|
||||
from .powershell import Powershell
|
||||
|
||||
shells = {'bash': Bash,
|
||||
'fish': Fish,
|
||||
'zsh': Zsh,
|
||||
'csh': Tcsh,
|
||||
'tcsh': Tcsh,
|
||||
'powershell': Powershell}
|
||||
|
||||
|
||||
def _get_shell():
|
||||
proc = Process(os.getpid())
|
||||
|
||||
while proc is not None:
|
||||
try:
|
||||
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()
|
||||
47
thefuck/shells/bash.py
Normal file
47
thefuck/shells/bash.py
Normal file
@@ -0,0 +1,47 @@
|
||||
import os
|
||||
from ..conf import settings
|
||||
from ..utils import memoize
|
||||
from .generic import Generic
|
||||
|
||||
|
||||
class Bash(Generic):
|
||||
def app_alias(self, fuck):
|
||||
# It is VERY important to have the variables declared WITHIN the alias
|
||||
alias = "alias {0}='TF_CMD=$(TF_ALIAS={0}" \
|
||||
" PYTHONIOENCODING=utf-8" \
|
||||
" TF_SHELL_ALIASES=$(alias)" \
|
||||
" thefuck $(fc -ln -1)) &&" \
|
||||
" eval $TF_CMD".format(fuck)
|
||||
|
||||
if settings.alter_history:
|
||||
return alias + " && history -s $TF_CMD'"
|
||||
else:
|
||||
return alias + "'"
|
||||
|
||||
def _parse_alias(self, alias):
|
||||
name, value = alias.replace('alias ', '', 1).split('=', 1)
|
||||
if value[0] == value[-1] == '"' or value[0] == value[-1] == "'":
|
||||
value = value[1:-1]
|
||||
return name, value
|
||||
|
||||
@memoize
|
||||
def get_aliases(self):
|
||||
raw_aliases = os.environ.get('TF_SHELL_ALIASES', '').split('\n')
|
||||
return dict(self._parse_alias(alias)
|
||||
for alias in raw_aliases if alias and '=' in alias)
|
||||
|
||||
def _get_history_file_name(self):
|
||||
return os.environ.get("HISTFILE",
|
||||
os.path.expanduser('~/.bash_history'))
|
||||
|
||||
def _get_history_line(self, command_script):
|
||||
return u'{}\n'.format(command_script)
|
||||
|
||||
def how_to_configure(self):
|
||||
if os.path.join(os.path.expanduser('~'), '.bashrc'):
|
||||
config = '~/.bashrc'
|
||||
elif os.path.join(os.path.expanduser('~'), '.bash_profile'):
|
||||
config = '~/.bashrc'
|
||||
else:
|
||||
config = 'bash config'
|
||||
return 'eval $(thefuck --alias)', config
|
||||
87
thefuck/shells/fish.py
Normal file
87
thefuck/shells/fish.py
Normal file
@@ -0,0 +1,87 @@
|
||||
from subprocess import Popen, PIPE
|
||||
from time import time
|
||||
import os
|
||||
import sys
|
||||
import six
|
||||
from .. import logs
|
||||
from ..utils import DEVNULL, memoize, cache
|
||||
from .generic import Generic
|
||||
|
||||
|
||||
class Fish(Generic):
|
||||
def _get_overridden_aliases(self):
|
||||
overridden = os.environ.get('THEFUCK_OVERRIDDEN_ALIASES',
|
||||
os.environ.get('TF_OVERRIDDEN_ALIASES', ''))
|
||||
default = {'cd', 'grep', 'ls', 'man', 'open'}
|
||||
for alias in overridden.split(','):
|
||||
default.add(alias.strip())
|
||||
return default
|
||||
|
||||
def app_alias(self, fuck):
|
||||
# It is VERY important to have the variables declared WITHIN the alias
|
||||
return ('function {0} -d "Correct your previous console command"\n'
|
||||
' set -l fucked_up_command $history[1]\n'
|
||||
' env TF_ALIAS={0} PYTHONIOENCODING=utf-8'
|
||||
' thefuck $fucked_up_command | read -l unfucked_command\n'
|
||||
' if [ "$unfucked_command" != "" ]\n'
|
||||
' eval $unfucked_command\n'
|
||||
' history --delete $fucked_up_command\n'
|
||||
' history --merge ^ /dev/null\n'
|
||||
' end\n'
|
||||
'end').format(fuck)
|
||||
|
||||
@memoize
|
||||
@cache('.config/fish/config.fish', '.config/fish/functions')
|
||||
def get_aliases(self):
|
||||
overridden = self._get_overridden_aliases()
|
||||
proc = Popen(['fish', '-ic', 'functions'], stdout=PIPE, stderr=DEVNULL)
|
||||
functions = proc.stdout.read().decode('utf-8').strip().split('\n')
|
||||
return {func: func for func in functions if func not in overridden}
|
||||
|
||||
def _expand_aliases(self, command_script):
|
||||
aliases = self.get_aliases()
|
||||
binary = command_script.split(' ')[0]
|
||||
if binary in aliases:
|
||||
return u'fish -ic "{}"'.format(command_script.replace('"', r'\"'))
|
||||
else:
|
||||
return command_script
|
||||
|
||||
def from_shell(self, command_script):
|
||||
"""Prepares command before running in app."""
|
||||
return self._expand_aliases(command_script)
|
||||
|
||||
def _get_history_file_name(self):
|
||||
return os.path.expanduser('~/.config/fish/fish_history')
|
||||
|
||||
def _get_history_line(self, command_script):
|
||||
return u'- cmd: {}\n when: {}\n'.format(command_script, int(time()))
|
||||
|
||||
def _script_from_history(self, line):
|
||||
if '- cmd: ' in line:
|
||||
return line.split('- cmd: ', 1)[1]
|
||||
else:
|
||||
return ''
|
||||
|
||||
def and_(self, *commands):
|
||||
return u'; and '.join(commands)
|
||||
|
||||
def how_to_configure(self):
|
||||
return (r"eval (thefuck --alias | tr '\n' ';')",
|
||||
'~/.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)
|
||||
91
thefuck/shells/generic.py
Normal file
91
thefuck/shells/generic.py
Normal file
@@ -0,0 +1,91 @@
|
||||
import io
|
||||
import os
|
||||
import shlex
|
||||
import six
|
||||
from ..utils import memoize
|
||||
from ..conf import settings
|
||||
|
||||
|
||||
class Generic(object):
|
||||
def get_aliases(self):
|
||||
return {}
|
||||
|
||||
def _expand_aliases(self, command_script):
|
||||
aliases = self.get_aliases()
|
||||
binary = command_script.split(' ')[0]
|
||||
if binary in aliases:
|
||||
return command_script.replace(binary, aliases[binary], 1)
|
||||
else:
|
||||
return command_script
|
||||
|
||||
def from_shell(self, command_script):
|
||||
"""Prepares command before running in app."""
|
||||
return self._expand_aliases(command_script)
|
||||
|
||||
def to_shell(self, command_script):
|
||||
"""Prepares command for running in shell."""
|
||||
return command_script
|
||||
|
||||
def app_alias(self, fuck):
|
||||
return "alias {0}='eval $(TF_ALIAS={0} PYTHONIOENCODING=utf-8 " \
|
||||
"thefuck $(fc -ln -1))'".format(fuck)
|
||||
|
||||
def _get_history_file_name(self):
|
||||
return ''
|
||||
|
||||
def _get_history_line(self, command_script):
|
||||
return ''
|
||||
|
||||
@memoize
|
||||
def get_history(self):
|
||||
return list(self._get_history_lines())
|
||||
|
||||
def _get_history_lines(self):
|
||||
"""Returns list of history entries."""
|
||||
history_file_name = self._get_history_file_name()
|
||||
if os.path.isfile(history_file_name):
|
||||
with io.open(history_file_name, 'r',
|
||||
encoding='utf-8', errors='ignore') as history_file:
|
||||
|
||||
lines = history_file.readlines()
|
||||
if settings.history_limit:
|
||||
lines = lines[-settings.history_limit:]
|
||||
|
||||
for line in lines:
|
||||
prepared = self._script_from_history(line) \
|
||||
.strip()
|
||||
if prepared:
|
||||
yield prepared
|
||||
|
||||
def and_(self, *commands):
|
||||
return u' && '.join(commands)
|
||||
|
||||
def how_to_configure(self):
|
||||
return
|
||||
|
||||
def split_command(self, command):
|
||||
"""Split the command using shell-like syntax."""
|
||||
if six.PY2:
|
||||
return [s.decode('utf8') for s in shlex.split(command.encode('utf8'))]
|
||||
return shlex.split(command)
|
||||
|
||||
def quote(self, s):
|
||||
"""Return a shell-escaped version of the string s."""
|
||||
|
||||
if six.PY2:
|
||||
from pipes import quote
|
||||
else:
|
||||
from shlex import quote
|
||||
|
||||
return quote(s)
|
||||
|
||||
def _script_from_history(self, 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'
|
||||
34
thefuck/shells/tcsh.py
Normal file
34
thefuck/shells/tcsh.py
Normal file
@@ -0,0 +1,34 @@
|
||||
from subprocess import Popen, PIPE
|
||||
from time import time
|
||||
import os
|
||||
from ..utils import DEVNULL, memoize
|
||||
from .generic import Generic
|
||||
|
||||
|
||||
class Tcsh(Generic):
|
||||
def app_alias(self, fuck):
|
||||
return ("alias {0} 'setenv TF_ALIAS {0} && "
|
||||
"set fucked_cmd=`history -h 2 | head -n 1` && "
|
||||
"eval `thefuck ${{fucked_cmd}}`'").format(fuck)
|
||||
|
||||
def _parse_alias(self, alias):
|
||||
name, value = alias.split("\t", 1)
|
||||
return name, value
|
||||
|
||||
@memoize
|
||||
def get_aliases(self):
|
||||
proc = Popen(['tcsh', '-ic', 'alias'], stdout=PIPE, stderr=DEVNULL)
|
||||
return dict(
|
||||
self._parse_alias(alias)
|
||||
for alias in proc.stdout.read().decode('utf-8').split('\n')
|
||||
if alias and '\t' in alias)
|
||||
|
||||
def _get_history_file_name(self):
|
||||
return os.environ.get("HISTFILE",
|
||||
os.path.expanduser('~/.history'))
|
||||
|
||||
def _get_history_line(self, command_script):
|
||||
return u'#+{}\n{}\n'.format(int(time()), command_script)
|
||||
|
||||
def how_to_configure(self):
|
||||
return 'eval `thefuck --alias`', '~/.tcshrc'
|
||||
48
thefuck/shells/zsh.py
Normal file
48
thefuck/shells/zsh.py
Normal file
@@ -0,0 +1,48 @@
|
||||
from time import time
|
||||
import os
|
||||
from ..conf import settings
|
||||
from ..utils import memoize
|
||||
from .generic import Generic
|
||||
|
||||
|
||||
class Zsh(Generic):
|
||||
def app_alias(self, alias_name):
|
||||
# It is VERY important to have the variables declared WITHIN the alias
|
||||
alias = "alias {0}='TF_CMD=$(TF_ALIAS={0}" \
|
||||
" PYTHONIOENCODING=utf-8" \
|
||||
" TF_SHELL_ALIASES=$(alias)" \
|
||||
" thefuck $(fc -ln -1 | tail -n 1)) &&" \
|
||||
" eval $TF_CMD".format(alias_name)
|
||||
|
||||
if settings.alter_history:
|
||||
return alias + " && print -s $TF_CMD'"
|
||||
else:
|
||||
return alias + "'"
|
||||
|
||||
def _parse_alias(self, alias):
|
||||
name, value = alias.split('=', 1)
|
||||
if value[0] == value[-1] == '"' or value[0] == value[-1] == "'":
|
||||
value = value[1:-1]
|
||||
return name, value
|
||||
|
||||
@memoize
|
||||
def get_aliases(self):
|
||||
raw_aliases = os.environ.get('TF_SHELL_ALIASES', '').split('\n')
|
||||
return dict(self._parse_alias(alias)
|
||||
for alias in raw_aliases if alias and '=' in alias)
|
||||
|
||||
def _get_history_file_name(self):
|
||||
return os.environ.get("HISTFILE",
|
||||
os.path.expanduser('~/.zsh_history'))
|
||||
|
||||
def _get_history_line(self, command_script):
|
||||
return u': {}:0;{}\n'.format(int(time()), command_script)
|
||||
|
||||
def _script_from_history(self, line):
|
||||
if ';' in line:
|
||||
return line.split(';', 1)[1]
|
||||
else:
|
||||
return ''
|
||||
|
||||
def how_to_configure(self):
|
||||
return 'eval $(thefuck --alias)', '~/.zshrc'
|
||||
3
thefuck/specific/apt.py
Normal file
3
thefuck/specific/apt.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from thefuck.utils import which
|
||||
|
||||
apt_available = bool(which('apt-get'))
|
||||
@@ -2,7 +2,7 @@ import subprocess
|
||||
from ..utils import memoize, which
|
||||
|
||||
|
||||
enabled_by_default = bool(which('brew'))
|
||||
brew_available = bool(which('brew'))
|
||||
|
||||
|
||||
@memoize
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import re
|
||||
from decorator import decorator
|
||||
from ..utils import is_app
|
||||
from ..shells import quote, split_command
|
||||
from ..shells import shell
|
||||
|
||||
|
||||
@decorator
|
||||
@@ -23,7 +23,8 @@ def git_support(fn, command):
|
||||
# 'commit' '--amend'
|
||||
# which is surprising and does not allow to easily test for
|
||||
# eg. 'git commit'
|
||||
expansion = ' '.join(map(quote, split_command(search.group(2))))
|
||||
expansion = ' '.join(shell.quote(part)
|
||||
for part in shell.split_command(search.group(2)))
|
||||
new_script = command.script.replace(alias, expansion)
|
||||
|
||||
command = command.update(script=new_script)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import os
|
||||
import sys
|
||||
import msvcrt
|
||||
import win_unicode_console
|
||||
@@ -16,10 +17,11 @@ def get_key():
|
||||
ch = msvcrt.getch() # second call returns the actual key code
|
||||
|
||||
if ch == b'\x03':
|
||||
raise const.KEY_CTRL_C
|
||||
return const.KEY_CTRL_C
|
||||
if ch == b'H':
|
||||
return const.KEY_UP
|
||||
if ch == b'P':
|
||||
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)
|
||||
|
||||
@@ -4,8 +4,10 @@ import os
|
||||
import sys
|
||||
import six
|
||||
from psutil import Process, TimeoutExpired
|
||||
from . import logs, shells
|
||||
from .conf import settings, DEFAULT_PRIORITY, ALL_ENABLED
|
||||
from . import logs
|
||||
from .shells import shell
|
||||
from .conf import settings
|
||||
from .const import DEFAULT_PRIORITY, ALL_ENABLED
|
||||
from .exceptions import EmptyCommand
|
||||
from .utils import compatibility_call
|
||||
|
||||
@@ -29,7 +31,7 @@ class Command(object):
|
||||
def script_parts(self):
|
||||
if not hasattr(self, '_script_parts'):
|
||||
try:
|
||||
self._script_parts = shells.split_command(self.script)
|
||||
self._script_parts = shell.split_command(self.script)
|
||||
except Exception:
|
||||
logs.debug(u"Can't split command script {} because:\n {}".format(
|
||||
self, sys.exc_info()))
|
||||
@@ -93,7 +95,7 @@ class Command(object):
|
||||
script = ' '.join(raw_script)
|
||||
|
||||
script = script.strip()
|
||||
return shells.from_shell(script)
|
||||
return shell.from_shell(script)
|
||||
|
||||
@classmethod
|
||||
def from_raw_script(cls, raw_script):
|
||||
@@ -112,7 +114,7 @@ class Command(object):
|
||||
env.update(settings.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):
|
||||
stdout = result.stdout.read().decode('utf-8')
|
||||
stderr = result.stderr.read().decode('utf-8')
|
||||
@@ -279,6 +281,8 @@ class CorrectedCommand(object):
|
||||
if self.side_effect:
|
||||
compatibility_call(self.side_effect, old_cmd, self.script)
|
||||
if settings.alter_history:
|
||||
shells.put_to_history(self.script)
|
||||
shell.put_to_history(self.script)
|
||||
# This depends on correct setting of PYTHONIOENCODING by the alias:
|
||||
logs.debug(u'PYTHONIOENCODING: {}'.format(
|
||||
os.environ.get('PYTHONIOENCODING', '!!not-set!!')))
|
||||
print(self.script)
|
||||
|
||||
@@ -6,11 +6,6 @@ from .exceptions import NoRuleMatched
|
||||
from .system import get_key
|
||||
from . import logs, const
|
||||
|
||||
SELECT = 0
|
||||
ABORT = 1
|
||||
PREVIOUS = 2
|
||||
NEXT = 3
|
||||
|
||||
|
||||
def read_actions():
|
||||
"""Yields actions for pressed keys."""
|
||||
@@ -18,13 +13,13 @@ def read_actions():
|
||||
key = get_key()
|
||||
|
||||
if key in (const.KEY_UP, 'k'):
|
||||
yield PREVIOUS
|
||||
yield const.ACTION_PREVIOUS
|
||||
elif key in (const.KEY_DOWN, 'j'):
|
||||
yield NEXT
|
||||
yield const.ACTION_NEXT
|
||||
elif key == const.KEY_CTRL_C:
|
||||
yield ABORT
|
||||
yield const.ACTION_ABORT
|
||||
elif key in ('\n', '\r'):
|
||||
yield SELECT
|
||||
yield const.ACTION_SELECT
|
||||
|
||||
|
||||
class CommandSelector(object):
|
||||
@@ -83,15 +78,15 @@ def select_command(corrected_commands):
|
||||
logs.confirm_text(selector.value)
|
||||
|
||||
for action in read_actions():
|
||||
if action == SELECT:
|
||||
if action == const.ACTION_SELECT:
|
||||
sys.stderr.write('\n')
|
||||
return selector.value
|
||||
elif action == ABORT:
|
||||
elif action == const.ACTION_ABORT:
|
||||
logs.failed('\nAborted')
|
||||
return
|
||||
elif action == PREVIOUS:
|
||||
elif action == const.ACTION_PREVIOUS:
|
||||
selector.previous()
|
||||
logs.confirm_text(selector.value)
|
||||
elif action == NEXT:
|
||||
elif action == const.ACTION_NEXT:
|
||||
selector.next()
|
||||
logs.confirm_text(selector.value)
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import dbm
|
||||
import os
|
||||
import pickle
|
||||
import pkg_resources
|
||||
import re
|
||||
import shelve
|
||||
import six
|
||||
from .conf import settings
|
||||
from contextlib import closing
|
||||
from decorator import decorator
|
||||
@@ -15,6 +15,13 @@ from warnings import warn
|
||||
|
||||
DEVNULL = open(os.devnull, 'w')
|
||||
|
||||
if six.PY2:
|
||||
import anydbm
|
||||
shelve_open_error = anydbm.error
|
||||
else:
|
||||
import dbm
|
||||
shelve_open_error = dbm.error
|
||||
|
||||
|
||||
def memoize(fn):
|
||||
"""Caches previous calls to the function."""
|
||||
@@ -40,22 +47,26 @@ memoize.disabled = False
|
||||
@memoize
|
||||
def which(program):
|
||||
"""Returns `program` path or `None`."""
|
||||
try:
|
||||
from shutil import which
|
||||
|
||||
def is_exe(fpath):
|
||||
return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
|
||||
return which(program)
|
||||
except ImportError:
|
||||
def is_exe(fpath):
|
||||
return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
|
||||
|
||||
fpath, fname = os.path.split(program)
|
||||
if fpath:
|
||||
if is_exe(program):
|
||||
return program
|
||||
else:
|
||||
for path in os.environ["PATH"].split(os.pathsep):
|
||||
path = path.strip('"')
|
||||
exe_file = os.path.join(path, program)
|
||||
if is_exe(exe_file):
|
||||
return exe_file
|
||||
fpath, fname = os.path.split(program)
|
||||
if fpath:
|
||||
if is_exe(program):
|
||||
return program
|
||||
else:
|
||||
for path in os.environ["PATH"].split(os.pathsep):
|
||||
path = path.strip('"')
|
||||
exe_file = os.path.join(path, program)
|
||||
if is_exe(exe_file):
|
||||
return exe_file
|
||||
|
||||
return None
|
||||
return None
|
||||
|
||||
|
||||
def default_settings(params):
|
||||
@@ -87,7 +98,7 @@ def get_closest(word, possibilities, n=3, cutoff=0.6, fallback_to_first=True):
|
||||
|
||||
@memoize
|
||||
def get_all_executables():
|
||||
from thefuck.shells import thefuck_alias, get_aliases
|
||||
from thefuck.shells import shell
|
||||
|
||||
def _safe(fn, fallback):
|
||||
try:
|
||||
@@ -95,7 +106,7 @@ def get_all_executables():
|
||||
except OSError:
|
||||
return fallback
|
||||
|
||||
tf_alias = thefuck_alias()
|
||||
tf_alias = get_alias()
|
||||
tf_entry_points = get_installation_info().get_entry_map()\
|
||||
.get('console_scripts', {})\
|
||||
.keys()
|
||||
@@ -104,7 +115,7 @@ def get_all_executables():
|
||||
for exe in _safe(lambda: list(Path(path).iterdir()), [])
|
||||
if not _safe(exe.is_dir, True)
|
||||
and exe.name not in tf_entry_points]
|
||||
aliases = [alias for alias in get_aliases() if alias != tf_alias]
|
||||
aliases = [alias for alias in shell.get_aliases() if alias != tf_alias]
|
||||
return bins + aliases
|
||||
|
||||
|
||||
@@ -217,8 +228,8 @@ def cache(*depends_on):
|
||||
value = fn(*args, **kwargs)
|
||||
db[key] = {'etag': etag, 'value': value}
|
||||
return value
|
||||
except dbm.error:
|
||||
# Caused when going from Python 2 to Python 3
|
||||
except (shelve_open_error, ImportError):
|
||||
# Caused when switching between Python versions
|
||||
warn("Removing possibly out-dated cache")
|
||||
os.remove(cache_path)
|
||||
|
||||
@@ -254,3 +265,28 @@ def compatibility_call(fn, *args):
|
||||
|
||||
def get_installation_info():
|
||||
return pkg_resources.require('thefuck')[0]
|
||||
|
||||
|
||||
def get_alias():
|
||||
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