thefuck-leeguoo 3.41__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- thefuck/__init__.py +0 -0
- thefuck/ai.py +765 -0
- thefuck/argument_parser.py +96 -0
- thefuck/conf.py +141 -0
- thefuck/const.py +111 -0
- thefuck/corrector.py +92 -0
- thefuck/entrypoints/__init__.py +0 -0
- thefuck/entrypoints/alias.py +28 -0
- thefuck/entrypoints/fix_command.py +105 -0
- thefuck/entrypoints/main.py +50 -0
- thefuck/entrypoints/not_configured.py +201 -0
- thefuck/entrypoints/setup.py +227 -0
- thefuck/entrypoints/shell_logger.py +79 -0
- thefuck/exceptions.py +10 -0
- thefuck/logs.py +255 -0
- thefuck/output_readers/__init__.py +20 -0
- thefuck/output_readers/read_log.py +108 -0
- thefuck/output_readers/rerun.py +72 -0
- thefuck/output_readers/shell_logger.py +60 -0
- thefuck/rules/__init__.py +0 -0
- thefuck/rules/adb_unknown_command.py +54 -0
- thefuck/rules/ag_literal.py +10 -0
- thefuck/rules/apt_get.py +50 -0
- thefuck/rules/apt_get_search.py +14 -0
- thefuck/rules/apt_invalid_operation.py +63 -0
- thefuck/rules/apt_list_upgradable.py +16 -0
- thefuck/rules/apt_upgrade.py +16 -0
- thefuck/rules/aws_cli.py +17 -0
- thefuck/rules/az_cli.py +17 -0
- thefuck/rules/brew_cask_dependency.py +33 -0
- thefuck/rules/brew_install.py +24 -0
- thefuck/rules/brew_link.py +15 -0
- thefuck/rules/brew_reinstall.py +19 -0
- thefuck/rules/brew_uninstall.py +14 -0
- thefuck/rules/brew_unknown_command.py +82 -0
- thefuck/rules/brew_update_formula.py +12 -0
- thefuck/rules/cargo.py +6 -0
- thefuck/rules/cargo_no_command.py +15 -0
- thefuck/rules/cat_dir.py +14 -0
- thefuck/rules/cd_correction.py +61 -0
- thefuck/rules/cd_cs.py +21 -0
- thefuck/rules/cd_mkdir.py +21 -0
- thefuck/rules/cd_parent.py +16 -0
- thefuck/rules/chmod_x.py +15 -0
- thefuck/rules/choco_install.py +25 -0
- thefuck/rules/composer_not_command.py +22 -0
- thefuck/rules/conda_mistype.py +17 -0
- thefuck/rules/cp_create_destination.py +15 -0
- thefuck/rules/cp_omitting_directory.py +15 -0
- thefuck/rules/cpp11.py +12 -0
- thefuck/rules/dirty_untar.py +53 -0
- thefuck/rules/dirty_unzip.py +60 -0
- thefuck/rules/django_south_ghost.py +8 -0
- thefuck/rules/django_south_merge.py +8 -0
- thefuck/rules/dnf_no_such_command.py +37 -0
- thefuck/rules/docker_image_being_used_by_container.py +20 -0
- thefuck/rules/docker_login.py +13 -0
- thefuck/rules/docker_not_command.py +49 -0
- thefuck/rules/dry.py +15 -0
- thefuck/rules/fab_command_not_found.py +38 -0
- thefuck/rules/fix_alt_space.py +15 -0
- thefuck/rules/fix_file.py +80 -0
- thefuck/rules/gem_unknown_command.py +36 -0
- thefuck/rules/git_add.py +27 -0
- thefuck/rules/git_add_force.py +13 -0
- thefuck/rules/git_bisect_usage.py +16 -0
- thefuck/rules/git_branch_0flag.py +24 -0
- thefuck/rules/git_branch_delete.py +13 -0
- thefuck/rules/git_branch_delete_checked_out.py +19 -0
- thefuck/rules/git_branch_exists.py +25 -0
- thefuck/rules/git_branch_list.py +14 -0
- thefuck/rules/git_checkout.py +49 -0
- thefuck/rules/git_clone_git_clone.py +12 -0
- thefuck/rules/git_clone_missing.py +42 -0
- thefuck/rules/git_commit_add.py +17 -0
- thefuck/rules/git_commit_amend.py +11 -0
- thefuck/rules/git_commit_reset.py +11 -0
- thefuck/rules/git_diff_no_index.py +16 -0
- thefuck/rules/git_diff_staged.py +13 -0
- thefuck/rules/git_fix_stash.py +37 -0
- thefuck/rules/git_flag_after_filename.py +31 -0
- thefuck/rules/git_help_aliased.py +12 -0
- thefuck/rules/git_hook_bypass.py +27 -0
- thefuck/rules/git_lfs_mistype.py +18 -0
- thefuck/rules/git_main_master.py +16 -0
- thefuck/rules/git_merge.py +18 -0
- thefuck/rules/git_merge_unrelated.py +12 -0
- thefuck/rules/git_not_command.py +18 -0
- thefuck/rules/git_pull.py +16 -0
- thefuck/rules/git_pull_clone.py +13 -0
- thefuck/rules/git_pull_uncommitted_changes.py +14 -0
- thefuck/rules/git_push.py +44 -0
- thefuck/rules/git_push_different_branch_names.py +12 -0
- thefuck/rules/git_push_force.py +18 -0
- thefuck/rules/git_push_pull.py +20 -0
- thefuck/rules/git_push_without_commits.py +12 -0
- thefuck/rules/git_rebase_merge_dir.py +17 -0
- thefuck/rules/git_rebase_no_changes.py +13 -0
- thefuck/rules/git_remote_delete.py +13 -0
- thefuck/rules/git_remote_seturl_add.py +12 -0
- thefuck/rules/git_rm_local_modifications.py +19 -0
- thefuck/rules/git_rm_recursive.py +16 -0
- thefuck/rules/git_rm_staged.py +19 -0
- thefuck/rules/git_stash.py +15 -0
- thefuck/rules/git_stash_pop.py +18 -0
- thefuck/rules/git_tag_force.py +13 -0
- thefuck/rules/git_two_dashes.py +14 -0
- thefuck/rules/go_run.py +16 -0
- thefuck/rules/go_unknown_command.py +28 -0
- thefuck/rules/gradle_no_task.py +34 -0
- thefuck/rules/gradle_wrapper.py +13 -0
- thefuck/rules/grep_arguments_order.py +23 -0
- thefuck/rules/grep_recursive.py +10 -0
- thefuck/rules/grunt_task_not_found.py +37 -0
- thefuck/rules/gulp_not_task.py +22 -0
- thefuck/rules/has_exists_script.py +13 -0
- thefuck/rules/heroku_multiple_apps.py +12 -0
- thefuck/rules/heroku_not_command.py +11 -0
- thefuck/rules/history.py +15 -0
- thefuck/rules/hostscli.py +27 -0
- thefuck/rules/ifconfig_device_not_found.py +23 -0
- thefuck/rules/java.py +17 -0
- thefuck/rules/javac.py +18 -0
- thefuck/rules/lein_not_task.py +19 -0
- thefuck/rules/ln_no_hard_link.py +23 -0
- thefuck/rules/ln_s_order.py +26 -0
- thefuck/rules/long_form_help.py +27 -0
- thefuck/rules/ls_all.py +10 -0
- thefuck/rules/ls_lah.py +12 -0
- thefuck/rules/man.py +33 -0
- thefuck/rules/man_no_space.py +10 -0
- thefuck/rules/mercurial.py +27 -0
- thefuck/rules/missing_space_before_subcommand.py +21 -0
- thefuck/rules/mkdir_p.py +13 -0
- thefuck/rules/mvn_no_command.py +11 -0
- thefuck/rules/mvn_unknown_lifecycle_phase.py +30 -0
- thefuck/rules/nixos_cmd_not_found.py +15 -0
- thefuck/rules/no_command.py +41 -0
- thefuck/rules/no_such_file.py +30 -0
- thefuck/rules/npm_missing_script.py +17 -0
- thefuck/rules/npm_run_script.py +17 -0
- thefuck/rules/npm_wrong_command.py +42 -0
- thefuck/rules/omnienv_no_such_command.py +35 -0
- thefuck/rules/open.py +40 -0
- thefuck/rules/pacman.py +17 -0
- thefuck/rules/pacman_invalid_option.py +20 -0
- thefuck/rules/pacman_not_found.py +26 -0
- thefuck/rules/path_from_history.py +53 -0
- thefuck/rules/php_s.py +11 -0
- thefuck/rules/pip_install.py +15 -0
- thefuck/rules/pip_unknown_command.py +19 -0
- thefuck/rules/port_already_in_use.py +40 -0
- thefuck/rules/prove_recursively.py +27 -0
- thefuck/rules/python_command.py +17 -0
- thefuck/rules/python_execute.py +15 -0
- thefuck/rules/python_module_error.py +13 -0
- thefuck/rules/quotation_marks.py +12 -0
- thefuck/rules/rails_migrations_pending.py +14 -0
- thefuck/rules/react_native_command_unrecognized.py +34 -0
- thefuck/rules/remove_shell_prompt_literal.py +23 -0
- thefuck/rules/remove_trailing_cedilla.py +11 -0
- thefuck/rules/rm_dir.py +16 -0
- thefuck/rules/rm_root.py +16 -0
- thefuck/rules/scm_correction.py +32 -0
- thefuck/rules/sed_unterminated_s.py +18 -0
- thefuck/rules/sl_ls.py +14 -0
- thefuck/rules/ssh_known_hosts.py +37 -0
- thefuck/rules/sudo.py +47 -0
- thefuck/rules/sudo_command_from_user_path.py +21 -0
- thefuck/rules/switch_lang.py +117 -0
- thefuck/rules/systemctl.py +22 -0
- thefuck/rules/terraform_init.py +13 -0
- thefuck/rules/terraform_no_command.py +16 -0
- thefuck/rules/test.py.py +10 -0
- thefuck/rules/tmux.py +18 -0
- thefuck/rules/touch.py +14 -0
- thefuck/rules/tsuru_login.py +12 -0
- thefuck/rules/tsuru_not_command.py +15 -0
- thefuck/rules/unknown_command.py +13 -0
- thefuck/rules/unsudo.py +15 -0
- thefuck/rules/vagrant_up.py +21 -0
- thefuck/rules/whois.py +34 -0
- thefuck/rules/workon_doesnt_exists.py +32 -0
- thefuck/rules/wrong_hyphen_before_subcommand.py +20 -0
- thefuck/rules/yarn_alias.py +14 -0
- thefuck/rules/yarn_command_not_found.py +43 -0
- thefuck/rules/yarn_command_replaced.py +13 -0
- thefuck/rules/yarn_help.py +17 -0
- thefuck/rules/yum_invalid_operation.py +39 -0
- thefuck/shells/__init__.py +52 -0
- thefuck/shells/bash.py +94 -0
- thefuck/shells/fish.py +131 -0
- thefuck/shells/generic.py +154 -0
- thefuck/shells/powershell.py +43 -0
- thefuck/shells/tcsh.py +44 -0
- thefuck/shells/zsh.py +98 -0
- thefuck/specific/__init__.py +0 -0
- thefuck/specific/apt.py +3 -0
- thefuck/specific/archlinux.py +48 -0
- thefuck/specific/brew.py +15 -0
- thefuck/specific/dnf.py +3 -0
- thefuck/specific/git.py +32 -0
- thefuck/specific/nix.py +3 -0
- thefuck/specific/npm.py +21 -0
- thefuck/specific/sudo.py +18 -0
- thefuck/specific/yum.py +3 -0
- thefuck/system/__init__.py +7 -0
- thefuck/system/unix.py +57 -0
- thefuck/system/win32.py +43 -0
- thefuck/types.py +261 -0
- thefuck/ui.py +116 -0
- thefuck/utils.py +385 -0
- thefuck_leeguoo-3.41.dist-info/METADATA +681 -0
- thefuck_leeguoo-3.41.dist-info/RECORD +218 -0
- thefuck_leeguoo-3.41.dist-info/WHEEL +6 -0
- thefuck_leeguoo-3.41.dist-info/entry_points.txt +3 -0
- thefuck_leeguoo-3.41.dist-info/licenses/LICENSE.md +22 -0
- thefuck_leeguoo-3.41.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from argparse import ArgumentParser, SUPPRESS
|
|
3
|
+
from .const import ARGUMENT_PLACEHOLDER
|
|
4
|
+
from .utils import get_alias
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Parser(object):
|
|
8
|
+
"""Argument parser that can handle arguments with our special
|
|
9
|
+
placeholder.
|
|
10
|
+
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
def __init__(self):
|
|
14
|
+
self._parser = ArgumentParser(prog='thefuck', add_help=False)
|
|
15
|
+
self._add_arguments()
|
|
16
|
+
|
|
17
|
+
def _add_arguments(self):
|
|
18
|
+
"""Adds arguments to parser."""
|
|
19
|
+
self._parser.add_argument(
|
|
20
|
+
'-v', '--version',
|
|
21
|
+
action='store_true',
|
|
22
|
+
help="show program's version number and exit")
|
|
23
|
+
self._parser.add_argument(
|
|
24
|
+
'-a', '--alias',
|
|
25
|
+
nargs='?',
|
|
26
|
+
const=get_alias(),
|
|
27
|
+
help='[custom-alias-name] prints alias for current shell')
|
|
28
|
+
self._parser.add_argument(
|
|
29
|
+
'-l', '--shell-logger',
|
|
30
|
+
action='store',
|
|
31
|
+
help='log shell output to the file')
|
|
32
|
+
self._parser.add_argument(
|
|
33
|
+
'--setup',
|
|
34
|
+
action='store_true',
|
|
35
|
+
help='interactive setup for AI environment variables')
|
|
36
|
+
self._parser.add_argument(
|
|
37
|
+
'--enable-experimental-instant-mode',
|
|
38
|
+
action='store_true',
|
|
39
|
+
help='enable experimental instant mode, use on your own risk')
|
|
40
|
+
self._parser.add_argument(
|
|
41
|
+
'-h', '--help',
|
|
42
|
+
action='store_true',
|
|
43
|
+
help='show this help message and exit')
|
|
44
|
+
self._add_conflicting_arguments()
|
|
45
|
+
self._parser.add_argument(
|
|
46
|
+
'-d', '--debug',
|
|
47
|
+
action='store_true',
|
|
48
|
+
help='enable debug output')
|
|
49
|
+
self._parser.add_argument(
|
|
50
|
+
'--force-command',
|
|
51
|
+
action='store',
|
|
52
|
+
help=SUPPRESS)
|
|
53
|
+
self._parser.add_argument(
|
|
54
|
+
'command',
|
|
55
|
+
nargs='*',
|
|
56
|
+
help='command that should be fixed')
|
|
57
|
+
|
|
58
|
+
def _add_conflicting_arguments(self):
|
|
59
|
+
"""It's too dangerous to use `-y` and `-r` together."""
|
|
60
|
+
group = self._parser.add_mutually_exclusive_group()
|
|
61
|
+
group.add_argument(
|
|
62
|
+
'-y', '--yes', '--yeah', '--hard',
|
|
63
|
+
action='store_true',
|
|
64
|
+
help='execute fixed command without confirmation')
|
|
65
|
+
group.add_argument(
|
|
66
|
+
'-r', '--repeat',
|
|
67
|
+
action='store_true',
|
|
68
|
+
help='repeat on failure')
|
|
69
|
+
|
|
70
|
+
def _prepare_arguments(self, argv):
|
|
71
|
+
"""Prepares arguments by:
|
|
72
|
+
|
|
73
|
+
- removing placeholder and moving arguments after it to beginning,
|
|
74
|
+
we need this to distinguish arguments from `command` with ours;
|
|
75
|
+
|
|
76
|
+
- adding `--` before `command`, so our parse would ignore arguments
|
|
77
|
+
of `command`.
|
|
78
|
+
|
|
79
|
+
"""
|
|
80
|
+
if ARGUMENT_PLACEHOLDER in argv:
|
|
81
|
+
index = argv.index(ARGUMENT_PLACEHOLDER)
|
|
82
|
+
return argv[index + 1:] + ['--'] + argv[:index]
|
|
83
|
+
elif argv and not argv[0].startswith('-') and argv[0] != '--':
|
|
84
|
+
return ['--'] + argv
|
|
85
|
+
else:
|
|
86
|
+
return argv
|
|
87
|
+
|
|
88
|
+
def parse(self, argv):
|
|
89
|
+
arguments = self._prepare_arguments(argv[1:])
|
|
90
|
+
return self._parser.parse_args(arguments)
|
|
91
|
+
|
|
92
|
+
def print_usage(self):
|
|
93
|
+
self._parser.print_usage(sys.stderr)
|
|
94
|
+
|
|
95
|
+
def print_help(self):
|
|
96
|
+
self._parser.print_help(sys.stderr)
|
thefuck/conf.py
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
from warnings import warn
|
|
4
|
+
from six import text_type
|
|
5
|
+
from . import const
|
|
6
|
+
from .system import Path
|
|
7
|
+
|
|
8
|
+
try:
|
|
9
|
+
import importlib.util
|
|
10
|
+
|
|
11
|
+
def load_source(name, pathname, _file=None):
|
|
12
|
+
module_spec = importlib.util.spec_from_file_location(name, pathname)
|
|
13
|
+
module = importlib.util.module_from_spec(module_spec)
|
|
14
|
+
module_spec.loader.exec_module(module)
|
|
15
|
+
return module
|
|
16
|
+
except ImportError:
|
|
17
|
+
from imp import load_source
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Settings(dict):
|
|
21
|
+
def __getattr__(self, item):
|
|
22
|
+
return self.get(item)
|
|
23
|
+
|
|
24
|
+
def __setattr__(self, key, value):
|
|
25
|
+
self[key] = value
|
|
26
|
+
|
|
27
|
+
def init(self, args=None):
|
|
28
|
+
"""Fills `settings` with values from `settings.py` and env."""
|
|
29
|
+
from .logs import exception
|
|
30
|
+
|
|
31
|
+
self._setup_user_dir()
|
|
32
|
+
self._init_settings_file()
|
|
33
|
+
|
|
34
|
+
try:
|
|
35
|
+
self.update(self._settings_from_file())
|
|
36
|
+
except Exception:
|
|
37
|
+
exception("Can't load settings from file", sys.exc_info())
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
self.update(self._settings_from_env())
|
|
41
|
+
except Exception:
|
|
42
|
+
exception("Can't load settings from env", sys.exc_info())
|
|
43
|
+
|
|
44
|
+
self.update(self._settings_from_args(args))
|
|
45
|
+
|
|
46
|
+
def _init_settings_file(self):
|
|
47
|
+
settings_path = self.user_dir.joinpath('settings.py')
|
|
48
|
+
if not settings_path.is_file():
|
|
49
|
+
with settings_path.open(mode='w') as settings_file:
|
|
50
|
+
settings_file.write(const.SETTINGS_HEADER)
|
|
51
|
+
for setting in const.DEFAULT_SETTINGS.items():
|
|
52
|
+
settings_file.write(u'# {} = {}\n'.format(*setting))
|
|
53
|
+
|
|
54
|
+
def _get_user_dir_path(self):
|
|
55
|
+
"""Returns Path object representing the user config resource"""
|
|
56
|
+
xdg_config_home = os.environ.get('XDG_CONFIG_HOME', '~/.config')
|
|
57
|
+
user_dir = Path(xdg_config_home, 'thefuck').expanduser()
|
|
58
|
+
legacy_user_dir = Path('~', '.thefuck').expanduser()
|
|
59
|
+
|
|
60
|
+
# For backward compatibility use legacy '~/.thefuck' if it exists:
|
|
61
|
+
if legacy_user_dir.is_dir():
|
|
62
|
+
warn(u'Config path {} is deprecated. Please move to {}'.format(
|
|
63
|
+
legacy_user_dir, user_dir))
|
|
64
|
+
return legacy_user_dir
|
|
65
|
+
else:
|
|
66
|
+
return user_dir
|
|
67
|
+
|
|
68
|
+
def _setup_user_dir(self):
|
|
69
|
+
"""Returns user config dir, create it when it doesn't exist."""
|
|
70
|
+
user_dir = self._get_user_dir_path()
|
|
71
|
+
|
|
72
|
+
rules_dir = user_dir.joinpath('rules')
|
|
73
|
+
if not rules_dir.is_dir():
|
|
74
|
+
rules_dir.mkdir(parents=True)
|
|
75
|
+
self.user_dir = user_dir
|
|
76
|
+
|
|
77
|
+
def _settings_from_file(self):
|
|
78
|
+
"""Loads settings from file."""
|
|
79
|
+
settings = load_source(
|
|
80
|
+
'settings', text_type(self.user_dir.joinpath('settings.py')))
|
|
81
|
+
return {key: getattr(settings, key)
|
|
82
|
+
for key in const.DEFAULT_SETTINGS.keys()
|
|
83
|
+
if hasattr(settings, key)}
|
|
84
|
+
|
|
85
|
+
def _rules_from_env(self, val):
|
|
86
|
+
"""Transforms rules list from env-string to python."""
|
|
87
|
+
val = val.split(':')
|
|
88
|
+
if 'DEFAULT_RULES' in val:
|
|
89
|
+
val = const.DEFAULT_RULES + [rule for rule in val if rule != 'DEFAULT_RULES']
|
|
90
|
+
return val
|
|
91
|
+
|
|
92
|
+
def _priority_from_env(self, val):
|
|
93
|
+
"""Gets priority pairs from env."""
|
|
94
|
+
for part in val.split(':'):
|
|
95
|
+
try:
|
|
96
|
+
rule, priority = part.split('=')
|
|
97
|
+
yield rule, int(priority)
|
|
98
|
+
except ValueError:
|
|
99
|
+
continue
|
|
100
|
+
|
|
101
|
+
def _val_from_env(self, env, attr):
|
|
102
|
+
"""Transforms env-strings to python."""
|
|
103
|
+
val = os.environ[env]
|
|
104
|
+
if attr in ('rules', 'exclude_rules'):
|
|
105
|
+
return self._rules_from_env(val)
|
|
106
|
+
elif attr == 'priority':
|
|
107
|
+
return dict(self._priority_from_env(val))
|
|
108
|
+
elif attr in ('wait_command', 'history_limit', 'wait_slow_command',
|
|
109
|
+
'num_close_matches', 'ai_timeout'):
|
|
110
|
+
return int(val)
|
|
111
|
+
elif attr in ('require_confirmation', 'no_colors', 'debug',
|
|
112
|
+
'alter_history', 'instant_mode', 'ai_enabled',
|
|
113
|
+
'ai_stream', 'ai_stream_output'):
|
|
114
|
+
return val.lower() == 'true'
|
|
115
|
+
elif attr in ('slow_commands', 'excluded_search_path_prefixes'):
|
|
116
|
+
return val.split(':')
|
|
117
|
+
else:
|
|
118
|
+
return val
|
|
119
|
+
|
|
120
|
+
def _settings_from_env(self):
|
|
121
|
+
"""Loads settings from env."""
|
|
122
|
+
return {attr: self._val_from_env(env, attr)
|
|
123
|
+
for env, attr in const.ENV_TO_ATTR.items()
|
|
124
|
+
if env in os.environ}
|
|
125
|
+
|
|
126
|
+
def _settings_from_args(self, args):
|
|
127
|
+
"""Loads settings from args."""
|
|
128
|
+
if not args:
|
|
129
|
+
return {}
|
|
130
|
+
|
|
131
|
+
from_args = {}
|
|
132
|
+
if args.yes:
|
|
133
|
+
from_args['require_confirmation'] = not args.yes
|
|
134
|
+
if args.debug:
|
|
135
|
+
from_args['debug'] = args.debug
|
|
136
|
+
if args.repeat:
|
|
137
|
+
from_args['repeat'] = args.repeat
|
|
138
|
+
return from_args
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
settings = Settings(const.DEFAULT_SETTINGS)
|
thefuck/const.py
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class _GenConst(object):
|
|
5
|
+
def __init__(self, name):
|
|
6
|
+
self._name = name
|
|
7
|
+
|
|
8
|
+
def __repr__(self):
|
|
9
|
+
return u'<const: {}>'.format(self._name)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
KEY_UP = _GenConst('↑')
|
|
13
|
+
KEY_DOWN = _GenConst('↓')
|
|
14
|
+
KEY_CTRL_C = _GenConst('Ctrl+C')
|
|
15
|
+
KEY_CTRL_N = _GenConst('Ctrl+N')
|
|
16
|
+
KEY_CTRL_P = _GenConst('Ctrl+P')
|
|
17
|
+
KEY_TAB = _GenConst('Tab')
|
|
18
|
+
|
|
19
|
+
KEY_MAPPING = {'\x0e': KEY_CTRL_N,
|
|
20
|
+
'\x03': KEY_CTRL_C,
|
|
21
|
+
'\x10': KEY_CTRL_P,
|
|
22
|
+
'\t': KEY_TAB}
|
|
23
|
+
|
|
24
|
+
ACTION_SELECT = _GenConst('select')
|
|
25
|
+
ACTION_ABORT = _GenConst('abort')
|
|
26
|
+
ACTION_PREVIOUS = _GenConst('previous')
|
|
27
|
+
ACTION_NEXT = _GenConst('next')
|
|
28
|
+
|
|
29
|
+
ALL_ENABLED = _GenConst('All rules enabled')
|
|
30
|
+
DEFAULT_RULES = [ALL_ENABLED]
|
|
31
|
+
DEFAULT_PRIORITY = 1000
|
|
32
|
+
|
|
33
|
+
DEFAULT_SETTINGS = {'rules': DEFAULT_RULES,
|
|
34
|
+
'exclude_rules': [],
|
|
35
|
+
'wait_command': 3,
|
|
36
|
+
'require_confirmation': True,
|
|
37
|
+
'no_colors': False,
|
|
38
|
+
'debug': False,
|
|
39
|
+
'priority': {},
|
|
40
|
+
'history_limit': None,
|
|
41
|
+
'alter_history': True,
|
|
42
|
+
'wait_slow_command': 15,
|
|
43
|
+
'slow_commands': ['lein', 'react-native', 'gradle',
|
|
44
|
+
'./gradlew', 'vagrant'],
|
|
45
|
+
'repeat': False,
|
|
46
|
+
'instant_mode': False,
|
|
47
|
+
'num_close_matches': 3,
|
|
48
|
+
'env': {'LC_ALL': 'C', 'LANG': 'C', 'GIT_TRACE': '1'},
|
|
49
|
+
'excluded_search_path_prefixes': [],
|
|
50
|
+
'ai_enabled': True,
|
|
51
|
+
'ai_url': 'http://127.0.0.1:8000/v1/chat/completions',
|
|
52
|
+
'ai_token': 'devtoken',
|
|
53
|
+
'ai_model': 'gpt-5.2',
|
|
54
|
+
'ai_timeout': 5,
|
|
55
|
+
'ai_reasoning_effort': 'low',
|
|
56
|
+
'ai_stream': True,
|
|
57
|
+
'ai_mode': 'prefer',
|
|
58
|
+
'ai_stream_output': True}
|
|
59
|
+
|
|
60
|
+
ENV_TO_ATTR = {'THEFUCK_RULES': 'rules',
|
|
61
|
+
'THEFUCK_EXCLUDE_RULES': 'exclude_rules',
|
|
62
|
+
'THEFUCK_WAIT_COMMAND': 'wait_command',
|
|
63
|
+
'THEFUCK_REQUIRE_CONFIRMATION': 'require_confirmation',
|
|
64
|
+
'THEFUCK_NO_COLORS': 'no_colors',
|
|
65
|
+
'THEFUCK_DEBUG': 'debug',
|
|
66
|
+
'THEFUCK_PRIORITY': 'priority',
|
|
67
|
+
'THEFUCK_HISTORY_LIMIT': 'history_limit',
|
|
68
|
+
'THEFUCK_ALTER_HISTORY': 'alter_history',
|
|
69
|
+
'THEFUCK_WAIT_SLOW_COMMAND': 'wait_slow_command',
|
|
70
|
+
'THEFUCK_SLOW_COMMANDS': 'slow_commands',
|
|
71
|
+
'THEFUCK_REPEAT': 'repeat',
|
|
72
|
+
'THEFUCK_INSTANT_MODE': 'instant_mode',
|
|
73
|
+
'THEFUCK_NUM_CLOSE_MATCHES': 'num_close_matches',
|
|
74
|
+
'THEFUCK_EXCLUDED_SEARCH_PATH_PREFIXES': 'excluded_search_path_prefixes',
|
|
75
|
+
'THEFUCK_AI_ENABLED': 'ai_enabled',
|
|
76
|
+
'THEFUCK_AI_URL': 'ai_url',
|
|
77
|
+
'THEFUCK_AI_TOKEN': 'ai_token',
|
|
78
|
+
'THEFUCK_AI_MODEL': 'ai_model',
|
|
79
|
+
'THEFUCK_AI_TIMEOUT': 'ai_timeout',
|
|
80
|
+
'THEFUCK_AI_REASONING_EFFORT': 'ai_reasoning_effort',
|
|
81
|
+
'THEFUCK_AI_STREAM': 'ai_stream',
|
|
82
|
+
'THEFUCK_AI_MODE': 'ai_mode',
|
|
83
|
+
'THEFUCK_AI_STREAM_OUTPUT': 'ai_stream_output'}
|
|
84
|
+
|
|
85
|
+
SETTINGS_HEADER = u"""# The Fuck settings file
|
|
86
|
+
#
|
|
87
|
+
# The rules are defined as in the example bellow:
|
|
88
|
+
#
|
|
89
|
+
# rules = ['cd_parent', 'git_push', 'python_command', 'sudo']
|
|
90
|
+
#
|
|
91
|
+
# The default values are as follows. Uncomment and change to fit your needs.
|
|
92
|
+
# See https://github.com/nvbn/thefuck#settings for more information.
|
|
93
|
+
#
|
|
94
|
+
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
ARGUMENT_PLACEHOLDER = 'THEFUCK_ARGUMENT_PLACEHOLDER'
|
|
98
|
+
|
|
99
|
+
CONFIGURATION_TIMEOUT = 60
|
|
100
|
+
|
|
101
|
+
USER_COMMAND_MARK = u'\u200B' * 10
|
|
102
|
+
|
|
103
|
+
LOG_SIZE_IN_BYTES = 1024 * 1024
|
|
104
|
+
|
|
105
|
+
LOG_SIZE_TO_CLEAN = 10 * 1024
|
|
106
|
+
|
|
107
|
+
DIFF_WITH_ALIAS = 0.5
|
|
108
|
+
|
|
109
|
+
SHELL_LOGGER_SOCKET_ENV = 'SHELL_LOGGER_SOCKET'
|
|
110
|
+
|
|
111
|
+
SHELL_LOGGER_LIMIT = 5
|
thefuck/corrector.py
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from .conf import settings
|
|
3
|
+
from .types import Rule
|
|
4
|
+
from .system import Path
|
|
5
|
+
from . import logs
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def get_loaded_rules(rules_paths):
|
|
9
|
+
"""Yields all available rules.
|
|
10
|
+
|
|
11
|
+
:type rules_paths: [Path]
|
|
12
|
+
:rtype: Iterable[Rule]
|
|
13
|
+
|
|
14
|
+
"""
|
|
15
|
+
for path in rules_paths:
|
|
16
|
+
if path.name != '__init__.py':
|
|
17
|
+
rule = Rule.from_path(path)
|
|
18
|
+
if rule and rule.is_enabled:
|
|
19
|
+
yield rule
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def get_rules_import_paths():
|
|
23
|
+
"""Yields all rules import paths.
|
|
24
|
+
|
|
25
|
+
:rtype: Iterable[Path]
|
|
26
|
+
|
|
27
|
+
"""
|
|
28
|
+
# Bundled rules:
|
|
29
|
+
yield Path(__file__).parent.joinpath('rules')
|
|
30
|
+
# Rules defined by user:
|
|
31
|
+
yield settings.user_dir.joinpath('rules')
|
|
32
|
+
# Packages with third-party rules:
|
|
33
|
+
for path in sys.path:
|
|
34
|
+
for contrib_module in Path(path).glob('thefuck_contrib_*'):
|
|
35
|
+
contrib_rules = contrib_module.joinpath('rules')
|
|
36
|
+
if contrib_rules.is_dir():
|
|
37
|
+
yield contrib_rules
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def get_rules():
|
|
41
|
+
"""Returns all enabled rules.
|
|
42
|
+
|
|
43
|
+
:rtype: [Rule]
|
|
44
|
+
|
|
45
|
+
"""
|
|
46
|
+
paths = [rule_path for path in get_rules_import_paths()
|
|
47
|
+
for rule_path in sorted(path.glob('*.py'))]
|
|
48
|
+
return sorted(get_loaded_rules(paths),
|
|
49
|
+
key=lambda rule: rule.priority)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def organize_commands(corrected_commands):
|
|
53
|
+
"""Yields sorted commands without duplicates.
|
|
54
|
+
|
|
55
|
+
:type corrected_commands: Iterable[thefuck.types.CorrectedCommand]
|
|
56
|
+
:rtype: Iterable[thefuck.types.CorrectedCommand]
|
|
57
|
+
|
|
58
|
+
"""
|
|
59
|
+
try:
|
|
60
|
+
first_command = next(corrected_commands)
|
|
61
|
+
yield first_command
|
|
62
|
+
except StopIteration:
|
|
63
|
+
return
|
|
64
|
+
|
|
65
|
+
without_duplicates = {
|
|
66
|
+
command for command in sorted(
|
|
67
|
+
corrected_commands, key=lambda command: command.priority)
|
|
68
|
+
if command != first_command}
|
|
69
|
+
|
|
70
|
+
sorted_commands = sorted(
|
|
71
|
+
without_duplicates,
|
|
72
|
+
key=lambda corrected_command: corrected_command.priority)
|
|
73
|
+
|
|
74
|
+
logs.debug(u'Corrected commands: {}'.format(
|
|
75
|
+
', '.join(u'{}'.format(cmd) for cmd in [first_command] + sorted_commands)))
|
|
76
|
+
|
|
77
|
+
for command in sorted_commands:
|
|
78
|
+
yield command
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def get_corrected_commands(command):
|
|
82
|
+
"""Returns generator with sorted and unique corrected commands.
|
|
83
|
+
|
|
84
|
+
:type command: thefuck.types.Command
|
|
85
|
+
:rtype: Iterable[thefuck.types.CorrectedCommand]
|
|
86
|
+
|
|
87
|
+
"""
|
|
88
|
+
corrected_commands = (
|
|
89
|
+
corrected for rule in get_rules()
|
|
90
|
+
if rule.is_match(command)
|
|
91
|
+
for corrected in rule.get_corrected_commands(command))
|
|
92
|
+
return organize_commands(corrected_commands)
|
|
File without changes
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import six
|
|
2
|
+
from ..conf import settings
|
|
3
|
+
from ..logs import warn
|
|
4
|
+
from ..shells import shell
|
|
5
|
+
from ..utils import which
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _get_alias(known_args):
|
|
9
|
+
if six.PY2:
|
|
10
|
+
warn("The Fuck will drop Python 2 support soon, more details "
|
|
11
|
+
"https://github.com/nvbn/thefuck/issues/685")
|
|
12
|
+
|
|
13
|
+
alias = shell.app_alias(known_args.alias)
|
|
14
|
+
|
|
15
|
+
if known_args.enable_experimental_instant_mode:
|
|
16
|
+
if six.PY2:
|
|
17
|
+
warn("Instant mode requires Python 3")
|
|
18
|
+
elif not which('script'):
|
|
19
|
+
warn("Instant mode requires `script` app")
|
|
20
|
+
else:
|
|
21
|
+
return shell.instant_mode_alias(known_args.alias)
|
|
22
|
+
|
|
23
|
+
return alias
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def print_alias(known_args):
|
|
27
|
+
settings.init(known_args)
|
|
28
|
+
print(_get_alias(known_args))
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
from pprint import pformat
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
from difflib import SequenceMatcher
|
|
5
|
+
from itertools import chain
|
|
6
|
+
from .. import logs, types, const
|
|
7
|
+
from ..conf import settings
|
|
8
|
+
from ..corrector import get_corrected_commands
|
|
9
|
+
from ..exceptions import EmptyCommand
|
|
10
|
+
from ..ui import select_command
|
|
11
|
+
from ..utils import format_raw_script, get_alias, get_all_executables
|
|
12
|
+
from ..ai import (build_corrected_commands, emit_ai_commands, emit_ai_result,
|
|
13
|
+
fallback_corrected_commands, get_ai_suggestion, is_enabled)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _get_raw_command(known_args):
|
|
17
|
+
if known_args.force_command:
|
|
18
|
+
return [known_args.force_command]
|
|
19
|
+
tf_command = os.environ.get('TF_COMMAND')
|
|
20
|
+
if tf_command:
|
|
21
|
+
return [tf_command]
|
|
22
|
+
elif not os.environ.get('TF_HISTORY'):
|
|
23
|
+
return known_args.command
|
|
24
|
+
else:
|
|
25
|
+
history = os.environ['TF_HISTORY'].split('\n')[::-1]
|
|
26
|
+
alias = get_alias()
|
|
27
|
+
executables = get_all_executables()
|
|
28
|
+
for command in history:
|
|
29
|
+
diff = SequenceMatcher(a=alias, b=command).ratio()
|
|
30
|
+
if diff < const.DIFF_WITH_ALIAS or command in executables:
|
|
31
|
+
return [command]
|
|
32
|
+
return []
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _get_ai_prompt(known_args):
|
|
36
|
+
env_prompt = os.environ.get('TF_PROMPT', '').strip()
|
|
37
|
+
if env_prompt:
|
|
38
|
+
return env_prompt
|
|
39
|
+
if not os.environ.get('TF_HISTORY'):
|
|
40
|
+
return
|
|
41
|
+
prompt = format_raw_script(known_args.command)
|
|
42
|
+
return prompt or None
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _emit_ai_if_needed(ai_result):
|
|
46
|
+
if not ai_result:
|
|
47
|
+
return ai_result
|
|
48
|
+
if ai_result.streamed:
|
|
49
|
+
if ai_result.commands:
|
|
50
|
+
emit_ai_commands(ai_result)
|
|
51
|
+
return ai_result
|
|
52
|
+
if ai_result.explanation or ai_result.commands:
|
|
53
|
+
emit_ai_result(ai_result)
|
|
54
|
+
return ai_result._replace(streamed=True)
|
|
55
|
+
return ai_result
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def fix_command(known_args):
|
|
59
|
+
"""Fixes previous command. Used when `thefuck` called without arguments."""
|
|
60
|
+
settings.init(known_args)
|
|
61
|
+
with logs.debug_time('Total'):
|
|
62
|
+
logs.debug(u'Run with settings: {}'.format(pformat(settings)))
|
|
63
|
+
raw_command = _get_raw_command(known_args)
|
|
64
|
+
|
|
65
|
+
try:
|
|
66
|
+
command = types.Command.from_raw_script(raw_command)
|
|
67
|
+
except EmptyCommand:
|
|
68
|
+
logs.debug('Empty command, nothing to do')
|
|
69
|
+
return
|
|
70
|
+
|
|
71
|
+
corrected_commands = get_corrected_commands(command)
|
|
72
|
+
ai_prompt = _get_ai_prompt(known_args)
|
|
73
|
+
if is_enabled():
|
|
74
|
+
if ai_prompt:
|
|
75
|
+
ai_result = get_ai_suggestion(
|
|
76
|
+
command, prompt=ai_prompt, warn_on_error=True)
|
|
77
|
+
if ai_result:
|
|
78
|
+
ai_result = _emit_ai_if_needed(ai_result)
|
|
79
|
+
if ai_result.commands:
|
|
80
|
+
corrected_commands = iter(
|
|
81
|
+
build_corrected_commands(ai_result))
|
|
82
|
+
elif ai_result.explanation:
|
|
83
|
+
sys.exit(1)
|
|
84
|
+
else:
|
|
85
|
+
sys.exit(1)
|
|
86
|
+
elif settings.ai_mode == 'prefer':
|
|
87
|
+
ai_result = get_ai_suggestion(command)
|
|
88
|
+
if ai_result:
|
|
89
|
+
ai_result = _emit_ai_if_needed(ai_result)
|
|
90
|
+
if ai_result.commands:
|
|
91
|
+
ai_commands = build_corrected_commands(ai_result)
|
|
92
|
+
corrected_commands = chain(ai_commands,
|
|
93
|
+
corrected_commands)
|
|
94
|
+
else:
|
|
95
|
+
corrected_commands, ai_result = fallback_corrected_commands(
|
|
96
|
+
command, corrected_commands)
|
|
97
|
+
ai_result = _emit_ai_if_needed(ai_result)
|
|
98
|
+
if ai_result and not ai_result.commands and ai_result.explanation:
|
|
99
|
+
sys.exit(1)
|
|
100
|
+
selected_command = select_command(corrected_commands)
|
|
101
|
+
|
|
102
|
+
if selected_command:
|
|
103
|
+
selected_command.run(command)
|
|
104
|
+
else:
|
|
105
|
+
sys.exit(1)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Initialize output before importing any module, that can use colorama.
|
|
2
|
+
from ..system import init_output
|
|
3
|
+
|
|
4
|
+
init_output()
|
|
5
|
+
|
|
6
|
+
import os # noqa: E402
|
|
7
|
+
import sys # noqa: E402
|
|
8
|
+
from .. import logs # noqa: E402
|
|
9
|
+
from ..argument_parser import Parser # noqa: E402
|
|
10
|
+
from ..utils import get_installation_version # noqa: E402
|
|
11
|
+
from ..shells import shell # noqa: E402
|
|
12
|
+
from .alias import print_alias # noqa: E402
|
|
13
|
+
from .fix_command import fix_command # noqa: E402
|
|
14
|
+
from .setup import setup # noqa: E402
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _is_setup_command(known_args):
|
|
18
|
+
if getattr(known_args, 'setup', False):
|
|
19
|
+
return True
|
|
20
|
+
return bool(known_args.command and known_args.command[0] in (
|
|
21
|
+
'setup', 'ai-setup'))
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def main():
|
|
25
|
+
parser = Parser()
|
|
26
|
+
known_args = parser.parse(sys.argv)
|
|
27
|
+
|
|
28
|
+
if known_args.help:
|
|
29
|
+
parser.print_help()
|
|
30
|
+
elif known_args.version:
|
|
31
|
+
logs.version(get_installation_version(),
|
|
32
|
+
sys.version.split()[0], shell.info())
|
|
33
|
+
# It's important to check if an alias is being requested before checking if
|
|
34
|
+
# `TF_HISTORY` is in `os.environ`, otherwise it might mess with subshells.
|
|
35
|
+
# Check https://github.com/nvbn/thefuck/issues/921 for reference
|
|
36
|
+
elif known_args.alias:
|
|
37
|
+
print_alias(known_args)
|
|
38
|
+
elif _is_setup_command(known_args):
|
|
39
|
+
setup()
|
|
40
|
+
elif known_args.command or 'TF_HISTORY' in os.environ:
|
|
41
|
+
fix_command(known_args)
|
|
42
|
+
elif known_args.shell_logger:
|
|
43
|
+
try:
|
|
44
|
+
from .shell_logger import shell_logger # noqa: E402
|
|
45
|
+
except ImportError:
|
|
46
|
+
logs.warn('Shell logger supports only Linux and macOS')
|
|
47
|
+
else:
|
|
48
|
+
shell_logger(known_args.shell_logger)
|
|
49
|
+
else:
|
|
50
|
+
parser.print_usage()
|