1
0
mirror of https://github.com/nvbn/thefuck.git synced 2025-11-03 00:22:10 +00:00

Compare commits

..

14 Commits
1.35 ... 1.37

Author SHA1 Message Date
nvbn
29e70e14a0 Bump to 1.37 2015-05-07 14:16:17 +02:00
nvbn
0cdd23edcf Use wheel 2015-05-07 14:16:07 +02:00
nvbn
36d80859a4 Add tox config 2015-05-07 13:51:27 +02:00
nvbn
2b12b4bfce Improve tests with mocker 2015-05-07 13:42:52 +02:00
nvbn
91c1fe414a Update thefuck-alias entry point 2015-05-07 13:32:23 +02:00
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
18 changed files with 204 additions and 85 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; * `cd_mkdir` – creates directories before cd'ing into them;
* `cp_omitting_directory` – adds `-a` when you `cp` directory; * `cp_omitting_directory` – adds `-a` when you `cp` directory;
* `fix_alt_space` – replaces Alt+Space with Space character; * `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_no_command` – fixes wrong git commands like `git brnch`;
* `git_push` – adds `--set-upstream origin $branch` to previous failed `git push`; * `git_push` – adds `--set-upstream origin $branch` to previous failed `git push`;
* `has_exists_script` – prepends `./` when script/binary exists; * `has_exists_script` – prepends `./` when script/binary exists;
* `lein_not_task` – fixes wrong `lein` tasks like `lein rpl`; * `lein_not_task` – fixes wrong `lein` tasks like `lein rpl`;
* `mkdir_p` – adds `-p` when you trying to create directory without parent; * `mkdir_p` – adds `-p` when you trying to create directory without parent;
* `no_command` – fixes wrong console commands, for example `vom/vim`; * `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`; * `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; * `python_command` – prepends `python` when you trying to run not executable/without `./` python script;
* `sl_ls` – changes `sl` to `ls`; * `sl_ls` – changes `sl` to `ls`;
@@ -197,6 +199,8 @@ enabled_by_default = True
def side_effect(command, settings): def side_effect(command, settings):
subprocess.call('chmod 777 .', shell=True) subprocess.call('chmod 777 .', shell=True)
priority = 1000 # Lower first
``` ```
[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),
@@ -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`; * `rules` – list of enabled rules, by default `thefuck.conf.DEFAULT_RULES`;
* `require_confirmation` – require confirmation before running new command, by default `False`; * `require_confirmation` – require confirmation before running new command, by default `False`;
* `wait_command` – max amount of time in seconds for getting previous command output; * `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`: Example of `settings.py`:
@@ -218,6 +223,7 @@ rules = ['sudo', 'no_command']
require_confirmation = True require_confirmation = True
wait_command = 10 wait_command = 10
no_colors = False no_colors = False
priority = {'sudo': 100, 'no_command': 9999}
``` ```
Or via environment variables: 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_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_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_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: For example:
@@ -234,6 +242,7 @@ export THEFUCK_RULES='sudo:no_command'
export THEFUCK_REQUIRE_CONFIRMATION='true' export THEFUCK_REQUIRE_CONFIRMATION='true'
export THEFUCK_WAIT_COMMAND=10 export THEFUCK_WAIT_COMMAND=10
export THEFUCK_NO_COLORS='false' export THEFUCK_NO_COLORS='false'
export THEFUCK_PRIORITY='no_command=9999:apt_get=100'
``` ```
## Developing ## Developing

View File

@@ -28,4 +28,4 @@ call('git commit -am "Bump to {}"'.format(version), shell=True)
call('git tag {}'.format(version), shell=True) call('git tag {}'.format(version), shell=True)
call('git push', shell=True) call('git push', shell=True)
call('git push --tags', shell=True) call('git push --tags', shell=True)
call('python setup.py sdist upload', shell=True) call('python setup.py sdist bdist_wheel upload', shell=True)

View File

@@ -1,2 +1,4 @@
pytest pytest
mock mock
pytest-mock
wheel

2
setup.cfg Normal file
View File

@@ -0,0 +1,2 @@
[bdist_wheel]
universal = 1

View File

@@ -1,7 +1,7 @@
from setuptools import setup, find_packages from setuptools import setup, find_packages
VERSION = '1.35' VERSION = '1.37'
setup(name='thefuck', setup(name='thefuck',
@@ -17,4 +17,5 @@ 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-alias = thefuck.main:alias']}) 'thefuck = thefuck.main:main',
'thefuck-alias = thefuck.shells:app_alias']})

View File

@@ -14,10 +14,8 @@ def test_default(enabled, rules, result):
@pytest.fixture @pytest.fixture
def load_source(monkeypatch): def load_source(mocker):
mock = Mock() return mocker.patch('thefuck.conf.load_source')
monkeypatch.setattr('thefuck.conf.load_source', mock)
return mock
@pytest.fixture @pytest.fixture
@@ -40,12 +38,14 @@ class TestSettingsFromFile(object):
load_source.return_value = Mock(rules=['test'], load_source.return_value = Mock(rules=['test'],
wait_command=10, wait_command=10,
require_confirmation=True, require_confirmation=True,
no_colors=True) no_colors=True,
priority={'vim': 100})
settings = conf.get_settings(Mock()) settings = conf.get_settings(Mock())
assert settings.rules == ['test'] assert settings.rules == ['test']
assert settings.wait_command == 10 assert settings.wait_command == 10
assert settings.require_confirmation is True assert settings.require_confirmation is True
assert settings.no_colors is True assert settings.no_colors is True
assert settings.priority == {'vim': 100}
def test_from_file_with_DEFAULT(self, load_source): def test_from_file_with_DEFAULT(self, load_source):
load_source.return_value = Mock(rules=conf.DEFAULT_RULES + ['test'], load_source.return_value = Mock(rules=conf.DEFAULT_RULES + ['test'],
@@ -62,12 +62,14 @@ class TestSettingsFromEnv(object):
environ.update({'THEFUCK_RULES': 'bash:lisp', environ.update({'THEFUCK_RULES': 'bash:lisp',
'THEFUCK_WAIT_COMMAND': '55', 'THEFUCK_WAIT_COMMAND': '55',
'THEFUCK_REQUIRE_CONFIRMATION': 'true', '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()) settings = conf.get_settings(Mock())
assert settings.rules == ['bash', 'lisp'] assert settings.rules == ['bash', 'lisp']
assert settings.wait_command == 55 assert settings.wait_command == 55
assert settings.require_confirmation is True assert settings.require_confirmation is True
assert settings.no_colors is False assert settings.no_colors is False
assert settings.priority == {'bash': 10, 'vim': 15}
def test_from_env_with_DEFAULT(self, environ): def test_from_env_with_DEFAULT(self, environ):
environ.update({'THEFUCK_RULES': 'DEFAULT_RULES:bash:lisp'}) environ.update({'THEFUCK_RULES': 'DEFAULT_RULES:bash:lisp'})

View File

@@ -6,34 +6,55 @@ from thefuck import main, conf, types
from tests.utils import Rule, Command from tests.utils import Rule, Command
def test_load_rule(monkeypatch): def test_load_rule(mocker):
match = object() match = object()
get_new_command = object() get_new_command = object()
load_source = Mock() load_source = mocker.patch(
load_source.return_value = Mock(match=match, 'thefuck.main.load_source',
get_new_command=get_new_command, return_value=Mock(match=match,
enabled_by_default=True) get_new_command=get_new_command,
monkeypatch.setattr('thefuck.main.load_source', load_source) enabled_by_default=True,
priority=900))
assert main.load_rule(Path('/rules/bash.py')) \ 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') load_source.assert_called_once_with('bash', '/rules/bash.py')
@pytest.mark.parametrize('conf_rules, rules', [ class TestGetRules(object):
(conf.DEFAULT_RULES, [Rule('bash', 'bash', 'bash'), @pytest.fixture(autouse=True)
Rule('lisp', 'lisp', 'lisp'), def glob(self, mocker):
Rule('bash', 'bash', 'bash'), return mocker.patch('thefuck.main.Path.glob', return_value=[])
Rule('lisp', 'lisp', 'lisp')]),
(types.RulesNamesList(['bash']), [Rule('bash', 'bash', 'bash'), def _compare_names(self, rules, names):
Rule('bash', 'bash', 'bash')])]) return [r.name for r in rules] == names
def test_get_rules(monkeypatch, conf_rules, rules):
monkeypatch.setattr( @pytest.mark.parametrize('conf_rules, rules', [
'thefuck.main.Path.glob', (conf.DEFAULT_RULES, ['bash', 'lisp', 'bash', 'lisp']),
lambda *_: [PosixPath('bash.py'), PosixPath('lisp.py')]) (types.RulesNamesList(['bash']), ['bash', 'bash'])])
monkeypatch.setattr('thefuck.main.load_source', def test_get(self, monkeypatch, glob, conf_rules, rules):
lambda x, _: Mock(match=x, get_new_command=x, glob.return_value = [PosixPath('bash.py'), PosixPath('lisp.py')]
enabled_by_default=True)) monkeypatch.setattr('thefuck.main.load_source',
assert list(main.get_rules(Path('~'), Mock(rules=conf_rules))) == rules 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): class TestGetCommand(object):
@@ -64,6 +85,7 @@ class TestGetCommand(object):
stdout=PIPE, stdout=PIPE,
stderr=PIPE, stderr=PIPE,
env={'LANG': 'C'}) env={'LANG': 'C'})
@pytest.mark.parametrize('args, result', [ @pytest.mark.parametrize('args, result', [
(['thefuck', 'ls', '-la'], 'ls -la'), (['thefuck', 'ls', '-la'], 'ls -la'),
(['thefuck', 'ls'], 'ls')]) (['thefuck', 'ls'], 'ls')])
@@ -94,10 +116,8 @@ class TestGetMatchedRule(object):
class TestRunRule(object): class TestRunRule(object):
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def confirm(self, monkeypatch): def confirm(self, mocker):
mock = Mock(return_value=True) return mocker.patch('thefuck.main.confirm', return_value=True)
monkeypatch.setattr('thefuck.main.confirm', mock)
return mock
def test_run_rule(self, capsys): def test_run_rule(self, capsys):
main.run_rule(Rule(get_new_command=lambda *_: 'new-command'), main.run_rule(Rule(get_new_command=lambda *_: 'new-command'),
@@ -123,10 +143,8 @@ class TestRunRule(object):
class TestConfirm(object): class TestConfirm(object):
@pytest.fixture @pytest.fixture
def stdin(self, monkeypatch): def stdin(self, mocker):
mock = Mock(return_value='\n') return mocker.patch('sys.stdin.read', return_value='\n')
monkeypatch.setattr('sys.stdin.read', mock)
return mock
def test_when_not_required(self, capsys): def test_when_not_required(self, capsys):
assert main.confirm('command', None, Mock(require_confirmation=False)) assert main.confirm('command', None, Mock(require_confirmation=False))

View File

@@ -1,20 +1,15 @@
import pytest import pytest
from mock import Mock, MagicMock
from thefuck import shells from thefuck import shells
@pytest.fixture @pytest.fixture
def builtins_open(monkeypatch): def builtins_open(mocker):
mock = MagicMock() return mocker.patch('six.moves.builtins.open')
monkeypatch.setattr('six.moves.builtins.open', mock)
return mock
@pytest.fixture @pytest.fixture
def isfile(monkeypatch): def isfile(mocker):
mock = Mock(return_value=True) return mocker.patch('os.path.isfile', return_value=True)
monkeypatch.setattr('os.path.isfile', mock)
return mock
class TestGeneric(object): class TestGeneric(object):
@@ -32,9 +27,8 @@ class TestGeneric(object):
@pytest.mark.usefixtures('isfile') @pytest.mark.usefixtures('isfile')
class TestBash(object): class TestBash(object):
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def Popen(self, monkeypatch): def Popen(self, mocker):
mock = Mock() mock = mocker.patch('thefuck.shells.Popen')
monkeypatch.setattr('thefuck.shells.Popen', mock)
mock.return_value.stdout.read.return_value = ( mock.return_value.stdout.read.return_value = (
b'alias l=\'ls -CF\'\n' b'alias l=\'ls -CF\'\n'
b'alias la=\'ls -A\'\n' b'alias la=\'ls -A\'\n'
@@ -52,16 +46,15 @@ class TestBash(object):
def test_put_to_history(self, builtins_open): def test_put_to_history(self, builtins_open):
shells.Bash().put_to_history('ls') shells.Bash().put_to_history('ls')
builtins_open.return_value.__enter__.return_value.\ builtins_open.return_value.__enter__.return_value. \
write.assert_called_once_with('ls\n') write.assert_called_once_with('ls\n')
@pytest.mark.usefixtures('isfile') @pytest.mark.usefixtures('isfile')
class TestZsh(object): class TestZsh(object):
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def Popen(self, monkeypatch): def Popen(self, mocker):
mock = Mock() mock = mocker.patch('thefuck.shells.Popen')
monkeypatch.setattr('thefuck.shells.Popen', mock)
mock.return_value.stdout.read.return_value = ( mock.return_value.stdout.read.return_value = (
b'l=\'ls -CF\'\n' b'l=\'ls -CF\'\n'
b'la=\'ls -A\'\n' b'la=\'ls -A\'\n'
@@ -77,9 +70,9 @@ class TestZsh(object):
def test_to_shell(self): def test_to_shell(self):
assert shells.Zsh().to_shell('pwd') == 'pwd' assert shells.Zsh().to_shell('pwd') == 'pwd'
def test_put_to_history(self, builtins_open, monkeypatch): def test_put_to_history(self, builtins_open, mocker):
monkeypatch.setattr('thefuck.shells.time', mocker.patch('thefuck.shells.time',
lambda: 1430707243.3517463) return_value=1430707243.3517463)
shells.Zsh().put_to_history('ls') shells.Zsh().put_to_history('ls')
builtins_open.return_value.__enter__.return_value. \ builtins_open.return_value.__enter__.return_value. \
write.assert_called_once_with(': 1430707243:0;ls\n') write.assert_called_once_with(': 1430707243:0;ls\n')

View File

@@ -1,4 +1,5 @@
from thefuck import types from thefuck import types
from thefuck.conf import DEFAULT_PRIORITY
def Command(script='', stdout='', stderr=''): def Command(script='', stdout='', stderr=''):
@@ -8,6 +9,8 @@ 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,
side_effect=None): side_effect=None,
priority=DEFAULT_PRIORITY):
return types.Rule(name, match, get_new_command, 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_RULES = _DefaultRulesNames([])
DEFAULT_PRIORITY = 1000
DEFAULT_SETTINGS = {'rules': DEFAULT_RULES, DEFAULT_SETTINGS = {'rules': DEFAULT_RULES,
'wait_command': 3, 'wait_command': 3,
'require_confirmation': False, 'require_confirmation': False,
'no_colors': False} 'no_colors': False,
'priority': {}}
ENV_TO_ATTR = {'THEFUCK_RULES': 'rules', ENV_TO_ATTR = {'THEFUCK_RULES': 'rules',
'THEFUCK_WAIT_COMMAND': 'wait_command', 'THEFUCK_WAIT_COMMAND': 'wait_command',
'THEFUCK_REQUIRE_CONFIRMATION': 'require_confirmation', '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 SETTINGS_HEADER = u"""# ~/.thefuck/settings.py: The Fuck settings file
@@ -65,16 +68,29 @@ def _rules_from_env(val):
return 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): def _val_from_env(env, attr):
"""Transforms env-strings to python.""" """Transforms env-strings to python."""
val = os.environ[env] val = os.environ[env]
if attr == 'rules': 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': elif attr == 'wait_command':
val = int(val) return int(val)
elif attr in ('require_confirmation', 'no_colors'): elif attr in ('require_confirmation', 'no_colors'):
val = val.lower() == 'true' return val.lower() == 'true'
return val else:
return val
def _settings_from_env(): def _settings_from_env():

View File

@@ -26,7 +26,17 @@ def load_rule(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)) 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): def get_rules(user_dir, settings):
@@ -35,11 +45,9 @@ def get_rules(user_dir, settings):
.joinpath('rules') \ .joinpath('rules') \
.glob('*.py') .glob('*.py')
user = user_dir.joinpath('rules').glob('*.py') user = user_dir.joinpath('rules').glob('*.py')
for rule in sorted(list(bundled)) + list(user): rules = _get_loaded_rules(sorted(bundled) + sorted(user), settings)
if rule.name != '__init__.py': return sorted(rules, key=lambda rule: settings.priority.get(
loaded_rule = load_rule(rule) rule.name, rule.priority))
if loaded_rule in settings.rules:
yield loaded_rule
def wait_output(settings, popen): def wait_output(settings, popen):
@@ -113,10 +121,6 @@ def run_rule(rule, command, settings):
print(new_command) print(new_command)
def alias():
print(shells.app_alias())
def main(): def main():
colorama.init() colorama.init()
user_dir = setup_user_dir() user_dir = setup_user_dir()

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, new_command = get_close_matches(old_command,
_get_all_bins())[0] _get_all_bins())[0]
return ' '.join([new_command] + command.script.split(' ')[1:]) 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 from time import time
import os import os
from psutil import Process from psutil import Process
from .utils import DEVNULL
FNULL = open(os.devnull, 'w')
class Generic(object): class Generic(object):
@@ -58,7 +56,7 @@ class Bash(Generic):
return name, value return name, value
def _get_aliases(self): 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( return dict(
self._parse_alias(alias) self._parse_alias(alias)
for alias in proc.stdout.read().decode('utf-8').split('\n') for alias in proc.stdout.read().decode('utf-8').split('\n')
@@ -80,7 +78,7 @@ class Zsh(Generic):
return name, value return name, value
def _get_aliases(self): 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( return dict(
self._parse_alias(alias) self._parse_alias(alias)
for alias in proc.stdout.read().decode('utf-8').split('\n') 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')) 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', 'side_effect')) 'enabled_by_default', 'side_effect',
'priority'))
class RulesNamesList(list): class RulesNamesList(list):

View File

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

6
tox.ini Normal file
View File

@@ -0,0 +1,6 @@
[tox]
envlist = py27,py33,py34
[testenv]
deps = -rrequirements.txt
commands = py.test