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,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
@@ -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
@@ -0,0 +1,3 @@
1
+ from thefuck.utils import which
2
+
3
+ dnf_available = bool(which('dnf'))
@@ -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)
@@ -0,0 +1,3 @@
1
+ from thefuck.utils import which
2
+
3
+ nix_available = bool(which('nix'))
@@ -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]
@@ -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
@@ -0,0 +1,3 @@
1
+ from thefuck.utils import which
2
+
3
+ yum_available = bool(which('yum'))
@@ -0,0 +1,7 @@
1
+ import sys
2
+
3
+
4
+ if sys.platform == 'win32':
5
+ from .win32 import * # noqa: F401,F403
6
+ else:
7
+ from .unix import * # noqa: F401,F403
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
@@ -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)