mirror of
https://github.com/nvbn/thefuck.git
synced 2025-11-01 15:42:06 +00:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
86efc6a252 | ||
|
|
89207d6d7c | ||
|
|
f6e50bef82 | ||
|
|
81042514c8 | ||
|
|
2df1a5a45b | ||
|
|
72e88d6ba3 | ||
|
|
8db3cf6048 | ||
|
|
68949a5922 | ||
|
|
216d82b464 | ||
|
|
97f2d743b3 |
15
README.md
15
README.md
@@ -118,6 +118,11 @@ sudo portsnap fetch update
|
||||
cd /usr/ports/misc/thefuck && sudo make install clean
|
||||
```
|
||||
|
||||
On ChromeOS, install *The Fuck* using [chromebrew](https://github.com/skycocker/chromebrew) with the following command:
|
||||
```bash
|
||||
crew install thefuck
|
||||
```
|
||||
|
||||
On other systems, install *The Fuck* by using `pip`:
|
||||
|
||||
```bash
|
||||
@@ -227,7 +232,7 @@ following rules are enabled by default:
|
||||
* `gradle_no_task` – fixes not found or ambiguous `gradle` task;
|
||||
* `gradle_wrapper` – replaces `gradle` with `./gradlew`;
|
||||
* `grep_arguments_order` – fixes grep arguments order for situations like `grep -lir . test`;
|
||||
* `grep_recursive` – adds `-r` when you trying to `grep` directory;
|
||||
* `grep_recursive` – adds `-r` when you try to `grep` directory;
|
||||
* `grunt_task_not_found` – fixes misspelled `grunt` commands;
|
||||
* `gulp_not_task` – fixes misspelled `gulp` tasks;
|
||||
* `has_exists_script` – prepends `./` when script/binary exists;
|
||||
@@ -239,6 +244,7 @@ following rules are enabled by default:
|
||||
* `java` – removes `.java` extension when running Java programs;
|
||||
* `javac` – appends missing `.java` when compiling Java files;
|
||||
* `lein_not_task` – fixes wrong `lein` tasks like `lein rpl`;
|
||||
* `long_form_help` – changes `-h` to `--help` when the short form version is not supported
|
||||
* `ln_no_hard_link` – catches hard link creation on directories, suggest symbolic link;
|
||||
* `ln_s_order` – fixes `ln -s` arguments order;
|
||||
* `ls_all` – adds `-A` to `ls` when output is empty;
|
||||
@@ -247,7 +253,7 @@ following rules are enabled by default:
|
||||
* `man_no_space` – fixes man commands without spaces, for example `mandiff`;
|
||||
* `mercurial` – fixes wrong `hg` commands;
|
||||
* `missing_space_before_subcommand` – fixes command with missing space like `npminstall`;
|
||||
* `mkdir_p` – adds `-p` when you trying to create directory without parent;
|
||||
* `mkdir_p` – adds `-p` when you try to create a directory without parent;
|
||||
* `mvn_no_command` – adds `clean package` to `mvn`;
|
||||
* `mvn_unknown_lifecycle_phase` – fixes misspelled lifecycle phases with `mvn`;
|
||||
* `npm_missing_script` – fixes `npm` custom script name in `npm run-script <script>`;
|
||||
@@ -260,13 +266,13 @@ following rules are enabled by default:
|
||||
* `php_s` – replaces `-s` by `-S` when trying to run a local php server;
|
||||
* `port_already_in_use` – kills process that bound port;
|
||||
* `prove_recursively` – adds `-r` when called with directory;
|
||||
* `python_command` – prepends `python` when you trying to run not executable/without `./` python script;
|
||||
* `python_command` – prepends `python` when you try to run non-executable/without `./` python script;
|
||||
* `python_execute` – appends missing `.py` when executing Python files;
|
||||
* `quotation_marks` – fixes uneven usage of `'` and `"` when containing args';
|
||||
* `path_from_history` – replaces not found path with similar absolute path from history;
|
||||
* `react_native_command_unrecognized` – fixes unrecognized `react-native` commands;
|
||||
* `remove_trailing_cedilla` – remove trailling cedillas `ç`, a common typo for european keyboard layouts;
|
||||
* `rm_dir` – adds `-rf` when you trying to remove directory;
|
||||
* `rm_dir` – adds `-rf` when you try to remove a directory;
|
||||
* `scm_correction` – corrects wrong scm like `hg log` to `git log`;
|
||||
* `sed_unterminated_s` – adds missing '/' to `sed`'s `s` commands;
|
||||
* `sl_ls` – changes `sl` to `ls`;
|
||||
@@ -299,6 +305,7 @@ The following rules are enabled by default on specific platforms only:
|
||||
* `apt_upgrade` – helps you run `apt upgrade` after `apt list --upgradable`;
|
||||
* `brew_cask_dependency` – installs cask dependencies;
|
||||
* `brew_install` – fixes formula name for `brew install`;
|
||||
* `brew_reinstall` – turns `brew install <formula>` into `brew reinstall <formula>`;
|
||||
* `brew_link` – adds `--overwrite --dry-run` if linking fails;
|
||||
* `brew_uninstall` – adds `--force` to `brew uninstall` if multiple versions were installed;
|
||||
* `brew_unknown_command` – fixes wrong brew commands, for example `brew docto/brew doctor`;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
pip
|
||||
flake8
|
||||
pytest
|
||||
mock
|
||||
|
||||
2
setup.py
2
setup.py
@@ -31,7 +31,7 @@ elif (3, 0) < version < (3, 4):
|
||||
' ({}.{} detected).'.format(*version))
|
||||
sys.exit(-1)
|
||||
|
||||
VERSION = '3.26'
|
||||
VERSION = '3.27'
|
||||
|
||||
install_requires = ['psutil', 'colorama', 'six', 'decorator', 'pyte']
|
||||
extras_require = {':python_version<"3.4"': ['pathlib2'],
|
||||
|
||||
28
tests/rules/test_brew_reinstall.py
Normal file
28
tests/rules/test_brew_reinstall.py
Normal file
@@ -0,0 +1,28 @@
|
||||
import pytest
|
||||
from thefuck.types import Command
|
||||
from thefuck.rules.brew_reinstall import get_new_command, match
|
||||
|
||||
|
||||
output = ("Warning: thefuck 9.9 is already installed and up-to-date\nTo "
|
||||
"reinstall 9.9, run `brew reinstall thefuck`")
|
||||
|
||||
|
||||
def test_match():
|
||||
command = Command('brew install thefuck', output)
|
||||
assert match(command)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('script', [
|
||||
'brew reinstall thefuck',
|
||||
'brew install foo'])
|
||||
def test_not_match(script):
|
||||
assert not match(Command(script, ''))
|
||||
|
||||
|
||||
@pytest.mark.parametrize('script, formula, ', [
|
||||
('brew install foo', 'foo'),
|
||||
('brew install bar zap', 'bar zap')])
|
||||
def test_get_new_command(script, formula):
|
||||
command = Command(script, output)
|
||||
new_command = 'brew reinstall {}'.format(formula)
|
||||
assert get_new_command(command) == new_command
|
||||
22
tests/rules/test_long_form_help.py
Normal file
22
tests/rules/test_long_form_help.py
Normal file
@@ -0,0 +1,22 @@
|
||||
import pytest
|
||||
from thefuck.rules.long_form_help import match, get_new_command
|
||||
from thefuck.types import Command
|
||||
|
||||
|
||||
@pytest.mark.parametrize('output', [
|
||||
'Try \'grep --help\' for more information.'])
|
||||
def test_match(output):
|
||||
assert match(Command('grep -h', output))
|
||||
|
||||
|
||||
def test_not_match():
|
||||
assert not match(Command('', ''))
|
||||
|
||||
|
||||
@pytest.mark.parametrize('before, after', [
|
||||
('grep -h', 'grep --help'),
|
||||
('tar -h', 'tar --help'),
|
||||
('docker run -h', 'docker run --help'),
|
||||
('cut -h', 'cut --help')])
|
||||
def test_get_new_command(before, after):
|
||||
assert get_new_command(Command(before, '')) == after
|
||||
@@ -16,7 +16,8 @@ class TestFish(object):
|
||||
mock.return_value.stdout.read.side_effect = [(
|
||||
b'cd\nfish_config\nfuck\nfunced\nfuncsave\ngrep\nhistory\nll\nls\n'
|
||||
b'man\nmath\npopd\npushd\nruby'),
|
||||
b'alias fish_key_reader /usr/bin/fish_key_reader\nalias g git']
|
||||
(b'alias fish_key_reader /usr/bin/fish_key_reader\nalias g git\n'
|
||||
b'alias alias_with_equal_sign=echo\ninvalid_alias'), b'func1\nfunc2', b'']
|
||||
return mock
|
||||
|
||||
@pytest.mark.parametrize('key, value', [
|
||||
@@ -69,7 +70,9 @@ class TestFish(object):
|
||||
'pushd': 'pushd',
|
||||
'ruby': 'ruby',
|
||||
'g': 'git',
|
||||
'fish_key_reader': '/usr/bin/fish_key_reader'}
|
||||
'fish_key_reader': '/usr/bin/fish_key_reader',
|
||||
'alias_with_equal_sign': 'echo'}
|
||||
assert shell.get_aliases() == {'func1': 'func1', 'func2': 'func2'}
|
||||
|
||||
def test_app_alias(self, shell):
|
||||
assert 'function fuck' in shell.app_alias('fuck')
|
||||
|
||||
@@ -81,3 +81,7 @@ LOG_SIZE_IN_BYTES = 1024 * 1024
|
||||
LOG_SIZE_TO_CLEAN = 10 * 1024
|
||||
|
||||
DIFF_WITH_ALIAS = 0.5
|
||||
|
||||
SHELL_LOGGER_SOCKET_ENV = 'SHELL_LOGGER_SOCKET'
|
||||
|
||||
SHELL_LOGGER_LIMIT = 5
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from ..conf import settings
|
||||
from . import read_log, rerun
|
||||
from . import read_log, rerun, shell_logger
|
||||
|
||||
|
||||
def get_output(script, expanded):
|
||||
@@ -12,6 +12,8 @@ def get_output(script, expanded):
|
||||
:rtype: str
|
||||
|
||||
"""
|
||||
if shell_logger.is_available():
|
||||
return shell_logger.get_output(script)
|
||||
if settings.instant_mode:
|
||||
return read_log.get_output(script)
|
||||
else:
|
||||
|
||||
60
thefuck/output_readers/shell_logger.py
Normal file
60
thefuck/output_readers/shell_logger.py
Normal file
@@ -0,0 +1,60 @@
|
||||
import json
|
||||
import os
|
||||
import socket
|
||||
try:
|
||||
from shutil import get_terminal_size
|
||||
except ImportError:
|
||||
from backports.shutil_get_terminal_size import get_terminal_size
|
||||
import pyte
|
||||
from .. import const, logs
|
||||
|
||||
|
||||
def _get_socket_path():
|
||||
return os.environ.get(const.SHELL_LOGGER_SOCKET_ENV)
|
||||
|
||||
|
||||
def is_available():
|
||||
"""Returns `True` if shell logger socket available.
|
||||
|
||||
:rtype: book
|
||||
|
||||
"""
|
||||
path = _get_socket_path()
|
||||
if not path:
|
||||
return False
|
||||
|
||||
return os.path.exists(path)
|
||||
|
||||
|
||||
def _get_last_n(n):
|
||||
with socket.socket(socket.AF_UNIX) as client:
|
||||
client.connect(_get_socket_path())
|
||||
request = json.dumps({
|
||||
"type": "list",
|
||||
"count": n,
|
||||
}) + '\n'
|
||||
client.sendall(request.encode('utf-8'))
|
||||
response = client.makefile().readline()
|
||||
return json.loads(response)['commands']
|
||||
|
||||
|
||||
def _get_output_lines(output):
|
||||
lines = output.split('\n')
|
||||
screen = pyte.Screen(get_terminal_size().columns, len(lines))
|
||||
stream = pyte.Stream(screen)
|
||||
stream.feed('\n'.join(lines))
|
||||
return screen.display
|
||||
|
||||
|
||||
def get_output(script):
|
||||
"""Gets command output from shell logger."""
|
||||
with logs.debug_time(u'Read output from external shell logger'):
|
||||
commands = _get_last_n(const.SHELL_LOGGER_LIMIT)
|
||||
for command in commands:
|
||||
if command['command'] == script:
|
||||
lines = _get_output_lines(command['output'])
|
||||
output = '\n'.join(lines).strip()
|
||||
return output
|
||||
else:
|
||||
logs.warn("Output isn't available in shell logger")
|
||||
return None
|
||||
19
thefuck/rules/brew_reinstall.py
Normal file
19
thefuck/rules/brew_reinstall.py
Normal file
@@ -0,0 +1,19 @@
|
||||
import re
|
||||
from thefuck.utils import for_app
|
||||
|
||||
|
||||
warning_regex = re.compile(r'Warning: (?:.(?!is ))+ is already installed and '
|
||||
r'up-to-date')
|
||||
message_regex = re.compile(r'To reinstall (?:(?!, ).)+, run `brew reinstall '
|
||||
r'[^`]+`')
|
||||
|
||||
|
||||
@for_app('brew', at_least=2)
|
||||
def match(command):
|
||||
return ('install' in command.script
|
||||
and warning_regex.search(command.output)
|
||||
and message_regex.search(command.output))
|
||||
|
||||
|
||||
def get_new_command(command):
|
||||
return command.script.replace('install', 'reinstall')
|
||||
27
thefuck/rules/long_form_help.py
Normal file
27
thefuck/rules/long_form_help.py
Normal file
@@ -0,0 +1,27 @@
|
||||
from thefuck.utils import replace_argument
|
||||
import re
|
||||
|
||||
# regex to match a suggested help command from the tool output
|
||||
help_regex = r"(?:Run|Try) '([^']+)'(?: or '[^']+')? for (?:details|more information)."
|
||||
|
||||
|
||||
def match(command):
|
||||
if re.search(help_regex, command.output, re.I) is not None:
|
||||
return True
|
||||
|
||||
if '--help' in command.output:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def get_new_command(command):
|
||||
if re.search(help_regex, command.output) is not None:
|
||||
match_obj = re.search(help_regex, command.output, re.I)
|
||||
return match_obj.group(1)
|
||||
|
||||
return replace_argument(command.script, '-h', '--help')
|
||||
|
||||
|
||||
enabled_by_default = True
|
||||
priority = 5000
|
||||
@@ -20,9 +20,17 @@ def _get_functions(overridden):
|
||||
def _get_aliases(overridden):
|
||||
aliases = {}
|
||||
proc = Popen(['fish', '-ic', 'alias'], stdout=PIPE, stderr=DEVNULL)
|
||||
alias_out = proc.stdout.read().decode('utf-8').strip().split('\n')
|
||||
for alias in alias_out:
|
||||
name, value = alias.replace('alias ', '', 1).split(' ', 1)
|
||||
alias_out = proc.stdout.read().decode('utf-8').strip()
|
||||
if not alias_out:
|
||||
return aliases
|
||||
for alias in alias_out.split('\n'):
|
||||
for separator in (' ', '='):
|
||||
split_alias = alias.replace('alias ', '', 1).split(separator, 1)
|
||||
if len(split_alias) == 2:
|
||||
name, value = split_alias
|
||||
break
|
||||
else:
|
||||
continue
|
||||
if name not in overridden:
|
||||
aliases[name] = value
|
||||
return aliases
|
||||
|
||||
@@ -12,6 +12,7 @@ class Powershell(Generic):
|
||||
' else { iex "$fuck"; }\n' \
|
||||
' }\n' \
|
||||
' }\n' \
|
||||
' [Console]::ResetColor() \n' \
|
||||
'}\n'
|
||||
|
||||
def and_(self, *commands):
|
||||
|
||||
Reference in New Issue
Block a user