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,48 @@
|
|
|
1
|
+
""" This file provide some utility functions for Arch Linux specific rules."""
|
|
2
|
+
import subprocess
|
|
3
|
+
from .. import utils
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@utils.memoize
|
|
7
|
+
def get_pkgfile(command):
|
|
8
|
+
""" Gets the packages that provide the given command using `pkgfile`.
|
|
9
|
+
|
|
10
|
+
If the command is of the form `sudo foo`, searches for the `foo` command
|
|
11
|
+
instead.
|
|
12
|
+
"""
|
|
13
|
+
try:
|
|
14
|
+
command = command.strip()
|
|
15
|
+
|
|
16
|
+
if command.startswith('sudo '):
|
|
17
|
+
command = command[5:]
|
|
18
|
+
|
|
19
|
+
command = command.split(" ")[0]
|
|
20
|
+
|
|
21
|
+
packages = subprocess.check_output(
|
|
22
|
+
['pkgfile', '-b', '-v', command],
|
|
23
|
+
universal_newlines=True, stderr=utils.DEVNULL
|
|
24
|
+
).splitlines()
|
|
25
|
+
|
|
26
|
+
return [package.split()[0] for package in packages]
|
|
27
|
+
except subprocess.CalledProcessError as err:
|
|
28
|
+
if err.returncode == 1 and err.output == "":
|
|
29
|
+
return []
|
|
30
|
+
else:
|
|
31
|
+
raise err
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def archlinux_env():
|
|
35
|
+
if utils.which('yay'):
|
|
36
|
+
pacman = 'yay'
|
|
37
|
+
elif utils.which('pikaur'):
|
|
38
|
+
pacman = 'pikaur'
|
|
39
|
+
elif utils.which('yaourt'):
|
|
40
|
+
pacman = 'yaourt'
|
|
41
|
+
elif utils.which('pacman'):
|
|
42
|
+
pacman = 'sudo pacman'
|
|
43
|
+
else:
|
|
44
|
+
return False, None
|
|
45
|
+
|
|
46
|
+
enabled_by_default = utils.which('pkgfile')
|
|
47
|
+
|
|
48
|
+
return enabled_by_default, pacman
|
thefuck/specific/brew.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
from ..utils import memoize, which
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
brew_available = bool(which('brew'))
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@memoize
|
|
9
|
+
def get_brew_path_prefix():
|
|
10
|
+
"""To get brew path"""
|
|
11
|
+
try:
|
|
12
|
+
return subprocess.check_output(['brew', '--prefix'],
|
|
13
|
+
universal_newlines=True).strip()
|
|
14
|
+
except Exception:
|
|
15
|
+
return None
|
thefuck/specific/dnf.py
ADDED
thefuck/specific/git.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from decorator import decorator
|
|
3
|
+
from ..utils import is_app
|
|
4
|
+
from ..shells import shell
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@decorator
|
|
8
|
+
def git_support(fn, command):
|
|
9
|
+
"""Resolves git aliases and supports testing for both git and hub."""
|
|
10
|
+
# supports GitHub's `hub` command
|
|
11
|
+
# which is recommended to be used with `alias git=hub`
|
|
12
|
+
# but at this point, shell aliases have already been resolved
|
|
13
|
+
if not is_app(command, 'git', 'hub'):
|
|
14
|
+
return False
|
|
15
|
+
|
|
16
|
+
# perform git aliases expansion
|
|
17
|
+
if command.output and 'trace: alias expansion:' in command.output:
|
|
18
|
+
search = re.search("trace: alias expansion: ([^ ]*) => ([^\n]*)",
|
|
19
|
+
command.output)
|
|
20
|
+
alias = search.group(1)
|
|
21
|
+
|
|
22
|
+
# by default git quotes everything, for example:
|
|
23
|
+
# 'commit' '--amend'
|
|
24
|
+
# which is surprising and does not allow to easily test for
|
|
25
|
+
# eg. 'git commit'
|
|
26
|
+
expansion = ' '.join(shell.quote(part)
|
|
27
|
+
for part in shell.split_command(search.group(2)))
|
|
28
|
+
new_script = re.sub(r"\b{}\b".format(alias), expansion, command.script)
|
|
29
|
+
|
|
30
|
+
command = command.update(script=new_script)
|
|
31
|
+
|
|
32
|
+
return fn(command)
|
thefuck/specific/nix.py
ADDED
thefuck/specific/npm.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from subprocess import Popen, PIPE
|
|
3
|
+
from thefuck.utils import memoize, eager, which
|
|
4
|
+
|
|
5
|
+
npm_available = bool(which('npm'))
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@memoize
|
|
9
|
+
@eager
|
|
10
|
+
def get_scripts():
|
|
11
|
+
"""Get custom npm scripts."""
|
|
12
|
+
proc = Popen(['npm', 'run-script'], stdout=PIPE)
|
|
13
|
+
should_yeild = False
|
|
14
|
+
for line in proc.stdout.readlines():
|
|
15
|
+
line = line.decode()
|
|
16
|
+
if 'available via `npm run-script`:' in line:
|
|
17
|
+
should_yeild = True
|
|
18
|
+
continue
|
|
19
|
+
|
|
20
|
+
if should_yeild and re.match(r'^ [^ ]+', line):
|
|
21
|
+
yield line.strip().split(' ')[0]
|
thefuck/specific/sudo.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import six
|
|
2
|
+
from decorator import decorator
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@decorator
|
|
6
|
+
def sudo_support(fn, command):
|
|
7
|
+
"""Removes sudo before calling fn and adds it after."""
|
|
8
|
+
if not command.script.startswith('sudo '):
|
|
9
|
+
return fn(command)
|
|
10
|
+
|
|
11
|
+
result = fn(command.update(script=command.script[5:]))
|
|
12
|
+
|
|
13
|
+
if result and isinstance(result, six.string_types):
|
|
14
|
+
return u'sudo {}'.format(result)
|
|
15
|
+
elif isinstance(result, list):
|
|
16
|
+
return [u'sudo {}'.format(x) for x in result]
|
|
17
|
+
else:
|
|
18
|
+
return result
|
thefuck/specific/yum.py
ADDED
thefuck/system/unix.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import tty
|
|
4
|
+
import termios
|
|
5
|
+
import colorama
|
|
6
|
+
from distutils.spawn import find_executable
|
|
7
|
+
from .. import const
|
|
8
|
+
|
|
9
|
+
init_output = colorama.init
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def getch():
|
|
13
|
+
fd = sys.stdin.fileno()
|
|
14
|
+
old = termios.tcgetattr(fd)
|
|
15
|
+
try:
|
|
16
|
+
tty.setraw(fd)
|
|
17
|
+
return sys.stdin.read(1)
|
|
18
|
+
finally:
|
|
19
|
+
termios.tcsetattr(fd, termios.TCSADRAIN, old)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def get_key():
|
|
23
|
+
ch = getch()
|
|
24
|
+
|
|
25
|
+
if ch in const.KEY_MAPPING:
|
|
26
|
+
return const.KEY_MAPPING[ch]
|
|
27
|
+
elif ch == '\x1b':
|
|
28
|
+
next_ch = getch()
|
|
29
|
+
if next_ch == '[':
|
|
30
|
+
last_ch = getch()
|
|
31
|
+
|
|
32
|
+
if last_ch == 'A':
|
|
33
|
+
return const.KEY_UP
|
|
34
|
+
elif last_ch == 'B':
|
|
35
|
+
return const.KEY_DOWN
|
|
36
|
+
|
|
37
|
+
return ch
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def open_command(arg):
|
|
41
|
+
if find_executable('xdg-open'):
|
|
42
|
+
return 'xdg-open ' + arg
|
|
43
|
+
return 'open ' + arg
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
from pathlib import Path
|
|
48
|
+
except ImportError:
|
|
49
|
+
from pathlib2 import Path
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _expanduser(self):
|
|
53
|
+
return self.__class__(os.path.expanduser(str(self)))
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
if not hasattr(Path, 'expanduser'):
|
|
57
|
+
Path.expanduser = _expanduser
|
thefuck/system/win32.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import msvcrt
|
|
3
|
+
import win_unicode_console
|
|
4
|
+
from .. import const
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def init_output():
|
|
8
|
+
import colorama
|
|
9
|
+
win_unicode_console.enable()
|
|
10
|
+
colorama.init()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def get_key():
|
|
14
|
+
ch = msvcrt.getwch()
|
|
15
|
+
if ch in ('\x00', '\xe0'): # arrow or function key prefix?
|
|
16
|
+
ch = msvcrt.getwch() # second call returns the actual key code
|
|
17
|
+
|
|
18
|
+
if ch in const.KEY_MAPPING:
|
|
19
|
+
return const.KEY_MAPPING[ch]
|
|
20
|
+
if ch == 'H':
|
|
21
|
+
return const.KEY_UP
|
|
22
|
+
if ch == 'P':
|
|
23
|
+
return const.KEY_DOWN
|
|
24
|
+
|
|
25
|
+
return ch
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def open_command(arg):
|
|
29
|
+
return 'cmd /c start ' + arg
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
try:
|
|
33
|
+
from pathlib import Path
|
|
34
|
+
except ImportError:
|
|
35
|
+
from pathlib2 import Path
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _expanduser(self):
|
|
39
|
+
return self.__class__(os.path.expanduser(str(self)))
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# pathlib's expanduser fails on windows, see http://bugs.python.org/issue19776
|
|
43
|
+
Path.expanduser = _expanduser
|
thefuck/types.py
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
from . import logs
|
|
4
|
+
from .shells import shell
|
|
5
|
+
from .conf import settings, load_source
|
|
6
|
+
from .const import DEFAULT_PRIORITY, ALL_ENABLED
|
|
7
|
+
from .exceptions import EmptyCommand
|
|
8
|
+
from .utils import get_alias, format_raw_script
|
|
9
|
+
from .output_readers import get_output
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Command(object):
|
|
13
|
+
"""Command that should be fixed."""
|
|
14
|
+
|
|
15
|
+
def __init__(self, script, output):
|
|
16
|
+
"""Initializes command with given values.
|
|
17
|
+
|
|
18
|
+
:type script: basestring
|
|
19
|
+
:type output: basestring
|
|
20
|
+
|
|
21
|
+
"""
|
|
22
|
+
self.script = script
|
|
23
|
+
self.output = output
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def stdout(self):
|
|
27
|
+
logs.warn('`stdout` is deprecated, please use `output` instead')
|
|
28
|
+
return self.output
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def stderr(self):
|
|
32
|
+
logs.warn('`stderr` is deprecated, please use `output` instead')
|
|
33
|
+
return self.output
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def script_parts(self):
|
|
37
|
+
if not hasattr(self, '_script_parts'):
|
|
38
|
+
try:
|
|
39
|
+
self._script_parts = shell.split_command(self.script)
|
|
40
|
+
except Exception:
|
|
41
|
+
logs.debug(u"Can't split command script {} because:\n {}".format(
|
|
42
|
+
self, sys.exc_info()))
|
|
43
|
+
self._script_parts = []
|
|
44
|
+
|
|
45
|
+
return self._script_parts
|
|
46
|
+
|
|
47
|
+
def __eq__(self, other):
|
|
48
|
+
if isinstance(other, Command):
|
|
49
|
+
return (self.script, self.output) == (other.script, other.output)
|
|
50
|
+
else:
|
|
51
|
+
return False
|
|
52
|
+
|
|
53
|
+
def __repr__(self):
|
|
54
|
+
return u'Command(script={}, output={})'.format(
|
|
55
|
+
self.script, self.output)
|
|
56
|
+
|
|
57
|
+
def update(self, **kwargs):
|
|
58
|
+
"""Returns new command with replaced fields.
|
|
59
|
+
|
|
60
|
+
:rtype: Command
|
|
61
|
+
|
|
62
|
+
"""
|
|
63
|
+
kwargs.setdefault('script', self.script)
|
|
64
|
+
kwargs.setdefault('output', self.output)
|
|
65
|
+
return Command(**kwargs)
|
|
66
|
+
|
|
67
|
+
@classmethod
|
|
68
|
+
def from_raw_script(cls, raw_script):
|
|
69
|
+
"""Creates instance of `Command` from a list of script parts.
|
|
70
|
+
|
|
71
|
+
:type raw_script: [basestring]
|
|
72
|
+
:rtype: Command
|
|
73
|
+
:raises: EmptyCommand
|
|
74
|
+
|
|
75
|
+
"""
|
|
76
|
+
script = format_raw_script(raw_script)
|
|
77
|
+
if not script:
|
|
78
|
+
raise EmptyCommand
|
|
79
|
+
|
|
80
|
+
expanded = shell.from_shell(script)
|
|
81
|
+
output = get_output(script, expanded)
|
|
82
|
+
return cls(expanded, output)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class Rule(object):
|
|
86
|
+
"""Rule for fixing commands."""
|
|
87
|
+
|
|
88
|
+
def __init__(self, name, match, get_new_command,
|
|
89
|
+
enabled_by_default, side_effect,
|
|
90
|
+
priority, requires_output):
|
|
91
|
+
"""Initializes rule with given fields.
|
|
92
|
+
|
|
93
|
+
:type name: basestring
|
|
94
|
+
:type match: (Command) -> bool
|
|
95
|
+
:type get_new_command: (Command) -> (basestring | [basestring])
|
|
96
|
+
:type enabled_by_default: boolean
|
|
97
|
+
:type side_effect: (Command, basestring) -> None
|
|
98
|
+
:type priority: int
|
|
99
|
+
:type requires_output: bool
|
|
100
|
+
|
|
101
|
+
"""
|
|
102
|
+
self.name = name
|
|
103
|
+
self.match = match
|
|
104
|
+
self.get_new_command = get_new_command
|
|
105
|
+
self.enabled_by_default = enabled_by_default
|
|
106
|
+
self.side_effect = side_effect
|
|
107
|
+
self.priority = priority
|
|
108
|
+
self.requires_output = requires_output
|
|
109
|
+
|
|
110
|
+
def __eq__(self, other):
|
|
111
|
+
if isinstance(other, Rule):
|
|
112
|
+
return ((self.name, self.match, self.get_new_command,
|
|
113
|
+
self.enabled_by_default, self.side_effect,
|
|
114
|
+
self.priority, self.requires_output)
|
|
115
|
+
== (other.name, other.match, other.get_new_command,
|
|
116
|
+
other.enabled_by_default, other.side_effect,
|
|
117
|
+
other.priority, other.requires_output))
|
|
118
|
+
else:
|
|
119
|
+
return False
|
|
120
|
+
|
|
121
|
+
def __repr__(self):
|
|
122
|
+
return 'Rule(name={}, match={}, get_new_command={}, ' \
|
|
123
|
+
'enabled_by_default={}, side_effect={}, ' \
|
|
124
|
+
'priority={}, requires_output={})'.format(
|
|
125
|
+
self.name, self.match, self.get_new_command,
|
|
126
|
+
self.enabled_by_default, self.side_effect,
|
|
127
|
+
self.priority, self.requires_output)
|
|
128
|
+
|
|
129
|
+
@classmethod
|
|
130
|
+
def from_path(cls, path):
|
|
131
|
+
"""Creates rule instance from path.
|
|
132
|
+
|
|
133
|
+
:type path: pathlib.Path
|
|
134
|
+
:rtype: Rule
|
|
135
|
+
|
|
136
|
+
"""
|
|
137
|
+
name = path.name[:-3]
|
|
138
|
+
if name in settings.exclude_rules:
|
|
139
|
+
logs.debug(u'Ignoring excluded rule: {}'.format(name))
|
|
140
|
+
return
|
|
141
|
+
with logs.debug_time(u'Importing rule: {};'.format(name)):
|
|
142
|
+
try:
|
|
143
|
+
rule_module = load_source(name, str(path))
|
|
144
|
+
except Exception:
|
|
145
|
+
logs.exception(u"Rule {} failed to load".format(name), sys.exc_info())
|
|
146
|
+
return
|
|
147
|
+
priority = getattr(rule_module, 'priority', DEFAULT_PRIORITY)
|
|
148
|
+
return cls(name, rule_module.match,
|
|
149
|
+
rule_module.get_new_command,
|
|
150
|
+
getattr(rule_module, 'enabled_by_default', True),
|
|
151
|
+
getattr(rule_module, 'side_effect', None),
|
|
152
|
+
settings.priority.get(name, priority),
|
|
153
|
+
getattr(rule_module, 'requires_output', True))
|
|
154
|
+
|
|
155
|
+
@property
|
|
156
|
+
def is_enabled(self):
|
|
157
|
+
"""Returns `True` when rule enabled.
|
|
158
|
+
|
|
159
|
+
:rtype: bool
|
|
160
|
+
|
|
161
|
+
"""
|
|
162
|
+
return (
|
|
163
|
+
self.name in settings.rules
|
|
164
|
+
or self.enabled_by_default
|
|
165
|
+
and ALL_ENABLED in settings.rules
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
def is_match(self, command):
|
|
169
|
+
"""Returns `True` if rule matches the command.
|
|
170
|
+
|
|
171
|
+
:type command: Command
|
|
172
|
+
:rtype: bool
|
|
173
|
+
|
|
174
|
+
"""
|
|
175
|
+
if command.output is None and self.requires_output:
|
|
176
|
+
return False
|
|
177
|
+
|
|
178
|
+
try:
|
|
179
|
+
with logs.debug_time(u'Trying rule: {};'.format(self.name)):
|
|
180
|
+
if self.match(command):
|
|
181
|
+
return True
|
|
182
|
+
except Exception:
|
|
183
|
+
logs.rule_failed(self, sys.exc_info())
|
|
184
|
+
|
|
185
|
+
def get_corrected_commands(self, command):
|
|
186
|
+
"""Returns generator with corrected commands.
|
|
187
|
+
|
|
188
|
+
:type command: Command
|
|
189
|
+
:rtype: Iterable[CorrectedCommand]
|
|
190
|
+
|
|
191
|
+
"""
|
|
192
|
+
new_commands = self.get_new_command(command)
|
|
193
|
+
if not isinstance(new_commands, list):
|
|
194
|
+
new_commands = (new_commands,)
|
|
195
|
+
for n, new_command in enumerate(new_commands):
|
|
196
|
+
yield CorrectedCommand(script=new_command,
|
|
197
|
+
side_effect=self.side_effect,
|
|
198
|
+
priority=(n + 1) * self.priority)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
class CorrectedCommand(object):
|
|
202
|
+
"""Corrected by rule command."""
|
|
203
|
+
|
|
204
|
+
def __init__(self, script, side_effect, priority):
|
|
205
|
+
"""Initializes instance with given fields.
|
|
206
|
+
|
|
207
|
+
:type script: basestring
|
|
208
|
+
:type side_effect: (Command, basestring) -> None
|
|
209
|
+
:type priority: int
|
|
210
|
+
|
|
211
|
+
"""
|
|
212
|
+
self.script = script
|
|
213
|
+
self.side_effect = side_effect
|
|
214
|
+
self.priority = priority
|
|
215
|
+
|
|
216
|
+
def __eq__(self, other):
|
|
217
|
+
"""Ignores `priority` field."""
|
|
218
|
+
if isinstance(other, CorrectedCommand):
|
|
219
|
+
return (other.script, other.side_effect) == \
|
|
220
|
+
(self.script, self.side_effect)
|
|
221
|
+
else:
|
|
222
|
+
return False
|
|
223
|
+
|
|
224
|
+
def __hash__(self):
|
|
225
|
+
return (self.script, self.side_effect).__hash__()
|
|
226
|
+
|
|
227
|
+
def __repr__(self):
|
|
228
|
+
return u'CorrectedCommand(script={}, side_effect={}, priority={})'.format(
|
|
229
|
+
self.script, self.side_effect, self.priority)
|
|
230
|
+
|
|
231
|
+
def _get_script(self):
|
|
232
|
+
"""Returns fixed commands script.
|
|
233
|
+
|
|
234
|
+
If `settings.repeat` is `True`, appends command with second attempt
|
|
235
|
+
of running fuck in case fixed command fails again.
|
|
236
|
+
|
|
237
|
+
"""
|
|
238
|
+
if settings.repeat:
|
|
239
|
+
repeat_fuck = '{} --repeat {}--force-command {}'.format(
|
|
240
|
+
get_alias(),
|
|
241
|
+
'--debug ' if settings.debug else '',
|
|
242
|
+
shell.quote(self.script))
|
|
243
|
+
return shell.or_(self.script, repeat_fuck)
|
|
244
|
+
else:
|
|
245
|
+
return self.script
|
|
246
|
+
|
|
247
|
+
def run(self, old_cmd):
|
|
248
|
+
"""Runs command from rule for passed command.
|
|
249
|
+
|
|
250
|
+
:type old_cmd: Command
|
|
251
|
+
|
|
252
|
+
"""
|
|
253
|
+
if self.side_effect:
|
|
254
|
+
self.side_effect(old_cmd, self.script)
|
|
255
|
+
if settings.alter_history:
|
|
256
|
+
shell.put_to_history(self.script)
|
|
257
|
+
# This depends on correct setting of PYTHONIOENCODING by the alias:
|
|
258
|
+
logs.debug(u'PYTHONIOENCODING: {}'.format(
|
|
259
|
+
os.environ.get('PYTHONIOENCODING', '!!not-set!!')))
|
|
260
|
+
|
|
261
|
+
sys.stdout.write(self._get_script())
|
thefuck/ui.py
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from .conf import settings
|
|
5
|
+
from .exceptions import NoRuleMatched
|
|
6
|
+
from .system import get_key
|
|
7
|
+
from .utils import get_alias
|
|
8
|
+
from . import logs, const
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def read_actions():
|
|
12
|
+
"""Yields actions for pressed keys."""
|
|
13
|
+
while True:
|
|
14
|
+
key = get_key()
|
|
15
|
+
|
|
16
|
+
# Handle arrows, j/k (qwerty), and n/e (colemak)
|
|
17
|
+
if key in (const.KEY_UP, const.KEY_CTRL_N, 'k', 'e'):
|
|
18
|
+
yield const.ACTION_PREVIOUS
|
|
19
|
+
elif key in (const.KEY_DOWN, const.KEY_CTRL_P, 'j', 'n',
|
|
20
|
+
const.KEY_TAB):
|
|
21
|
+
yield const.ACTION_NEXT
|
|
22
|
+
elif key in (const.KEY_CTRL_C, 'q'):
|
|
23
|
+
yield const.ACTION_ABORT
|
|
24
|
+
elif key in ('\n', '\r'):
|
|
25
|
+
yield const.ACTION_SELECT
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class CommandSelector(object):
|
|
29
|
+
"""Helper for selecting rule from rules list."""
|
|
30
|
+
|
|
31
|
+
def __init__(self, commands):
|
|
32
|
+
""":type commands: Iterable[thefuck.types.CorrectedCommand]"""
|
|
33
|
+
self._commands_gen = commands
|
|
34
|
+
try:
|
|
35
|
+
self._commands = [next(self._commands_gen)]
|
|
36
|
+
except StopIteration:
|
|
37
|
+
raise NoRuleMatched
|
|
38
|
+
self._realised = False
|
|
39
|
+
self._index = 0
|
|
40
|
+
|
|
41
|
+
def _realise(self):
|
|
42
|
+
if not self._realised:
|
|
43
|
+
self._commands += list(self._commands_gen)
|
|
44
|
+
self._realised = True
|
|
45
|
+
|
|
46
|
+
def next(self):
|
|
47
|
+
self._realise()
|
|
48
|
+
self._index = (self._index + 1) % len(self._commands)
|
|
49
|
+
|
|
50
|
+
def previous(self):
|
|
51
|
+
self._realise()
|
|
52
|
+
self._index = (self._index - 1) % len(self._commands)
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def value(self):
|
|
56
|
+
""":rtype thefuck.types.CorrectedCommand"""
|
|
57
|
+
return self._commands[self._index]
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def all_ai(self):
|
|
61
|
+
self._realise()
|
|
62
|
+
return all(getattr(cmd, '_tf_source', None) == 'ai'
|
|
63
|
+
for cmd in self._commands)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def select_command(corrected_commands):
|
|
67
|
+
"""Returns:
|
|
68
|
+
|
|
69
|
+
- the first command when confirmation disabled;
|
|
70
|
+
- None when ctrl+c pressed;
|
|
71
|
+
- selected command.
|
|
72
|
+
|
|
73
|
+
:type corrected_commands: Iterable[thefuck.types.CorrectedCommand]
|
|
74
|
+
:rtype: thefuck.types.CorrectedCommand | None
|
|
75
|
+
|
|
76
|
+
"""
|
|
77
|
+
try:
|
|
78
|
+
selector = CommandSelector(corrected_commands)
|
|
79
|
+
except NoRuleMatched:
|
|
80
|
+
logs.failed('No fucks given' if get_alias() == 'fuck'
|
|
81
|
+
else 'Nothing found')
|
|
82
|
+
return
|
|
83
|
+
|
|
84
|
+
if not settings.require_confirmation:
|
|
85
|
+
logs.show_corrected_command(selector.value)
|
|
86
|
+
return selector.value
|
|
87
|
+
|
|
88
|
+
logs.reset_confirm_text()
|
|
89
|
+
ai_mode = selector.all_ai
|
|
90
|
+
if ai_mode:
|
|
91
|
+
logs.ai_choose_header()
|
|
92
|
+
logs.confirm_choice(selector.value)
|
|
93
|
+
else:
|
|
94
|
+
logs.confirm_text(selector.value)
|
|
95
|
+
|
|
96
|
+
for action in read_actions():
|
|
97
|
+
if action == const.ACTION_SELECT:
|
|
98
|
+
sys.stderr.write('\n')
|
|
99
|
+
logs.reset_confirm_text()
|
|
100
|
+
return selector.value
|
|
101
|
+
elif action == const.ACTION_ABORT:
|
|
102
|
+
logs.failed('\nAborted')
|
|
103
|
+
logs.reset_confirm_text()
|
|
104
|
+
return
|
|
105
|
+
elif action == const.ACTION_PREVIOUS:
|
|
106
|
+
selector.previous()
|
|
107
|
+
if ai_mode:
|
|
108
|
+
logs.confirm_choice(selector.value)
|
|
109
|
+
else:
|
|
110
|
+
logs.confirm_text(selector.value)
|
|
111
|
+
elif action == const.ACTION_NEXT:
|
|
112
|
+
selector.next()
|
|
113
|
+
if ai_mode:
|
|
114
|
+
logs.confirm_choice(selector.value)
|
|
115
|
+
else:
|
|
116
|
+
logs.confirm_text(selector.value)
|