mirror of
https://github.com/nvbn/thefuck.git
synced 2025-11-01 07:32:09 +00:00
Compare commits
40 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3a134f250d | ||
|
|
b54cdf7c49 | ||
|
|
1b05a497e8 | ||
|
|
79602383ec | ||
|
|
84c42168df | ||
|
|
f53d772ac3 | ||
|
|
93d4a4fc3a | ||
|
|
2cb23b1805 | ||
|
|
33f28cf76d | ||
|
|
6322dbd9ed | ||
|
|
fc09818351 | ||
|
|
2788ef1471 | ||
|
|
ef3aabe7c5 | ||
|
|
2af54d036d | ||
|
|
99c10b50ff | ||
|
|
802fcd96fd | ||
|
|
900e83e028 | ||
|
|
d41cbb6810 | ||
|
|
b36cf59b46 | ||
|
|
cfa831c88d | ||
|
|
818d06fb95 | ||
|
|
c3eca8234a | ||
|
|
d47ff8cbf2 | ||
|
|
1a52e98fbd | ||
|
|
53c11d2ef4 | ||
|
|
beda1854cf | ||
|
|
7532c65c62 | ||
|
|
ec37998a10 | ||
|
|
58d5eff6d0 | ||
|
|
d28567bb31 | ||
|
|
b016bb2255 | ||
|
|
bf109ee548 | ||
|
|
1aaaca1220 | ||
|
|
b096560469 | ||
|
|
5b1f3ff816 | ||
|
|
c5f7c89222 | ||
|
|
e61271dae3 | ||
|
|
bddb43b987 | ||
|
|
b22a3ac891 | ||
|
|
f4cc88f6c7 |
@@ -188,6 +188,7 @@ using the matched rule and runs it. Rules enabled by default are as follows:
|
||||
* `git_pull_uncommitted_changes` – stashes changes before pulling and pops them afterwards;
|
||||
* `git_push` – adds `--set-upstream origin $branch` to previous failed `git push`;
|
||||
* `git_push_pull` – runs `git pull` when `push` was rejected;
|
||||
* `git_push_without_commits` – Creates an initial commit if you forget and only `git add .`, when setting up a new project;
|
||||
* `git_rebase_no_changes` – runs `git rebase --skip` instead of `git rebase --continue` when there are no changes;
|
||||
* `git_rm_local_modifications` – adds `-f` or `--cached` when you try to `rm` a locally modified file;
|
||||
* `git_rm_recursive` – adds `-r` when you try to `rm` a directory;
|
||||
@@ -258,6 +259,7 @@ using the matched rule and runs it. Rules enabled by default are as follows:
|
||||
* `workon_doesnt_exists` – fixes `virtualenvwrapper` env name os suggests to create new.
|
||||
* `yarn_alias` – fixes aliased `yarn` commands like `yarn ls`;
|
||||
* `yarn_command_not_found` – fixes misspelled `yarn` commands;
|
||||
* `yarn_command_replaced` – fixes replaced `yarn` commands;
|
||||
* `yarn_help` – makes it easier to open `yarn` documentation;
|
||||
|
||||
Enabled by default only on specific platforms:
|
||||
@@ -427,9 +429,9 @@ 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-badge]: https://travis-ci.org/nvbn/thefuck.svg?branch=master
|
||||
[travis-link]: https://travis-ci.org/nvbn/thefuck
|
||||
[appveyor-badge]: https://img.shields.io/appveyor/ci/nvbn/thefuck.svg?label=windows%20build
|
||||
[appveyor-badge]: https://ci.appveyor.com/api/projects/status/1sskj4imj02um0gu/branch/master?svg=true
|
||||
[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
|
||||
|
||||
2
setup.py
2
setup.py
@@ -29,7 +29,7 @@ elif (3, 0) < version < (3, 3):
|
||||
' ({}.{} detected).'.format(*version))
|
||||
sys.exit(-1)
|
||||
|
||||
VERSION = '3.15'
|
||||
VERSION = '3.17'
|
||||
|
||||
install_requires = ['psutil', 'colorama', 'six', 'decorator']
|
||||
extras_require = {':python_version<"3.4"': ['pathlib2'],
|
||||
|
||||
@@ -7,7 +7,7 @@ shells.shell = shells.Generic()
|
||||
|
||||
|
||||
def pytest_addoption(parser):
|
||||
"""Adds `--run-without-docker` argument."""
|
||||
"""Adds `--enable-functional` argument."""
|
||||
group = parser.getgroup("thefuck")
|
||||
group.addoption('--enable-functional', action="store_true", default=False,
|
||||
help="Enable functional tests")
|
||||
|
||||
@@ -81,6 +81,5 @@ def without_confirmation(proc, TIMEOUT):
|
||||
|
||||
|
||||
def how_to_configure(proc, TIMEOUT):
|
||||
proc.sendline(u'unalias fuck')
|
||||
proc.sendline(u'fuck')
|
||||
assert proc.expect([TIMEOUT, u"alias isn't configured"])
|
||||
|
||||
@@ -48,4 +48,5 @@ def test_without_confirmation(proc, TIMEOUT):
|
||||
|
||||
@pytest.mark.functional
|
||||
def test_how_to_configure_alias(proc, TIMEOUT):
|
||||
proc.sendline('unset -f fuck')
|
||||
how_to_configure(proc, TIMEOUT)
|
||||
|
||||
@@ -55,4 +55,5 @@ def test_without_confirmation(proc, TIMEOUT):
|
||||
|
||||
@pytest.mark.functional
|
||||
def test_how_to_configure_alias(proc, TIMEOUT):
|
||||
proc.sendline(u'unfunction fuck')
|
||||
how_to_configure(proc, TIMEOUT)
|
||||
|
||||
27
tests/rules/test_git_push_without_commits.py
Normal file
27
tests/rules/test_git_push_without_commits.py
Normal file
@@ -0,0 +1,27 @@
|
||||
import pytest
|
||||
|
||||
from tests.utils import Command
|
||||
from thefuck.rules.git_push_without_commits import (
|
||||
fix,
|
||||
get_new_command,
|
||||
match,
|
||||
)
|
||||
|
||||
command = 'git push -u origin master'
|
||||
expected_error = '''
|
||||
error: src refspec master does not match any.
|
||||
error: failed to push some refs to 'git@github.com:User/repo.git'
|
||||
'''
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command', [Command(command, stderr=expected_error)])
|
||||
def test_match(command):
|
||||
assert match(command)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command, result', [(
|
||||
Command(command, stderr=expected_error),
|
||||
fix.format(command=command),
|
||||
)])
|
||||
def test_get_new_command(command, result):
|
||||
assert get_new_command(command) == result
|
||||
@@ -15,4 +15,4 @@ def test_match(stderr):
|
||||
|
||||
def test_get_new_command(stderr):
|
||||
assert (get_new_command(Command('git stash pop', stderr=stderr))
|
||||
== "git add . && git stash pop && git reset .")
|
||||
== "git add --update && git stash pop && git reset .")
|
||||
|
||||
@@ -1,34 +1,31 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import pytest
|
||||
from tests.utils import Command
|
||||
from thefuck.rules.heroku_not_command import match, get_new_command
|
||||
|
||||
|
||||
def suggest_stderr(cmd):
|
||||
return ''' ! `{}` is not a heroku command.
|
||||
! Perhaps you meant `logs`, `pg`.
|
||||
! See `heroku help` for a list of available commands.'''.format(cmd)
|
||||
suggest_stderr = '''
|
||||
▸ log is not a heroku command.
|
||||
▸ Perhaps you meant logs?
|
||||
▸ Run heroku _ to run heroku logs.
|
||||
▸ Run heroku help for a list of available commands.'''
|
||||
|
||||
|
||||
no_suggest_stderr = ''' ! `aaaaa` is not a heroku command.
|
||||
! See `heroku help` for a list of available commands.'''
|
||||
|
||||
|
||||
@pytest.mark.parametrize('cmd', ['log', 'pge'])
|
||||
@pytest.mark.parametrize('cmd', ['log'])
|
||||
def test_match(cmd):
|
||||
assert match(
|
||||
Command('heroku {}'.format(cmd), stderr=suggest_stderr(cmd)))
|
||||
Command('heroku {}'.format(cmd), stderr=suggest_stderr))
|
||||
|
||||
|
||||
@pytest.mark.parametrize('script, stderr', [
|
||||
('cat log', suggest_stderr('log')),
|
||||
('heroku aaa', no_suggest_stderr)])
|
||||
('cat log', suggest_stderr)])
|
||||
def test_not_match(script, stderr):
|
||||
assert not match(Command(script, stderr=stderr))
|
||||
|
||||
|
||||
@pytest.mark.parametrize('cmd, result', [
|
||||
('log', ['heroku logs', 'heroku pg']),
|
||||
('pge', ['heroku pg', 'heroku logs'])])
|
||||
('log', 'heroku logs')])
|
||||
def test_get_new_command(cmd, result):
|
||||
command = Command('heroku {}'.format(cmd), stderr=suggest_stderr(cmd))
|
||||
command = Command('heroku {}'.format(cmd), stderr=suggest_stderr)
|
||||
assert get_new_command(command) == result
|
||||
|
||||
@@ -4,12 +4,6 @@ from thefuck.rules.missing_space_before_subcommand import (
|
||||
from tests.utils import Command
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def which(mocker):
|
||||
return mocker.patch('thefuck.rules.missing_space_before_subcommand.which',
|
||||
return_value=None)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def all_executables(mocker):
|
||||
return mocker.patch(
|
||||
@@ -23,11 +17,8 @@ def test_match(script):
|
||||
assert match(Command(script))
|
||||
|
||||
|
||||
@pytest.mark.parametrize('script, which_result', [
|
||||
('git branch', '/usr/bin/git'),
|
||||
('vimfile', None)])
|
||||
def test_not_match(script, which_result, which):
|
||||
which.return_value = which_result
|
||||
@pytest.mark.parametrize('script', ['git branch', 'vimfile'])
|
||||
def test_not_match(script):
|
||||
assert not match(Command(script))
|
||||
|
||||
|
||||
|
||||
@@ -4,12 +4,13 @@ from tests.utils import Command
|
||||
|
||||
|
||||
stderr_remove = 'error Did you mean `yarn remove`?'
|
||||
|
||||
stderr_etl = 'error Command "etil" not found. Did you mean "etl"?'
|
||||
stderr_list = 'error Did you mean `yarn list`?'
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command', [
|
||||
Command(script='yarn rm', stderr=stderr_remove),
|
||||
Command(script='yarn etil', stderr=stderr_etl),
|
||||
Command(script='yarn ls', stderr=stderr_list)])
|
||||
def test_match(command):
|
||||
assert match(command)
|
||||
@@ -17,6 +18,7 @@ def test_match(command):
|
||||
|
||||
@pytest.mark.parametrize('command, new_command', [
|
||||
(Command('yarn rm', stderr=stderr_remove), 'yarn remove'),
|
||||
(Command('yarn etil', stderr=stderr_etl), 'yarn etl'),
|
||||
(Command('yarn ls', stderr=stderr_list), 'yarn list')])
|
||||
def test_get_new_command(command, new_command):
|
||||
assert get_new_command(command) == new_command
|
||||
|
||||
@@ -106,6 +106,13 @@ def test_not_match(command):
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command, result', [
|
||||
(Command('yarn whyy webpack', stderr=stderr('whyy')), 'yarn why webpack')])
|
||||
(Command('yarn whyy webpack', stderr=stderr('whyy')),
|
||||
'yarn why webpack'),
|
||||
(Command('yarn require lodash', stderr=stderr('require')),
|
||||
'yarn add lodash')])
|
||||
def test_get_new_command(command, result):
|
||||
assert get_new_command(command)[0] == result
|
||||
fixed_command = get_new_command(command)
|
||||
if isinstance(fixed_command, list):
|
||||
fixed_command = fixed_command[0]
|
||||
|
||||
assert fixed_command == result
|
||||
|
||||
32
tests/rules/test_yarn_command_replaced.py
Normal file
32
tests/rules/test_yarn_command_replaced.py
Normal file
@@ -0,0 +1,32 @@
|
||||
import pytest
|
||||
from tests.utils import Command
|
||||
from thefuck.rules.yarn_command_replaced import match, get_new_command
|
||||
|
||||
|
||||
stderr = ('error `install` has been replaced with `add` to add new '
|
||||
'dependencies. Run "yarn add {}" instead.').format
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command', [
|
||||
Command(script='yarn install redux', stderr=stderr('redux')),
|
||||
Command(script='yarn install moment', stderr=stderr('moment')),
|
||||
Command(script='yarn install lodash', stderr=stderr('lodash'))])
|
||||
def test_match(command):
|
||||
assert match(command)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command', [
|
||||
Command('yarn install')])
|
||||
def test_not_match(command):
|
||||
assert not match(command)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command, new_command', [
|
||||
(Command('yarn install redux', stderr=stderr('redux')),
|
||||
'yarn add redux'),
|
||||
(Command('yarn install moment', stderr=stderr('moment')),
|
||||
'yarn add moment'),
|
||||
(Command('yarn install lodash', stderr=stderr('lodash')),
|
||||
'yarn add lodash')])
|
||||
def test_get_new_command(command, new_command):
|
||||
assert get_new_command(command) == new_command
|
||||
@@ -33,6 +33,9 @@ class TestBash(object):
|
||||
def test_and_(self, shell):
|
||||
assert shell.and_('ls', 'cd') == 'ls && cd'
|
||||
|
||||
def test_or_(self, shell):
|
||||
assert shell.or_('ls', 'cd') == 'ls || cd'
|
||||
|
||||
def test_get_aliases(self, shell):
|
||||
assert shell.get_aliases() == {'fuck': 'eval $(thefuck $(fc -ln -1))',
|
||||
'l': 'ls -CF',
|
||||
@@ -40,18 +43,17 @@ class TestBash(object):
|
||||
'll': 'ls -alF'}
|
||||
|
||||
def test_app_alias(self, shell):
|
||||
assert 'alias fuck' in shell.app_alias('fuck')
|
||||
assert 'alias FUCK' in shell.app_alias('FUCK')
|
||||
assert 'fuck () {' in shell.app_alias('fuck')
|
||||
assert 'FUCK () {' in shell.app_alias('FUCK')
|
||||
assert 'thefuck' in shell.app_alias('fuck')
|
||||
assert 'TF_ALIAS=fuck' in shell.app_alias('fuck')
|
||||
assert 'PYTHONIOENCODING=utf-8' in shell.app_alias('fuck')
|
||||
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
|
||||
assert "fuck () {" in alias
|
||||
assert "TF_ALIAS=fuck" in alias
|
||||
assert 'PYTHONIOENCODING=utf-8' in alias
|
||||
assert 'TF_SHELL_ALIASES=$(alias)' in alias
|
||||
|
||||
def test_get_history(self, history_lines, shell):
|
||||
history_lines(['ls', 'rm'])
|
||||
|
||||
@@ -55,6 +55,9 @@ class TestFish(object):
|
||||
def test_and_(self, shell):
|
||||
assert shell.and_('foo', 'bar') == 'foo; and bar'
|
||||
|
||||
def test_or_(self, shell):
|
||||
assert shell.or_('foo', 'bar') == 'foo; or bar'
|
||||
|
||||
def test_get_aliases(self, shell):
|
||||
assert shell.get_aliases() == {'fish_config': 'fish_config',
|
||||
'fuck': 'fuck',
|
||||
|
||||
@@ -18,6 +18,9 @@ class TestGeneric(object):
|
||||
def test_and_(self, shell):
|
||||
assert shell.and_('ls', 'cd') == 'ls && cd'
|
||||
|
||||
def test_or_(self, shell):
|
||||
assert shell.or_('ls', 'cd') == 'ls || cd'
|
||||
|
||||
def test_get_aliases(self, shell):
|
||||
assert shell.get_aliases() == {}
|
||||
|
||||
|
||||
@@ -34,6 +34,9 @@ class TestTcsh(object):
|
||||
def test_and_(self, shell):
|
||||
assert shell.and_('ls', 'cd') == 'ls && cd'
|
||||
|
||||
def test_or_(self, shell):
|
||||
assert shell.or_('ls', 'cd') == 'ls || cd'
|
||||
|
||||
def test_get_aliases(self, shell):
|
||||
assert shell.get_aliases() == {'fuck': 'eval $(thefuck $(fc -ln -1))',
|
||||
'l': 'ls -CF',
|
||||
|
||||
@@ -32,6 +32,9 @@ class TestZsh(object):
|
||||
def test_and_(self, shell):
|
||||
assert shell.and_('ls', 'cd') == 'ls && cd'
|
||||
|
||||
def test_or_(self, shell):
|
||||
assert shell.or_('ls', 'cd') == 'ls || cd'
|
||||
|
||||
def test_get_aliases(self, shell):
|
||||
assert shell.get_aliases() == {
|
||||
'fuck': 'eval $(thefuck $(fc -ln -1 | tail -n 1))',
|
||||
@@ -40,17 +43,17 @@ class TestZsh(object):
|
||||
'll': 'ls -alF'}
|
||||
|
||||
def test_app_alias(self, shell):
|
||||
assert 'alias fuck' in shell.app_alias('fuck')
|
||||
assert 'alias FUCK' in shell.app_alias('FUCK')
|
||||
assert 'fuck () {' in shell.app_alias('fuck')
|
||||
assert 'FUCK () {' in shell.app_alias('FUCK')
|
||||
assert 'thefuck' in shell.app_alias('fuck')
|
||||
assert 'PYTHONIOENCODING' in shell.app_alias('fuck')
|
||||
|
||||
def test_app_alias_variables_correctly_set(self, shell):
|
||||
alias = shell.app_alias('fuck')
|
||||
assert "alias fuck='TF_CMD=$(TF_ALIAS" in alias
|
||||
assert '$(TF_ALIAS=fuck PYTHONIOENCODING' in alias
|
||||
assert 'PYTHONIOENCODING=utf-8 TF_SHELL_ALIASES' in alias
|
||||
assert 'ALIASES=$(alias) thefuck' in alias
|
||||
assert "fuck () {" in alias
|
||||
assert "TF_ALIAS=fuck" in alias
|
||||
assert 'PYTHONIOENCODING=utf-8' in alias
|
||||
assert 'TF_SHELL_ALIASES=$(alias)' in alias
|
||||
|
||||
def test_get_history(self, history_lines, shell):
|
||||
history_lines([': 1432613911:0;ls', ': 1432613916:0;rm'])
|
||||
|
||||
29
tests/test_argument_parser.py
Normal file
29
tests/test_argument_parser.py
Normal file
@@ -0,0 +1,29 @@
|
||||
import pytest
|
||||
from thefuck.argument_parser import Parser
|
||||
from thefuck.const import ARGUMENT_PLACEHOLDER
|
||||
|
||||
|
||||
def _args(**override):
|
||||
args = {'alias': None, 'command': [], 'yes': False,
|
||||
'help': False, 'version': False, 'debug': False,
|
||||
'force_command': None, 'repeat': False}
|
||||
args.update(override)
|
||||
return args
|
||||
|
||||
|
||||
@pytest.mark.parametrize('argv, result', [
|
||||
(['thefuck'], _args()),
|
||||
(['thefuck', '-a'], _args(alias='fuck')),
|
||||
(['thefuck', '-a', 'fix'], _args(alias='fix')),
|
||||
(['thefuck', 'git', 'branch', ARGUMENT_PLACEHOLDER, '-y'],
|
||||
_args(command=['git', 'branch'], yes=True)),
|
||||
(['thefuck', 'git', 'branch', '-a', ARGUMENT_PLACEHOLDER, '-y'],
|
||||
_args(command=['git', 'branch', '-a'], yes=True)),
|
||||
(['thefuck', ARGUMENT_PLACEHOLDER, '-v'], _args(version=True)),
|
||||
(['thefuck', ARGUMENT_PLACEHOLDER, '--help'], _args(help=True)),
|
||||
(['thefuck', 'git', 'branch', '-a', ARGUMENT_PLACEHOLDER, '-y', '-d'],
|
||||
_args(command=['git', 'branch', '-a'], yes=True, debug=True)),
|
||||
(['thefuck', 'git', 'branch', '-a', ARGUMENT_PLACEHOLDER, '-r', '-d'],
|
||||
_args(command=['git', 'branch', '-a'], repeat=True, debug=True))])
|
||||
def test_parse(argv, result):
|
||||
assert vars(Parser().parse(argv)) == result
|
||||
@@ -79,6 +79,13 @@ class TestSettingsFromEnv(object):
|
||||
assert settings.rules == const.DEFAULT_RULES + ['bash', 'lisp']
|
||||
|
||||
|
||||
def test_settings_from_args(settings):
|
||||
settings.init(Mock(yes=True, debug=True, repeat=True))
|
||||
assert not settings.require_confirmation
|
||||
assert settings.debug
|
||||
assert settings.repeat
|
||||
|
||||
|
||||
class TestInitializeSettingsFile(object):
|
||||
def test_ignore_if_exists(self, settings):
|
||||
settings_path_mock = Mock(is_file=Mock(return_value=True), open=Mock())
|
||||
|
||||
@@ -28,6 +28,20 @@ class TestCorrectedCommand(object):
|
||||
assert u'{}'.format(CorrectedCommand(u'echo café', None, 100)) == \
|
||||
u'CorrectedCommand(script=echo café, side_effect=None, priority=100)'
|
||||
|
||||
@pytest.mark.parametrize('script, printed, override_settings', [
|
||||
('git branch', 'git branch', {'repeat': False, 'debug': False}),
|
||||
('git brunch',
|
||||
"git brunch || fuck --repeat --force-command 'git brunch'",
|
||||
{'repeat': True, 'debug': False}),
|
||||
('git brunch',
|
||||
"git brunch || fuck --repeat --debug --force-command 'git brunch'",
|
||||
{'repeat': True, 'debug': True})])
|
||||
def test_run(self, capsys, settings, script, printed, override_settings):
|
||||
settings.update(override_settings)
|
||||
CorrectedCommand(script, None, 1000).run(Command())
|
||||
out, _ = capsys.readouterr()
|
||||
assert out[:-1] == printed
|
||||
|
||||
|
||||
class TestRule(object):
|
||||
def test_from_path(self, mocker):
|
||||
|
||||
84
thefuck/argument_parser.py
Normal file
84
thefuck/argument_parser.py
Normal file
@@ -0,0 +1,84 @@
|
||||
import sys
|
||||
from argparse import ArgumentParser, SUPPRESS
|
||||
from .const import ARGUMENT_PLACEHOLDER
|
||||
from .utils import get_alias
|
||||
|
||||
|
||||
class Parser(object):
|
||||
"""Argument parser that can handle arguments with our special
|
||||
placeholder.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self._parser = ArgumentParser(prog='thefuck', add_help=False)
|
||||
self._add_arguments()
|
||||
|
||||
def _add_arguments(self):
|
||||
"""Adds arguments to parser."""
|
||||
self._parser.add_argument(
|
||||
'-v', '--version',
|
||||
action='store_true',
|
||||
help="show program's version number and exit")
|
||||
self._parser.add_argument(
|
||||
'-a', '--alias',
|
||||
nargs='?',
|
||||
const=get_alias(),
|
||||
help='[custom-alias-name] prints alias for current shell')
|
||||
self._parser.add_argument(
|
||||
'-h', '--help',
|
||||
action='store_true',
|
||||
help='show this help message and exit')
|
||||
self._add_conflicting_arguments()
|
||||
self._parser.add_argument(
|
||||
'-d', '--debug',
|
||||
action='store_true',
|
||||
help='enable debug output')
|
||||
self._parser.add_argument(
|
||||
'--force-command',
|
||||
action='store',
|
||||
help=SUPPRESS)
|
||||
self._parser.add_argument(
|
||||
'command',
|
||||
nargs='*',
|
||||
help='command that should be fixed')
|
||||
|
||||
def _add_conflicting_arguments(self):
|
||||
"""It's too dangerous to use `-y` and `-r` together."""
|
||||
group = self._parser.add_mutually_exclusive_group()
|
||||
group.add_argument(
|
||||
'-y', '--yes',
|
||||
action='store_true',
|
||||
help='execute fixed command without confirmation')
|
||||
group.add_argument(
|
||||
'-r', '--repeat',
|
||||
action='store_true',
|
||||
help='repeat on failure')
|
||||
|
||||
def _prepare_arguments(self, argv):
|
||||
"""Prepares arguments by:
|
||||
|
||||
- removing placeholder and moving arguments after it to beginning,
|
||||
we need this to distinguish arguments from `command` with ours;
|
||||
|
||||
- adding `--` before `command`, so our parse would ignore arguments
|
||||
of `command`.
|
||||
|
||||
"""
|
||||
if ARGUMENT_PLACEHOLDER in argv:
|
||||
index = argv.index(ARGUMENT_PLACEHOLDER)
|
||||
return argv[index + 1:] + ['--'] + argv[:index]
|
||||
elif argv and not argv[0].startswith('-') and argv[0] != '--':
|
||||
return ['--'] + argv
|
||||
else:
|
||||
return argv
|
||||
|
||||
def parse(self, argv):
|
||||
arguments = self._prepare_arguments(argv[1:])
|
||||
return self._parser.parse_args(arguments)
|
||||
|
||||
def print_usage(self):
|
||||
self._parser.print_usage(sys.stderr)
|
||||
|
||||
def print_help(self):
|
||||
self._parser.print_help(sys.stderr)
|
||||
@@ -14,7 +14,7 @@ class Settings(dict):
|
||||
def __setattr__(self, key, value):
|
||||
self[key] = value
|
||||
|
||||
def init(self):
|
||||
def init(self, args=None):
|
||||
"""Fills `settings` with values from `settings.py` and env."""
|
||||
from .logs import exception
|
||||
|
||||
@@ -31,6 +31,8 @@ class Settings(dict):
|
||||
except Exception:
|
||||
exception("Can't load settings from env", sys.exc_info())
|
||||
|
||||
self.update(self._settings_from_args(args))
|
||||
|
||||
def _init_settings_file(self):
|
||||
settings_path = self.user_dir.joinpath('settings.py')
|
||||
if not settings_path.is_file():
|
||||
@@ -109,5 +111,19 @@ class Settings(dict):
|
||||
for env, attr in const.ENV_TO_ATTR.items()
|
||||
if env in os.environ}
|
||||
|
||||
def _settings_from_args(self, args):
|
||||
"""Loads settings from args."""
|
||||
if not args:
|
||||
return {}
|
||||
|
||||
from_args = {}
|
||||
if args.yes:
|
||||
from_args['require_confirmation'] = not args.yes
|
||||
if args.debug:
|
||||
from_args['debug'] = args.debug
|
||||
if args.repeat:
|
||||
from_args['repeat'] = args.repeat
|
||||
return from_args
|
||||
|
||||
|
||||
settings = Settings(const.DEFAULT_SETTINGS)
|
||||
|
||||
@@ -34,6 +34,7 @@ DEFAULT_SETTINGS = {'rules': DEFAULT_RULES,
|
||||
'wait_slow_command': 15,
|
||||
'slow_commands': ['lein', 'react-native', 'gradle',
|
||||
'./gradlew', 'vagrant'],
|
||||
'repeat': False,
|
||||
'env': {'LC_ALL': 'C', 'LANG': 'C', 'GIT_TRACE': '1'}}
|
||||
|
||||
ENV_TO_ATTR = {'THEFUCK_RULES': 'rules',
|
||||
@@ -46,7 +47,8 @@ ENV_TO_ATTR = {'THEFUCK_RULES': 'rules',
|
||||
'THEFUCK_HISTORY_LIMIT': 'history_limit',
|
||||
'THEFUCK_ALTER_HISTORY': 'alter_history',
|
||||
'THEFUCK_WAIT_SLOW_COMMAND': 'wait_slow_command',
|
||||
'THEFUCK_SLOW_COMMANDS': 'slow_commands'}
|
||||
'THEFUCK_SLOW_COMMANDS': 'slow_commands',
|
||||
'THEFUCK_REPEAT': 'repeat'}
|
||||
|
||||
SETTINGS_HEADER = u"""# The Fuck settings file
|
||||
#
|
||||
@@ -59,3 +61,5 @@ SETTINGS_HEADER = u"""# The Fuck settings file
|
||||
#
|
||||
|
||||
"""
|
||||
|
||||
ARGUMENT_PLACEHOLDER = 'THEFUCK_ARGUMENT_PLACEHOLDER'
|
||||
|
||||
@@ -121,3 +121,9 @@ def configured_successfully(configuration_details):
|
||||
bold=color(colorama.Style.BRIGHT),
|
||||
reset=color(colorama.Style.RESET_ALL),
|
||||
reload=configuration_details.reload))
|
||||
|
||||
|
||||
def version(thefuck_version, python_version):
|
||||
sys.stderr.write(
|
||||
u'The Fuck {} using Python {}\n'.format(thefuck_version,
|
||||
python_version))
|
||||
|
||||
@@ -3,7 +3,6 @@ from .system import init_output
|
||||
|
||||
init_output()
|
||||
|
||||
from argparse import ArgumentParser # noqa: E402
|
||||
from pprint import pformat # noqa: E402
|
||||
import sys # noqa: E402
|
||||
from . import logs, types # noqa: E402
|
||||
@@ -11,18 +10,21 @@ from .shells import shell # noqa: E402
|
||||
from .conf import settings # noqa: E402
|
||||
from .corrector import get_corrected_commands # noqa: E402
|
||||
from .exceptions import EmptyCommand # noqa: E402
|
||||
from .utils import get_installation_info, get_alias # noqa: E402
|
||||
from .ui import select_command # noqa: E402
|
||||
from .argument_parser import Parser # noqa: E402
|
||||
from .utils import get_installation_info # noqa: E402
|
||||
|
||||
|
||||
def fix_command():
|
||||
def fix_command(known_args):
|
||||
"""Fixes previous command. Used when `thefuck` called without arguments."""
|
||||
settings.init()
|
||||
settings.init(known_args)
|
||||
with logs.debug_time('Total'):
|
||||
logs.debug(u'Run with settings: {}'.format(pformat(settings)))
|
||||
raw_command = ([known_args.force_command] if known_args.force_command
|
||||
else known_args.command)
|
||||
|
||||
try:
|
||||
command = types.Command.from_raw_script(sys.argv[1:])
|
||||
command = types.Command.from_raw_script(raw_command)
|
||||
except EmptyCommand:
|
||||
logs.debug('Empty command, nothing to do')
|
||||
return
|
||||
@@ -36,34 +38,18 @@ def fix_command():
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def print_alias():
|
||||
"""Prints alias for current shell."""
|
||||
try:
|
||||
alias = sys.argv[2]
|
||||
except IndexError:
|
||||
alias = get_alias()
|
||||
|
||||
print(shell.app_alias(alias))
|
||||
|
||||
|
||||
def main():
|
||||
parser = ArgumentParser(prog='thefuck')
|
||||
version = get_installation_info().version
|
||||
parser.add_argument('-v', '--version',
|
||||
action='version',
|
||||
version='The Fuck {} using Python {}'.format(
|
||||
version, sys.version.split()[0]))
|
||||
parser.add_argument('-a', '--alias',
|
||||
action='store_true',
|
||||
help='[custom-alias-name] prints alias for current shell')
|
||||
parser.add_argument('command',
|
||||
nargs='*',
|
||||
help='command that should be fixed')
|
||||
known_args = parser.parse_args(sys.argv[1:2])
|
||||
parser = Parser()
|
||||
known_args = parser.parse(sys.argv)
|
||||
|
||||
if known_args.alias:
|
||||
print_alias()
|
||||
if known_args.help:
|
||||
parser.print_help()
|
||||
elif known_args.version:
|
||||
logs.version(get_installation_info().version,
|
||||
sys.version.split()[0])
|
||||
elif known_args.command:
|
||||
fix_command()
|
||||
fix_command(known_args)
|
||||
elif known_args.alias:
|
||||
print(shell.app_alias(known_args.alias))
|
||||
else:
|
||||
parser.print_usage()
|
||||
|
||||
14
thefuck/rules/git_push_without_commits.py
Normal file
14
thefuck/rules/git_push_without_commits.py
Normal file
@@ -0,0 +1,14 @@
|
||||
import re
|
||||
from thefuck.specific.git import git_support
|
||||
|
||||
fix = u'git commit -m "Initial commit." && {command}'
|
||||
refspec_does_not_match = re.compile(r'src refspec \w+ does not match any\.')
|
||||
|
||||
|
||||
@git_support
|
||||
def match(command):
|
||||
return bool(refspec_does_not_match.search(command.stderr))
|
||||
|
||||
|
||||
def get_new_command(command):
|
||||
return fix.format(command=command.script)
|
||||
@@ -11,7 +11,7 @@ def match(command):
|
||||
|
||||
@git_support
|
||||
def get_new_command(command):
|
||||
return shell.and_('git add .', 'git stash pop', 'git reset .')
|
||||
return shell.and_('git add --update', 'git stash pop', 'git reset .')
|
||||
|
||||
|
||||
# make it come before the other applicable rules
|
||||
|
||||
@@ -1,19 +1,11 @@
|
||||
import re
|
||||
from thefuck.utils import replace_command, for_app
|
||||
from thefuck.utils import for_app
|
||||
|
||||
|
||||
@for_app('heroku')
|
||||
def match(command):
|
||||
return 'is not a heroku command' in command.stderr and \
|
||||
'Perhaps you meant' in command.stderr
|
||||
|
||||
|
||||
def _get_suggests(stderr):
|
||||
for line in stderr.split('\n'):
|
||||
if 'Perhaps you meant' in line:
|
||||
return re.findall(r'`([^`]+)`', line)
|
||||
return 'Run heroku _ to run' in command.stderr
|
||||
|
||||
|
||||
def get_new_command(command):
|
||||
wrong = re.findall(r'`(\w+)` is not a heroku command', command.stderr)[0]
|
||||
return replace_command(command, wrong, _get_suggests(command.stderr))
|
||||
return re.findall('Run heroku _ to run ([^.]*)', command.stderr)[0]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from thefuck.utils import get_all_executables, memoize, which
|
||||
from thefuck.utils import get_all_executables, memoize
|
||||
|
||||
|
||||
@memoize
|
||||
@@ -9,7 +9,7 @@ def _get_executable(script_part):
|
||||
|
||||
|
||||
def match(command):
|
||||
return (not which(command.script_parts[0])
|
||||
return (not command.script_parts[0] in get_all_executables()
|
||||
and _get_executable(command.script_parts[0]))
|
||||
|
||||
|
||||
|
||||
@@ -21,7 +21,8 @@ patterns = ['permission denied',
|
||||
'edspermissionerror',
|
||||
'you don\'t have write permissions',
|
||||
'use `sudo`',
|
||||
'SudoRequiredError']
|
||||
'SudoRequiredError',
|
||||
'error: insufficient privileges']
|
||||
|
||||
|
||||
def match(command):
|
||||
|
||||
@@ -9,6 +9,6 @@ def match(command):
|
||||
|
||||
def get_new_command(command):
|
||||
broken = command.script_parts[1]
|
||||
fix = re.findall(r'Did you mean `yarn ([^`]*)`', command.stderr)[0]
|
||||
fix = re.findall(r'Did you mean [`"](?:yarn )?([^`"]*)[`"]', command.stderr)[0]
|
||||
|
||||
return replace_argument(command.script, broken, fix)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import re
|
||||
from subprocess import Popen, PIPE
|
||||
from thefuck.utils import for_app, eager, replace_command
|
||||
from thefuck.utils import for_app, eager, replace_command, replace_argument
|
||||
|
||||
regex = re.compile(r'error Command "(.*)" not found.')
|
||||
|
||||
@@ -10,6 +10,9 @@ def match(command):
|
||||
return regex.findall(command.stderr)
|
||||
|
||||
|
||||
npm_commands = {'require': 'add'}
|
||||
|
||||
|
||||
@eager
|
||||
def _get_all_tasks():
|
||||
proc = Popen(['yarn', '--help'], stdout=PIPE)
|
||||
@@ -27,5 +30,9 @@ def _get_all_tasks():
|
||||
|
||||
def get_new_command(command):
|
||||
misspelled_task = regex.findall(command.stderr)[0]
|
||||
tasks = _get_all_tasks()
|
||||
return replace_command(command, misspelled_task, tasks)
|
||||
if misspelled_task in npm_commands:
|
||||
yarn_command = npm_commands[misspelled_task]
|
||||
return replace_argument(command.script, misspelled_task, yarn_command)
|
||||
else:
|
||||
tasks = _get_all_tasks()
|
||||
return replace_command(command, misspelled_task, tasks)
|
||||
|
||||
13
thefuck/rules/yarn_command_replaced.py
Normal file
13
thefuck/rules/yarn_command_replaced.py
Normal file
@@ -0,0 +1,13 @@
|
||||
import re
|
||||
from thefuck.utils import for_app
|
||||
|
||||
regex = re.compile(r'Run "(.*)" instead')
|
||||
|
||||
|
||||
@for_app('yarn', at_least=1)
|
||||
def match(command):
|
||||
return regex.findall(command.stderr)
|
||||
|
||||
|
||||
def get_new_command(command):
|
||||
return regex.findall(command.stderr)[0]
|
||||
@@ -1,22 +1,29 @@
|
||||
import os
|
||||
from ..conf import settings
|
||||
from ..const import ARGUMENT_PLACEHOLDER
|
||||
from ..utils import memoize
|
||||
from .generic import Generic
|
||||
|
||||
|
||||
class Bash(Generic):
|
||||
def app_alias(self, fuck):
|
||||
# It is VERY important to have the variables declared WITHIN the alias
|
||||
alias = "alias {0}='TF_CMD=$(TF_ALIAS={0}" \
|
||||
" PYTHONIOENCODING=utf-8" \
|
||||
" TF_SHELL_ALIASES=$(alias)" \
|
||||
" thefuck $(fc -ln -1)) &&" \
|
||||
" eval $TF_CMD".format(fuck)
|
||||
|
||||
if settings.alter_history:
|
||||
return alias + "; history -s $TF_CMD'"
|
||||
else:
|
||||
return alias + "'"
|
||||
def app_alias(self, alias_name):
|
||||
# It is VERY important to have the variables declared WITHIN the function
|
||||
return '''
|
||||
function {name} () {{
|
||||
TF_PREVIOUS=$(fc -ln -1);
|
||||
TF_CMD=$(
|
||||
export TF_ALIAS={name}
|
||||
export TF_SHELL_ALIASES=$(alias)
|
||||
export PYTHONIOENCODING=utf-8
|
||||
thefuck $TF_PREVIOUS {argument_placeholder} $@
|
||||
) && eval $TF_CMD;
|
||||
{alter_history}
|
||||
}}
|
||||
'''.format(
|
||||
name=alias_name,
|
||||
argument_placeholder=ARGUMENT_PLACEHOLDER,
|
||||
alter_history=('history -s $TF_CMD;'
|
||||
if settings.alter_history else ''))
|
||||
|
||||
def _parse_alias(self, alias):
|
||||
name, value = alias.replace('alias ', '', 1).split('=', 1)
|
||||
@@ -41,7 +48,7 @@ class Bash(Generic):
|
||||
if os.path.join(os.path.expanduser('~'), '.bashrc'):
|
||||
config = '~/.bashrc'
|
||||
elif os.path.join(os.path.expanduser('~'), '.bash_profile'):
|
||||
config = '~/.bashrc'
|
||||
config = '~/.bash_profile'
|
||||
else:
|
||||
config = 'bash config'
|
||||
|
||||
|
||||
@@ -66,6 +66,9 @@ class Fish(Generic):
|
||||
def and_(self, *commands):
|
||||
return u'; and '.join(commands)
|
||||
|
||||
def or_(self, *commands):
|
||||
return u'; or '.join(commands)
|
||||
|
||||
def how_to_configure(self):
|
||||
return self._create_shell_configuration(
|
||||
content=u"eval (thefuck --alias | tr '\n' ';')",
|
||||
|
||||
@@ -66,6 +66,9 @@ class Generic(object):
|
||||
def and_(self, *commands):
|
||||
return u' && '.join(commands)
|
||||
|
||||
def or_(self, *commands):
|
||||
return u' || '.join(commands)
|
||||
|
||||
def how_to_configure(self):
|
||||
return
|
||||
|
||||
|
||||
@@ -1,23 +1,30 @@
|
||||
from time import time
|
||||
import os
|
||||
from ..conf import settings
|
||||
from ..const import ARGUMENT_PLACEHOLDER
|
||||
from ..utils import memoize
|
||||
from .generic import Generic
|
||||
|
||||
|
||||
class Zsh(Generic):
|
||||
def app_alias(self, alias_name):
|
||||
# It is VERY important to have the variables declared WITHIN the alias
|
||||
alias = "alias {0}='TF_CMD=$(TF_ALIAS={0}" \
|
||||
" PYTHONIOENCODING=utf-8" \
|
||||
" TF_SHELL_ALIASES=$(alias)" \
|
||||
" thefuck $(fc -ln -1 | tail -n 1)) &&" \
|
||||
" eval $TF_CMD".format(alias_name)
|
||||
|
||||
if settings.alter_history:
|
||||
return alias + " ; test -n \"$TF_CMD\" && print -s $TF_CMD'"
|
||||
else:
|
||||
return alias + "'"
|
||||
# It is VERY important to have the variables declared WITHIN the function
|
||||
return '''
|
||||
{name} () {{
|
||||
TF_PREVIOUS=$(fc -ln -1 | tail -n 1);
|
||||
TF_CMD=$(
|
||||
TF_ALIAS={name}
|
||||
TF_SHELL_ALIASES=$(alias)
|
||||
PYTHONIOENCODING=utf-8
|
||||
thefuck $TF_PREVIOUS {argument_placeholder} $*
|
||||
) && eval $TF_CMD;
|
||||
{alter_history}
|
||||
}}
|
||||
'''.format(
|
||||
name=alias_name,
|
||||
argument_placeholder=ARGUMENT_PLACEHOLDER,
|
||||
alter_history=('test -n "$TF_CMD" && print -s $TF_CMD'
|
||||
if settings.alter_history else ''))
|
||||
|
||||
def _parse_alias(self, alias):
|
||||
name, value = alias.split('=', 1)
|
||||
|
||||
@@ -9,6 +9,7 @@ from .shells import shell
|
||||
from .conf import settings
|
||||
from .const import DEFAULT_PRIORITY, ALL_ENABLED
|
||||
from .exceptions import EmptyCommand
|
||||
from .utils import get_alias
|
||||
|
||||
|
||||
class Command(object):
|
||||
@@ -276,6 +277,22 @@ class CorrectedCommand(object):
|
||||
return u'CorrectedCommand(script={}, side_effect={}, priority={})'.format(
|
||||
self.script, self.side_effect, self.priority)
|
||||
|
||||
def _get_script(self):
|
||||
"""Returns fixed commands script.
|
||||
|
||||
If `settings.repeat` is `True`, appends command with second attempt
|
||||
of running fuck in case fixed command fails again.
|
||||
|
||||
"""
|
||||
if settings.repeat:
|
||||
repeat_fuck = '{} --repeat {}--force-command {}'.format(
|
||||
get_alias(),
|
||||
'--debug ' if settings.debug else '',
|
||||
shell.quote(self.script))
|
||||
return shell.or_(self.script, repeat_fuck)
|
||||
else:
|
||||
return self.script
|
||||
|
||||
def run(self, old_cmd):
|
||||
"""Runs command from rule for passed command.
|
||||
|
||||
@@ -289,4 +306,5 @@ class CorrectedCommand(object):
|
||||
# This depends on correct setting of PYTHONIOENCODING by the alias:
|
||||
logs.debug(u'PYTHONIOENCODING: {}'.format(
|
||||
os.environ.get('PYTHONIOENCODING', '!!not-set!!')))
|
||||
print(self.script)
|
||||
|
||||
print(self._get_script())
|
||||
|
||||
@@ -4,6 +4,7 @@ import sys
|
||||
from .conf import settings
|
||||
from .exceptions import NoRuleMatched
|
||||
from .system import get_key
|
||||
from .utils import get_alias
|
||||
from . import logs, const
|
||||
|
||||
|
||||
@@ -69,7 +70,8 @@ def select_command(corrected_commands):
|
||||
try:
|
||||
selector = CommandSelector(corrected_commands)
|
||||
except NoRuleMatched:
|
||||
logs.failed('No fucks given')
|
||||
logs.failed('No fucks given' if get_alias() == 'fuck'
|
||||
else 'Nothing found')
|
||||
return
|
||||
|
||||
if not settings.require_confirmation:
|
||||
|
||||
@@ -111,12 +111,15 @@ def get_all_executables():
|
||||
tf_entry_points = get_installation_info().get_entry_map()\
|
||||
.get('console_scripts', {})\
|
||||
.keys()
|
||||
|
||||
bins = [exe.name.decode('utf8') if six.PY2 else 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)
|
||||
and exe.name not in tf_entry_points]
|
||||
aliases = [alias for alias in shell.get_aliases() if alias != tf_alias]
|
||||
aliases = [alias.decode('utf8') if six.PY2 else alias
|
||||
for alias in shell.get_aliases() if alias != tf_alias]
|
||||
|
||||
return bins + aliases
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user