From 1de9c5f77b4eab61f55d421e8baee07c0dc6f88d Mon Sep 17 00:00:00 2001 From: nvbn Date: Fri, 17 Apr 2015 17:01:30 +0200 Subject: [PATCH] Add information about writting yourself rules, revert no_command changes --- README.md | 34 +++++++++++++++++++ setup.py | 2 +- ...t_no_command_apt.py => test_no_command.py} | 27 +++++++++------ .../{no_command_apt.py => no_command.py} | 11 +++--- thefuck/utils.py | 24 +++++++++++++ 5 files changed, 82 insertions(+), 16 deletions(-) rename tests/rules/{test_no_command_apt.py => test_no_command.py} (71%) rename thefuck/rules/{no_command_apt.py => no_command.py} (72%) diff --git a/README.md b/README.md index 9858b3cf..8b9a6af8 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,40 @@ And add to `.bashrc` or `.zshrc`: alias fuck='$(thefuck $(fc -ln -1))' ``` +## Creating your own rules + +For adding your own rule you should create `your-rule-name.py` +in `~/.thefuck/rules`. Rule should contain two functions: +`match(command: Command, settings: Settings) -> bool` +and `get_new_command(command: Command, settings: Settings) -> str`. + +`Command` have three attributes: `script`, `stdout` and `stderr`. + +`Settings` is `~/.thefuck/settings.py`. + +Simple example of the rule for running script with `sudo`: + +```python +def match(command, settings): + return ('permission denied' in command.stderr.lower() + or 'EACCES' in command.stderr) + + +def get_new_command(command, settings): + return 'sudo {}'.format(command.script) +``` + +[More examples of rules](https://github.com/nvbn/thefuck/tree/master/thefuck/rules), +[utility functions for rules]((https://github.com/nvbn/thefuck/tree/master/thefuck/utils.py)). + +## Settings + +The Fuck have a few settings parameters: + +* `rules` – list of enabled rules, by default all; +* `command_not_found` – path to `command_not_found` binary, +by default `/usr/lib/command-not-found`. + ## Developing Install `The Fuck` for development: diff --git a/setup.py b/setup.py index a90af29b..c7ed8500 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ from setuptools import setup, find_packages setup(name='thefuck', - version=1.3, + version=1.4, description="Magnificent app which corrects your previous console command", author='Vladimir Iakovlev', author_email='nvbn.rm@gmail.com', diff --git a/tests/rules/test_no_command_apt.py b/tests/rules/test_no_command.py similarity index 71% rename from tests/rules/test_no_command_apt.py rename to tests/rules/test_no_command.py index c3356dff..55983788 100644 --- a/tests/rules/test_no_command_apt.py +++ b/tests/rules/test_no_command.py @@ -1,7 +1,7 @@ from subprocess import PIPE from mock import patch, Mock import pytest -from thefuck.rules.no_command_apt import match, get_new_command +from thefuck.rules.no_command import match, get_new_command from thefuck.main import Command @@ -21,23 +21,30 @@ vom: command not found @pytest.fixture def bins_exists(request): - p = patch('thefuck.rules.no_command_apt.which', + p = patch('thefuck.rules.no_command.which', return_value=True) p.start() request.addfinalizer(p.stop) +@pytest.fixture +def settings(): + class _Settings(object): + pass + return _Settings + + @pytest.mark.usefixtures('bins_exists') -def test_match(command_found, command_not_found): - with patch('thefuck.rules.no_command_apt.Popen') as Popen: +def test_match(command_found, command_not_found, settings): + with patch('thefuck.rules.no_command.Popen') as Popen: Popen.return_value.stderr.read.return_value = command_found - assert match(Command('aptget install vim', '', ''), None) + assert match(Command('aptget install vim', '', ''), settings) Popen.assert_called_once_with('/usr/lib/command-not-found aptget', shell=True, stderr=PIPE) Popen.return_value.stderr.read.return_value = command_not_found - assert not match(Command('ls', '', ''), None) + assert not match(Command('ls', '', ''), settings) - with patch('thefuck.rules.no_command_apt.Popen') as Popen: + with patch('thefuck.rules.no_command.Popen') as Popen: Popen.return_value.stderr.read.return_value = command_found assert match(Command('sudo aptget install vim', '', ''), Mock(command_not_found='test')) @@ -47,9 +54,9 @@ def test_match(command_found, command_not_found): @pytest.mark.usefixtures('bins_exists') def test_get_new_command(command_found): - with patch('thefuck.rules.no_command_apt._get_output', + with patch('thefuck.rules.no_command._get_output', return_value=command_found.decode()): - assert get_new_command(Command('aptget install vim', '', ''), None)\ + assert get_new_command(Command('aptget install vim', '', ''), settings)\ == 'apt-get install vim' - assert get_new_command(Command('sudo aptget install vim', '', ''), None) \ + assert get_new_command(Command('sudo aptget install vim', '', ''), settings) \ == 'sudo apt-get install vim' diff --git a/thefuck/rules/no_command_apt.py b/thefuck/rules/no_command.py similarity index 72% rename from thefuck/rules/no_command_apt.py rename to thefuck/rules/no_command.py index 3e4b628b..cf058375 100644 --- a/thefuck/rules/no_command_apt.py +++ b/thefuck/rules/no_command.py @@ -1,25 +1,26 @@ from subprocess import Popen, PIPE import re -from thefuck.utils import which +from thefuck.utils import which, wrap_settings -def _get_bin(settings): - return getattr(settings, 'command_not_found', '/usr/lib/command-not-found') +local_settings = {'command_not_found': '/usr/lib/command-not-found'} def _get_output(command, settings): name = command.script.split(' ')[command.script.startswith('sudo')] - check_script = '{} {}'.format(_get_bin(settings), name) + check_script = '{} {}'.format(settings.command_not_found, name) result = Popen(check_script, shell=True, stderr=PIPE) return result.stderr.read().decode() +@wrap_settings(local_settings) def match(command, settings): - if which('apt-get') and which(_get_bin(settings)): + if which(settings.command_not_found): output = _get_output(command, settings) return "No command" in output and "from package" in output +@wrap_settings(local_settings) def get_new_command(command, settings): output = _get_output(command, settings) broken_name = re.findall(r"No command '([^']*)' found", diff --git a/thefuck/utils.py b/thefuck/utils.py index 2046c4c2..fa4ee1e9 100644 --- a/thefuck/utils.py +++ b/thefuck/utils.py @@ -1,7 +1,10 @@ +from functools import wraps import os def which(program): + """Returns `program` path or `None`.""" + def is_exe(fpath): return os.path.isfile(fpath) and os.access(fpath, os.X_OK) @@ -17,3 +20,24 @@ def which(program): return exe_file return None + + +def wrap_settings(params): + """Adds default values to settings if it not presented. + + Usage: + + @wrap_settings({'apt': '/usr/bin/apt'}) + def match(command, settings): + print(settings.apt) + + """ + def decorator(fn): + @wraps(fn) + def wrapper(command, settings): + for key, val in params.items(): + if not hasattr(settings, key): + setattr(settings, key, val) + return fn(command, settings) + return wrapper + return decorator