mirror of
https://github.com/nvbn/thefuck.git
synced 2025-11-01 15:42:06 +00:00
Compare commits
37 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c8d748e095 | ||
|
|
e0af35819d | ||
|
|
9e4c250e4e | ||
|
|
8c395377f8 | ||
|
|
f165523247 | ||
|
|
6d39b78824 | ||
|
|
1285303363 | ||
|
|
66e2ec7e3f | ||
|
|
92cca7b641 | ||
|
|
e572cab1f3 | ||
|
|
33b1536c28 | ||
|
|
d4fada8e4c | ||
|
|
afc089bc3c | ||
|
|
300c8f528a | ||
|
|
7b011a504d | ||
|
|
164103693b | ||
|
|
a21c99200e | ||
|
|
1b961c4b87 | ||
|
|
a849b65352 | ||
|
|
dee018e792 | ||
|
|
c67560864a | ||
|
|
b636e9bec7 | ||
|
|
36450b740f | ||
|
|
0f67aad93b | ||
|
|
bb7579ead5 | ||
|
|
569709388d | ||
|
|
baf7796295 | ||
|
|
7b32f1df04 | ||
|
|
cd084c8ba6 | ||
|
|
4f5659caad | ||
|
|
370f258b89 | ||
|
|
9a069daada | ||
|
|
ee87d1c547 | ||
|
|
7e03b55729 | ||
|
|
db76462802 | ||
|
|
dbf20ebc73 | ||
|
|
b8a74b1425 |
@@ -7,6 +7,7 @@ install:
|
||||
- pip install -r requirements.txt
|
||||
- python setup.py develop
|
||||
- pip install coveralls
|
||||
- rm -rf build
|
||||
script:
|
||||
- export COVERAGE_PYTHON_VERSION=python-${TRAVIS_PYTHON_VERSION:0:1}
|
||||
- coverage run --source=thefuck,tests -m py.test -v
|
||||
|
||||
79
README.md
79
README.md
@@ -1,6 +1,4 @@
|
||||
# The Fuck [](https://travis-ci.org/nvbn/thefuck) [](https://coveralls.io/github/nvbn/thefuck?branch=master)
|
||||
|
||||
**Aliases changed in 1.34.**
|
||||
# The Fuck [](https://travis-ci.org/nvbn/thefuck)
|
||||
|
||||
Magnificent app which corrects your previous console command,
|
||||
inspired by a [@liamosaur](https://twitter.com/liamosaur/)
|
||||
@@ -16,7 +14,7 @@ E: Could not open lock file /var/lib/dpkg/lock - open (13: Permission denied)
|
||||
E: Unable to lock the administration directory (/var/lib/dpkg/), are you root?
|
||||
|
||||
➜ fuck
|
||||
sudo apt-get install vim
|
||||
sudo apt-get install vim [enter/ctrl+c]
|
||||
[sudo] password for nvbn:
|
||||
Reading package lists... Done
|
||||
...
|
||||
@@ -31,7 +29,7 @@ To push the current branch and set the remote as upstream, use
|
||||
|
||||
|
||||
➜ fuck
|
||||
git push --set-upstream origin master
|
||||
git push --set-upstream origin master [enter/ctrl+c]
|
||||
Counting objects: 9, done.
|
||||
...
|
||||
```
|
||||
@@ -44,7 +42,7 @@ No command 'puthon' found, did you mean:
|
||||
zsh: command not found: puthon
|
||||
|
||||
➜ fuck
|
||||
python
|
||||
python [enter/ctrl+c]
|
||||
Python 3.4.2 (default, Oct 8 2014, 13:08:17)
|
||||
...
|
||||
```
|
||||
@@ -57,7 +55,7 @@ Did you mean this?
|
||||
branch
|
||||
|
||||
➜ fuck
|
||||
git branch
|
||||
git branch [enter/ctrl+c]
|
||||
* master
|
||||
```
|
||||
|
||||
@@ -69,13 +67,13 @@ Did you mean this?
|
||||
repl
|
||||
|
||||
➜ fuck
|
||||
lein repl
|
||||
lein repl [enter/ctrl+c]
|
||||
nREPL server started on port 54848 on host 127.0.0.1 - nrepl://127.0.0.1:54848
|
||||
REPL-y 0.3.1
|
||||
...
|
||||
```
|
||||
|
||||
If you are scared to blindly run the changed command, there is a `require_confirmation`
|
||||
If you are not scared to blindly run the changed command, there is a `require_confirmation`
|
||||
[settings](#settings) option:
|
||||
|
||||
```bash
|
||||
@@ -84,7 +82,7 @@ E: Could not open lock file /var/lib/dpkg/lock - open (13: Permission denied)
|
||||
E: Unable to lock the administration directory (/var/lib/dpkg/), are you root?
|
||||
|
||||
➜ fuck
|
||||
sudo apt-get install vim [Enter/Ctrl+C]
|
||||
sudo apt-get install vim
|
||||
[sudo] password for nvbn:
|
||||
Reading package lists... Done
|
||||
...
|
||||
@@ -106,32 +104,15 @@ sudo pip install thefuck
|
||||
|
||||
[Or using an OS package manager (OS X, Ubuntu, Arch).](https://github.com/nvbn/thefuck/wiki/Installation)
|
||||
|
||||
And add to the `.bashrc` or `.bash_profile`(for OSX):
|
||||
You should place this command in your `.bash_profile`, `.bashrc`, `.zshrc` or other startup script:
|
||||
|
||||
```bash
|
||||
alias fuck='eval $(thefuck $(fc -ln -1)); history -r'
|
||||
eval "$(thefuck-alias)"
|
||||
# You can use whatever you want as an alias, like for Mondays:
|
||||
alias FUCK='fuck'
|
||||
eval "$(thefuck-alias FUCK)"
|
||||
```
|
||||
|
||||
Or in your `.zshrc`:
|
||||
|
||||
```bash
|
||||
alias fuck='eval $(thefuck $(fc -ln -1 | tail -n 1)); fc -R'
|
||||
```
|
||||
|
||||
If you are using `tcsh`:
|
||||
```tcsh
|
||||
alias fuck 'set fucked_cmd=`history -h 2 | head -n 1` && eval `thefuck ${fucked_cmd}`'
|
||||
```
|
||||
|
||||
Alternatively, you can redirect the output of `thefuck-alias`:
|
||||
|
||||
```bash
|
||||
thefuck-alias >> ~/.bashrc
|
||||
```
|
||||
|
||||
[Or in your shell config (Bash, Zsh, Fish, Powershell).](https://github.com/nvbn/thefuck/wiki/Shell-aliases)
|
||||
[Or in your shell config (Bash, Zsh, Fish, Powershell, tcsh).](https://github.com/nvbn/thefuck/wiki/Shell-aliases)
|
||||
|
||||
Changes will be available only in a new shell session.
|
||||
To make them available immediately, run `source ~/.bashrc` (or your shell config file like `.zshrc`).
|
||||
@@ -143,6 +124,8 @@ To make them available immediately, run `source ~/.bashrc` (or your shell config
|
||||
sudo pip install thefuck --upgrade
|
||||
```
|
||||
|
||||
**Aliases changed in 1.34.**
|
||||
|
||||
## How it works
|
||||
|
||||
The Fuck tries to match a rule for the previous command, creates a new command
|
||||
@@ -155,19 +138,21 @@ using the matched rule and runs it. Rules enabled by default are as follows:
|
||||
* `cd_parent` – changes `cd..` to `cd ..`;
|
||||
* `composer_not_command` – fixes composer command name;
|
||||
* `cp_omitting_directory` – adds `-a` when you `cp` directory;
|
||||
* `cpp11` – add missing `-std=c++11` to `g++` or `clang++`;
|
||||
* `cpp11` – adds missing `-std=c++11` to `g++` or `clang++`;
|
||||
* `django_south_ghost` – adds `--delete-ghost-migrations` to failed because ghosts django south migration;
|
||||
* `django_south_merge` – adds `--merge` to inconsistent django south migration;
|
||||
* `dry` – fix repetitions like "git git push";
|
||||
* `dry` – fixes repetitions like "git git push";
|
||||
* `fix_alt_space` – replaces Alt+Space with Space character;
|
||||
* `git_add` – fix *"Did you forget to 'git add'?"*;
|
||||
* `git_add` – fixes *"Did you forget to 'git add'?"*;
|
||||
* `git_branch_delete` – changes `git branch -d` to `git branch -D`;
|
||||
* `git_branch_list` – catches `git branch list` in place of `git branch` and removes created branch;
|
||||
* `git_checkout` – creates the branch before checking-out;
|
||||
* `git_checkout` – fixes branch name or creates new branch;
|
||||
* `git_diff_staged` – adds `--staged` to previous `git diff` with unexpected output;
|
||||
* `git_no_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_stash` – stashes you local modifications before rebasing or switching branch;
|
||||
* `go_run` – appends `.go` extension when compiling/running Go programs
|
||||
* `grep_recursive` – adds `-r` when you trying to grep directory;
|
||||
@@ -177,7 +162,7 @@ using the matched rule and runs it. Rules enabled by default are as follows:
|
||||
* `javac` – appends missing `.java` when compiling Java files;
|
||||
* `lein_not_task` – fixes wrong `lein` tasks like `lein rpl`;
|
||||
* `ls_lah` – adds -lah to ls;
|
||||
* `man` – change manual section;
|
||||
* `man` – changes manual section;
|
||||
* `man_no_space` – fixes man commands without spaces, for example `mandiff`;
|
||||
* `mercurial` – fixes wrong `hg` commands;
|
||||
* `mkdir_p` – adds `-p` when you trying to create directory without parent;
|
||||
@@ -204,26 +189,30 @@ Enabled by default only on specific platforms:
|
||||
* `apt_get` – installs app from apt if it not installed;
|
||||
* `brew_install` – fixes formula name for `brew install`;
|
||||
* `brew_unknown_command` – fixes wrong brew commands, for example `brew docto/brew doctor`;
|
||||
* `brew_upgrade` – appends `--all` to `brew upgrade` as per Homebrew's new behaviour
|
||||
* `brew_upgrade` – appends `--all` to `brew upgrade` as per Homebrew's new behaviour;
|
||||
* `pacman` – installs app with `pacman` or `yaourt` if it is not installed.
|
||||
|
||||
Bundled, but not enabled by default:
|
||||
|
||||
* `git_push_force` – adds `--force` to a `git push` (may conflict with `git_push_pull`);
|
||||
* `rm_root` – adds `--no-preserve-root` to `rm -rf /` command.
|
||||
|
||||
## Creating your own rules
|
||||
|
||||
For adding your own rule you should create `your-rule-name.py`
|
||||
in `~/.thefuck/rules`. Rule should contain two functions:
|
||||
`match(command: Command, settings: Settings) -> bool`
|
||||
and `get_new_command(command: Command, settings: Settings) -> str`.
|
||||
Also the rule can contain optional function
|
||||
`side_effect(command: Command, settings: Settings) -> None` and
|
||||
in `~/.thefuck/rules`. The rule should contain two functions:
|
||||
```python
|
||||
match(command: Command, settings: Settings) -> bool
|
||||
get_new_command(command: Command, settings: Settings) -> str
|
||||
```
|
||||
|
||||
Also the rule can contain an optional function
|
||||
`side_effect(command: Command, settings: Settings) -> None` and an
|
||||
optional boolean `enabled_by_default`.
|
||||
|
||||
`Command` has three attributes: `script`, `stdout` and `stderr`.
|
||||
|
||||
`Settings` is a special object filled with `~/.thefuck/settings.py` and values from env, [more](#settings).
|
||||
`Settings` is a special object filled with `~/.thefuck/settings.py` and values from env ([see more below](#settings)).
|
||||
|
||||
Simple example of the rule for running script with `sudo`:
|
||||
|
||||
@@ -242,7 +231,7 @@ enabled_by_default = True
|
||||
def side_effect(command, settings):
|
||||
subprocess.call('chmod 777 .', shell=True)
|
||||
|
||||
priority = 1000 # Lower first
|
||||
priority = 1000 # Lower first, default is 1000
|
||||
```
|
||||
|
||||
[More examples of rules](https://github.com/nvbn/thefuck/tree/master/thefuck/rules),
|
||||
@@ -257,7 +246,7 @@ The Fuck has a few settings parameters which can be changed in `~/.thefuck/setti
|
||||
* `wait_command` – max amount of time in seconds for getting previous command output;
|
||||
* `no_colors` – disable colored output;
|
||||
* `priority` – dict with rules priorities, rule with lower `priority` will be matched first;
|
||||
* `debug` – enabled debug output, by default `False`;
|
||||
* `debug` – enables debug output, by default `False`.
|
||||
|
||||
Example of `settings.py`:
|
||||
|
||||
|
||||
4
setup.py
4
setup.py
@@ -11,7 +11,7 @@ elif (3, 0) < sys.version_info < (3, 3):
|
||||
' ({}.{} detected).'.format(*sys.version_info[:2]))
|
||||
sys.exit(-1)
|
||||
|
||||
VERSION = '2.0'
|
||||
VERSION = '2.2'
|
||||
|
||||
install_requires = ['psutil', 'colorama', 'six']
|
||||
extras_require = {':python_version<"3.4"': ['pathlib']}
|
||||
@@ -31,4 +31,4 @@ setup(name='thefuck',
|
||||
extras_require=extras_require,
|
||||
entry_points={'console_scripts': [
|
||||
'thefuck = thefuck.main:main',
|
||||
'thefuck-alias = thefuck.shells:app_alias']})
|
||||
'thefuck-alias = thefuck.main:print_alias']})
|
||||
|
||||
@@ -12,6 +12,11 @@ def did_not_match(target, did_you_forget=False):
|
||||
return error
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def get_branches(mocker):
|
||||
return mocker.patch('thefuck.rules.git_checkout')
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command', [
|
||||
Command(script='git checkout unknown', stderr=did_not_match('unknown')),
|
||||
Command(script='git commit unknown', stderr=did_not_match('unknown'))])
|
||||
@@ -28,10 +33,19 @@ def test_not_match(command):
|
||||
assert not match(command, None)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command, new_command', [
|
||||
(Command(script='git checkout unknown', stderr=did_not_match('unknown')),
|
||||
@pytest.mark.parametrize('branches, command, new_command', [
|
||||
([],
|
||||
Command(script='git checkout unknown', stderr=did_not_match('unknown')),
|
||||
'git branch unknown && git checkout unknown'),
|
||||
(Command('git commit unknown', stderr=did_not_match('unknown')),
|
||||
'git branch unknown && git commit unknown')])
|
||||
def test_get_new_command(command, new_command):
|
||||
([],
|
||||
Command('git commit unknown', stderr=did_not_match('unknown')),
|
||||
'git branch unknown && git commit unknown'),
|
||||
(['master'],
|
||||
Command(script='git checkout amster', stderr=did_not_match('amster')),
|
||||
'git checkout master'),
|
||||
(['master'],
|
||||
Command(script='git commit amster', stderr=did_not_match('amster')),
|
||||
'git commit master')])
|
||||
def test_get_new_command(branches, command, new_command, get_branches):
|
||||
get_branches.return_value = branches
|
||||
assert get_new_command(command, None) == new_command
|
||||
|
||||
@@ -3,7 +3,9 @@ from thefuck.rules.git_diff_staged import match, get_new_command
|
||||
from tests.utils import Command
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command', [Command(script='git diff')])
|
||||
@pytest.mark.parametrize('command', [
|
||||
Command(script='git diff foo'),
|
||||
Command(script='git diff')])
|
||||
def test_match(command):
|
||||
assert match(command, None)
|
||||
|
||||
@@ -18,6 +20,7 @@ def test_not_match(command):
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command, new_command', [
|
||||
(Command('git diff'), 'git diff --staged')])
|
||||
(Command('git diff'), 'git diff --staged'),
|
||||
(Command('git diff foo'), 'git diff --staged foo')])
|
||||
def test_get_new_command(command, new_command):
|
||||
assert get_new_command(command, None) == new_command
|
||||
|
||||
21
tests/rules/test_git_pull_clone.py
Normal file
21
tests/rules/test_git_pull_clone.py
Normal file
@@ -0,0 +1,21 @@
|
||||
import pytest
|
||||
from thefuck.rules.git_pull_clone import match, get_new_command
|
||||
from tests.utils import Command
|
||||
|
||||
|
||||
git_err = '''
|
||||
fatal: Not a git repository (or any parent up to mount point /home)
|
||||
Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set).
|
||||
'''
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command', [
|
||||
Command(script='git pull git@github.com:mcarton/thefuck.git', stderr=git_err)])
|
||||
def test_match(command):
|
||||
assert match(command, None)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command, output', [
|
||||
(Command(script='git pull git@github.com:mcarton/thefuck.git', stderr=git_err), 'git clone git@github.com:mcarton/thefuck.git')])
|
||||
def test_get_new_command(command, output):
|
||||
assert get_new_command(command, None) == output
|
||||
52
tests/rules/test_git_push_force.py
Normal file
52
tests/rules/test_git_push_force.py
Normal file
@@ -0,0 +1,52 @@
|
||||
import pytest
|
||||
from thefuck.rules.git_push_force import match, get_new_command
|
||||
from tests.utils import Command
|
||||
|
||||
|
||||
git_err = '''
|
||||
To /tmp/foo
|
||||
! [rejected] master -> master (non-fast-forward)
|
||||
error: failed to push some refs to '/tmp/bar'
|
||||
hint: Updates were rejected because the tip of your current branch is behind
|
||||
hint: its remote counterpart. Integrate the remote changes (e.g.
|
||||
hint: 'git pull ...') before pushing again.
|
||||
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
|
||||
'''
|
||||
|
||||
git_uptodate = 'Everything up-to-date'
|
||||
git_ok = '''
|
||||
Counting objects: 3, done.
|
||||
Delta compression using up to 4 threads.
|
||||
Compressing objects: 100% (2/2), done.
|
||||
Writing objects: 100% (3/3), 282 bytes | 0 bytes/s, done.
|
||||
Total 3 (delta 0), reused 0 (delta 0)
|
||||
To /tmp/bar
|
||||
514eed3..f269c79 master -> master
|
||||
'''
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command', [
|
||||
Command(script='git push', stderr=git_err),
|
||||
Command(script='git push nvbn', stderr=git_err),
|
||||
Command(script='git push nvbn master', stderr=git_err)])
|
||||
def test_match(command):
|
||||
assert match(command, None)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command', [
|
||||
Command(script='git push', stderr=git_ok),
|
||||
Command(script='git push', stderr=git_uptodate),
|
||||
Command(script='git push nvbn', stderr=git_ok),
|
||||
Command(script='git push nvbn master', stderr=git_uptodate),
|
||||
Command(script='git push nvbn', stderr=git_ok),
|
||||
Command(script='git push nvbn master', stderr=git_uptodate)])
|
||||
def test_not_match(command):
|
||||
assert not match(command, None)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command, output', [
|
||||
(Command(script='git push', stderr=git_err), 'git push --force'),
|
||||
(Command(script='git push nvbn', stderr=git_err), 'git push --force nvbn'),
|
||||
(Command(script='git push nvbn master', stderr=git_err), 'git push --force nvbn master')])
|
||||
def test_get_new_command(command, output):
|
||||
assert get_new_command(command, None) == output
|
||||
54
tests/rules/test_git_push_pull.py
Normal file
54
tests/rules/test_git_push_pull.py
Normal file
@@ -0,0 +1,54 @@
|
||||
import pytest
|
||||
from thefuck.rules.git_push_pull import match, get_new_command
|
||||
from tests.utils import Command
|
||||
|
||||
|
||||
git_err = '''
|
||||
To /tmp/foo
|
||||
! [rejected] master -> master (non-fast-forward)
|
||||
error: failed to push some refs to '/tmp/bar'
|
||||
hint: Updates were rejected because the tip of your current branch is behind
|
||||
hint: its remote counterpart. Integrate the remote changes (e.g.
|
||||
hint: 'git pull ...') before pushing again.
|
||||
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
|
||||
'''
|
||||
|
||||
git_uptodate = 'Everything up-to-date'
|
||||
git_ok = '''
|
||||
Counting objects: 3, done.
|
||||
Delta compression using up to 4 threads.
|
||||
Compressing objects: 100% (2/2), done.
|
||||
Writing objects: 100% (3/3), 282 bytes | 0 bytes/s, done.
|
||||
Total 3 (delta 0), reused 0 (delta 0)
|
||||
To /tmp/bar
|
||||
514eed3..f269c79 master -> master
|
||||
'''
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command', [
|
||||
Command(script='git push', stderr=git_err),
|
||||
Command(script='git push nvbn', stderr=git_err),
|
||||
Command(script='git push nvbn master', stderr=git_err)])
|
||||
def test_match(command):
|
||||
assert match(command, None)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command', [
|
||||
Command(script='git push', stderr=git_ok),
|
||||
Command(script='git push', stderr=git_uptodate),
|
||||
Command(script='git push nvbn', stderr=git_ok),
|
||||
Command(script='git push nvbn master', stderr=git_uptodate),
|
||||
Command(script='git push nvbn', stderr=git_ok),
|
||||
Command(script='git push nvbn master', stderr=git_uptodate)])
|
||||
def test_not_match(command):
|
||||
assert not match(command, None)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command, output', [
|
||||
(Command(script='git push', stderr=git_err), 'git pull && git push'),
|
||||
(Command(script='git push nvbn', stderr=git_err),
|
||||
'git pull nvbn && git push nvbn'),
|
||||
(Command(script='git push nvbn master', stderr=git_err),
|
||||
'git pull nvbn master && git push nvbn master')])
|
||||
def test_get_new_command(command, output):
|
||||
assert get_new_command(command, None) == output
|
||||
@@ -6,28 +6,35 @@ from tests.utils import Command
|
||||
@pytest.fixture
|
||||
def history(mocker):
|
||||
return mocker.patch('thefuck.rules.history.get_history',
|
||||
return_value=['ls cat', 'diff x', 'nocommand x'])
|
||||
return_value=['le cat', 'fuck', 'ls cat',
|
||||
'diff x', 'nocommand 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_callables',
|
||||
return mocker.patch('thefuck.rules.history.get_all_executables',
|
||||
return_value=['diff', 'ls'])
|
||||
|
||||
|
||||
@pytest.mark.usefixtures('history', 'callables', 'no_memoize')
|
||||
@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), None)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures('history', 'callables', 'no_memoize')
|
||||
@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), None)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures('history', 'callables', 'no_memoize')
|
||||
@pytest.mark.usefixtures('history', 'callables', 'no_memoize', 'alias')
|
||||
@pytest.mark.parametrize('script, result', [
|
||||
('ls cet', 'ls cat'),
|
||||
('daff x', 'diff x')])
|
||||
|
||||
@@ -1,29 +1,16 @@
|
||||
import pytest
|
||||
from thefuck.rules.no_command import match, get_new_command, get_all_callables
|
||||
from thefuck.rules.no_command import match, get_new_command
|
||||
from tests.utils import Command
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _safe(mocker):
|
||||
mocker.patch('thefuck.rules.no_command._safe', return_value=[])
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def get_aliases(mocker):
|
||||
mocker.patch('thefuck.rules.no_command.get_aliases',
|
||||
return_value=['vim', 'apt-get', 'fsck', 'fuck'])
|
||||
def get_all_executables(mocker):
|
||||
mocker.patch('thefuck.rules.no_command.get_all_executables',
|
||||
return_value=['vim', 'apt-get', 'fsck'])
|
||||
|
||||
|
||||
@pytest.mark.usefixtures('no_memoize')
|
||||
def test_get_all_callables(*args):
|
||||
all_callables = get_all_callables()
|
||||
assert 'vim' in all_callables
|
||||
assert 'fsck' in all_callables
|
||||
assert 'fuck' not in all_callables
|
||||
|
||||
|
||||
@pytest.mark.usefixtures('no_memoize')
|
||||
def test_match(*args):
|
||||
def test_match():
|
||||
assert match(Command(stderr='vom: not found', script='vom file.py'), None)
|
||||
assert match(Command(stderr='fucck: not found', script='fucck'), None)
|
||||
assert not match(Command(stderr='qweqwe: not found', script='qweqwe'), None)
|
||||
@@ -31,7 +18,7 @@ def test_match(*args):
|
||||
|
||||
|
||||
@pytest.mark.usefixtures('no_memoize')
|
||||
def test_get_new_command(*args):
|
||||
def test_get_new_command():
|
||||
assert get_new_command(
|
||||
Command(stderr='vom: not found',
|
||||
script='vom file.py'),
|
||||
|
||||
@@ -11,6 +11,7 @@ from tests.utils import Command
|
||||
('need to be root', ''),
|
||||
('need root', ''),
|
||||
('must be root', ''),
|
||||
('You don\'t have access to the history DB.', ''),
|
||||
('', "error: [Errno 13] Permission denied: '/usr/local/lib/python2.7/dist-packages/ipaddr.py'")])
|
||||
def test_match(stderr, stdout):
|
||||
assert match(Command(stderr=stderr, stdout=stdout), None)
|
||||
|
||||
@@ -15,6 +15,7 @@ def test_match(command):
|
||||
@pytest.mark.parametrize('command', [
|
||||
Command(stderr='command not found: pat-get', script=u'pat-get'),
|
||||
Command(stderr='command not found: ls', script=u'ls'),
|
||||
Command(stderr='command not found: агсл', script=u'агсл'),
|
||||
Command(stderr='some info', script=u'фзе-пуе')])
|
||||
def test_not_match(command):
|
||||
assert not switch_lang.match(command, None)
|
||||
|
||||
@@ -44,9 +44,10 @@ class TestGeneric(object):
|
||||
assert shell.get_aliases() == {}
|
||||
|
||||
def test_app_alias(self, shell):
|
||||
assert 'alias fuck' in shell.app_alias()
|
||||
assert 'thefuck' in shell.app_alias()
|
||||
assert 'TF_ALIAS' in shell.app_alias()
|
||||
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')
|
||||
|
||||
def test_get_history(self, history_lines, shell):
|
||||
history_lines(['ls', 'rm'])
|
||||
@@ -97,9 +98,10 @@ class TestBash(object):
|
||||
'll': 'ls -alF'}
|
||||
|
||||
def test_app_alias(self, shell):
|
||||
assert 'alias fuck' in shell.app_alias()
|
||||
assert 'thefuck' in shell.app_alias()
|
||||
assert 'TF_ALIAS' in shell.app_alias()
|
||||
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')
|
||||
|
||||
def test_get_history(self, history_lines, shell):
|
||||
history_lines(['ls', 'rm'])
|
||||
@@ -173,9 +175,10 @@ class TestFish(object):
|
||||
'ruby': 'ruby'}
|
||||
|
||||
def test_app_alias(self, shell):
|
||||
assert 'function fuck' in shell.app_alias()
|
||||
assert 'thefuck' in shell.app_alias()
|
||||
assert 'TF_ALIAS' in shell.app_alias()
|
||||
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')
|
||||
|
||||
|
||||
@pytest.mark.usefixtures('isfile')
|
||||
@@ -222,9 +225,10 @@ class TestZsh(object):
|
||||
'll': 'ls -alF'}
|
||||
|
||||
def test_app_alias(self, shell):
|
||||
assert 'alias fuck' in shell.app_alias()
|
||||
assert 'thefuck' in shell.app_alias()
|
||||
assert 'TF_ALIAS' in shell.app_alias()
|
||||
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')
|
||||
|
||||
def test_get_history(self, history_lines, shell):
|
||||
history_lines([': 1432613911:0;ls', ': 1432613916:0;rm'])
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import pytest
|
||||
from mock import Mock
|
||||
from thefuck.utils import git_support, sudo_support, wrap_settings, memoize, get_closest
|
||||
from thefuck.utils import git_support, sudo_support, wrap_settings,\
|
||||
memoize, get_closest, get_all_executables
|
||||
from thefuck.types import Settings
|
||||
from tests.utils import Command
|
||||
|
||||
@@ -59,3 +60,21 @@ class TestGetClosest(object):
|
||||
|
||||
def test_when_cant_match(self):
|
||||
assert 'status' == get_closest('st', ['status', 'reset'])
|
||||
|
||||
def test_without_fallback(self):
|
||||
assert get_closest('st', ['status', 'reset'],
|
||||
fallback_to_first=False) is None
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def get_aliases(mocker):
|
||||
mocker.patch('thefuck.shells.get_aliases',
|
||||
return_value=['vim', 'apt-get', 'fsck', 'fuck'])
|
||||
|
||||
|
||||
@pytest.mark.usefixtures('no_memoize', 'get_aliases')
|
||||
def test_get_all_callables():
|
||||
all_callables = get_all_executables()
|
||||
assert 'vim' in all_callables
|
||||
assert 'fsck' in all_callables
|
||||
assert 'fuck' not in all_callables
|
||||
|
||||
@@ -80,11 +80,11 @@ def get_command(settings, args):
|
||||
return
|
||||
|
||||
script = shells.from_shell(script)
|
||||
logs.debug('Call: {}'.format(script), settings)
|
||||
logs.debug(u'Call: {}'.format(script), settings)
|
||||
|
||||
env = dict(os.environ)
|
||||
env.update(settings.env)
|
||||
logs.debug('Executing with env: {}'.format(env), settings)
|
||||
logs.debug(u'Executing with env: {}'.format(env), settings)
|
||||
|
||||
result = Popen(script, shell=True, stdout=PIPE, stderr=PIPE, env=env)
|
||||
if wait_output(settings, result):
|
||||
@@ -128,26 +128,35 @@ def run_rule(rule, command, settings):
|
||||
print(new_command)
|
||||
|
||||
|
||||
# Entry points:
|
||||
|
||||
def main():
|
||||
colorama.init()
|
||||
user_dir = setup_user_dir()
|
||||
settings = conf.get_settings(user_dir)
|
||||
logs.debug('Run with settings: {}'.format(pformat(settings)), settings)
|
||||
logs.debug(u'Run with settings: {}'.format(pformat(settings)), settings)
|
||||
|
||||
command = get_command(settings, sys.argv)
|
||||
if command:
|
||||
logs.debug('Received stdout: {}'.format(command.stdout), settings)
|
||||
logs.debug('Received stderr: {}'.format(command.stderr), settings)
|
||||
logs.debug(u'Received stdout: {}'.format(command.stdout), settings)
|
||||
logs.debug(u'Received stderr: {}'.format(command.stderr), settings)
|
||||
|
||||
rules = get_rules(user_dir, settings)
|
||||
logs.debug(
|
||||
'Loaded rules: {}'.format(', '.join(rule.name for rule in rules)),
|
||||
u'Loaded rules: {}'.format(', '.join(rule.name for rule in rules)),
|
||||
settings)
|
||||
|
||||
matched_rule = get_matched_rule(command, rules, settings)
|
||||
if matched_rule:
|
||||
logs.debug('Matched rule: {}'.format(matched_rule.name), settings)
|
||||
logs.debug(u'Matched rule: {}'.format(matched_rule.name), settings)
|
||||
run_rule(matched_rule, command, settings)
|
||||
return
|
||||
|
||||
logs.failed('No fuck given', settings)
|
||||
|
||||
|
||||
def print_alias():
|
||||
alias = shells.thefuck_alias()
|
||||
if len(sys.argv) > 1:
|
||||
alias = sys.argv[1]
|
||||
print(shells.app_alias(alias))
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
from thefuck import utils
|
||||
|
||||
|
||||
@utils.git_support
|
||||
def match(command, settings):
|
||||
return ('git branch -d' in command.script
|
||||
and 'If you are sure you want to delete it' in command.stderr)
|
||||
|
||||
|
||||
@utils.git_support
|
||||
def get_new_command(command, settings):
|
||||
return command.script.replace('-d', '-D')
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import re
|
||||
import subprocess
|
||||
from thefuck import shells, utils
|
||||
|
||||
|
||||
@@ -9,11 +10,28 @@ def match(command, settings):
|
||||
and "Did you forget to 'git add'?" not in command.stderr)
|
||||
|
||||
|
||||
def get_branches():
|
||||
proc = subprocess.Popen(
|
||||
['git', 'branch', '-a', '--no-color', '--no-column'],
|
||||
stdout=subprocess.PIPE)
|
||||
for line in proc.stdout.readlines():
|
||||
line = line.decode('utf-8')
|
||||
if line.startswith('*'):
|
||||
line = line.split(' ')[1]
|
||||
if '/' in line:
|
||||
line = line.split('/')[-1]
|
||||
yield line.strip()
|
||||
|
||||
|
||||
@utils.git_support
|
||||
def get_new_command(command, settings):
|
||||
missing_file = re.findall(
|
||||
r"error: pathspec '([^']*)' "
|
||||
"did not match any file\(s\) known to git.", command.stderr)[0]
|
||||
|
||||
formatme = shells.and_('git branch {}', '{}')
|
||||
return formatme.format(missing_file, command.script)
|
||||
r"error: pathspec '([^']*)' "
|
||||
"did not match any file\(s\) known to git.", command.stderr)[0]
|
||||
closest_branch = utils.get_closest(missing_file, get_branches(),
|
||||
fallback_to_first=False)
|
||||
if closest_branch:
|
||||
return command.script.replace(missing_file, closest_branch, 1)
|
||||
else:
|
||||
return shells.and_('git branch {}', '{}').format(
|
||||
missing_file, command.script)
|
||||
|
||||
@@ -10,4 +10,4 @@ def match(command, settings):
|
||||
|
||||
@utils.git_support
|
||||
def get_new_command(command, settings):
|
||||
return '{} --staged'.format(command.script)
|
||||
return command.script.replace(' diff', ' diff --staged')
|
||||
|
||||
14
thefuck/rules/git_pull_clone.py
Normal file
14
thefuck/rules/git_pull_clone.py
Normal file
@@ -0,0 +1,14 @@
|
||||
import re
|
||||
from thefuck import utils, shells
|
||||
|
||||
|
||||
@utils.git_support
|
||||
def match(command, settings):
|
||||
return ('git pull' in command.script
|
||||
and 'fatal: Not a git repository' in command.stderr
|
||||
and "Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set)." in command.stderr)
|
||||
|
||||
|
||||
@utils.git_support
|
||||
def get_new_command(command, settings):
|
||||
return command.script.replace(' pull ', ' clone ')
|
||||
18
thefuck/rules/git_push_force.py
Normal file
18
thefuck/rules/git_push_force.py
Normal file
@@ -0,0 +1,18 @@
|
||||
from thefuck import utils
|
||||
|
||||
|
||||
@utils.git_support
|
||||
def match(command, settings):
|
||||
return ('git' in command.script
|
||||
and 'push' in command.script
|
||||
and '! [rejected]' in command.stderr
|
||||
and 'failed to push some refs to' in command.stderr
|
||||
and 'Updates were rejected because the tip of your current branch is behind' in command.stderr)
|
||||
|
||||
|
||||
@utils.git_support
|
||||
def get_new_command(command, settings):
|
||||
return command.script.replace('push', 'push --force')
|
||||
|
||||
|
||||
enabled_by_default = False
|
||||
17
thefuck/rules/git_push_pull.py
Normal file
17
thefuck/rules/git_push_pull.py
Normal file
@@ -0,0 +1,17 @@
|
||||
from thefuck import utils
|
||||
from thefuck.shells import and_
|
||||
|
||||
|
||||
@utils.git_support
|
||||
def match(command, settings):
|
||||
return ('git' in command.script
|
||||
and 'push' in command.script
|
||||
and '! [rejected]' in command.stderr
|
||||
and 'failed to push some refs to' in command.stderr
|
||||
and 'Updates were rejected because the tip of your current branch is behind' in command.stderr)
|
||||
|
||||
|
||||
@utils.git_support
|
||||
def get_new_command(command, settings):
|
||||
return and_(command.script.replace('push', 'pull'),
|
||||
command.script)
|
||||
@@ -1,16 +1,27 @@
|
||||
from difflib import get_close_matches
|
||||
from thefuck.shells import get_history
|
||||
from thefuck.utils import get_closest, memoize
|
||||
from thefuck.rules.no_command import get_all_callables
|
||||
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):
|
||||
callables = get_all_callables()
|
||||
return [line for line in get_history()
|
||||
if line != command.script
|
||||
and line.split(' ')[0] in callables]
|
||||
|
||||
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]
|
||||
|
||||
def match(command, settings):
|
||||
return len(get_close_matches(command.script,
|
||||
@@ -21,4 +32,5 @@ def get_new_command(command, settings):
|
||||
return get_closest(command.script,
|
||||
_history_of_exists_without_current(command))
|
||||
|
||||
|
||||
priority = 9999
|
||||
|
||||
@@ -1,39 +1,19 @@
|
||||
from difflib import get_close_matches
|
||||
import os
|
||||
from pathlib import Path
|
||||
from thefuck.utils import sudo_support, memoize
|
||||
from thefuck.shells import thefuck_alias, get_aliases
|
||||
|
||||
|
||||
def _safe(fn, fallback):
|
||||
try:
|
||||
return fn()
|
||||
except OSError:
|
||||
return fallback
|
||||
|
||||
|
||||
@memoize
|
||||
def get_all_callables():
|
||||
tf_alias = thefuck_alias()
|
||||
return [exe.name
|
||||
for path in os.environ.get('PATH', '').split(':')
|
||||
for exe in _safe(lambda: list(Path(path).iterdir()), [])
|
||||
if not _safe(exe.is_dir, True)] + [
|
||||
alias for alias in get_aliases() if alias != tf_alias]
|
||||
from thefuck.utils import sudo_support, get_all_executables
|
||||
|
||||
|
||||
@sudo_support
|
||||
def match(command, settings):
|
||||
return 'not found' in command.stderr and \
|
||||
bool(get_close_matches(command.script.split(' ')[0],
|
||||
get_all_callables()))
|
||||
get_all_executables()))
|
||||
|
||||
|
||||
@sudo_support
|
||||
def get_new_command(command, settings):
|
||||
old_command = command.script.split(' ')[0]
|
||||
new_command = get_close_matches(old_command,
|
||||
get_all_callables())[0]
|
||||
get_all_executables())[0]
|
||||
return ' '.join([new_command] + command.script.split(' ')[1:])
|
||||
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ patterns = ['permission denied',
|
||||
'need to be root',
|
||||
'need root',
|
||||
'only root can do that',
|
||||
'You don\'t have access to the history DB.',
|
||||
'authentication is required']
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
from thefuck.shells import thefuck_alias
|
||||
from thefuck.utils import memoize
|
||||
|
||||
target_layout = '''qwertyuiop[]asdfghjkl;'zxcvbnm,./QWERTYUIOP{}ASDFGHJKL:"ZXCVBNM<>?'''
|
||||
|
||||
@@ -7,6 +9,7 @@ source_layouts = [u'''йцукенгшщзхъфывапролджэячсмит
|
||||
u''';ςερτυθιοπ[]ασδφγηξκλ΄ζχψωβνμ,./:΅ΕΡΤΥΘΙΟΠ{}ΑΣΔΦΓΗΞΚΛ¨"ΖΧΨΩΒΝΜ<>?''']
|
||||
|
||||
|
||||
@memoize
|
||||
def _get_matched_layout(command):
|
||||
for source_layout in source_layouts:
|
||||
if all([ch in source_layout or ch in '-_'
|
||||
@@ -14,10 +17,6 @@ def _get_matched_layout(command):
|
||||
return source_layout
|
||||
|
||||
|
||||
def match(command, settings):
|
||||
return 'not found' in command.stderr and _get_matched_layout(command)
|
||||
|
||||
|
||||
def _switch(ch, layout):
|
||||
if ch in layout:
|
||||
return target_layout[layout.index(ch)]
|
||||
@@ -25,7 +24,18 @@ def _switch(ch, layout):
|
||||
return ch
|
||||
|
||||
|
||||
def _switch_command(command, layout):
|
||||
return ''.join(_switch(ch, layout) for ch in command.script)
|
||||
|
||||
|
||||
def match(command, settings):
|
||||
if 'not found' not in command.stderr:
|
||||
return False
|
||||
matched_layout = _get_matched_layout(command)
|
||||
return matched_layout and \
|
||||
_switch_command(command, matched_layout) != thefuck_alias()
|
||||
|
||||
|
||||
def get_new_command(command, settings):
|
||||
matched_layout = _get_matched_layout(command)
|
||||
return ''.join(_switch(ch, matched_layout) for ch in command.script)
|
||||
|
||||
return _switch_command(command, matched_layout)
|
||||
|
||||
@@ -4,11 +4,11 @@ methods.
|
||||
|
||||
"""
|
||||
from collections import defaultdict
|
||||
from psutil import Process
|
||||
from subprocess import Popen, PIPE
|
||||
from time import time
|
||||
import os
|
||||
import io
|
||||
from psutil import Process
|
||||
import os
|
||||
from .utils import DEVNULL, memoize
|
||||
|
||||
|
||||
@@ -33,8 +33,8 @@ class Generic(object):
|
||||
"""Prepares command for running in shell."""
|
||||
return command_script
|
||||
|
||||
def app_alias(self):
|
||||
return "\nalias fuck='TF_ALIAS=fuck eval $(thefuck $(fc -ln -1))'\n"
|
||||
def app_alias(self, fuck):
|
||||
return "alias {0}='TF_ALIAS={0} eval $(thefuck $(fc -ln -1))'".format(fuck)
|
||||
|
||||
def _get_history_file_name(self):
|
||||
return ''
|
||||
@@ -74,9 +74,9 @@ class Generic(object):
|
||||
|
||||
|
||||
class Bash(Generic):
|
||||
def app_alias(self):
|
||||
return "\nTF_ALIAS=fuck alias fuck='eval $(thefuck $(fc -ln -1));" \
|
||||
" history -r'\n"
|
||||
def app_alias(self, fuck):
|
||||
return "TF_ALIAS={0} alias {0}='eval $(thefuck $(fc -ln -1));" \
|
||||
" history -r'".format(fuck)
|
||||
|
||||
def _parse_alias(self, alias):
|
||||
name, value = alias.replace('alias ', '', 1).split('=', 1)
|
||||
@@ -100,7 +100,6 @@ class Bash(Generic):
|
||||
return u'{}\n'.format(command_script)
|
||||
|
||||
def _script_from_history(self, line):
|
||||
print(line)
|
||||
return line
|
||||
|
||||
|
||||
@@ -113,9 +112,9 @@ class Fish(Generic):
|
||||
else:
|
||||
return ['cd', 'grep', 'ls', 'man', 'open']
|
||||
|
||||
def app_alias(self):
|
||||
return ("set TF_ALIAS fuck\n"
|
||||
"function fuck -d 'Correct your previous console command'\n"
|
||||
def app_alias(self, fuck):
|
||||
return ("set TF_ALIAS {0}\n"
|
||||
"function {0} -d 'Correct your previous console command'\n"
|
||||
" set -l exit_code $status\n"
|
||||
" set -l eval_script"
|
||||
" (mktemp 2>/dev/null ; or mktemp -t 'thefuck')\n"
|
||||
@@ -126,7 +125,7 @@ class Fish(Generic):
|
||||
" if test $exit_code -ne 0\n"
|
||||
" history --delete $fucked_up_commandd\n"
|
||||
" end\n"
|
||||
"end")
|
||||
"end").format(fuck)
|
||||
|
||||
def get_aliases(self):
|
||||
overridden = self._get_overridden_aliases()
|
||||
@@ -158,10 +157,10 @@ class Fish(Generic):
|
||||
|
||||
|
||||
class Zsh(Generic):
|
||||
def app_alias(self):
|
||||
return "\nTF_ALIAS=fuck" \
|
||||
" alias fuck='eval $(thefuck $(fc -ln -1 | tail -n 1));" \
|
||||
" fc -R'\n"
|
||||
def app_alias(self, fuck):
|
||||
return "TF_ALIAS={0}" \
|
||||
" alias {0}='eval $(thefuck $(fc -ln -1 | tail -n 1));" \
|
||||
" fc -R'".format(fuck)
|
||||
|
||||
def _parse_alias(self, alias):
|
||||
name, value = alias.split('=', 1)
|
||||
@@ -192,8 +191,10 @@ class Zsh(Generic):
|
||||
|
||||
|
||||
class Tcsh(Generic):
|
||||
def app_alias(self):
|
||||
return "\nalias fuck 'setenv TF_ALIAS fuck && set fucked_cmd=`history -h 2 | head -n 1` && eval `thefuck ${fucked_cmd}`'\n"
|
||||
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)
|
||||
@@ -239,8 +240,8 @@ def to_shell(command):
|
||||
return _get_shell().to_shell(command)
|
||||
|
||||
|
||||
def app_alias():
|
||||
print(_get_shell().app_alias())
|
||||
def app_alias(alias):
|
||||
return _get_shell().app_alias(alias)
|
||||
|
||||
|
||||
def thefuck_alias():
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from difflib import get_close_matches
|
||||
from functools import wraps
|
||||
from pathlib import Path
|
||||
from shlex import split
|
||||
import os
|
||||
import pickle
|
||||
@@ -113,10 +114,29 @@ def memoize(fn):
|
||||
memoize.disabled = False
|
||||
|
||||
|
||||
def get_closest(word, possibilities, n=3, cutoff=0.6):
|
||||
def get_closest(word, possibilities, n=3, cutoff=0.6, fallback_to_first=True):
|
||||
"""Returns closest match or just first from possibilities."""
|
||||
possibilities = list(possibilities)
|
||||
try:
|
||||
return get_close_matches(word, possibilities, n, cutoff)[0]
|
||||
except IndexError:
|
||||
return possibilities[0]
|
||||
if fallback_to_first:
|
||||
return possibilities[0]
|
||||
|
||||
|
||||
@memoize
|
||||
def get_all_executables():
|
||||
from thefuck.shells import thefuck_alias, get_aliases
|
||||
|
||||
def _safe(fn, fallback):
|
||||
try:
|
||||
return fn()
|
||||
except OSError:
|
||||
return fallback
|
||||
|
||||
tf_alias = thefuck_alias()
|
||||
return [exe.name
|
||||
for path in os.environ.get('PATH', '').split(':')
|
||||
for exe in _safe(lambda: list(Path(path).iterdir()), [])
|
||||
if not _safe(exe.is_dir, True)] + [
|
||||
alias for alias in get_aliases() if alias != tf_alias]
|
||||
|
||||
Reference in New Issue
Block a user