mirror of
https://github.com/nvbn/thefuck.git
synced 2025-11-02 08:02:04 +00:00
Compare commits
60 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
051f5fcb89 | ||
|
|
6590da623f | ||
|
|
dc53f58b2a | ||
|
|
961d4d5293 | ||
|
|
1ffc9624ed | ||
|
|
afcee5844b | ||
|
|
881967f4c5 | ||
|
|
3c673e0972 | ||
|
|
8fdcff776a | ||
|
|
1b5c935f30 | ||
|
|
8d256390a1 | ||
|
|
51800afca8 | ||
|
|
07831666db | ||
|
|
252859e63a | ||
|
|
a54c97f624 | ||
|
|
9ef346468c | ||
|
|
f04c4396eb | ||
|
|
9ade21bf0a | ||
|
|
179839c32f | ||
|
|
3d0d4be4a9 | ||
|
|
d854320acc | ||
|
|
bb4b42d2f1 | ||
|
|
6539c853b4 | ||
|
|
5f2b2433b1 | ||
|
|
d41b1d48d2 | ||
|
|
bbdac1884a | ||
|
|
d5bd57fb49 | ||
|
|
fc8f1b1136 | ||
|
|
d7c67ad09d | ||
|
|
73939836d4 | ||
|
|
744f17d905 | ||
|
|
08a2065119 | ||
|
|
5504aa44a1 | ||
|
|
3c4f9d50a9 | ||
|
|
371a4b0ad3 | ||
|
|
9cf41f8e43 | ||
|
|
d2e511fa2c | ||
|
|
a1437db40a | ||
|
|
239f91b670 | ||
|
|
7b29b54ac7 | ||
|
|
a83d75991b | ||
|
|
14d14c5ac6 | ||
|
|
65c624ad52 | ||
|
|
a77db59da5 | ||
|
|
8ac4dafe6d | ||
|
|
779e29906e | ||
|
|
e8de4ee7e8 | ||
|
|
47a1faa881 | ||
|
|
ab97b94faf | ||
|
|
7489040f8f | ||
|
|
484a53e314 | ||
|
|
0fc7c00e8d | ||
|
|
64318c09b7 | ||
|
|
5b6e17b5f1 | ||
|
|
6cdc2c27fb | ||
|
|
62c605d0ac | ||
|
|
8930d01601 | ||
|
|
c749615ad6 | ||
|
|
f03d8c54b1 | ||
|
|
20f1c76d27 |
@@ -6,4 +6,4 @@ python:
|
||||
install:
|
||||
- python setup.py develop
|
||||
- pip install -r requirements.txt
|
||||
script: py.test
|
||||
script: py.test -v
|
||||
|
||||
21
README.md
21
README.md
@@ -73,7 +73,7 @@ REPL-y 0.3.1
|
||||
...
|
||||
```
|
||||
|
||||
If you are scared to blindly run changed command, there's `require_confirmation`
|
||||
If you are scared to blindly run the changed command, there is a `require_confirmation`
|
||||
[settings](#settings) option:
|
||||
|
||||
```bash
|
||||
@@ -104,7 +104,7 @@ sudo pip install thefuck
|
||||
|
||||
[Or using an OS package manager (OS X, Ubuntu, Arch).](https://github.com/nvbn/thefuck/wiki/Installation)
|
||||
|
||||
And add to `.bashrc` or `.bash_profile`(for OSX):
|
||||
And add to the `.bashrc` or `.bash_profile`(for OSX):
|
||||
|
||||
```bash
|
||||
alias fuck='eval $(thefuck $(fc -ln -1)); history -r'
|
||||
@@ -118,6 +118,11 @@ Or in your `.zshrc`:
|
||||
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
|
||||
@@ -137,10 +142,11 @@ sudo pip install thefuck --upgrade
|
||||
|
||||
## How it works
|
||||
|
||||
The Fuck tries to match rule for the previous command, create new command
|
||||
using matched rule and run it. Rules enabled by default:
|
||||
The Fuck tries to match a rule for the previous command, creates a new command
|
||||
using the matched rule and runs it. Rules enabled by default are as follows:
|
||||
|
||||
* `brew_unknown_command` – fixes wrong brew commands, for example `brew docto/brew doctor`;
|
||||
* `cpp11` – add missing `-std=c++11` to `g++` or `clang++`;
|
||||
* `cd_parent` – changes `cd..` to `cd ..`;
|
||||
* `cd_mkdir` – creates directories before cd'ing into them;
|
||||
* `cp_omitting_directory` – adds `-a` when you `cp` directory;
|
||||
@@ -150,10 +156,12 @@ using matched rule and run it. Rules enabled by default:
|
||||
* `git_checkout` – creates the branch before checking-out;
|
||||
* `git_no_command` – fixes wrong git commands like `git brnch`;
|
||||
* `git_push` – adds `--set-upstream origin $branch` to previous failed `git push`;
|
||||
* `git_stash` – stashes you local modifications before rebasing or switching branch;
|
||||
* `has_exists_script` – prepends `./` when script/binary exists;
|
||||
* `lein_not_task` – fixes wrong `lein` tasks like `lein rpl`;
|
||||
* `mkdir_p` – adds `-p` when you trying to create directory without parent;
|
||||
* `no_command` – fixes wrong console commands, for example `vom/vim`;
|
||||
* `no_such_file` – creates missing directories with `mv` and `cp` commands;
|
||||
* `man_no_space` – fixes man commands without spaces, for example `mandiff`;
|
||||
* `pacman` – installs app with `pacman` or `yaourt` if it is not installed;
|
||||
* `pip_unknown_command` – fixes wrong pip commands, for example `pip instatl/pip install`;
|
||||
@@ -163,6 +171,7 @@ using matched rule and run it. Rules enabled by default:
|
||||
* `ssh_known_hosts` – removes host from `known_hosts` on warning;
|
||||
* `sudo` – prepends `sudo` to previous command if it failed because of permissions;
|
||||
* `switch_layout` – switches command from your local layout to en;
|
||||
* `whois` – fixes `whois` command;
|
||||
* `apt_get` – installs app from apt if it not installed;
|
||||
* `brew_install` – fixes formula name for `brew install`;
|
||||
* `composer_not_command` – fixes composer command name.
|
||||
@@ -211,10 +220,10 @@ priority = 1000 # Lower first
|
||||
|
||||
## Settings
|
||||
|
||||
The Fuck has a few settings parameters, they can be changed in `~/.thefuck/settings.py`:
|
||||
The Fuck has a few settings parameters which can be changed in `~/.thefuck/settings.py`:
|
||||
|
||||
* `rules` – list of enabled rules, by default `thefuck.conf.DEFAULT_RULES`;
|
||||
* `require_confirmation` – require confirmation before running new command, by default `False`;
|
||||
* `require_confirmation` – requires confirmation before running new command, by default `False`;
|
||||
* `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.
|
||||
|
||||
2
setup.py
2
setup.py
@@ -1,7 +1,7 @@
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
|
||||
VERSION = '1.39'
|
||||
VERSION = '1.41'
|
||||
|
||||
|
||||
setup(name='thefuck',
|
||||
|
||||
0
tests/rules/__init__.py
Normal file
0
tests/rules/__init__.py
Normal file
6
tests/rules/conftest.py
Normal file
6
tests/rules/conftest.py
Normal file
@@ -0,0 +1,6 @@
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def generic_shell(monkeypatch):
|
||||
monkeypatch.setattr('thefuck.shells.and_', lambda *x: ' && '.join(x))
|
||||
59
tests/rules/test_apt_get.py
Normal file
59
tests/rules/test_apt_get.py
Normal file
@@ -0,0 +1,59 @@
|
||||
import pytest
|
||||
from mock import Mock, patch
|
||||
from thefuck.rules import apt_get
|
||||
from thefuck.rules.apt_get import match, get_new_command
|
||||
from tests.utils import 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')
|
||||
@pytest.mark.parametrize('command', [
|
||||
Command(script='vim', stderr='vim: command not found')])
|
||||
def test_match(command):
|
||||
assert match(command, None)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command, return_value', [
|
||||
(Command(script='vim', stderr='vim: command not found'),
|
||||
[('vim', 'main'), ('vim-tiny', 'main')])])
|
||||
@patch('thefuck.rules.apt_get.CommandNotFound', create=True)
|
||||
@patch.multiple(apt_get, create=True, apt_get='apt_get')
|
||||
def test_match_mocked(cmdnf_mock, command, return_value):
|
||||
get_packages = Mock(return_value=return_value)
|
||||
cmdnf_mock.CommandNotFound.return_value = Mock(getPackages=get_packages)
|
||||
assert match(command, None)
|
||||
assert cmdnf_mock.CommandNotFound.called
|
||||
assert get_packages.called
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command', [
|
||||
Command(script='vim', stderr=''), Command()])
|
||||
def test_not_match(command):
|
||||
assert not match(command, None)
|
||||
|
||||
|
||||
# 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, new_command', [
|
||||
(Command('vim'), 'sudo apt-get install vim && vim'),
|
||||
(Command('convert'), 'sudo apt-get install imagemagick && convert')])
|
||||
def test_get_new_command(command, new_command):
|
||||
assert get_new_command(command, None) == new_command
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command, new_command, return_value', [
|
||||
(Command('vim'), 'sudo apt-get install vim && vim',
|
||||
[('vim', 'main'), ('vim-tiny', 'main')]),
|
||||
(Command('convert'), 'sudo apt-get install imagemagick && convert',
|
||||
[('imagemagick', 'main'),
|
||||
('graphicsmagick-imagemagick-compat', 'universe')])])
|
||||
@patch('thefuck.rules.apt_get.CommandNotFound', create=True)
|
||||
@patch.multiple(apt_get, create=True, apt_get='apt_get')
|
||||
def test_get_new_command_mocked(cmdnf_mock, command, new_command, return_value):
|
||||
get_packages = Mock(return_value=return_value)
|
||||
cmdnf_mock.CommandNotFound.return_value = Mock(getPackages=get_packages)
|
||||
assert get_new_command(command, None) == new_command
|
||||
assert cmdnf_mock.CommandNotFound.called
|
||||
assert get_packages.called
|
||||
39
tests/rules/test_git_add.py
Normal file
39
tests/rules/test_git_add.py
Normal file
@@ -0,0 +1,39 @@
|
||||
import pytest
|
||||
from thefuck.rules.git_add import match, get_new_command
|
||||
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
|
||||
|
||||
|
||||
@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, None)
|
||||
|
||||
|
||||
@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, None)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command, new_command', [
|
||||
(Command('git submodule update unknown', stderr=did_not_match('unknown')),
|
||||
'git add -- unknown && git submodule update unknown'),
|
||||
(Command('git commit unknown', stderr=did_not_match('unknown')), # Old Git
|
||||
'git add -- unknown && git commit unknown')])
|
||||
def test_get_new_command(command, new_command):
|
||||
assert get_new_command(command, None) == new_command
|
||||
37
tests/rules/test_git_checkout.py
Normal file
37
tests/rules/test_git_checkout.py
Normal file
@@ -0,0 +1,37 @@
|
||||
import pytest
|
||||
from thefuck.rules.git_checkout import match, get_new_command
|
||||
from tests.utils import Command
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def did_not_match(target, did_you_forget=False):
|
||||
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
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command', [
|
||||
Command(script='git checkout unknown', stderr=did_not_match('unknown')),
|
||||
Command(script='git commit unknown', stderr=did_not_match('unknown'))])
|
||||
def test_match(command):
|
||||
assert match(command, None)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command', [
|
||||
Command(script='git submodule update unknown',
|
||||
stderr=did_not_match('unknown', True)),
|
||||
Command(script='git checkout known', stderr=('')),
|
||||
Command(script='git commit known', stderr=(''))])
|
||||
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')),
|
||||
'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):
|
||||
assert get_new_command(command, None) == new_command
|
||||
39
tests/rules/test_git_stash.py
Normal file
39
tests/rules/test_git_stash.py
Normal file
@@ -0,0 +1,39 @@
|
||||
import pytest
|
||||
from thefuck.rules.git_stash import match, get_new_command
|
||||
from tests.utils import Command
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def cherry_pick_error():
|
||||
return ('error: Your local changes would be overwritten by cherry-pick.\n'
|
||||
'hint: Commit your changes or stash them to proceed.\n'
|
||||
'fatal: cherry-pick failed')
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def rebase_error():
|
||||
return ('Cannot rebase: Your index contains uncommitted changes.\n'
|
||||
'Please commit or stash them.')
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command', [
|
||||
Command(script='git cherry-pick a1b2c3d', stderr=cherry_pick_error()),
|
||||
Command(script='git rebase -i HEAD~7', stderr=rebase_error())])
|
||||
def test_match(command):
|
||||
assert match(command, None)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command', [
|
||||
Command(script='git cherry-pick a1b2c3d', stderr=('')),
|
||||
Command(script='git rebase -i HEAD~7', stderr=(''))])
|
||||
def test_not_match(command):
|
||||
assert not match(command, None)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command, new_command', [
|
||||
(Command(script='git cherry-pick a1b2c3d', stderr=cherry_pick_error),
|
||||
'git stash && git cherry-pick a1b2c3d'),
|
||||
(Command('git rebase -i HEAD~7', stderr=rebase_error),
|
||||
'git stash && git rebase -i HEAD~7')])
|
||||
def test_get_new_command(command, new_command):
|
||||
assert get_new_command(command, None) == new_command
|
||||
12
tests/rules/test_grep_recursive.py
Normal file
12
tests/rules/test_grep_recursive.py
Normal file
@@ -0,0 +1,12 @@
|
||||
from thefuck.rules.grep_recursive import match, get_new_command
|
||||
from tests.utils import Command
|
||||
|
||||
|
||||
def test_match():
|
||||
assert match(Command('grep blah .', stderr='grep: .: Is a directory'), None)
|
||||
assert not match(Command(), None)
|
||||
|
||||
|
||||
def test_get_new_command():
|
||||
assert get_new_command(
|
||||
Command('grep blah .'), None) == 'grep -r blah .'
|
||||
19
tests/rules/test_no_such_file.py
Normal file
19
tests/rules/test_no_such_file.py
Normal file
@@ -0,0 +1,19 @@
|
||||
import pytest
|
||||
from thefuck.rules.no_such_file import match, get_new_command
|
||||
from tests.utils import Command
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command', [
|
||||
Command(script='mv foo bar/foo', stderr="mv: cannot move 'foo' to 'bar/foo': No such file or directory"),
|
||||
Command(script='mv foo bar/', stderr="mv: cannot move 'foo' to 'bar/': No such file or directory"),
|
||||
])
|
||||
def test_match(command):
|
||||
assert match(command, None)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command, new_command', [
|
||||
(Command(script='mv foo bar/foo', stderr="mv: cannot move 'foo' to 'bar/foo': No such file or directory"), 'mkdir -p bar && mv foo bar/foo'),
|
||||
(Command(script='mv foo bar/', stderr="mv: cannot move 'foo' to 'bar/': No such file or directory"), 'mkdir -p bar && mv foo bar/'),
|
||||
])
|
||||
def test_get_new_command(command, new_command):
|
||||
assert get_new_command(command, None) == new_command
|
||||
66
tests/rules/test_pacman.py
Normal file
66
tests/rules/test_pacman.py
Normal file
@@ -0,0 +1,66 @@
|
||||
import pytest
|
||||
from mock import patch
|
||||
from thefuck.rules import pacman
|
||||
from thefuck.rules.pacman import match, get_new_command
|
||||
from tests.utils import Command
|
||||
|
||||
|
||||
pacman_cmd = getattr(pacman, 'pacman', 'pacman')
|
||||
|
||||
PKGFILE_OUTPUT_CONVERT = '''
|
||||
extra/imagemagick 6.9.1.0-1\t/usr/bin/convert
|
||||
'''
|
||||
|
||||
PKGFILE_OUTPUT_VIM = '''
|
||||
extra/gvim 7.4.712-1 \t/usr/bin/vim
|
||||
extra/gvim-python3 7.4.712-1\t/usr/bin/vim
|
||||
extra/vim 7.4.712-1 \t/usr/bin/vim
|
||||
extra/vim-minimal 7.4.712-1 \t/usr/bin/vim
|
||||
extra/vim-python3 7.4.712-1 \t/usr/bin/vim
|
||||
'''
|
||||
|
||||
|
||||
@pytest.mark.skipif(not getattr(pacman, 'enabled_by_default', True),
|
||||
reason='Skip if pacman is not available')
|
||||
@pytest.mark.parametrize('command', [
|
||||
Command(script='vim', stderr='vim: command not found')])
|
||||
def test_match(command):
|
||||
assert match(command, None)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command, return_value', [
|
||||
(Command(script='vim', stderr='vim: command not found'), PKGFILE_OUTPUT_VIM)])
|
||||
@patch('thefuck.rules.pacman.subprocess')
|
||||
@patch.multiple(pacman, create=True, pacman=pacman_cmd)
|
||||
def test_match_mocked(subp_mock, command, return_value):
|
||||
subp_mock.check_output.return_value = return_value
|
||||
assert match(command, None)
|
||||
assert subp_mock.check_output.called
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command', [
|
||||
Command(script='vim', stderr=''), Command()])
|
||||
def test_not_match(command):
|
||||
assert not match(command, None)
|
||||
|
||||
|
||||
@pytest.mark.skipif(not getattr(pacman, 'enabled_by_default', True),
|
||||
reason='Skip if pacman is not available')
|
||||
@pytest.mark.parametrize('command, new_command', [
|
||||
(Command('vim'), '{} -S extra/gvim && vim'.format(pacman_cmd)),
|
||||
(Command('convert'), '{} -S extra/imagemagick && convert'.format(pacman_cmd))])
|
||||
def test_get_new_command(command, new_command, mocker):
|
||||
assert get_new_command(command, None) == new_command
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command, new_command, return_value', [
|
||||
(Command('vim'), '{} -S extra/gvim && vim'.format(pacman_cmd),
|
||||
PKGFILE_OUTPUT_VIM),
|
||||
(Command('convert'), '{} -S extra/imagemagick && convert'.format(pacman_cmd),
|
||||
PKGFILE_OUTPUT_CONVERT)])
|
||||
@patch('thefuck.rules.pacman.subprocess')
|
||||
@patch.multiple(pacman, create=True, pacman=pacman_cmd)
|
||||
def test_get_new_command_mocked(subp_mock, command, new_command, return_value):
|
||||
subp_mock.check_output.return_value = return_value
|
||||
assert get_new_command(command, None) == new_command
|
||||
assert subp_mock.check_output.called
|
||||
19
tests/rules/test_whois.py
Normal file
19
tests/rules/test_whois.py
Normal file
@@ -0,0 +1,19 @@
|
||||
import pytest
|
||||
from thefuck.rules.whois import match, get_new_command
|
||||
from tests.utils import Command
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command', [
|
||||
Command(script='whois https://en.wikipedia.org/wiki/Main_Page'),
|
||||
Command(script='whois https://en.wikipedia.org/'),
|
||||
Command(script='whois en.wikipedia.org')])
|
||||
def test_match(command):
|
||||
assert match(command, None)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command, new_command', [
|
||||
(Command('whois https://en.wikipedia.org/wiki/Main_Page'), 'whois en.wikipedia.org'),
|
||||
(Command('whois https://en.wikipedia.org/'), 'whois en.wikipedia.org'),
|
||||
(Command('whois en.wikipedia.org'), 'whois wikipedia.org')])
|
||||
def test_get_new_command(command, new_command):
|
||||
assert get_new_command(command, None) == new_command
|
||||
@@ -50,6 +50,28 @@ class TestBash(object):
|
||||
write.assert_called_once_with('ls\n')
|
||||
|
||||
|
||||
@pytest.mark.usefixtures('isfile')
|
||||
class TestFish(object):
|
||||
@pytest.mark.parametrize('before, after', [
|
||||
('pwd', 'pwd'),
|
||||
('ll', 'll')]) # Fish has no aliases but functions
|
||||
def test_from_shell(self, before, after):
|
||||
assert shells.Fish().from_shell(before) == after
|
||||
|
||||
def test_to_shell(self):
|
||||
assert shells.Fish().to_shell('pwd') == 'pwd'
|
||||
|
||||
def test_put_to_history(self, builtins_open, mocker):
|
||||
mocker.patch('thefuck.shells.time',
|
||||
return_value=1430707243.3517463)
|
||||
shells.Fish().put_to_history('ls')
|
||||
builtins_open.return_value.__enter__.return_value. \
|
||||
write.assert_called_once_with('- cmd: ls\n when: 1430707243\n')
|
||||
|
||||
def test_and_(self):
|
||||
assert shells.Fish().and_('foo', 'bar') == 'foo; and bar'
|
||||
|
||||
|
||||
@pytest.mark.usefixtures('isfile')
|
||||
class TestZsh(object):
|
||||
@pytest.fixture(autouse=True)
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from thefuck import shells
|
||||
|
||||
try:
|
||||
import CommandNotFound
|
||||
except ImportError:
|
||||
@@ -20,4 +22,5 @@ def get_new_command(command, settings):
|
||||
c = CommandNotFound.CommandNotFound()
|
||||
pkgs = c.getPackages(command.script.split(" ")[0])
|
||||
name, _ = pkgs[0]
|
||||
return "sudo apt-get install {} && {}".format(name, command.script)
|
||||
formatme = shells.and_('sudo apt-get install {}', '{}')
|
||||
return formatme.format(name, command.script)
|
||||
|
||||
@@ -3,12 +3,11 @@ import os
|
||||
import re
|
||||
from subprocess import check_output
|
||||
|
||||
import thefuck.logs
|
||||
|
||||
# Formulars are base on each local system's status
|
||||
brew_formulas = []
|
||||
try:
|
||||
brew_path_prefix = check_output(['brew', '--prefix']).strip()
|
||||
brew_path_prefix = check_output(['brew', '--prefix'],
|
||||
universal_newlines=True).strip()
|
||||
brew_formula_path = brew_path_prefix + '/Library/Formula'
|
||||
|
||||
for file_name in os.listdir(brew_formula_path):
|
||||
|
||||
@@ -12,7 +12,8 @@ TAP_CMD_PATH = '/%s/%s/cmd'
|
||||
def _get_brew_path_prefix():
|
||||
"""To get brew path"""
|
||||
try:
|
||||
return subprocess.check_output(['brew', '--prefix']).strip()
|
||||
return subprocess.check_output(['brew', '--prefix'],
|
||||
universal_newlines=True).strip()
|
||||
except:
|
||||
return None
|
||||
|
||||
|
||||
53
thefuck/rules/cd_correction.py
Normal file
53
thefuck/rules/cd_correction.py
Normal file
@@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env python
|
||||
__author__ = "mmussomele"
|
||||
|
||||
"""Attempts to spellcheck and correct failed cd commands"""
|
||||
|
||||
import os
|
||||
from difflib import get_close_matches
|
||||
from thefuck.utils import sudo_support
|
||||
from thefuck.rules import cd_mkdir
|
||||
|
||||
MAX_ALLOWED_DIFF = 0.6
|
||||
|
||||
|
||||
def _get_sub_dirs(parent):
|
||||
"""Returns a list of the child directories of the given parent directory"""
|
||||
return [child for child in os.listdir(parent) if os.path.isdir(os.path.join(parent, child))]
|
||||
|
||||
|
||||
@sudo_support
|
||||
def match(command, settings):
|
||||
"""Match function copied from cd_mkdir.py"""
|
||||
return (command.script.startswith('cd ')
|
||||
and ('no such file or directory' in command.stderr.lower()
|
||||
or 'cd: can\'t cd to' in command.stderr.lower()))
|
||||
|
||||
|
||||
@sudo_support
|
||||
def get_new_command(command, settings):
|
||||
"""
|
||||
Attempt to rebuild the path string by spellchecking the directories.
|
||||
If it fails (i.e. no directories are a close enough match), then it
|
||||
defaults to the rules of cd_mkdir.
|
||||
Change sensitivity by changing MAX_ALLOWED_DIFF. Default value is 0.6
|
||||
"""
|
||||
dest = command.script.split()[1].split(os.sep)
|
||||
if dest[-1] == '':
|
||||
dest = dest[:-1]
|
||||
cwd = os.getcwd()
|
||||
for directory in dest:
|
||||
if directory == ".":
|
||||
continue
|
||||
elif directory == "..":
|
||||
cwd = os.path.split(cwd)[0]
|
||||
continue
|
||||
best_matches = get_close_matches(directory, _get_sub_dirs(cwd), cutoff=MAX_ALLOWED_DIFF)
|
||||
if best_matches:
|
||||
cwd = os.path.join(cwd, best_matches[0])
|
||||
else:
|
||||
return cd_mkdir.get_new_command(command, settings)
|
||||
return "cd {0}".format(cwd)
|
||||
|
||||
|
||||
enabled_by_default = True
|
||||
@@ -1,4 +1,5 @@
|
||||
import re
|
||||
from thefuck import shells
|
||||
from thefuck.utils import sudo_support
|
||||
|
||||
|
||||
@@ -11,4 +12,5 @@ def match(command, settings):
|
||||
|
||||
@sudo_support
|
||||
def get_new_command(command, settings):
|
||||
return re.sub(r'^cd (.*)', 'mkdir -p \\1 && cd \\1', command.script)
|
||||
repl = shells.and_('mkdir -p \\1', 'cd \\1')
|
||||
return re.sub(r'^cd (.*)', repl, command.script)
|
||||
|
||||
9
thefuck/rules/cpp11.py
Normal file
9
thefuck/rules/cpp11.py
Normal file
@@ -0,0 +1,9 @@
|
||||
def match(command, settings):
|
||||
return (('g++' in command.script or 'clang++' in command.script) and
|
||||
('This file requires compiler and library support for the '
|
||||
'ISO C++ 2011 standard.' in command.stderr or
|
||||
'-Wc++11-extensions' in command.stderr))
|
||||
|
||||
|
||||
def get_new_command(command, settings):
|
||||
return command.script + ' -std=c++11'
|
||||
@@ -1,4 +1,5 @@
|
||||
import re
|
||||
from thefuck import shells
|
||||
|
||||
|
||||
def match(command, settings):
|
||||
@@ -12,4 +13,5 @@ def get_new_command(command, settings):
|
||||
r"error: pathspec '([^']*)' "
|
||||
"did not match any file\(s\) known to git.", command.stderr)[0]
|
||||
|
||||
return 'git add -- {} && {}'.format(missing_file, command.script)
|
||||
formatme = shells.and_('git add -- {}', '{}')
|
||||
return formatme.format(missing_file, command.script)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import re
|
||||
from thefuck import shells
|
||||
|
||||
|
||||
def match(command, settings):
|
||||
@@ -12,4 +13,5 @@ def get_new_command(command, settings):
|
||||
r"error: pathspec '([^']*)' "
|
||||
"did not match any file\(s\) known to git.", command.stderr)[0]
|
||||
|
||||
return 'git branch {} && {}'.format(missing_file, command.script)
|
||||
formatme = shells.and_('git branch {}', '{}')
|
||||
return formatme.format(missing_file, command.script)
|
||||
|
||||
12
thefuck/rules/git_stash.py
Normal file
12
thefuck/rules/git_stash.py
Normal file
@@ -0,0 +1,12 @@
|
||||
from thefuck import shells
|
||||
|
||||
|
||||
def match(command, settings):
|
||||
# catches "Please commit or stash them" and "Please, commit your changes or
|
||||
# stash them before you can switch branches."
|
||||
return 'git' in command.script and 'or stash them' in command.stderr
|
||||
|
||||
|
||||
def get_new_command(command, settings):
|
||||
formatme = shells.and_('git stash', '{}')
|
||||
return formatme.format(command.script)
|
||||
7
thefuck/rules/grep_recursive.py
Normal file
7
thefuck/rules/grep_recursive.py
Normal file
@@ -0,0 +1,7 @@
|
||||
def match(command, settings):
|
||||
return (command.script.startswith('grep')
|
||||
and 'is a directory' in command.stderr.lower())
|
||||
|
||||
|
||||
def get_new_command(command, settings):
|
||||
return 'grep -r {}'.format(command.script[5:])
|
||||
30
thefuck/rules/no_such_file.py
Normal file
30
thefuck/rules/no_such_file.py
Normal file
@@ -0,0 +1,30 @@
|
||||
import re
|
||||
from thefuck import shells
|
||||
|
||||
|
||||
patterns = (
|
||||
r"mv: cannot move '[^']*' to '([^']*)': No such file or directory",
|
||||
r"mv: cannot move '[^']*' to '([^']*)': Not a directory",
|
||||
r"cp: cannot create regular file '([^']*)': No such file or directory",
|
||||
r"cp: cannot create regular file '([^']*)': Not a directory",
|
||||
)
|
||||
|
||||
|
||||
def match(command, settings):
|
||||
for pattern in patterns:
|
||||
if re.search(pattern, command.stderr):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def get_new_command(command, settings):
|
||||
for pattern in patterns:
|
||||
file = re.findall(pattern, command.stderr)
|
||||
|
||||
if file:
|
||||
file = file[0]
|
||||
dir = file[0:file.rfind('/')]
|
||||
|
||||
formatme = shells.and_('mkdir -p {}', '{}')
|
||||
return formatme.format(dir, command.script)
|
||||
@@ -1,23 +1,13 @@
|
||||
import subprocess
|
||||
from thefuck.utils import DEVNULL
|
||||
|
||||
|
||||
def __command_available(command):
|
||||
try:
|
||||
subprocess.check_output([command], stderr=DEVNULL)
|
||||
return True
|
||||
except subprocess.CalledProcessError:
|
||||
# command exists but is not happy to be called without any argument
|
||||
return True
|
||||
except OSError:
|
||||
return False
|
||||
from thefuck.utils import DEVNULL, which
|
||||
from thefuck import shells
|
||||
|
||||
|
||||
def __get_pkgfile(command):
|
||||
try:
|
||||
return subprocess.check_output(
|
||||
['pkgfile', '-b', '-v', command.script.split(" ")[0]],
|
||||
universal_newlines=True, stderr=subprocess.DEVNULL
|
||||
universal_newlines=True, stderr=DEVNULL
|
||||
).split()
|
||||
except subprocess.CalledProcessError:
|
||||
return None
|
||||
@@ -30,14 +20,15 @@ def match(command, settings):
|
||||
def get_new_command(command, settings):
|
||||
package = __get_pkgfile(command)[0]
|
||||
|
||||
return '{} -S {} && {}'.format(pacman, package, command.script)
|
||||
formatme = shells.and_('{} -S {}', '{}')
|
||||
return formatme.format(pacman, package, command.script)
|
||||
|
||||
|
||||
if not __command_available('pkgfile'):
|
||||
if not which('pkgfile'):
|
||||
enabled_by_default = False
|
||||
elif __command_available('yaourt'):
|
||||
elif which('yaourt'):
|
||||
pacman = 'yaourt'
|
||||
elif __command_available('pacman'):
|
||||
elif which('pacman'):
|
||||
pacman = 'sudo pacman'
|
||||
else:
|
||||
enabled_by_default = False
|
||||
|
||||
31
thefuck/rules/whois.py
Normal file
31
thefuck/rules/whois.py
Normal file
@@ -0,0 +1,31 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
from six.moves.urllib.parse import urlparse
|
||||
|
||||
|
||||
def match(command, settings):
|
||||
"""
|
||||
What the `whois` command returns depends on the 'Whois server' it contacted
|
||||
and is not consistent through different servers. But there can be only two
|
||||
types of errors I can think of with `whois`:
|
||||
- `whois https://en.wikipedia.org/` → `whois en.wikipedia.org`;
|
||||
- `whois en.wikipedia.org` → `whois wikipedia.org`.
|
||||
So we match any `whois` command and then:
|
||||
- if there is a slash: keep only the FQDN;
|
||||
- if there is no slash but there is a point: removes the left-most
|
||||
subdomain.
|
||||
|
||||
We cannot either remove all subdomains because we cannot know which part is
|
||||
the subdomains and which is the domain, consider:
|
||||
- www.google.fr → subdomain: www, domain: 'google.fr';
|
||||
- google.co.uk → subdomain: None, domain; 'google.co.uk'.
|
||||
"""
|
||||
return 'whois' in command.script
|
||||
|
||||
|
||||
def get_new_command(command, settings):
|
||||
url = command.script.split()[1]
|
||||
|
||||
if '/' in command.script:
|
||||
return 'whois ' + urlparse(url).netloc
|
||||
elif '.' in command.script:
|
||||
return 'whois ' + '.'.join(urlparse(url).path.split('.')[1:])
|
||||
@@ -47,8 +47,14 @@ class Generic(object):
|
||||
with open(history_file_name, 'a') as history:
|
||||
history.write(self._get_history_line(command_script))
|
||||
|
||||
def and_(self, *commands):
|
||||
return ' && '.join(commands)
|
||||
|
||||
|
||||
class Bash(Generic):
|
||||
def app_alias(self):
|
||||
return "\nalias fuck='eval $(thefuck $(fc -ln -1)); history -r'\n"
|
||||
|
||||
def _parse_alias(self, alias):
|
||||
name, value = alias.replace('alias ', '', 1).split('=', 1)
|
||||
if value[0] == value[-1] == '"' or value[0] == value[-1] == "'":
|
||||
@@ -70,7 +76,35 @@ class Bash(Generic):
|
||||
return u'{}\n'.format(command_script)
|
||||
|
||||
|
||||
class Fish(Generic):
|
||||
def app_alias(self):
|
||||
return ("function fuck -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"
|
||||
" set -l fucked_up_commandd $history[1]\n"
|
||||
" thefuck $fucked_up_commandd > $eval_script\n"
|
||||
" . $eval_script\n"
|
||||
" rm $eval_script\n"
|
||||
" if test $exit_code -ne 0\n"
|
||||
" history --delete $fucked_up_commandd\n"
|
||||
" end\n"
|
||||
"end")
|
||||
|
||||
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 and_(self, *commands):
|
||||
return '; and '.join(commands)
|
||||
|
||||
|
||||
class Zsh(Generic):
|
||||
def app_alias(self):
|
||||
return "\nalias fuck='eval $(thefuck $(fc -ln -1 | tail -n 1)); fc -R'\n"
|
||||
|
||||
def _parse_alias(self, alias):
|
||||
name, value = alias.split('=', 1)
|
||||
if value[0] == value[-1] == '"' or value[0] == value[-1] == "'":
|
||||
@@ -92,14 +126,43 @@ class Zsh(Generic):
|
||||
return u': {}:0;{}\n'.format(int(time()), command_script)
|
||||
|
||||
|
||||
class Tcsh(Generic):
|
||||
def app_alias(self):
|
||||
return "\nalias fuck 'set fucked_cmd=`history -h 2 | head -n 1` && eval `thefuck ${fucked_cmd}`'\n"
|
||||
|
||||
def _parse_alias(self, alias):
|
||||
name, value = alias.split("\t", 1)
|
||||
return name, value
|
||||
|
||||
def _get_aliases(self):
|
||||
proc = Popen('tcsh -ic alias', stdout=PIPE, stderr=DEVNULL, shell=True)
|
||||
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)
|
||||
|
||||
|
||||
shells = defaultdict(lambda: Generic(), {
|
||||
'bash': Bash(),
|
||||
'zsh': Zsh()})
|
||||
'fish': Fish(),
|
||||
'zsh': Zsh(),
|
||||
'-csh': Tcsh(),
|
||||
'tcsh': Tcsh()})
|
||||
|
||||
|
||||
def _get_shell():
|
||||
shell = Process(os.getpid()).parent().cmdline()[0]
|
||||
return shells[shell]
|
||||
try:
|
||||
shell = Process(os.getpid()).parent().cmdline()[0]
|
||||
except TypeError:
|
||||
shell = Process(os.getpid()).parent.cmdline[0]
|
||||
return shells[os.path.basename(shell)]
|
||||
|
||||
|
||||
def from_shell(command):
|
||||
@@ -111,8 +174,12 @@ def to_shell(command):
|
||||
|
||||
|
||||
def app_alias():
|
||||
return _get_shell().app_alias()
|
||||
print(_get_shell().app_alias())
|
||||
|
||||
|
||||
def put_to_history(command):
|
||||
return _get_shell().put_to_history(command)
|
||||
|
||||
|
||||
def and_(*commands):
|
||||
return _get_shell().and_(*commands)
|
||||
|
||||
Reference in New Issue
Block a user