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.
Files changed (218) hide show
  1. thefuck/__init__.py +0 -0
  2. thefuck/ai.py +765 -0
  3. thefuck/argument_parser.py +96 -0
  4. thefuck/conf.py +141 -0
  5. thefuck/const.py +111 -0
  6. thefuck/corrector.py +92 -0
  7. thefuck/entrypoints/__init__.py +0 -0
  8. thefuck/entrypoints/alias.py +28 -0
  9. thefuck/entrypoints/fix_command.py +105 -0
  10. thefuck/entrypoints/main.py +50 -0
  11. thefuck/entrypoints/not_configured.py +201 -0
  12. thefuck/entrypoints/setup.py +227 -0
  13. thefuck/entrypoints/shell_logger.py +79 -0
  14. thefuck/exceptions.py +10 -0
  15. thefuck/logs.py +255 -0
  16. thefuck/output_readers/__init__.py +20 -0
  17. thefuck/output_readers/read_log.py +108 -0
  18. thefuck/output_readers/rerun.py +72 -0
  19. thefuck/output_readers/shell_logger.py +60 -0
  20. thefuck/rules/__init__.py +0 -0
  21. thefuck/rules/adb_unknown_command.py +54 -0
  22. thefuck/rules/ag_literal.py +10 -0
  23. thefuck/rules/apt_get.py +50 -0
  24. thefuck/rules/apt_get_search.py +14 -0
  25. thefuck/rules/apt_invalid_operation.py +63 -0
  26. thefuck/rules/apt_list_upgradable.py +16 -0
  27. thefuck/rules/apt_upgrade.py +16 -0
  28. thefuck/rules/aws_cli.py +17 -0
  29. thefuck/rules/az_cli.py +17 -0
  30. thefuck/rules/brew_cask_dependency.py +33 -0
  31. thefuck/rules/brew_install.py +24 -0
  32. thefuck/rules/brew_link.py +15 -0
  33. thefuck/rules/brew_reinstall.py +19 -0
  34. thefuck/rules/brew_uninstall.py +14 -0
  35. thefuck/rules/brew_unknown_command.py +82 -0
  36. thefuck/rules/brew_update_formula.py +12 -0
  37. thefuck/rules/cargo.py +6 -0
  38. thefuck/rules/cargo_no_command.py +15 -0
  39. thefuck/rules/cat_dir.py +14 -0
  40. thefuck/rules/cd_correction.py +61 -0
  41. thefuck/rules/cd_cs.py +21 -0
  42. thefuck/rules/cd_mkdir.py +21 -0
  43. thefuck/rules/cd_parent.py +16 -0
  44. thefuck/rules/chmod_x.py +15 -0
  45. thefuck/rules/choco_install.py +25 -0
  46. thefuck/rules/composer_not_command.py +22 -0
  47. thefuck/rules/conda_mistype.py +17 -0
  48. thefuck/rules/cp_create_destination.py +15 -0
  49. thefuck/rules/cp_omitting_directory.py +15 -0
  50. thefuck/rules/cpp11.py +12 -0
  51. thefuck/rules/dirty_untar.py +53 -0
  52. thefuck/rules/dirty_unzip.py +60 -0
  53. thefuck/rules/django_south_ghost.py +8 -0
  54. thefuck/rules/django_south_merge.py +8 -0
  55. thefuck/rules/dnf_no_such_command.py +37 -0
  56. thefuck/rules/docker_image_being_used_by_container.py +20 -0
  57. thefuck/rules/docker_login.py +13 -0
  58. thefuck/rules/docker_not_command.py +49 -0
  59. thefuck/rules/dry.py +15 -0
  60. thefuck/rules/fab_command_not_found.py +38 -0
  61. thefuck/rules/fix_alt_space.py +15 -0
  62. thefuck/rules/fix_file.py +80 -0
  63. thefuck/rules/gem_unknown_command.py +36 -0
  64. thefuck/rules/git_add.py +27 -0
  65. thefuck/rules/git_add_force.py +13 -0
  66. thefuck/rules/git_bisect_usage.py +16 -0
  67. thefuck/rules/git_branch_0flag.py +24 -0
  68. thefuck/rules/git_branch_delete.py +13 -0
  69. thefuck/rules/git_branch_delete_checked_out.py +19 -0
  70. thefuck/rules/git_branch_exists.py +25 -0
  71. thefuck/rules/git_branch_list.py +14 -0
  72. thefuck/rules/git_checkout.py +49 -0
  73. thefuck/rules/git_clone_git_clone.py +12 -0
  74. thefuck/rules/git_clone_missing.py +42 -0
  75. thefuck/rules/git_commit_add.py +17 -0
  76. thefuck/rules/git_commit_amend.py +11 -0
  77. thefuck/rules/git_commit_reset.py +11 -0
  78. thefuck/rules/git_diff_no_index.py +16 -0
  79. thefuck/rules/git_diff_staged.py +13 -0
  80. thefuck/rules/git_fix_stash.py +37 -0
  81. thefuck/rules/git_flag_after_filename.py +31 -0
  82. thefuck/rules/git_help_aliased.py +12 -0
  83. thefuck/rules/git_hook_bypass.py +27 -0
  84. thefuck/rules/git_lfs_mistype.py +18 -0
  85. thefuck/rules/git_main_master.py +16 -0
  86. thefuck/rules/git_merge.py +18 -0
  87. thefuck/rules/git_merge_unrelated.py +12 -0
  88. thefuck/rules/git_not_command.py +18 -0
  89. thefuck/rules/git_pull.py +16 -0
  90. thefuck/rules/git_pull_clone.py +13 -0
  91. thefuck/rules/git_pull_uncommitted_changes.py +14 -0
  92. thefuck/rules/git_push.py +44 -0
  93. thefuck/rules/git_push_different_branch_names.py +12 -0
  94. thefuck/rules/git_push_force.py +18 -0
  95. thefuck/rules/git_push_pull.py +20 -0
  96. thefuck/rules/git_push_without_commits.py +12 -0
  97. thefuck/rules/git_rebase_merge_dir.py +17 -0
  98. thefuck/rules/git_rebase_no_changes.py +13 -0
  99. thefuck/rules/git_remote_delete.py +13 -0
  100. thefuck/rules/git_remote_seturl_add.py +12 -0
  101. thefuck/rules/git_rm_local_modifications.py +19 -0
  102. thefuck/rules/git_rm_recursive.py +16 -0
  103. thefuck/rules/git_rm_staged.py +19 -0
  104. thefuck/rules/git_stash.py +15 -0
  105. thefuck/rules/git_stash_pop.py +18 -0
  106. thefuck/rules/git_tag_force.py +13 -0
  107. thefuck/rules/git_two_dashes.py +14 -0
  108. thefuck/rules/go_run.py +16 -0
  109. thefuck/rules/go_unknown_command.py +28 -0
  110. thefuck/rules/gradle_no_task.py +34 -0
  111. thefuck/rules/gradle_wrapper.py +13 -0
  112. thefuck/rules/grep_arguments_order.py +23 -0
  113. thefuck/rules/grep_recursive.py +10 -0
  114. thefuck/rules/grunt_task_not_found.py +37 -0
  115. thefuck/rules/gulp_not_task.py +22 -0
  116. thefuck/rules/has_exists_script.py +13 -0
  117. thefuck/rules/heroku_multiple_apps.py +12 -0
  118. thefuck/rules/heroku_not_command.py +11 -0
  119. thefuck/rules/history.py +15 -0
  120. thefuck/rules/hostscli.py +27 -0
  121. thefuck/rules/ifconfig_device_not_found.py +23 -0
  122. thefuck/rules/java.py +17 -0
  123. thefuck/rules/javac.py +18 -0
  124. thefuck/rules/lein_not_task.py +19 -0
  125. thefuck/rules/ln_no_hard_link.py +23 -0
  126. thefuck/rules/ln_s_order.py +26 -0
  127. thefuck/rules/long_form_help.py +27 -0
  128. thefuck/rules/ls_all.py +10 -0
  129. thefuck/rules/ls_lah.py +12 -0
  130. thefuck/rules/man.py +33 -0
  131. thefuck/rules/man_no_space.py +10 -0
  132. thefuck/rules/mercurial.py +27 -0
  133. thefuck/rules/missing_space_before_subcommand.py +21 -0
  134. thefuck/rules/mkdir_p.py +13 -0
  135. thefuck/rules/mvn_no_command.py +11 -0
  136. thefuck/rules/mvn_unknown_lifecycle_phase.py +30 -0
  137. thefuck/rules/nixos_cmd_not_found.py +15 -0
  138. thefuck/rules/no_command.py +41 -0
  139. thefuck/rules/no_such_file.py +30 -0
  140. thefuck/rules/npm_missing_script.py +17 -0
  141. thefuck/rules/npm_run_script.py +17 -0
  142. thefuck/rules/npm_wrong_command.py +42 -0
  143. thefuck/rules/omnienv_no_such_command.py +35 -0
  144. thefuck/rules/open.py +40 -0
  145. thefuck/rules/pacman.py +17 -0
  146. thefuck/rules/pacman_invalid_option.py +20 -0
  147. thefuck/rules/pacman_not_found.py +26 -0
  148. thefuck/rules/path_from_history.py +53 -0
  149. thefuck/rules/php_s.py +11 -0
  150. thefuck/rules/pip_install.py +15 -0
  151. thefuck/rules/pip_unknown_command.py +19 -0
  152. thefuck/rules/port_already_in_use.py +40 -0
  153. thefuck/rules/prove_recursively.py +27 -0
  154. thefuck/rules/python_command.py +17 -0
  155. thefuck/rules/python_execute.py +15 -0
  156. thefuck/rules/python_module_error.py +13 -0
  157. thefuck/rules/quotation_marks.py +12 -0
  158. thefuck/rules/rails_migrations_pending.py +14 -0
  159. thefuck/rules/react_native_command_unrecognized.py +34 -0
  160. thefuck/rules/remove_shell_prompt_literal.py +23 -0
  161. thefuck/rules/remove_trailing_cedilla.py +11 -0
  162. thefuck/rules/rm_dir.py +16 -0
  163. thefuck/rules/rm_root.py +16 -0
  164. thefuck/rules/scm_correction.py +32 -0
  165. thefuck/rules/sed_unterminated_s.py +18 -0
  166. thefuck/rules/sl_ls.py +14 -0
  167. thefuck/rules/ssh_known_hosts.py +37 -0
  168. thefuck/rules/sudo.py +47 -0
  169. thefuck/rules/sudo_command_from_user_path.py +21 -0
  170. thefuck/rules/switch_lang.py +117 -0
  171. thefuck/rules/systemctl.py +22 -0
  172. thefuck/rules/terraform_init.py +13 -0
  173. thefuck/rules/terraform_no_command.py +16 -0
  174. thefuck/rules/test.py.py +10 -0
  175. thefuck/rules/tmux.py +18 -0
  176. thefuck/rules/touch.py +14 -0
  177. thefuck/rules/tsuru_login.py +12 -0
  178. thefuck/rules/tsuru_not_command.py +15 -0
  179. thefuck/rules/unknown_command.py +13 -0
  180. thefuck/rules/unsudo.py +15 -0
  181. thefuck/rules/vagrant_up.py +21 -0
  182. thefuck/rules/whois.py +34 -0
  183. thefuck/rules/workon_doesnt_exists.py +32 -0
  184. thefuck/rules/wrong_hyphen_before_subcommand.py +20 -0
  185. thefuck/rules/yarn_alias.py +14 -0
  186. thefuck/rules/yarn_command_not_found.py +43 -0
  187. thefuck/rules/yarn_command_replaced.py +13 -0
  188. thefuck/rules/yarn_help.py +17 -0
  189. thefuck/rules/yum_invalid_operation.py +39 -0
  190. thefuck/shells/__init__.py +52 -0
  191. thefuck/shells/bash.py +94 -0
  192. thefuck/shells/fish.py +131 -0
  193. thefuck/shells/generic.py +154 -0
  194. thefuck/shells/powershell.py +43 -0
  195. thefuck/shells/tcsh.py +44 -0
  196. thefuck/shells/zsh.py +98 -0
  197. thefuck/specific/__init__.py +0 -0
  198. thefuck/specific/apt.py +3 -0
  199. thefuck/specific/archlinux.py +48 -0
  200. thefuck/specific/brew.py +15 -0
  201. thefuck/specific/dnf.py +3 -0
  202. thefuck/specific/git.py +32 -0
  203. thefuck/specific/nix.py +3 -0
  204. thefuck/specific/npm.py +21 -0
  205. thefuck/specific/sudo.py +18 -0
  206. thefuck/specific/yum.py +3 -0
  207. thefuck/system/__init__.py +7 -0
  208. thefuck/system/unix.py +57 -0
  209. thefuck/system/win32.py +43 -0
  210. thefuck/types.py +261 -0
  211. thefuck/ui.py +116 -0
  212. thefuck/utils.py +385 -0
  213. thefuck_leeguoo-3.41.dist-info/METADATA +681 -0
  214. thefuck_leeguoo-3.41.dist-info/RECORD +218 -0
  215. thefuck_leeguoo-3.41.dist-info/WHEEL +6 -0
  216. thefuck_leeguoo-3.41.dist-info/entry_points.txt +3 -0
  217. thefuck_leeguoo-3.41.dist-info/licenses/LICENSE.md +22 -0
  218. 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()