mirror of
https://github.com/nvbn/thefuck.git
synced 2025-11-01 15:42:06 +00:00
Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
28
README.md
28
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)
|
||||
@@ -155,8 +155,9 @@ 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;
|
||||
@@ -167,6 +168,7 @@ using the matched rule and runs it. Rules enabled by default are as follows:
|
||||
* `git_pull_clone` – clones instead of pulling when the repo does not exist;
|
||||
* `git_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`;
|
||||
@@ -305,7 +307,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`;
|
||||
@@ -357,3 +359,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"
|
||||
2
setup.py
2
setup.py
@@ -26,7 +26,7 @@ elif (3, 0) < version < (3, 3):
|
||||
' ({}.{} detected).'.format(*version))
|
||||
sys.exit(-1)
|
||||
|
||||
VERSION = '3.5'
|
||||
VERSION = '3.8'
|
||||
|
||||
install_requires = ['psutil', 'colorama', 'six', 'decorator']
|
||||
extras_require = {':python_version<"3.4"': ['pathlib'],
|
||||
|
||||
@@ -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
|
||||
27
tests/rules/test_git_rm_recursive.py
Normal file
27
tests/rules/test_git_rm_recursive.py
Normal file
@@ -0,0 +1,27 @@
|
||||
import pytest
|
||||
from thefuck.rules.git_rm_recursive import match, get_new_command
|
||||
from tests.utils import Command
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def stderr(target):
|
||||
return "fatal: not removing '{}' recursively without -r".format(target)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('script, target', [
|
||||
('git rm foo', 'foo'),
|
||||
('git rm foo bar', 'foo bar')])
|
||||
def test_match(stderr, script, target):
|
||||
assert match(Command(script=script, stderr=stderr))
|
||||
|
||||
|
||||
@pytest.mark.parametrize('script', ['git rm foo', 'git rm foo bar'])
|
||||
def test_not_match(script):
|
||||
assert not match(Command(script=script, stderr=''))
|
||||
|
||||
|
||||
@pytest.mark.parametrize('script, target, new_command', [
|
||||
('git rm foo', 'foo', 'git rm -r foo'),
|
||||
('git rm foo bar', 'foo bar', 'git rm -r foo bar')])
|
||||
def test_get_new_command(stderr, script, target, new_command):
|
||||
assert get_new_command(Command(script=script, stderr=stderr)) == new_command
|
||||
@@ -3,38 +3,23 @@ from thefuck.rules.history import match, get_new_command
|
||||
from tests.utils import Command
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def history(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 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.get_alias',
|
||||
return_value='fuck')
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def callables(mocker):
|
||||
return mocker.patch('thefuck.rules.history.get_all_executables',
|
||||
return_value=['diff', 'ls'])
|
||||
|
||||
|
||||
@pytest.mark.usefixtures('history', 'callables', 'no_memoize', 'alias')
|
||||
@pytest.mark.parametrize('script', ['ls cet', 'daff x'])
|
||||
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')])
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -19,14 +19,18 @@ class TestFish(object):
|
||||
return mock
|
||||
|
||||
@pytest.fixture
|
||||
def environ(self, monkeypatch):
|
||||
data = {'TF_OVERRIDDEN_ALIASES': 'cd, ls, man, open'}
|
||||
monkeypatch.setattr('thefuck.shells.fish.os.environ', data)
|
||||
return data
|
||||
def os_environ(self, monkeypatch, key, value):
|
||||
monkeypatch.setattr('os.environ', {key: value})
|
||||
|
||||
@pytest.mark.usefixture('environ')
|
||||
def test_get_overridden_aliases(self, shell, environ):
|
||||
assert shell._get_overridden_aliases() == ['cd', 'ls', 'man', 'open']
|
||||
@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'),
|
||||
|
||||
19
tests/shells/test_powershell.py
Normal file
19
tests/shells/test_powershell.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import pytest
|
||||
from thefuck.shells import Powershell
|
||||
|
||||
|
||||
@pytest.mark.usefixtures('isfile', 'no_memoize', 'no_cache')
|
||||
class TestPowershell(object):
|
||||
@pytest.fixture
|
||||
def shell(self):
|
||||
return Powershell()
|
||||
|
||||
def test_and_(self, shell):
|
||||
assert shell.and_('ls', 'cd') == '(ls) -and (cd)'
|
||||
|
||||
def test_app_alias(self, shell):
|
||||
assert 'function fuck' in shell.app_alias('fuck')
|
||||
assert 'function FUCK' in shell.app_alias('FUCK')
|
||||
assert 'thefuck' in shell.app_alias('fuck')
|
||||
@@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import pytest
|
||||
from pathlib import PosixPath
|
||||
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
|
||||
@@ -30,7 +30,7 @@ class TestGetRules(object):
|
||||
(['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,5 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
from subprocess import PIPE
|
||||
from mock import Mock
|
||||
from pathlib import Path
|
||||
@@ -39,9 +40,10 @@ 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', [
|
||||
(const.DEFAULT_RULES, [], Rule('git', enabled_by_default=True), True),
|
||||
@@ -110,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={})
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -5,15 +5,14 @@ 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 = 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)
|
||||
15
thefuck/rules/git_rm_recursive.py
Normal file
15
thefuck/rules/git_rm_recursive.py
Normal file
@@ -0,0 +1,15 @@
|
||||
from thefuck.specific.git import git_support
|
||||
|
||||
|
||||
@git_support
|
||||
def match(command):
|
||||
return (' rm ' in command.script
|
||||
and "fatal: not removing '" in command.stderr
|
||||
and "' recursively without -r" in command.stderr)
|
||||
|
||||
|
||||
@git_support
|
||||
def get_new_command(command):
|
||||
index = command.script_parts.index('rm') + 1
|
||||
command.script_parts.insert(index, '-r')
|
||||
return u' '.join(command.script_parts)
|
||||
@@ -1,37 +1,15 @@
|
||||
from difflib import get_close_matches
|
||||
from thefuck.shells import shell
|
||||
from thefuck.utils import get_closest, memoize, get_all_executables, get_alias
|
||||
|
||||
|
||||
def _not_corrected(history, tf_alias):
|
||||
"""Returns all lines from history except that comes before `fuck`."""
|
||||
previous = None
|
||||
for line in history:
|
||||
if previous is not None and line != tf_alias:
|
||||
yield previous
|
||||
previous = line
|
||||
if history:
|
||||
yield history[-1]
|
||||
|
||||
|
||||
@memoize
|
||||
def _history_of_exists_without_current(command):
|
||||
history = shell.get_history()
|
||||
tf_alias = get_alias()
|
||||
executables = get_all_executables()
|
||||
return [line for line in _not_corrected(history, tf_alias)
|
||||
if not line.startswith(tf_alias) and not line == command.script
|
||||
and line.split(' ')[0] in executables]
|
||||
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
|
||||
|
||||
@@ -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]
|
||||
|
||||
|
||||
@@ -9,20 +9,37 @@ 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}
|
||||
'tcsh': Tcsh,
|
||||
'powershell': Powershell}
|
||||
|
||||
|
||||
def _get_shell():
|
||||
try:
|
||||
shell_name = Process(os.getpid()).parent().name()
|
||||
except TypeError:
|
||||
shell_name = Process(os.getpid()).parent.name
|
||||
return shells.get(shell_name, Generic)()
|
||||
proc = Process(os.getpid())
|
||||
|
||||
while (proc is not None):
|
||||
name = 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()
|
||||
|
||||
@@ -7,11 +7,12 @@ from .generic import Generic
|
||||
|
||||
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']
|
||||
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
|
||||
|
||||
18
thefuck/shells/powershell.py
Normal file
18
thefuck/shells/powershell.py
Normal file
@@ -0,0 +1,18 @@
|
||||
from .generic import Generic
|
||||
|
||||
|
||||
class Powershell(Generic):
|
||||
def app_alias(self, fuck):
|
||||
return 'function ' + fuck + ' { \n' \
|
||||
' $fuck = $(thefuck (Get-History -Count 1).CommandLine)\n' \
|
||||
' if (-not [string]::IsNullOrWhiteSpace($fuck)) {\n' \
|
||||
' if ($fuck.StartsWith("echo")) { $fuck = $fuck.Substring(5) }\n' \
|
||||
' else { iex "$fuck" }\n' \
|
||||
' }\n' \
|
||||
'}\n'
|
||||
|
||||
def and_(self, *commands):
|
||||
return u' -and '.join('({0})'.format(c) for c in commands)
|
||||
|
||||
def how_to_configure(self):
|
||||
return 'iex "thefuck --alias"', '$profile'
|
||||
@@ -1,3 +1,4 @@
|
||||
import os
|
||||
import sys
|
||||
import 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)
|
||||
|
||||
@@ -114,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')
|
||||
|
||||
@@ -269,3 +269,24 @@ def get_installation_info():
|
||||
|
||||
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