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
thefuck/utils.py
ADDED
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
import atexit
|
|
2
|
+
import os
|
|
3
|
+
import pickle
|
|
4
|
+
import re
|
|
5
|
+
import shelve
|
|
6
|
+
import sys
|
|
7
|
+
import six
|
|
8
|
+
from decorator import decorator
|
|
9
|
+
from difflib import get_close_matches as difflib_get_close_matches
|
|
10
|
+
from functools import wraps
|
|
11
|
+
from .logs import warn, exception
|
|
12
|
+
from .conf import settings
|
|
13
|
+
from .system import Path
|
|
14
|
+
|
|
15
|
+
DEVNULL = open(os.devnull, 'w')
|
|
16
|
+
|
|
17
|
+
if six.PY2:
|
|
18
|
+
import anydbm
|
|
19
|
+
shelve_open_error = anydbm.error
|
|
20
|
+
else:
|
|
21
|
+
import dbm
|
|
22
|
+
shelve_open_error = dbm.error
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def memoize(fn):
|
|
26
|
+
"""Caches previous calls to the function."""
|
|
27
|
+
memo = {}
|
|
28
|
+
|
|
29
|
+
@wraps(fn)
|
|
30
|
+
def wrapper(*args, **kwargs):
|
|
31
|
+
if not memoize.disabled:
|
|
32
|
+
key = pickle.dumps((args, kwargs))
|
|
33
|
+
if key not in memo:
|
|
34
|
+
memo[key] = fn(*args, **kwargs)
|
|
35
|
+
value = memo[key]
|
|
36
|
+
else:
|
|
37
|
+
# Memoize is disabled, call the function
|
|
38
|
+
value = fn(*args, **kwargs)
|
|
39
|
+
|
|
40
|
+
return value
|
|
41
|
+
|
|
42
|
+
return wrapper
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
memoize.disabled = False
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@memoize
|
|
49
|
+
def which(program):
|
|
50
|
+
"""Returns `program` path or `None`."""
|
|
51
|
+
try:
|
|
52
|
+
from shutil import which
|
|
53
|
+
|
|
54
|
+
return which(program)
|
|
55
|
+
except ImportError:
|
|
56
|
+
def is_exe(fpath):
|
|
57
|
+
return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
|
|
58
|
+
|
|
59
|
+
fpath, fname = os.path.split(program)
|
|
60
|
+
if fpath:
|
|
61
|
+
if is_exe(program):
|
|
62
|
+
return program
|
|
63
|
+
else:
|
|
64
|
+
for path in os.environ["PATH"].split(os.pathsep):
|
|
65
|
+
path = path.strip('"')
|
|
66
|
+
exe_file = os.path.join(path, program)
|
|
67
|
+
if is_exe(exe_file):
|
|
68
|
+
return exe_file
|
|
69
|
+
|
|
70
|
+
return None
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def default_settings(params):
|
|
74
|
+
"""Adds default values to settings if it not presented.
|
|
75
|
+
|
|
76
|
+
Usage:
|
|
77
|
+
|
|
78
|
+
@default_settings({'apt': '/usr/bin/apt'})
|
|
79
|
+
def match(command):
|
|
80
|
+
print(settings.apt)
|
|
81
|
+
|
|
82
|
+
"""
|
|
83
|
+
def _default_settings(fn, command):
|
|
84
|
+
for k, w in params.items():
|
|
85
|
+
settings.setdefault(k, w)
|
|
86
|
+
return fn(command)
|
|
87
|
+
return decorator(_default_settings)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def get_closest(word, possibilities, cutoff=0.6, fallback_to_first=True):
|
|
91
|
+
"""Returns closest match or just first from possibilities."""
|
|
92
|
+
possibilities = list(possibilities)
|
|
93
|
+
try:
|
|
94
|
+
return difflib_get_close_matches(word, possibilities, 1, cutoff)[0]
|
|
95
|
+
except IndexError:
|
|
96
|
+
if fallback_to_first:
|
|
97
|
+
return possibilities[0]
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def get_close_matches(word, possibilities, n=None, cutoff=0.6):
|
|
101
|
+
"""Overrides `difflib.get_close_match` to control argument `n`."""
|
|
102
|
+
if n is None:
|
|
103
|
+
n = settings.num_close_matches
|
|
104
|
+
return difflib_get_close_matches(word, possibilities, n, cutoff)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def include_path_in_search(path):
|
|
108
|
+
return not any(path.startswith(x) for x in settings.excluded_search_path_prefixes)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@memoize
|
|
112
|
+
def get_all_executables():
|
|
113
|
+
from thefuck.shells import shell
|
|
114
|
+
|
|
115
|
+
def _safe(fn, fallback):
|
|
116
|
+
try:
|
|
117
|
+
return fn()
|
|
118
|
+
except OSError:
|
|
119
|
+
return fallback
|
|
120
|
+
|
|
121
|
+
tf_alias = get_alias()
|
|
122
|
+
tf_entry_points = ['thefuck', 'fuck']
|
|
123
|
+
|
|
124
|
+
bins = [exe.name.decode('utf8') if six.PY2 else exe.name
|
|
125
|
+
for path in os.environ.get('PATH', '').split(os.pathsep)
|
|
126
|
+
if include_path_in_search(path)
|
|
127
|
+
for exe in _safe(lambda: list(Path(path).iterdir()), [])
|
|
128
|
+
if not _safe(exe.is_dir, True)
|
|
129
|
+
and exe.name not in tf_entry_points]
|
|
130
|
+
aliases = [alias.decode('utf8') if six.PY2 else alias
|
|
131
|
+
for alias in shell.get_aliases() if alias != tf_alias]
|
|
132
|
+
|
|
133
|
+
return bins + aliases
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def replace_argument(script, from_, to):
|
|
137
|
+
"""Replaces command line argument."""
|
|
138
|
+
replaced_in_the_end = re.sub(u' {}$'.format(re.escape(from_)), u' {}'.format(to),
|
|
139
|
+
script, count=1)
|
|
140
|
+
if replaced_in_the_end != script:
|
|
141
|
+
return replaced_in_the_end
|
|
142
|
+
else:
|
|
143
|
+
return script.replace(
|
|
144
|
+
u' {} '.format(from_), u' {} '.format(to), 1)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
@decorator
|
|
148
|
+
def eager(fn, *args, **kwargs):
|
|
149
|
+
return list(fn(*args, **kwargs))
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
@eager
|
|
153
|
+
def get_all_matched_commands(stderr, separator='Did you mean'):
|
|
154
|
+
if not isinstance(separator, list):
|
|
155
|
+
separator = [separator]
|
|
156
|
+
should_yield = False
|
|
157
|
+
for line in stderr.split('\n'):
|
|
158
|
+
for sep in separator:
|
|
159
|
+
if sep in line:
|
|
160
|
+
should_yield = True
|
|
161
|
+
break
|
|
162
|
+
else:
|
|
163
|
+
if should_yield and line:
|
|
164
|
+
yield line.strip()
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def replace_command(command, broken, matched):
|
|
168
|
+
"""Helper for *_no_command rules."""
|
|
169
|
+
new_cmds = get_close_matches(broken, matched, cutoff=0.1)
|
|
170
|
+
return [replace_argument(command.script, broken, new_cmd.strip())
|
|
171
|
+
for new_cmd in new_cmds]
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
@memoize
|
|
175
|
+
def is_app(command, *app_names, **kwargs):
|
|
176
|
+
"""Returns `True` if command is call to one of passed app names."""
|
|
177
|
+
|
|
178
|
+
at_least = kwargs.pop('at_least', 0)
|
|
179
|
+
if kwargs:
|
|
180
|
+
raise TypeError("got an unexpected keyword argument '{}'".format(kwargs.keys()))
|
|
181
|
+
|
|
182
|
+
if len(command.script_parts) > at_least:
|
|
183
|
+
return os.path.basename(command.script_parts[0]) in app_names
|
|
184
|
+
|
|
185
|
+
return False
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def for_app(*app_names, **kwargs):
|
|
189
|
+
"""Specifies that matching script is for one of app names."""
|
|
190
|
+
def _for_app(fn, command):
|
|
191
|
+
if is_app(command, *app_names, **kwargs):
|
|
192
|
+
return fn(command)
|
|
193
|
+
else:
|
|
194
|
+
return False
|
|
195
|
+
|
|
196
|
+
return decorator(_for_app)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
class Cache(object):
|
|
200
|
+
"""Lazy read cache and save changes at exit."""
|
|
201
|
+
|
|
202
|
+
def __init__(self):
|
|
203
|
+
self._db = None
|
|
204
|
+
|
|
205
|
+
def _init_db(self):
|
|
206
|
+
try:
|
|
207
|
+
self._setup_db()
|
|
208
|
+
except Exception:
|
|
209
|
+
exception("Unable to init cache", sys.exc_info())
|
|
210
|
+
self._db = {}
|
|
211
|
+
|
|
212
|
+
def _setup_db(self):
|
|
213
|
+
cache_dir = self._get_cache_dir()
|
|
214
|
+
cache_path = Path(cache_dir).joinpath('thefuck').as_posix()
|
|
215
|
+
|
|
216
|
+
try:
|
|
217
|
+
self._db = shelve.open(cache_path)
|
|
218
|
+
except shelve_open_error + (ImportError,):
|
|
219
|
+
# Caused when switching between Python versions
|
|
220
|
+
warn("Removing possibly out-dated cache")
|
|
221
|
+
os.remove(cache_path)
|
|
222
|
+
self._db = shelve.open(cache_path)
|
|
223
|
+
|
|
224
|
+
atexit.register(self._db.close)
|
|
225
|
+
|
|
226
|
+
def _get_cache_dir(self):
|
|
227
|
+
default_xdg_cache_dir = os.path.expanduser("~/.cache")
|
|
228
|
+
cache_dir = os.getenv("XDG_CACHE_HOME", default_xdg_cache_dir)
|
|
229
|
+
|
|
230
|
+
# Ensure the cache_path exists, Python 2 does not have the exist_ok
|
|
231
|
+
# parameter
|
|
232
|
+
try:
|
|
233
|
+
os.makedirs(cache_dir)
|
|
234
|
+
except OSError:
|
|
235
|
+
if not os.path.isdir(cache_dir):
|
|
236
|
+
raise
|
|
237
|
+
|
|
238
|
+
return cache_dir
|
|
239
|
+
|
|
240
|
+
def _get_mtime(self, path):
|
|
241
|
+
try:
|
|
242
|
+
return str(os.path.getmtime(path))
|
|
243
|
+
except OSError:
|
|
244
|
+
return '0'
|
|
245
|
+
|
|
246
|
+
def _get_key(self, fn, depends_on, args, kwargs):
|
|
247
|
+
parts = (fn.__module__, repr(fn).split('at')[0],
|
|
248
|
+
depends_on, args, kwargs)
|
|
249
|
+
return str(pickle.dumps(parts))
|
|
250
|
+
|
|
251
|
+
def get_value(self, fn, depends_on, args, kwargs):
|
|
252
|
+
if self._db is None:
|
|
253
|
+
self._init_db()
|
|
254
|
+
|
|
255
|
+
depends_on = [Path(name).expanduser().absolute().as_posix()
|
|
256
|
+
for name in depends_on]
|
|
257
|
+
key = self._get_key(fn, depends_on, args, kwargs)
|
|
258
|
+
etag = '.'.join(self._get_mtime(path) for path in depends_on)
|
|
259
|
+
|
|
260
|
+
if self._db.get(key, {}).get('etag') == etag:
|
|
261
|
+
return self._db[key]['value']
|
|
262
|
+
else:
|
|
263
|
+
value = fn(*args, **kwargs)
|
|
264
|
+
self._db[key] = {'etag': etag, 'value': value}
|
|
265
|
+
return value
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
_cache = Cache()
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def cache(*depends_on):
|
|
272
|
+
"""Caches function result in temporary file.
|
|
273
|
+
|
|
274
|
+
Cache will be expired when modification date of files from `depends_on`
|
|
275
|
+
will be changed.
|
|
276
|
+
|
|
277
|
+
Only functions should be wrapped in `cache`, not methods.
|
|
278
|
+
|
|
279
|
+
"""
|
|
280
|
+
def cache_decorator(fn):
|
|
281
|
+
@memoize
|
|
282
|
+
@wraps(fn)
|
|
283
|
+
def wrapper(*args, **kwargs):
|
|
284
|
+
if cache.disabled:
|
|
285
|
+
return fn(*args, **kwargs)
|
|
286
|
+
else:
|
|
287
|
+
return _cache.get_value(fn, depends_on, args, kwargs)
|
|
288
|
+
|
|
289
|
+
return wrapper
|
|
290
|
+
|
|
291
|
+
return cache_decorator
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
cache.disabled = False
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def get_installation_version():
|
|
298
|
+
def _from_importlib():
|
|
299
|
+
try:
|
|
300
|
+
from importlib.metadata import ( # py3.8+
|
|
301
|
+
version, PackageNotFoundError, packages_distributions)
|
|
302
|
+
except Exception:
|
|
303
|
+
return
|
|
304
|
+
|
|
305
|
+
for name in ('thefuck',):
|
|
306
|
+
try:
|
|
307
|
+
return version(name)
|
|
308
|
+
except PackageNotFoundError:
|
|
309
|
+
pass
|
|
310
|
+
|
|
311
|
+
try:
|
|
312
|
+
dist_names = packages_distributions().get('thefuck', [])
|
|
313
|
+
except Exception:
|
|
314
|
+
dist_names = []
|
|
315
|
+
for name in dist_names:
|
|
316
|
+
try:
|
|
317
|
+
return version(name)
|
|
318
|
+
except PackageNotFoundError:
|
|
319
|
+
pass
|
|
320
|
+
|
|
321
|
+
for name in ('thefuck-leeguoo',):
|
|
322
|
+
try:
|
|
323
|
+
return version(name)
|
|
324
|
+
except PackageNotFoundError:
|
|
325
|
+
pass
|
|
326
|
+
return
|
|
327
|
+
|
|
328
|
+
result = _from_importlib()
|
|
329
|
+
if result:
|
|
330
|
+
return result
|
|
331
|
+
|
|
332
|
+
try:
|
|
333
|
+
import pkg_resources
|
|
334
|
+
except Exception:
|
|
335
|
+
return 'unknown'
|
|
336
|
+
|
|
337
|
+
try:
|
|
338
|
+
return pkg_resources.require('thefuck')[0].version
|
|
339
|
+
except Exception:
|
|
340
|
+
try:
|
|
341
|
+
return pkg_resources.require('thefuck-leeguoo')[0].version
|
|
342
|
+
except Exception:
|
|
343
|
+
return 'unknown'
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def get_alias():
|
|
347
|
+
return os.environ.get('TF_ALIAS', 'fuck')
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
@memoize
|
|
351
|
+
def get_valid_history_without_current(command):
|
|
352
|
+
def _not_corrected(history, tf_alias):
|
|
353
|
+
"""Returns all lines from history except that comes before `fuck`."""
|
|
354
|
+
previous = None
|
|
355
|
+
for line in history:
|
|
356
|
+
if previous is not None and line != tf_alias:
|
|
357
|
+
yield previous
|
|
358
|
+
previous = line
|
|
359
|
+
if history:
|
|
360
|
+
yield history[-1]
|
|
361
|
+
|
|
362
|
+
from thefuck.shells import shell
|
|
363
|
+
history = shell.get_history()
|
|
364
|
+
tf_alias = get_alias()
|
|
365
|
+
executables = set(get_all_executables())\
|
|
366
|
+
.union(shell.get_builtin_commands())
|
|
367
|
+
|
|
368
|
+
return [line for line in _not_corrected(history, tf_alias)
|
|
369
|
+
if not line.startswith(tf_alias) and not line == command.script
|
|
370
|
+
and line.split(' ')[0] in executables]
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def format_raw_script(raw_script):
|
|
374
|
+
"""Creates single script from a list of script parts.
|
|
375
|
+
|
|
376
|
+
:type raw_script: [basestring]
|
|
377
|
+
:rtype: basestring
|
|
378
|
+
|
|
379
|
+
"""
|
|
380
|
+
if six.PY2:
|
|
381
|
+
script = ' '.join(arg.decode('utf-8') for arg in raw_script)
|
|
382
|
+
else:
|
|
383
|
+
script = ' '.join(raw_script)
|
|
384
|
+
|
|
385
|
+
return script.lstrip()
|