mirror of
https://github.com/nvbn/thefuck.git
synced 2025-11-17 07:16:04 +00:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
39f7cc37eb | ||
|
|
251b69b5a0 | ||
|
|
8b1f078e27 | ||
|
|
a47e84fa6b | ||
|
|
bcab700215 | ||
|
|
4fb99fd7a8 | ||
|
|
bb5f6bb705 | ||
|
|
d92765d5df | ||
|
|
d8de5cfd20 | ||
|
|
d73b14ce4b | ||
|
|
04b83cf7e8 |
@@ -161,11 +161,13 @@ using the matched rule and runs it. Rules enabled by default are as follows:
|
||||
* `git_checkout` – fixes branch name or creates new branch;
|
||||
* `git_diff_staged` – adds `--staged` to previous `git diff` with unexpected output;
|
||||
* `git_fix_stash` – fixes `git stash` commands (misspelled subcommand and missing `save`);
|
||||
* `git_help_aliased` – fixes `git help <alias>` commands replacing <alias> with the aliased command;
|
||||
* `git_not_command` – fixes wrong git commands like `git brnch`;
|
||||
* `git_pull` – sets upstream before executing previous `git pull`;
|
||||
* `git_pull_clone` – clones instead of pulling when the repo does not exist;
|
||||
* `git_push` – adds `--set-upstream origin $branch` to previous failed `git push`;
|
||||
* `git_push_pull` – runs `git pull` when `push` was rejected;
|
||||
* `git_remote_seturl_add` – runs `git remote add` when `git remote set_url` on nonexistant remote;
|
||||
* `git_stash` – stashes you local modifications before rebasing or switching branch;
|
||||
* `git_two_dashes` – adds a missing dash to commands like `git commit -amend` or `git rebase -continue`;
|
||||
* `go_run` – appends `.go` extension when compiling/running Go programs;
|
||||
|
||||
2
setup.py
2
setup.py
@@ -26,7 +26,7 @@ elif (3, 0) < version < (3, 3):
|
||||
' ({}.{} detected).'.format(*version))
|
||||
sys.exit(-1)
|
||||
|
||||
VERSION = '3.4'
|
||||
VERSION = '3.6'
|
||||
|
||||
install_requires = ['psutil', 'colorama', 'six', 'decorator']
|
||||
extras_require = {':python_version<"3.4"': ['pathlib'],
|
||||
|
||||
24
tests/rules/test_git_help_aliased.py
Normal file
24
tests/rules/test_git_help_aliased.py
Normal file
@@ -0,0 +1,24 @@
|
||||
import pytest
|
||||
from thefuck.rules.git_help_aliased import match, get_new_command
|
||||
from tests.utils import Command
|
||||
|
||||
|
||||
@pytest.mark.parametrize('script, stdout', [
|
||||
('git help st', "`git st' is aliased to `status'"),
|
||||
('git help ds', "`git ds' is aliased to `diff --staged'")])
|
||||
def test_match(script, stdout):
|
||||
assert match(Command(script=script, stdout=stdout))
|
||||
|
||||
|
||||
@pytest.mark.parametrize('script, stdout', [
|
||||
('git help status', "GIT-STATUS(1)...Git Manual...GIT-STATUS(1)"),
|
||||
('git help diff', "GIT-DIFF(1)...Git Manual...GIT-DIFF(1)")])
|
||||
def test_not_match(script, stdout):
|
||||
assert not match(Command(script=script, stdout=stdout))
|
||||
|
||||
|
||||
@pytest.mark.parametrize('script, stdout, new_command', [
|
||||
('git help st', "`git st' is aliased to `status'", 'git help status'),
|
||||
('git help ds', "`git ds' is aliased to `diff --staged'", 'git help diff')])
|
||||
def test_get_new_command(script, stdout, new_command):
|
||||
assert get_new_command(Command(script=script, stdout=stdout)) == new_command
|
||||
26
tests/rules/test_git_remote_seturl_add.py
Normal file
26
tests/rules/test_git_remote_seturl_add.py
Normal file
@@ -0,0 +1,26 @@
|
||||
import pytest
|
||||
from thefuck.rules.git_remote_seturl_add import match, get_new_command
|
||||
from tests.utils import Command
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command', [
|
||||
Command(script='git remote set-url origin url', stderr="fatal: No such remote")])
|
||||
def test_match(command):
|
||||
assert match(command)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command', [
|
||||
Command('git remote set-url origin url', stderr=""),
|
||||
Command('git remote add origin url'),
|
||||
Command('git remote remove origin'),
|
||||
Command('git remote prune origin'),
|
||||
Command('git remote set-branches origin branch')
|
||||
])
|
||||
def test_not_match(command):
|
||||
assert not match(command)
|
||||
|
||||
@pytest.mark.parametrize('command, new_command', [
|
||||
(Command('git remote set-url origin git@github.com:nvbn/thefuck.git'),
|
||||
'git remote add origin git@github.com:nvbn/thefuck.git')])
|
||||
def test_get_new_command(command, new_command):
|
||||
assert get_new_command(command) == new_command
|
||||
@@ -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
|
||||
|
||||
@@ -46,6 +46,13 @@ class TestBash(object):
|
||||
assert 'TF_ALIAS=fuck' in shell.app_alias('fuck')
|
||||
assert 'PYTHONIOENCODING=utf-8' in shell.app_alias('fuck')
|
||||
|
||||
def test_app_alias_variables_correctly_set(self, shell):
|
||||
alias = shell.app_alias('fuck')
|
||||
assert "alias fuck='TF_CMD=$(TF_ALIAS" in alias
|
||||
assert '$(TF_ALIAS=fuck PYTHONIOENCODING' in alias
|
||||
assert 'PYTHONIOENCODING=utf-8 TF_SHELL_ALIASES' in alias
|
||||
assert 'ALIASES=$(alias) thefuck' in alias
|
||||
|
||||
def test_get_history(self, history_lines, shell):
|
||||
history_lines(['ls', 'rm'])
|
||||
assert list(shell.get_history()) == ['ls', 'rm']
|
||||
|
||||
@@ -45,6 +45,13 @@ class TestZsh(object):
|
||||
assert 'thefuck' in shell.app_alias('fuck')
|
||||
assert 'PYTHONIOENCODING' in shell.app_alias('fuck')
|
||||
|
||||
def test_app_alias_variables_correctly_set(self, shell):
|
||||
alias = shell.app_alias('fuck')
|
||||
assert "alias fuck='TF_CMD=$(TF_ALIAS" in alias
|
||||
assert '$(TF_ALIAS=fuck PYTHONIOENCODING' in alias
|
||||
assert 'PYTHONIOENCODING=utf-8 TF_SHELL_ALIASES' in alias
|
||||
assert 'ALIASES=$(alias) thefuck' in alias
|
||||
|
||||
def test_get_history(self, history_lines, shell):
|
||||
history_lines([': 1432613911:0;ls', ': 1432613916:0;rm'])
|
||||
assert list(shell.get_history()) == ['ls', 'rm']
|
||||
|
||||
@@ -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
|
||||
|
||||
12
thefuck/rules/git_help_aliased.py
Normal file
12
thefuck/rules/git_help_aliased.py
Normal file
@@ -0,0 +1,12 @@
|
||||
from thefuck.specific.git import git_support
|
||||
|
||||
|
||||
@git_support
|
||||
def match(command):
|
||||
return 'help' in command.script and ' is aliased to ' in command.stdout
|
||||
|
||||
|
||||
@git_support
|
||||
def get_new_command(command):
|
||||
aliased = command.stdout.split('`', 2)[2].split("'", 1)[0].split(' ', 1)[0]
|
||||
return 'git help {}'.format(aliased)
|
||||
14
thefuck/rules/git_remote_seturl_add.py
Normal file
14
thefuck/rules/git_remote_seturl_add.py
Normal file
@@ -0,0 +1,14 @@
|
||||
from thefuck.utils import replace_argument
|
||||
from thefuck.specific.git import git_support
|
||||
|
||||
|
||||
@git_support
|
||||
def match(command):
|
||||
return ('set-url' in command.script and 'fatal: No such remote' in command.stderr)
|
||||
|
||||
|
||||
def get_new_command(command):
|
||||
return replace_argument(command.script, 'set-url', 'add')
|
||||
|
||||
|
||||
enabled_by_default = True
|
||||
@@ -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]
|
||||
|
||||
|
||||
@@ -6,9 +6,11 @@ from .generic import Generic
|
||||
|
||||
class Bash(Generic):
|
||||
def app_alias(self, fuck):
|
||||
alias = "TF_ALIAS={0}" \
|
||||
" alias {0}='PYTHONIOENCODING=utf-8" \
|
||||
" TF_CMD=$(TF_SHELL_ALIASES=$(alias) thefuck $(fc -ln -1)) && " \
|
||||
# It is VERY important to have the variables declared WITHIN the alias
|
||||
alias = "alias {0}='TF_CMD=$(TF_ALIAS={0}" \
|
||||
" PYTHONIOENCODING=utf-8" \
|
||||
" TF_SHELL_ALIASES=$(alias)" \
|
||||
" thefuck $(fc -ln -1)) &&" \
|
||||
" eval $TF_CMD".format(fuck)
|
||||
|
||||
if settings.alter_history:
|
||||
|
||||
@@ -14,6 +14,7 @@ class Fish(Generic):
|
||||
return ['cd', 'grep', 'ls', 'man', 'open']
|
||||
|
||||
def app_alias(self, fuck):
|
||||
# It is VERY important to have the variables declared WITHIN the alias
|
||||
return ('function {0} -d "Correct your previous console command"\n'
|
||||
' set -l fucked_up_command $history[1]\n'
|
||||
' env TF_ALIAS={0} PYTHONIOENCODING=utf-8'
|
||||
|
||||
@@ -7,10 +7,11 @@ from .generic import Generic
|
||||
|
||||
class Zsh(Generic):
|
||||
def app_alias(self, alias_name):
|
||||
alias = "alias {0}='TF_ALIAS={0}" \
|
||||
# It is VERY important to have the variables declared WITHIN the alias
|
||||
alias = "alias {0}='TF_CMD=$(TF_ALIAS={0}" \
|
||||
" PYTHONIOENCODING=utf-8" \
|
||||
' TF_SHELL_ALIASES=$(alias)' \
|
||||
" TF_CMD=$(thefuck $(fc -ln -1 | tail -n 1)) &&" \
|
||||
" TF_SHELL_ALIASES=$(alias)" \
|
||||
" thefuck $(fc -ln -1 | tail -n 1)) &&" \
|
||||
" eval $TF_CMD".format(alias_name)
|
||||
|
||||
if settings.alter_history:
|
||||
|
||||
@@ -282,5 +282,5 @@ class CorrectedCommand(object):
|
||||
compatibility_call(self.side_effect, old_cmd, self.script)
|
||||
# This depends on correct setting of PYTHONIOENCODING by the alias:
|
||||
logs.debug(u'PYTHONIOENCODING: {}'.format(
|
||||
os.environ.get('PYTHONIOENCODING', '>-not-set-<')))
|
||||
os.environ.get('PYTHONIOENCODING', '!!not-set!!')))
|
||||
print(self.script)
|
||||
|
||||
@@ -228,8 +228,8 @@ def cache(*depends_on):
|
||||
value = fn(*args, **kwargs)
|
||||
db[key] = {'etag': etag, 'value': value}
|
||||
return value
|
||||
except shelve_open_error:
|
||||
# Caused when going from Python 2 to Python 3 and vice-versa
|
||||
except (shelve_open_error, ImportError):
|
||||
# Caused when switching between Python versions
|
||||
warn("Removing possibly out-dated cache")
|
||||
os.remove(cache_path)
|
||||
|
||||
@@ -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