1
0
mirror of https://github.com/nvbn/thefuck.git synced 2025-03-19 00:58:56 +00:00

#842: add settings var to control number of close matches

This commit is contained in:
Pablo Santiago Blum de Aguiar 2018-09-29 22:57:39 +02:00
parent 087c58f21b
commit 5ae637ca34
12 changed files with 101 additions and 18 deletions

View File

@ -390,7 +390,8 @@ Several *The Fuck* parameters can be changed in the file `$XDG_CONFIG_HOME/thefu
* `history_limit` – numeric value of how many history commands will be scanned, like `2000`; * `history_limit` – numeric value of how many history commands will be scanned, like `2000`;
* `alter_history` – push fixed command to history, by default `True`; * `alter_history` – push fixed command to history, by default `True`;
* `wait_slow_command` – max amount of time in seconds for getting previous command output if it in `slow_commands` list; * `wait_slow_command` – max amount of time in seconds for getting previous command output if it in `slow_commands` list;
* `slow_commands` – list of slow commands. * `slow_commands` – list of slow commands;
* `num_close_matches` – maximum number of close matches to suggest, by default `3`.
An example of `settings.py`: An example of `settings.py`:
@ -405,6 +406,7 @@ debug = False
history_limit = 9999 history_limit = 9999
wait_slow_command = 20 wait_slow_command = 20
slow_commands = ['react-native', 'gradle'] slow_commands = ['react-native', 'gradle']
num_close_matches = 5
``` ```
Or via environment variables: Or via environment variables:
@ -420,7 +422,8 @@ rule with lower `priority` will be matched first;
* `THEFUCK_HISTORY_LIMIT` – how many history commands will be scanned, like `2000`; * `THEFUCK_HISTORY_LIMIT` – how many history commands will be scanned, like `2000`;
* `THEFUCK_ALTER_HISTORY` – push fixed command to history `true/false`; * `THEFUCK_ALTER_HISTORY` – push fixed command to history `true/false`;
* `THEFUCK_WAIT_SLOW_COMMAND` – max amount of time in seconds for getting previous command output if it in `slow_commands` list; * `THEFUCK_WAIT_SLOW_COMMAND` – max amount of time in seconds for getting previous command output if it in `slow_commands` list;
* `THEFUCK_SLOW_COMMANDS` – list of slow commands, like `lein:gradle`. * `THEFUCK_SLOW_COMMANDS` – list of slow commands, like `lein:gradle`;
* `THEFUCK_NUM_CLOSE_MATCHES` – maximum number of close matches to suggest, like `5`.
For example: For example:
@ -432,6 +435,7 @@ export THEFUCK_WAIT_COMMAND=10
export THEFUCK_NO_COLORS='false' export THEFUCK_NO_COLORS='false'
export THEFUCK_PRIORITY='no_command=9999:apt_get=100' export THEFUCK_PRIORITY='no_command=9999:apt_get=100'
export THEFUCK_HISTORY_LIMIT='2000' export THEFUCK_HISTORY_LIMIT='2000'
export THEFUCK_NUM_CLOSE_MATCHES='5'
``` ```
## Third-party packages with rules ## Third-party packages with rules

View File

@ -0,0 +1,58 @@
# -*- encoding: utf-8 -*-
from mock import Mock, patch
from psutil import AccessDenied, TimeoutExpired
from thefuck.output_readers import rerun
class TestRerun(object):
def setup_method(self, test_method):
self.patcher = patch('thefuck.output_readers.rerun.Process')
process_mock = self.patcher.start()
self.proc_mock = process_mock.return_value = Mock()
def teardown_method(self, test_method):
self.patcher.stop()
@patch('thefuck.output_readers.rerun._wait_output', return_value=False)
@patch('thefuck.output_readers.rerun.Popen')
def test_get_output(self, popen_mock, wait_output_mock):
popen_mock.return_value.stdout.read.return_value = b'output'
assert rerun.get_output('', '') is None
wait_output_mock.assert_called_once()
def test_wait_output_is_slow(self, settings):
assert rerun._wait_output(Mock(), True)
self.proc_mock.wait.assert_called_once_with(settings.wait_slow_command)
def test_wait_output_is_not_slow(self, settings):
assert rerun._wait_output(Mock(), False)
self.proc_mock.wait.assert_called_once_with(settings.wait_command)
@patch('thefuck.output_readers.rerun._kill_process')
def test_wait_output_timeout(self, kill_process_mock):
self.proc_mock.wait.side_effect = TimeoutExpired(3)
self.proc_mock.children.return_value = []
assert not rerun._wait_output(Mock(), False)
kill_process_mock.assert_called_once_with(self.proc_mock)
@patch('thefuck.output_readers.rerun._kill_process')
def test_wait_output_timeout_children(self, kill_process_mock):
self.proc_mock.wait.side_effect = TimeoutExpired(3)
self.proc_mock.children.return_value = [Mock()] * 2
assert not rerun._wait_output(Mock(), False)
assert kill_process_mock.call_count == 3
def test_kill_process(self):
proc = Mock()
rerun._kill_process(proc)
proc.kill.assert_called_once_with()
@patch('thefuck.output_readers.rerun.logs')
def test_kill_process_access_denied(self, logs_mock):
proc = Mock()
proc.kill.side_effect = AccessDenied()
rerun._kill_process(proc)
proc.kill.assert_called_once_with()
logs_mock.debug.assert_called_once()

View File

@ -53,7 +53,8 @@ class TestSettingsFromEnv(object):
'THEFUCK_NO_COLORS': 'false', 'THEFUCK_NO_COLORS': 'false',
'THEFUCK_PRIORITY': 'bash=10:lisp=wrong:vim=15', 'THEFUCK_PRIORITY': 'bash=10:lisp=wrong:vim=15',
'THEFUCK_WAIT_SLOW_COMMAND': '999', 'THEFUCK_WAIT_SLOW_COMMAND': '999',
'THEFUCK_SLOW_COMMANDS': 'lein:react-native:./gradlew'}) 'THEFUCK_SLOW_COMMANDS': 'lein:react-native:./gradlew',
'THEFUCK_NUM_CLOSE_MATCHES': '359'})
settings.init() settings.init()
assert settings.rules == ['bash', 'lisp'] assert settings.rules == ['bash', 'lisp']
assert settings.exclude_rules == ['git', 'vim'] assert settings.exclude_rules == ['git', 'vim']
@ -63,6 +64,7 @@ class TestSettingsFromEnv(object):
assert settings.priority == {'bash': 10, 'vim': 15} assert settings.priority == {'bash': 10, 'vim': 15}
assert settings.wait_slow_command == 999 assert settings.wait_slow_command == 999
assert settings.slow_commands == ['lein', 'react-native', './gradlew'] assert settings.slow_commands == ['lein', 'react-native', './gradlew']
assert settings.num_close_matches == 359
def test_from_env_with_DEFAULT(self, os_environ, settings): def test_from_env_with_DEFAULT(self, os_environ, settings):
os_environ.update({'THEFUCK_RULES': 'DEFAULT_RULES:bash:lisp'}) os_environ.update({'THEFUCK_RULES': 'DEFAULT_RULES:bash:lisp'})

View File

@ -2,11 +2,11 @@
import pytest import pytest
import warnings import warnings
from mock import Mock from mock import Mock, patch
from thefuck.utils import default_settings, \ from thefuck.utils import default_settings, \
memoize, get_closest, get_all_executables, replace_argument, \ memoize, get_closest, get_all_executables, replace_argument, \
get_all_matched_commands, is_app, for_app, cache, \ get_all_matched_commands, is_app, for_app, cache, \
get_valid_history_without_current, _cache get_valid_history_without_current, _cache, get_close_matches
from thefuck.types import Command from thefuck.types import Command
@ -50,6 +50,18 @@ class TestGetClosest(object):
fallback_to_first=False) is None fallback_to_first=False) is None
class TestGetCloseMatches(object):
@patch('thefuck.utils.difflib_get_close_matches')
def test_call_with_n(self, difflib_mock):
get_close_matches('', [], 1)
assert difflib_mock.call_args[0][2] == 1
@patch('thefuck.utils.difflib_get_close_matches')
def test_call_without_n(self, difflib_mock, settings):
get_close_matches('', [])
assert difflib_mock.call_args[0][2] == settings.get('num_close_matches')
@pytest.fixture @pytest.fixture
def get_aliases(mocker): def get_aliases(mocker):
mocker.patch('thefuck.shells.shell.get_aliases', mocker.patch('thefuck.shells.shell.get_aliases',

View File

@ -95,7 +95,8 @@ class Settings(dict):
return self._rules_from_env(val) return self._rules_from_env(val)
elif attr == 'priority': elif attr == 'priority':
return dict(self._priority_from_env(val)) return dict(self._priority_from_env(val))
elif attr in ('wait_command', 'history_limit', 'wait_slow_command'): elif attr in ('wait_command', 'history_limit', 'wait_slow_command',
'num_close_matches'):
return int(val) return int(val)
elif attr in ('require_confirmation', 'no_colors', 'debug', elif attr in ('require_confirmation', 'no_colors', 'debug',
'alter_history', 'instant_mode'): 'alter_history', 'instant_mode'):

View File

@ -42,6 +42,7 @@ DEFAULT_SETTINGS = {'rules': DEFAULT_RULES,
'./gradlew', 'vagrant'], './gradlew', 'vagrant'],
'repeat': False, 'repeat': False,
'instant_mode': False, 'instant_mode': False,
'num_close_matches': 3,
'env': {'LC_ALL': 'C', 'LANG': 'C', 'GIT_TRACE': '1'}} 'env': {'LC_ALL': 'C', 'LANG': 'C', 'GIT_TRACE': '1'}}
ENV_TO_ATTR = {'THEFUCK_RULES': 'rules', ENV_TO_ATTR = {'THEFUCK_RULES': 'rules',
@ -56,7 +57,8 @@ ENV_TO_ATTR = {'THEFUCK_RULES': 'rules',
'THEFUCK_WAIT_SLOW_COMMAND': 'wait_slow_command', 'THEFUCK_WAIT_SLOW_COMMAND': 'wait_slow_command',
'THEFUCK_SLOW_COMMANDS': 'slow_commands', 'THEFUCK_SLOW_COMMANDS': 'slow_commands',
'THEFUCK_REPEAT': 'repeat', 'THEFUCK_REPEAT': 'repeat',
'THEFUCK_INSTANT_MODE': 'instant_mode'} 'THEFUCK_INSTANT_MODE': 'instant_mode',
'THEFUCK_NUM_CLOSE_MATCHES': 'num_close_matches'}
SETTINGS_HEADER = u"""# The Fuck settings file SETTINGS_HEADER = u"""# The Fuck settings file
# #

View File

@ -2,10 +2,9 @@
import os import os
import six import six
from difflib import get_close_matches
from thefuck.specific.sudo import sudo_support from thefuck.specific.sudo import sudo_support
from thefuck.rules import cd_mkdir from thefuck.rules import cd_mkdir
from thefuck.utils import for_app from thefuck.utils import for_app, get_close_matches
__author__ = "mmussomele" __author__ = "mmussomele"

View File

@ -1,4 +1,4 @@
from difflib import get_close_matches from thefuck.utils import get_close_matches
from thefuck.specific.git import git_support from thefuck.specific.git import git_support

View File

@ -1,5 +1,5 @@
from difflib import get_close_matches from thefuck.utils import get_close_matches, get_closest, \
from thefuck.utils import get_closest, get_valid_history_without_current get_valid_history_without_current
def match(command): def match(command):

View File

@ -1,5 +1,4 @@
from thefuck.utils import replace_command, for_app from thefuck.utils import for_app, get_close_matches, replace_command
from difflib import get_close_matches
import re import re

View File

@ -1,5 +1,4 @@
from difflib import get_close_matches from thefuck.utils import get_all_executables, get_close_matches, \
from thefuck.utils import get_all_executables, \
get_valid_history_without_current, get_closest, which get_valid_history_without_current, get_closest, which
from thefuck.specific.sudo import sudo_support from thefuck.specific.sudo import sudo_support

View File

@ -5,7 +5,7 @@ import re
import shelve import shelve
import six import six
from decorator import decorator from decorator import decorator
from difflib import get_close_matches from difflib import get_close_matches as difflib_get_close_matches
from functools import wraps from functools import wraps
from .logs import warn from .logs import warn
from .conf import settings from .conf import settings
@ -90,12 +90,19 @@ def get_closest(word, possibilities, n=3, cutoff=0.6, fallback_to_first=True):
"""Returns closest match or just first from possibilities.""" """Returns closest match or just first from possibilities."""
possibilities = list(possibilities) possibilities = list(possibilities)
try: try:
return get_close_matches(word, possibilities, n, cutoff)[0] return difflib_get_close_matches(word, possibilities, n, cutoff)[0]
except IndexError: except IndexError:
if fallback_to_first: if fallback_to_first:
return possibilities[0] return possibilities[0]
def get_close_matches(word, possibilities, n=None, cutoff=0.6):
"""Overrides `difflib.get_close_match` to controle argument `n`."""
if n is None:
n = settings.num_close_matches
return difflib_get_close_matches(word, possibilities, n, cutoff)
@memoize @memoize
def get_all_executables(): def get_all_executables():
from thefuck.shells import shell from thefuck.shells import shell