1
0
mirror of https://github.com/nvbn/thefuck.git synced 2025-11-01 15:42:06 +00:00

Compare commits

...

9 Commits
1.35 ... 1.36

Author SHA1 Message Date
nvbn
f3d377114e Bump to 1.36 2015-05-07 13:12:25 +02:00
nvbn
05f594b918 #154 Add ability to override priority in settings 2015-05-07 13:11:45 +02:00
nvbn
5bf1424613 #164 Decrease priority of no_command 2015-05-07 12:57:43 +02:00
nvbn
fc3fcf028a #154 Add priority to rules 2015-05-06 13:57:09 +02:00
nvbn
5864faadef #165 fix python 2 support 2015-05-06 13:17:14 +02:00
Vladimir Iakovlev
608d48e408 Merge pull request #166 from mcarton/git-add
Add a git_add rule
2015-05-06 13:10:23 +02:00
mcarton
9380eb1f56 Add a git_add rule 2015-05-06 11:31:31 +02:00
Vladimir Iakovlev
fb069b74d7 Merge pull request #159 from mcarton/master
Add a rule for pacman
2015-05-05 13:30:03 +02:00
mcarton
6624ecb3b8 Add a rule for pacman 2015-05-05 11:13:29 +02:00
13 changed files with 169 additions and 42 deletions

View File

@@ -145,12 +145,14 @@ using matched rule and run it. Rules enabled by default:
* `cd_mkdir` – creates directories before cd'ing into them;
* `cp_omitting_directory` – adds `-a` when you `cp` directory;
* `fix_alt_space` – replaces Alt+Space with Space character;
* `git_add` – fix *"Did you forget to 'git add'?"*;
* `git_no_command` – fixes wrong git commands like `git brnch`;
* `git_push` – adds `--set-upstream origin $branch` to previous failed `git push`;
* `has_exists_script` – prepends `./` when script/binary exists;
* `lein_not_task` – fixes wrong `lein` tasks like `lein rpl`;
* `mkdir_p` – adds `-p` when you trying to create directory without parent;
* `no_command` – fixes wrong console commands, for example `vom/vim`;
* `pacman` – installs app with `pacman` or `yaourt` if it is not installed;
* `pip_unknown_command` – fixes wrong pip commands, for example `pip instatl/pip install`;
* `python_command` – prepends `python` when you trying to run not executable/without `./` python script;
* `sl_ls` – changes `sl` to `ls`;
@@ -197,6 +199,8 @@ enabled_by_default = True
def side_effect(command, settings):
subprocess.call('chmod 777 .', shell=True)
priority = 1000 # Lower first
```
[More examples of rules](https://github.com/nvbn/thefuck/tree/master/thefuck/rules),
@@ -209,7 +213,8 @@ The Fuck has a few settings parameters, they can be changed in `~/.thefuck/setti
* `rules` – list of enabled rules, by default `thefuck.conf.DEFAULT_RULES`;
* `require_confirmation` – require confirmation before running new command, by default `False`;
* `wait_command` – max amount of time in seconds for getting previous command output;
* `no_colors` – disable colored output.
* `no_colors` – disable colored output;
* `priority` – dict with rules priorities, rule with lower `priority` will be matched first.
Example of `settings.py`:
@@ -218,6 +223,7 @@ rules = ['sudo', 'no_command']
require_confirmation = True
wait_command = 10
no_colors = False
priority = {'sudo': 100, 'no_command': 9999}
```
Or via environment variables:
@@ -225,7 +231,9 @@ Or via environment variables:
* `THEFUCK_RULES` – list of enabled rules, like `DEFAULT_RULES:rm_root` or `sudo:no_command`;
* `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`.
* `THEFUCK_NO_COLORS` – disable colored output, `true/false`;
* `THEFUCK_PRIORITY` – priority of the rules, like `no_command=9999:apt_get=100`,
rule with lower `priority` will be matched first.
For example:
@@ -234,6 +242,7 @@ export THEFUCK_RULES='sudo:no_command'
export THEFUCK_REQUIRE_CONFIRMATION='true'
export THEFUCK_WAIT_COMMAND=10
export THEFUCK_NO_COLORS='false'
export THEFUCK_PRIORITY='no_command=9999:apt_get=100'
```
## Developing

View File

@@ -1,7 +1,7 @@
from setuptools import setup, find_packages
VERSION = '1.35'
VERSION = '1.36'
setup(name='thefuck',

View File

@@ -40,12 +40,14 @@ class TestSettingsFromFile(object):
load_source.return_value = Mock(rules=['test'],
wait_command=10,
require_confirmation=True,
no_colors=True)
no_colors=True,
priority={'vim': 100})
settings = conf.get_settings(Mock())
assert settings.rules == ['test']
assert settings.wait_command == 10
assert settings.require_confirmation is True
assert settings.no_colors is True
assert settings.priority == {'vim': 100}
def test_from_file_with_DEFAULT(self, load_source):
load_source.return_value = Mock(rules=conf.DEFAULT_RULES + ['test'],
@@ -62,12 +64,14 @@ class TestSettingsFromEnv(object):
environ.update({'THEFUCK_RULES': 'bash:lisp',
'THEFUCK_WAIT_COMMAND': '55',
'THEFUCK_REQUIRE_CONFIRMATION': 'true',
'THEFUCK_NO_COLORS': 'false'})
'THEFUCK_NO_COLORS': 'false',
'THEFUCK_PRIORITY': 'bash=10:lisp=wrong:vim=15'})
settings = conf.get_settings(Mock())
assert settings.rules == ['bash', 'lisp']
assert settings.wait_command == 55
assert settings.require_confirmation is True
assert settings.no_colors is False
assert settings.priority == {'bash': 10, 'vim': 15}
def test_from_env_with_DEFAULT(self, environ):
environ.update({'THEFUCK_RULES': 'DEFAULT_RULES:bash:lisp'})

View File

@@ -12,28 +12,51 @@ def test_load_rule(monkeypatch):
load_source = Mock()
load_source.return_value = Mock(match=match,
get_new_command=get_new_command,
enabled_by_default=True)
enabled_by_default=True,
priority=900)
monkeypatch.setattr('thefuck.main.load_source', load_source)
assert main.load_rule(Path('/rules/bash.py')) \
== Rule('bash', match, get_new_command)
== Rule('bash', match, get_new_command, priority=900)
load_source.assert_called_once_with('bash', '/rules/bash.py')
@pytest.mark.parametrize('conf_rules, rules', [
(conf.DEFAULT_RULES, [Rule('bash', 'bash', 'bash'),
Rule('lisp', 'lisp', 'lisp'),
Rule('bash', 'bash', 'bash'),
Rule('lisp', 'lisp', 'lisp')]),
(types.RulesNamesList(['bash']), [Rule('bash', 'bash', 'bash'),
Rule('bash', 'bash', 'bash')])])
def test_get_rules(monkeypatch, conf_rules, rules):
monkeypatch.setattr(
'thefuck.main.Path.glob',
lambda *_: [PosixPath('bash.py'), PosixPath('lisp.py')])
monkeypatch.setattr('thefuck.main.load_source',
lambda x, _: Mock(match=x, get_new_command=x,
enabled_by_default=True))
assert list(main.get_rules(Path('~'), Mock(rules=conf_rules))) == rules
class TestGetRules(object):
@pytest.fixture(autouse=True)
def glob(self, monkeypatch):
mock = Mock(return_value=[])
monkeypatch.setattr('thefuck.main.Path.glob', mock)
return mock
def _compare_names(self, rules, names):
return [r.name for r in rules] == names
@pytest.mark.parametrize('conf_rules, rules', [
(conf.DEFAULT_RULES, ['bash', 'lisp', 'bash', 'lisp']),
(types.RulesNamesList(['bash']), ['bash', 'bash'])])
def test_get(self, monkeypatch, glob, conf_rules, rules):
glob.return_value = [PosixPath('bash.py'), PosixPath('lisp.py')]
monkeypatch.setattr('thefuck.main.load_source',
lambda x, _: Rule(x))
assert self._compare_names(
main.get_rules(Path('~'), Mock(rules=conf_rules, priority={})),
rules)
@pytest.mark.parametrize('priority, unordered, ordered', [
({},
[Rule('bash', priority=100), Rule('python', priority=5)],
['python', 'bash']),
({},
[Rule('lisp', priority=9999), Rule('c', priority=conf.DEFAULT_PRIORITY)],
['c', 'lisp']),
({'python': 9999},
[Rule('bash', priority=100), Rule('python', priority=5)],
['bash', 'python'])])
def test_ordered_by_priority(self, monkeypatch, priority, unordered, ordered):
monkeypatch.setattr('thefuck.main._get_loaded_rules',
lambda *_: unordered)
assert self._compare_names(
main.get_rules(Path('~'), Mock(priority=priority)),
ordered)
class TestGetCommand(object):
@@ -64,6 +87,7 @@ class TestGetCommand(object):
stdout=PIPE,
stderr=PIPE,
env={'LANG': 'C'})
@pytest.mark.parametrize('args, result', [
(['thefuck', 'ls', '-la'], 'ls -la'),
(['thefuck', 'ls'], 'ls')])

View File

@@ -1,4 +1,5 @@
from thefuck import types
from thefuck.conf import DEFAULT_PRIORITY
def Command(script='', stdout='', stderr=''):
@@ -8,6 +9,8 @@ def Command(script='', stdout='', stderr=''):
def Rule(name='', match=lambda *_: True,
get_new_command=lambda *_: '',
enabled_by_default=True,
side_effect=None):
side_effect=None,
priority=DEFAULT_PRIORITY):
return types.Rule(name, match, get_new_command,
enabled_by_default, side_effect)
enabled_by_default, side_effect,
priority)

View File

@@ -22,17 +22,20 @@ class _DefaultRulesNames(types.RulesNamesList):
DEFAULT_RULES = _DefaultRulesNames([])
DEFAULT_PRIORITY = 1000
DEFAULT_SETTINGS = {'rules': DEFAULT_RULES,
'wait_command': 3,
'require_confirmation': False,
'no_colors': False}
'no_colors': False,
'priority': {}}
ENV_TO_ATTR = {'THEFUCK_RULES': 'rules',
'THEFUCK_WAIT_COMMAND': 'wait_command',
'THEFUCK_REQUIRE_CONFIRMATION': 'require_confirmation',
'THEFUCK_NO_COLORS': 'no_colors'}
'THEFUCK_NO_COLORS': 'no_colors',
'THEFUCK_PRIORITY': 'priority'}
SETTINGS_HEADER = u"""# ~/.thefuck/settings.py: The Fuck settings file
@@ -65,16 +68,29 @@ def _rules_from_env(val):
return val
def _priority_from_env(val):
"""Gets priority pairs from env."""
for part in val.split(':'):
try:
rule, priority = part.split('=')
yield rule, int(priority)
except ValueError:
continue
def _val_from_env(env, attr):
"""Transforms env-strings to python."""
val = os.environ[env]
if attr == 'rules':
val = _rules_from_env(val)
return _rules_from_env(val)
elif attr == 'priority':
return dict(_priority_from_env(val))
elif attr == 'wait_command':
val = int(val)
return int(val)
elif attr in ('require_confirmation', 'no_colors'):
val = val.lower() == 'true'
return val
return val.lower() == 'true'
else:
return val
def _settings_from_env():

View File

@@ -26,7 +26,17 @@ def load_rule(rule):
return types.Rule(rule.name[:-3], rule_module.match,
rule_module.get_new_command,
getattr(rule_module, 'enabled_by_default', True),
getattr(rule_module, 'side_effect', None))
getattr(rule_module, 'side_effect', None),
getattr(rule_module, 'priority', conf.DEFAULT_PRIORITY))
def _get_loaded_rules(rules, settings):
"""Yields all available rules."""
for rule in rules:
if rule.name != '__init__.py':
loaded_rule = load_rule(rule)
if loaded_rule in settings.rules:
yield loaded_rule
def get_rules(user_dir, settings):
@@ -35,11 +45,9 @@ def get_rules(user_dir, settings):
.joinpath('rules') \
.glob('*.py')
user = user_dir.joinpath('rules').glob('*.py')
for rule in sorted(list(bundled)) + list(user):
if rule.name != '__init__.py':
loaded_rule = load_rule(rule)
if loaded_rule in settings.rules:
yield loaded_rule
rules = _get_loaded_rules(sorted(bundled) + sorted(user), settings)
return sorted(rules, key=lambda rule: settings.priority.get(
rule.name, rule.priority))
def wait_output(settings, popen):

15
thefuck/rules/git_add.py Normal file
View File

@@ -0,0 +1,15 @@
import re
def match(command, settings):
return ('git' in command.script
and 'did not match any file(s) known to git.' in command.stderr
and "Did you forget to 'git add'?" in command.stderr)
def get_new_command(command, settings):
missing_file = re.findall(
r"error: pathspec '([^']*)' "
"did not match any file\(s\) known to git.", command.stderr)[0]
return 'git add -- {} && {}'.format(missing_file, command.script)

View File

@@ -31,3 +31,6 @@ def get_new_command(command, settings):
new_command = get_close_matches(old_command,
_get_all_bins())[0]
return ' '.join([new_command] + command.script.split(' ')[1:])
priority = 3000

43
thefuck/rules/pacman.py Normal file
View File

@@ -0,0 +1,43 @@
import subprocess
from thefuck.utils import DEVNULL
def __command_available(command):
try:
subprocess.check_output([command], stderr=DEVNULL)
return True
except subprocess.CalledProcessError:
# command exists but is not happy to be called without any argument
return True
except OSError:
return False
def __get_pkgfile(command):
try:
return subprocess.check_output(
['pkgfile', '-b', '-v', command.script.split(" ")[0]],
universal_newlines=True, stderr=subprocess.DEVNULL
).split()
except subprocess.CalledProcessError:
return None
def match(command, settings):
return 'not found' in command.stderr and __get_pkgfile(command)
def get_new_command(command, settings):
package = __get_pkgfile(command)[0]
return '{} -S {} && {}'.format(pacman, package, command.script)
if not __command_available('pkgfile'):
enabled_by_default = False
elif __command_available('yaourt'):
pacman = 'yaourt'
elif __command_available('pacman'):
pacman = 'sudo pacman'
else:
enabled_by_default = False

View File

@@ -8,9 +8,7 @@ from subprocess import Popen, PIPE
from time import time
import os
from psutil import Process
FNULL = open(os.devnull, 'w')
from .utils import DEVNULL
class Generic(object):
@@ -58,7 +56,7 @@ class Bash(Generic):
return name, value
def _get_aliases(self):
proc = Popen('bash -ic alias', stdout=PIPE, stderr=FNULL, shell=True)
proc = Popen('bash -ic alias', stdout=PIPE, stderr=DEVNULL, shell=True)
return dict(
self._parse_alias(alias)
for alias in proc.stdout.read().decode('utf-8').split('\n')
@@ -80,7 +78,7 @@ class Zsh(Generic):
return name, value
def _get_aliases(self):
proc = Popen('zsh -ic alias', stdout=PIPE, stderr=FNULL, shell=True)
proc = Popen('zsh -ic alias', stdout=PIPE, stderr=DEVNULL, shell=True)
return dict(
self._parse_alias(alias)
for alias in proc.stdout.read().decode('utf-8').split('\n')

View File

@@ -4,7 +4,8 @@ from collections import namedtuple
Command = namedtuple('Command', ('script', 'stdout', 'stderr'))
Rule = namedtuple('Rule', ('name', 'match', 'get_new_command',
'enabled_by_default', 'side_effect'))
'enabled_by_default', 'side_effect',
'priority'))
class RulesNamesList(list):

View File

@@ -4,6 +4,9 @@ import six
from .types import Command
DEVNULL = open(os.devnull, 'w')
def which(program):
"""Returns `program` path or `None`."""