diff --git a/README.md b/README.md index 46a1d24b9..4f396ce95 100644 --- a/README.md +++ b/README.md @@ -434,6 +434,7 @@ Several *The Fuck* parameters can be changed in the file `$XDG_CONFIG_HOME/thefu * `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; * `num_close_matches` – maximum number of close matches to suggest, by default `3`. +* `excluded_search_path_prefixes` – path prefixes to ignore when searching for commands, by default `[]`. An example of `settings.py`: @@ -466,6 +467,7 @@ rule with lower `priority` will be matched first; * `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_NUM_CLOSE_MATCHES` – maximum number of close matches to suggest, like `5`. +* `THEFUCK_EXCLUDED_SEARCH_PATH_PREFIXES` – path prefixes to ignore when searching for commands, by default `[]`. For example: diff --git a/tests/test_conf.py b/tests/test_conf.py index 657e47556..e03473ab5 100644 --- a/tests/test_conf.py +++ b/tests/test_conf.py @@ -54,7 +54,8 @@ def test_from_env(self, os_environ, settings): 'THEFUCK_PRIORITY': 'bash=10:lisp=wrong:vim=15', 'THEFUCK_WAIT_SLOW_COMMAND': '999', 'THEFUCK_SLOW_COMMANDS': 'lein:react-native:./gradlew', - 'THEFUCK_NUM_CLOSE_MATCHES': '359'}) + 'THEFUCK_NUM_CLOSE_MATCHES': '359', + 'THEFUCK_EXCLUDED_SEARCH_PATH_PREFIXES': '/media/:/mnt/'}) settings.init() assert settings.rules == ['bash', 'lisp'] assert settings.exclude_rules == ['git', 'vim'] @@ -65,6 +66,7 @@ def test_from_env(self, os_environ, settings): assert settings.wait_slow_command == 999 assert settings.slow_commands == ['lein', 'react-native', './gradlew'] assert settings.num_close_matches == 359 + assert settings.excluded_search_path_prefixes == ['/media/', '/mnt/'] def test_from_env_with_DEFAULT(self, os_environ, settings): os_environ.update({'THEFUCK_RULES': 'DEFAULT_RULES:bash:lisp'}) diff --git a/tests/test_utils.py b/tests/test_utils.py index 5c3542a74..b32f695ad 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -94,6 +94,20 @@ def test_get_all_executables_pathsep(path, pathsep): Path_mock.assert_has_calls([call(p) for p in path.split(pathsep)], True) +@pytest.mark.usefixtures('no_memoize', 'os_environ_pathsep') +@pytest.mark.parametrize('path, pathsep, excluded', [ + ('/foo:/bar:/baz:/foo/bar:/mnt/foo', ':', '/mnt/foo'), + (r'C:\\foo;C:\\bar;C:\\baz;C:\\foo\\bar;Z:\\foo', ';', r'Z:\\foo')]) +def test_get_all_executables_exclude_paths(path, pathsep, excluded, settings): + settings.init() + settings.excluded_search_path_prefixes = [excluded] + with patch('thefuck.utils.Path') as Path_mock: + get_all_executables() + path_list = path.split(pathsep) + assert call(path_list[-1]) not in Path_mock.mock_calls + assert all(call(p) in Path_mock.mock_calls for p in path_list[:-1]) + + @pytest.mark.parametrize('args, result', [ (('apt-get instol vim', 'instol', 'install'), 'apt-get install vim'), (('git brnch', 'brnch', 'branch'), 'git branch')]) diff --git a/thefuck/conf.py b/thefuck/conf.py index b55196340..27876ef47 100644 --- a/thefuck/conf.py +++ b/thefuck/conf.py @@ -101,7 +101,7 @@ def _val_from_env(self, env, attr): elif attr in ('require_confirmation', 'no_colors', 'debug', 'alter_history', 'instant_mode'): return val.lower() == 'true' - elif attr == 'slow_commands': + elif attr in ('slow_commands', 'excluded_search_path_prefixes'): return val.split(':') else: return val diff --git a/thefuck/const.py b/thefuck/const.py index d272f1b20..8d339264d 100644 --- a/thefuck/const.py +++ b/thefuck/const.py @@ -43,7 +43,8 @@ def __repr__(self): 'repeat': 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'}, + 'excluded_search_path_prefixes': []} ENV_TO_ATTR = {'THEFUCK_RULES': 'rules', 'THEFUCK_EXCLUDE_RULES': 'exclude_rules', @@ -58,7 +59,8 @@ def __repr__(self): 'THEFUCK_SLOW_COMMANDS': 'slow_commands', 'THEFUCK_REPEAT': 'repeat', 'THEFUCK_INSTANT_MODE': 'instant_mode', - 'THEFUCK_NUM_CLOSE_MATCHES': 'num_close_matches'} + 'THEFUCK_NUM_CLOSE_MATCHES': 'num_close_matches', + 'THEFUCK_EXCLUDED_SEARCH_PATH_PREFIXES': 'excluded_search_path_prefixes'} SETTINGS_HEADER = u"""# The Fuck settings file # diff --git a/thefuck/utils.py b/thefuck/utils.py index 8d55f3725..1df113420 100644 --- a/thefuck/utils.py +++ b/thefuck/utils.py @@ -104,6 +104,10 @@ def get_close_matches(word, possibilities, n=None, cutoff=0.6): return difflib_get_close_matches(word, possibilities, n, cutoff) +def include_path_in_search(path): + return not any(path.startswith(x) for x in settings.excluded_search_path_prefixes) + + @memoize def get_all_executables(): from thefuck.shells import shell @@ -119,6 +123,7 @@ def _safe(fn, fallback): bins = [exe.name.decode('utf8') if six.PY2 else exe.name for path in os.environ.get('PATH', '').split(os.pathsep) + if include_path_in_search(path) for exe in _safe(lambda: list(Path(path).iterdir()), []) if not _safe(exe.is_dir, True) and exe.name not in tf_entry_points]