1
0
mirror of https://github.com/nvbn/thefuck.git synced 2025-11-11 04:16:07 +00:00

Compare commits

..

17 Commits
1.31 ... 1.32

Author SHA1 Message Date
nvbn
fb7376f5a5 Bump to 1.32 2015-05-01 04:47:25 +02:00
nvbn
ee5c40d427 Update rules list in readme 2015-05-01 04:46:58 +02:00
nvbn
9a43ba6e24 #102 Update readme 2015-05-01 04:43:55 +02:00
nvbn
5eeb9d704c #102 Use side_effect in ssh_known_host rule 2015-05-01 04:41:33 +02:00
nvbn
b985dfbffc #102 Add support of rules with side effects 2015-05-01 04:39:37 +02:00
Vladimir Iakovlev
b928a59672 Merge pull request #150 from SanketDG/add-alias
Add thefuck-alias for outputting the alias command.
2015-04-30 20:53:22 +02:00
SanketDG
32fd929e48 add instructions to use thefuck-alias 2015-05-01 00:13:08 +05:30
SanketDG
8a49b40f6a add entry point 2015-05-01 00:12:43 +05:30
SanketDG
4276e1b991 add alias function 2015-05-01 00:12:30 +05:30
nvbn
6372674351 Merge branch 'SanketDG-sudo-shutdown' 2015-04-30 19:57:01 +02:00
nvbn
9f9c5369ec Merge branch 'sudo-shutdown' of https://github.com/SanketDG/thefuck into SanketDG-sudo-shutdown
Conflicts:
	thefuck/rules/sudo.py
2015-04-30 19:56:45 +02:00
Vladimir Iakovlev
50ab7429d9 Merge pull request #148 from danybony/patch-1
Add more patterns to sudo.py
2015-04-30 19:50:59 +02:00
SanketDG
55cfdda203 add rule for shutdown command 2015-04-30 19:50:37 +05:30
Daniele
be9446635b Add more patterns to sudo.py
These patterns cover commands like
`reboot`
or
`dpkg-reconfigure something`
2015-04-30 13:54:02 +01:00
Vladimir Iakovlev
b4cbcd7a99 Merge pull request #146 from kimtree/brew-improve
Improve a logic to get recommended command based on local environment
2015-04-29 08:48:20 +02:00
Namwoo Kim
9bf910a2dd Improve a logic to get recommended command based on local environment 2015-04-29 15:18:48 +09:00
Vladimir Iakovlev
7e76ab1dc6 Fix typo 2015-04-29 05:06:30 +02:00
13 changed files with 163 additions and 49 deletions

View File

@@ -110,7 +110,13 @@ alias fuck='eval $(thefuck $(fc -ln -1))'
alias FUCK='fuck' alias FUCK='fuck'
``` ```
[On in your shell config (Bash, Zsh, Fish, Powershell).](https://github.com/nvbn/thefuck/wiki/Shell-aliases) Alternatively, you can redirect the output of `thefuck-alias`:
```bash
thefuck-alias >> ~/.bashrc
```
[Or in your shell config (Bash, Zsh, Fish, Powershell).](https://github.com/nvbn/thefuck/wiki/Shell-aliases)
Changes will be available only in a new shell session. Changes will be available only in a new shell session.
@@ -143,7 +149,10 @@ using matched rule and run it. Rules enabled by default:
* `rm_dir` – adds `-rf` when you trying to remove directory; * `rm_dir` – adds `-rf` when you trying to remove directory;
* `ssh_known_hosts` – removes host from `known_hosts` on warning; * `ssh_known_hosts` – removes host from `known_hosts` on warning;
* `sudo` – prepends `sudo` to previous command if it failed because of permissions; * `sudo` – prepends `sudo` to previous command if it failed because of permissions;
* `switch_layout` – switches command from your local layout to en. * `switch_layout` – switches command from your local layout to en;
* `apt_get` – installs app from apt if it not installed;
* `brew_install` – fixes formula name for `brew install`;
* `composer_not_command` – fixes composer command name.
Bundled, but not enabled by default: Bundled, but not enabled by default:
@@ -156,6 +165,9 @@ For adding your own rule you should create `your-rule-name.py`
in `~/.thefuck/rules`. Rule should contain two functions: in `~/.thefuck/rules`. Rule should contain two functions:
`match(command: Command, settings: Settings) -> bool` `match(command: Command, settings: Settings) -> bool`
and `get_new_command(command: Command, settings: Settings) -> str`. and `get_new_command(command: Command, settings: Settings) -> str`.
Also the rule can contain optional function
`side_effect(command: Command, settings: Settings) -> None` and
optional boolean `enabled_by_default`
`Command` has three attributes: `script`, `stdout` and `stderr`. `Command` has three attributes: `script`, `stdout` and `stderr`.
@@ -171,6 +183,12 @@ def match(command, settings):
def get_new_command(command, settings): def get_new_command(command, settings):
return 'sudo {}'.format(command.script) return 'sudo {}'.format(command.script)
# Optional:
enabled_by_default = True
def side_effect(command, settings):
subprocess.call('chmod 777 .', shell=True)
``` ```
[More examples of rules](https://github.com/nvbn/thefuck/tree/master/thefuck/rules), [More examples of rules](https://github.com/nvbn/thefuck/tree/master/thefuck/rules),

View File

@@ -1,7 +1,7 @@
from setuptools import setup, find_packages from setuptools import setup, find_packages
VERSION = '1.31' VERSION = '1.32'
setup(name='thefuck', setup(name='thefuck',
@@ -17,4 +17,4 @@ setup(name='thefuck',
zip_safe=False, zip_safe=False,
install_requires=['pathlib', 'psutil', 'colorama', 'six'], install_requires=['pathlib', 'psutil', 'colorama', 'six'],
entry_points={'console_scripts': [ entry_points={'console_scripts': [
'thefuck = thefuck.main:main']}) 'thefuck = thefuck.main:main', 'thefuck-alias = thefuck.main:alias']})

View File

@@ -10,7 +10,7 @@ def brew_unknown_cmd():
@pytest.fixture @pytest.fixture
def brew_unknown_cmd_instaa(): def brew_unknown_cmd2():
return '''Error: Unknown command: instaa''' return '''Error: Unknown command: instaa'''
@@ -20,9 +20,9 @@ def test_match(brew_unknown_cmd):
assert not match(Command('brew ' + command), None) assert not match(Command('brew ' + command), None)
def test_get_new_command(brew_unknown_cmd, brew_unknown_cmd_instaa): def test_get_new_command(brew_unknown_cmd, brew_unknown_cmd2):
assert get_new_command(Command('brew inst', stderr=brew_unknown_cmd), None)\ assert get_new_command(Command('brew inst', stderr=brew_unknown_cmd),
== 'brew list' None) == 'brew list'
assert get_new_command(Command('brew instaa', stderr=brew_unknown_cmd_instaa), assert get_new_command(Command('brew instaa', stderr=brew_unknown_cmd2),
None) == 'brew install' None) == 'brew install'

View File

@@ -2,7 +2,7 @@ import os
import pytest import pytest
from mock import Mock from mock import Mock
from thefuck.rules.ssh_known_hosts import match, get_new_command,\ from thefuck.rules.ssh_known_hosts import match, get_new_command,\
remove_offending_keys side_effect
from tests.utils import Command from tests.utils import Command
@@ -53,18 +53,14 @@ def test_match(ssh_error):
assert not match(Command('ssh'), None) assert not match(Command('ssh'), None)
def test_remove_offending_keys(ssh_error): def test_side_effect(ssh_error):
errormsg, path, reset, known_hosts = ssh_error errormsg, path, reset, known_hosts = ssh_error
command = Command('ssh user@host', stderr=errormsg) command = Command('ssh user@host', stderr=errormsg)
remove_offending_keys(command, None) side_effect(command, None)
expected = ['123.234.567.890 asdjkasjdakjsd\n', '111.222.333.444 qwepoiwqepoiss\n'] expected = ['123.234.567.890 asdjkasjdakjsd\n', '111.222.333.444 qwepoiwqepoiss\n']
assert known_hosts(path) == expected assert known_hosts(path) == expected
def test_get_new_command(ssh_error, monkeypatch): def test_get_new_command(ssh_error, monkeypatch):
errormsg, _, _, _ = ssh_error errormsg, _, _, _ = ssh_error
method = Mock()
monkeypatch.setattr('thefuck.rules.ssh_known_hosts.remove_offending_keys', method)
assert get_new_command(Command('ssh user@host', stderr=errormsg), None) == 'ssh user@host' assert get_new_command(Command('ssh user@host', stderr=errormsg), None) == 'ssh user@host'
assert method.call_count

View File

@@ -74,6 +74,15 @@ def test_run_rule(capsys):
main.run_rule(Rule(get_new_command=lambda *_: 'new-command'), main.run_rule(Rule(get_new_command=lambda *_: 'new-command'),
None, None) None, None)
assert capsys.readouterr() == ('new-command\n', '') assert capsys.readouterr() == ('new-command\n', '')
# With side effect:
side_effect = Mock()
settings = Mock()
command = Mock()
main.run_rule(Rule(get_new_command=lambda *_: 'new-command',
side_effect=side_effect),
command, settings)
assert capsys.readouterr() == ('new-command\n', '')
side_effect.assert_called_once_with(command, settings)
with patch('thefuck.main.confirm', return_value=False): with patch('thefuck.main.confirm', return_value=False):
main.run_rule(Rule(get_new_command=lambda *_: 'new-command'), main.run_rule(Rule(get_new_command=lambda *_: 'new-command'),
None, None) None, None)
@@ -82,15 +91,25 @@ def test_run_rule(capsys):
def test_confirm(capsys): def test_confirm(capsys):
# When confirmation not required: # When confirmation not required:
assert main.confirm('command', Mock(require_confirmation=False)) assert main.confirm('command', None, Mock(require_confirmation=False))
assert capsys.readouterr() == ('', 'command\n') assert capsys.readouterr() == ('', 'command\n')
# With side effect and without confirmation:
assert main.confirm('command', Mock(), Mock(require_confirmation=False))
assert capsys.readouterr() == ('', 'command*\n')
# When confirmation required and confirmed: # When confirmation required and confirmed:
with patch('thefuck.main.sys.stdin.read', return_value='\n'): with patch('thefuck.main.sys.stdin.read', return_value='\n'):
assert main.confirm('command', Mock(require_confirmation=True, assert main.confirm(
'command', None, Mock(require_confirmation=True,
no_colors=True)) no_colors=True))
assert capsys.readouterr() == ('', 'command [enter/ctrl+c]') assert capsys.readouterr() == ('', 'command [enter/ctrl+c]')
# With side effect:
assert main.confirm(
'command', Mock(), Mock(require_confirmation=True,
no_colors=True))
assert capsys.readouterr() == ('', 'command* [enter/ctrl+c]')
# When confirmation required and ctrl+c: # When confirmation required and ctrl+c:
with patch('thefuck.main.sys.stdin.read', side_effect=KeyboardInterrupt): with patch('thefuck.main.sys.stdin.read', side_effect=KeyboardInterrupt):
assert not main.confirm('command', Mock(require_confirmation=True, assert not main.confirm('command', None,
Mock(require_confirmation=True,
no_colors=True)) no_colors=True))
assert capsys.readouterr() == ('', 'command [enter/ctrl+c]Aborted\n') assert capsys.readouterr() == ('', 'command [enter/ctrl+c]Aborted\n')

View File

@@ -1,11 +1,12 @@
from thefuck.types import Rule, RulesNamesList, Settings from thefuck.types import RulesNamesList, Settings
from tests.utils import Rule
def test_rules_names_list(): def test_rules_names_list():
assert RulesNamesList(['bash', 'lisp']) == ['bash', 'lisp'] assert RulesNamesList(['bash', 'lisp']) == ['bash', 'lisp']
assert RulesNamesList(['bash', 'lisp']) == RulesNamesList(['bash', 'lisp']) assert RulesNamesList(['bash', 'lisp']) == RulesNamesList(['bash', 'lisp'])
assert Rule('lisp', None, None, False) in RulesNamesList(['lisp']) assert Rule('lisp') in RulesNamesList(['lisp'])
assert Rule('bash', None, None, False) not in RulesNamesList(['lisp']) assert Rule('bash') not in RulesNamesList(['lisp'])
def test_update_settings(): def test_update_settings():

View File

@@ -7,5 +7,7 @@ def Command(script='', stdout='', stderr=''):
def Rule(name='', match=lambda *_: True, def Rule(name='', match=lambda *_: True,
get_new_command=lambda *_: '', get_new_command=lambda *_: '',
enabled_by_default=True): enabled_by_default=True,
return types.Rule(name, match, get_new_command, enabled_by_default) side_effect=None):
return types.Rule(name, match, get_new_command,
enabled_by_default, side_effect)

View File

@@ -26,17 +26,20 @@ def rule_failed(rule, exc_info, settings):
exception('Rule {}'.format(rule.name), exc_info, settings) exception('Rule {}'.format(rule.name), exc_info, settings)
def show_command(new_command, settings): def show_command(new_command, side_effect, settings):
sys.stderr.write('{bold}{command}{reset}\n'.format( sys.stderr.write('{bold}{command}{side_effect}{reset}\n'.format(
command=new_command, command=new_command,
side_effect='*' if side_effect else '',
bold=color(colorama.Style.BRIGHT, settings), bold=color(colorama.Style.BRIGHT, settings),
reset=color(colorama.Style.RESET_ALL, settings))) reset=color(colorama.Style.RESET_ALL, settings)))
def confirm_command(new_command, settings): def confirm_command(new_command, side_effect, settings):
sys.stderr.write( sys.stderr.write(
'{bold}{command}{reset} [{green}enter{reset}/{red}ctrl+c{reset}]'.format( '{bold}{command}{side_effect}{reset} '
'[{green}enter{reset}/{red}ctrl+c{reset}]'.format(
command=new_command, command=new_command,
side_effect='*' if side_effect else '',
bold=color(colorama.Style.BRIGHT, settings), bold=color(colorama.Style.BRIGHT, settings),
green=color(colorama.Fore.GREEN, settings), green=color(colorama.Fore.GREEN, settings),
red=color(colorama.Fore.RED, settings), red=color(colorama.Fore.RED, settings),

View File

@@ -24,7 +24,8 @@ def load_rule(rule):
rule_module = load_source(rule.name[:-3], str(rule)) rule_module = load_source(rule.name[:-3], str(rule))
return types.Rule(rule.name[:-3], rule_module.match, return types.Rule(rule.name[:-3], rule_module.match,
rule_module.get_new_command, rule_module.get_new_command,
getattr(rule_module, 'enabled_by_default', True)) getattr(rule_module, 'enabled_by_default', True),
getattr(rule_module, 'side_effect', None))
def get_rules(user_dir, settings): def get_rules(user_dir, settings):
@@ -85,13 +86,13 @@ def get_matched_rule(command, rules, settings):
logs.rule_failed(rule, sys.exc_info(), settings) logs.rule_failed(rule, sys.exc_info(), settings)
def confirm(new_command, settings): def confirm(new_command, side_effect, settings):
"""Returns `True` when running of new command confirmed.""" """Returns `True` when running of new command confirmed."""
if not settings.require_confirmation: if not settings.require_confirmation:
logs.show_command(new_command, settings) logs.show_command(new_command, side_effect, settings)
return True return True
logs.confirm_command(new_command, settings) logs.confirm_command(new_command, side_effect, settings)
try: try:
sys.stdin.read(1) sys.stdin.read(1)
return True return True
@@ -103,7 +104,9 @@ def confirm(new_command, settings):
def run_rule(rule, command, settings): def run_rule(rule, command, settings):
"""Runs command from rule for passed command.""" """Runs command from rule for passed command."""
new_command = rule.get_new_command(command, settings) new_command = rule.get_new_command(command, settings)
if confirm(new_command, settings): if confirm(new_command, rule.side_effect, settings):
if rule.side_effect:
rule.side_effect(command, settings)
print(new_command) print(new_command)
@@ -112,6 +115,10 @@ def is_second_run(command):
return command.script.startswith('fuck') return command.script.startswith('fuck')
def alias():
print("\nalias fuck='eval $(thefuck $(fc -ln -1))'\n")
def main(): def main():
colorama.init() colorama.init()
user_dir = setup_user_dir() user_dir = setup_user_dir()

View File

@@ -1,11 +1,77 @@
import difflib import difflib
import os
import re import re
import subprocess
import thefuck.logs import thefuck.logs
# This commands are based on Homebrew 0.9.5 BREW_CMD_PATH = '/Library/Homebrew/cmd'
brew_commands = ['info', 'home', 'options', 'install', 'uninstall', 'search', TAP_PATH = '/Library/Taps'
'list', 'update', 'upgrade', 'pin', 'unpin', 'doctor', TAP_CMD_PATH = '/%s/%s/cmd'
'create', 'edit']
def _get_brew_path_prefix():
'''To get brew path'''
try:
return subprocess.check_output(['brew', '--prefix']).strip()
except:
return None
def _get_brew_commands(brew_path_prefix):
'''To get brew default commands on local environment'''
brew_cmd_path = brew_path_prefix + BREW_CMD_PATH
commands = (name.replace('.rb', '') for name in os.listdir(brew_cmd_path)
if name.endswith('.rb'))
return commands
def _get_brew_tap_specific_commands(brew_path_prefix):
'''To get tap's specific commands
https://github.com/Homebrew/homebrew/blob/master/Library/brew.rb#L115'''
commands = []
brew_taps_path = brew_path_prefix + TAP_PATH
for user in _get_directory_names_only(brew_taps_path):
taps = _get_directory_names_only(brew_taps_path + '/%s' % user)
# Brew Taps's naming rule
# https://github.com/Homebrew/homebrew/blob/master/share/doc/homebrew/brew-tap.md#naming-conventions-and-limitations
taps = (tap for tap in taps if tap.startswith('homebrew-'))
for tap in taps:
tap_cmd_path = brew_taps_path + TAP_CMD_PATH % (user, tap)
if os.path.isdir(tap_cmd_path):
commands += (name.replace('brew-', '').replace('.rb', '')
for name in os.listdir(tap_cmd_path)
if _is_brew_tap_cmd_naming(name))
return commands
def _is_brew_tap_cmd_naming(name):
if name.startswith('brew-') and name.endswith('.rb'):
return True
return False
def _get_directory_names_only(path):
return [d for d in os.listdir(path)
if os.path.isdir(os.path.join(path, d))]
brew_commands = []
brew_path_prefix = _get_brew_path_prefix()
if brew_path_prefix:
brew_commands += _get_brew_commands(brew_path_prefix)
brew_commands += _get_brew_tap_specific_commands(brew_path_prefix)
else:
# Failback commands for testing (Based on Homebrew 0.9.5)
brew_commands = ['info', 'home', 'options', 'install', 'uninstall',
'search', 'list', 'update', 'upgrade', 'pin', 'unpin',
'doctor', 'create', 'edit']
def _get_similar_commands(command): def _get_similar_commands(command):

View File

@@ -22,7 +22,11 @@ def match(command, settings):
return True return True
def remove_offending_keys(command, settings): def get_new_command(command, settings):
return command.script
def side_effect(command, settings):
offending = offending_pattern.findall(command.stderr) offending = offending_pattern.findall(command.stderr)
for filepath, lineno in offending: for filepath, lineno in offending:
with open(filepath, 'r') as fh: with open(filepath, 'r') as fh:
@@ -30,8 +34,3 @@ def remove_offending_keys(command, settings):
del lines[int(lineno) - 1] del lines[int(lineno) - 1]
with open(filepath, 'w') as fh: with open(filepath, 'w') as fh:
fh.writelines(lines) fh.writelines(lines)
def get_new_command(command, settings):
remove_offending_keys(command, settings)
return command.script

View File

@@ -8,7 +8,10 @@ patterns = ['permission denied',
'This command has to be run under the root user.', 'This command has to be run under the root user.',
'This operation requires root.', 'This operation requires root.',
'You need to be root to perform this command.', 'You need to be root to perform this command.',
'requested operation requires superuser privilege'] 'requested operation requires superuser privilege',
'must be run as root',
'must be superuser',
'Need to be root']
def match(command, settings): def match(command, settings):

View File

@@ -4,11 +4,11 @@ from collections import namedtuple
Command = namedtuple('Command', ('script', 'stdout', 'stderr')) Command = namedtuple('Command', ('script', 'stdout', 'stderr'))
Rule = namedtuple('Rule', ('name', 'match', 'get_new_command', Rule = namedtuple('Rule', ('name', 'match', 'get_new_command',
'enabled_by_default')) 'enabled_by_default', 'side_effect'))
class RulesNamesList(list): class RulesNamesList(list):
"""Wrapper a top of list for string rules names.""" """Wrapper a top of list for storing rules names."""
def __contains__(self, item): def __contains__(self, item):
return super(RulesNamesList, self).__contains__(item.name) return super(RulesNamesList, self).__contains__(item.name)