mirror of
https://github.com/nvbn/thefuck.git
synced 2025-02-22 12:58:33 +00:00
Merge pull request #684 from nvbn/682-instant-fuck-mode
682 instant fuck mode
This commit is contained in:
commit
bf3c16816d
20
README.md
20
README.md
@ -4,6 +4,8 @@ Magnificent app which corrects your previous console command,
|
||||
inspired by a [@liamosaur](https://twitter.com/liamosaur/)
|
||||
[tweet](https://twitter.com/liamosaur/status/506975850596536320).
|
||||
|
||||
The Fuck is too slow? [Try experimental instant mode!](#experimental-instant-mode)
|
||||
|
||||
[![gif with examples][examples-link]][examples-link]
|
||||
|
||||
Few more examples:
|
||||
@ -395,6 +397,23 @@ export THEFUCK_PRIORITY='no_command=9999:apt_get=100'
|
||||
export THEFUCK_HISTORY_LIMIT='2000'
|
||||
```
|
||||
|
||||
## Experimental instant mode
|
||||
|
||||
By default The Fuck reruns a previous command and that takes time,
|
||||
in instant mode The Fuck logs output with [script](https://en.wikipedia.org/wiki/Script_(Unix))
|
||||
and just reads the log.
|
||||
|
||||
[![gif with instant mode][instant-mode-gif-link]][instant-mode-gif-link]
|
||||
|
||||
At the moment only Python 3 with bash or zsh is supported.
|
||||
|
||||
For enabling instant mode you need to add `--enable-experimental-instant-mode`
|
||||
to alias initialization in your `.bashrc`, `.bash_profile` or `.zshrc` like:
|
||||
|
||||
```bash
|
||||
eval $(thefuck --alias --enable-experimental-instant-mode)
|
||||
```
|
||||
|
||||
## Developing
|
||||
|
||||
Install `The Fuck` for development:
|
||||
@ -443,4 +462,5 @@ Project License can be found [here](LICENSE.md).
|
||||
[coverage-link]: https://coveralls.io/github/nvbn/thefuck
|
||||
[license-badge]: https://img.shields.io/badge/license-MIT-007EC7.svg
|
||||
[examples-link]: https://raw.githubusercontent.com/nvbn/thefuck/master/example.gif
|
||||
[instant-mode-gif-link]: https://raw.githubusercontent.com/nvbn/thefuck/682-instant-fuck-mode/example_instant_mode.gif
|
||||
[homebrew]: http://brew.sh/
|
||||
|
BIN
example_instant_mode.gif
Normal file
BIN
example_instant_mode.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 535 KiB |
3
setup.py
3
setup.py
@ -33,8 +33,9 @@ elif (3, 0) < version < (3, 3):
|
||||
|
||||
VERSION = '3.21'
|
||||
|
||||
install_requires = ['psutil', 'colorama', 'six', 'decorator']
|
||||
install_requires = ['psutil', 'colorama', 'six', 'decorator', 'pyte']
|
||||
extras_require = {':python_version<"3.4"': ['pathlib2'],
|
||||
':python_version<"3.3"': ['backports.shutil_get_terminal_size'],
|
||||
":sys_platform=='win32'": ['win_unicode_console']}
|
||||
|
||||
setup(name='thefuck',
|
||||
|
@ -6,7 +6,8 @@ from thefuck.const import ARGUMENT_PLACEHOLDER
|
||||
def _args(**override):
|
||||
args = {'alias': None, 'command': [], 'yes': False,
|
||||
'help': False, 'version': False, 'debug': False,
|
||||
'force_command': None, 'repeat': False}
|
||||
'force_command': None, 'repeat': False,
|
||||
'enable_experimental_instant_mode': False}
|
||||
args.update(override)
|
||||
return args
|
||||
|
||||
@ -14,6 +15,8 @@ def _args(**override):
|
||||
@pytest.mark.parametrize('argv, result', [
|
||||
(['thefuck'], _args()),
|
||||
(['thefuck', '-a'], _args(alias='fuck')),
|
||||
(['thefuck', '--alias', '--enable-experimental-instant-mode'],
|
||||
_args(alias='fuck', enable_experimental_instant_mode=True)),
|
||||
(['thefuck', '-a', 'fix'], _args(alias='fix')),
|
||||
(['thefuck', 'git', 'branch', ARGUMENT_PLACEHOLDER, '-y'],
|
||||
_args(command=['git', 'branch'], yes=True)),
|
||||
|
@ -110,13 +110,13 @@ class TestCommand(object):
|
||||
Popen = Mock()
|
||||
Popen.return_value.stdout.read.return_value = b'stdout'
|
||||
Popen.return_value.stderr.read.return_value = b'stderr'
|
||||
monkeypatch.setattr('thefuck.types.Popen', Popen)
|
||||
monkeypatch.setattr('thefuck.output_readers.rerun.Popen', Popen)
|
||||
return Popen
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def prepare(self, monkeypatch):
|
||||
monkeypatch.setattr('thefuck.types.Command._wait_output',
|
||||
staticmethod(lambda *_: True))
|
||||
monkeypatch.setattr('thefuck.output_readers.rerun._wait_output',
|
||||
lambda *_: True)
|
||||
|
||||
def test_from_script_calls(self, Popen, settings, os_environ):
|
||||
settings.env = {}
|
||||
|
@ -69,34 +69,40 @@ class TestSelectCommand(object):
|
||||
def test_without_confirmation(self, capsys, commands, settings):
|
||||
settings.require_confirmation = False
|
||||
assert ui.select_command(iter(commands)) == commands[0]
|
||||
assert capsys.readouterr() == ('', 'ls\n')
|
||||
assert capsys.readouterr() == ('', const.USER_COMMAND_MARK + 'ls\n')
|
||||
|
||||
def test_without_confirmation_with_side_effects(
|
||||
self, capsys, commands_with_side_effect, settings):
|
||||
settings.require_confirmation = False
|
||||
assert (ui.select_command(iter(commands_with_side_effect))
|
||||
== commands_with_side_effect[0])
|
||||
assert capsys.readouterr() == ('', 'ls (+side effect)\n')
|
||||
assert capsys.readouterr() == ('', const.USER_COMMAND_MARK + 'ls (+side effect)\n')
|
||||
|
||||
def test_with_confirmation(self, capsys, patch_get_key, commands):
|
||||
patch_get_key(['\n'])
|
||||
assert ui.select_command(iter(commands)) == commands[0]
|
||||
assert capsys.readouterr() == ('', u'\x1b[1K\rls [enter/↑/↓/ctrl+c]\n')
|
||||
assert capsys.readouterr() == (
|
||||
'', const.USER_COMMAND_MARK + u'\x1b[1K\rls [enter/↑/↓/ctrl+c]\n')
|
||||
|
||||
def test_with_confirmation_abort(self, capsys, patch_get_key, commands):
|
||||
patch_get_key([const.KEY_CTRL_C])
|
||||
assert ui.select_command(iter(commands)) is None
|
||||
assert capsys.readouterr() == ('', u'\x1b[1K\rls [enter/↑/↓/ctrl+c]\nAborted\n')
|
||||
assert capsys.readouterr() == (
|
||||
'', const.USER_COMMAND_MARK + u'\x1b[1K\rls [enter/↑/↓/ctrl+c]\nAborted\n')
|
||||
|
||||
def test_with_confirmation_with_side_effct(self, capsys, patch_get_key,
|
||||
commands_with_side_effect):
|
||||
patch_get_key(['\n'])
|
||||
assert (ui.select_command(iter(commands_with_side_effect))
|
||||
== commands_with_side_effect[0])
|
||||
assert capsys.readouterr() == ('', u'\x1b[1K\rls (+side effect) [enter/↑/↓/ctrl+c]\n')
|
||||
assert capsys.readouterr() == (
|
||||
'', const.USER_COMMAND_MARK + u'\x1b[1K\rls (+side effect) [enter/↑/↓/ctrl+c]\n')
|
||||
|
||||
def test_with_confirmation_select_second(self, capsys, patch_get_key, commands):
|
||||
patch_get_key([const.KEY_DOWN, '\n'])
|
||||
assert ui.select_command(iter(commands)) == commands[1]
|
||||
assert capsys.readouterr() == (
|
||||
'', u'\x1b[1K\rls [enter/↑/↓/ctrl+c]\x1b[1K\rcd [enter/↑/↓/ctrl+c]\n')
|
||||
stderr = (
|
||||
u'{mark}\x1b[1K\rls [enter/↑/↓/ctrl+c]'
|
||||
u'{mark}\x1b[1K\rcd [enter/↑/↓/ctrl+c]\n'
|
||||
).format(mark=const.USER_COMMAND_MARK)
|
||||
assert capsys.readouterr() == ('', stderr)
|
||||
|
@ -25,6 +25,10 @@ class Parser(object):
|
||||
nargs='?',
|
||||
const=get_alias(),
|
||||
help='[custom-alias-name] prints alias for current shell')
|
||||
self._parser.add_argument(
|
||||
'--enable-experimental-instant-mode',
|
||||
action='store_true',
|
||||
help='enable experimental instant mode, use on your own risk')
|
||||
self._parser.add_argument(
|
||||
'-h', '--help',
|
||||
action='store_true',
|
||||
|
@ -98,7 +98,7 @@ class Settings(dict):
|
||||
elif attr in ('wait_command', 'history_limit', 'wait_slow_command'):
|
||||
return int(val)
|
||||
elif attr in ('require_confirmation', 'no_colors', 'debug',
|
||||
'alter_history'):
|
||||
'alter_history', 'instant_mode'):
|
||||
return val.lower() == 'true'
|
||||
elif attr == 'slow_commands':
|
||||
return val.split(':')
|
||||
|
@ -35,6 +35,7 @@ DEFAULT_SETTINGS = {'rules': DEFAULT_RULES,
|
||||
'slow_commands': ['lein', 'react-native', 'gradle',
|
||||
'./gradlew', 'vagrant'],
|
||||
'repeat': False,
|
||||
'instant_mode': False,
|
||||
'env': {'LC_ALL': 'C', 'LANG': 'C', 'GIT_TRACE': '1'}}
|
||||
|
||||
ENV_TO_ATTR = {'THEFUCK_RULES': 'rules',
|
||||
@ -48,7 +49,8 @@ ENV_TO_ATTR = {'THEFUCK_RULES': 'rules',
|
||||
'THEFUCK_ALTER_HISTORY': 'alter_history',
|
||||
'THEFUCK_WAIT_SLOW_COMMAND': 'wait_slow_command',
|
||||
'THEFUCK_SLOW_COMMANDS': 'slow_commands',
|
||||
'THEFUCK_REPEAT': 'repeat'}
|
||||
'THEFUCK_REPEAT': 'repeat',
|
||||
'THEFUCK_INSTANT_MODE': 'instant_mode'}
|
||||
|
||||
SETTINGS_HEADER = u"""# The Fuck settings file
|
||||
#
|
||||
@ -65,3 +67,7 @@ SETTINGS_HEADER = u"""# The Fuck settings file
|
||||
ARGUMENT_PLACEHOLDER = 'THEFUCK_ARGUMENT_PLACEHOLDER'
|
||||
|
||||
CONFIGURATION_TIMEOUT = 60
|
||||
|
||||
USER_COMMAND_MARK = u'\u200B' * 10
|
||||
|
||||
LOG_SIZE = 1000
|
||||
|
@ -4,3 +4,7 @@ class EmptyCommand(Exception):
|
||||
|
||||
class NoRuleMatched(Exception):
|
||||
"""Raised when no rule matched for some command."""
|
||||
|
||||
|
||||
class ScriptNotInLog(Exception):
|
||||
"""Script not found in log."""
|
||||
|
@ -6,6 +6,7 @@ import sys
|
||||
from traceback import format_exception
|
||||
import colorama
|
||||
from .conf import settings
|
||||
from . import const
|
||||
|
||||
|
||||
def color(color_):
|
||||
@ -16,6 +17,14 @@ def color(color_):
|
||||
return color_
|
||||
|
||||
|
||||
def warn(title):
|
||||
sys.stderr.write(u'{warn}[WARN] {title}{reset}\n'.format(
|
||||
warn=color(colorama.Back.RED + colorama.Fore.WHITE
|
||||
+ colorama.Style.BRIGHT),
|
||||
reset=color(colorama.Style.RESET_ALL),
|
||||
title=title))
|
||||
|
||||
|
||||
def exception(title, exc_info):
|
||||
sys.stderr.write(
|
||||
u'{warn}[WARN] {title}:{reset}\n{trace}'
|
||||
@ -39,7 +48,8 @@ def failed(msg):
|
||||
|
||||
|
||||
def show_corrected_command(corrected_command):
|
||||
sys.stderr.write(u'{bold}{script}{reset}{side_effect}\n'.format(
|
||||
sys.stderr.write(u'{prefix}{bold}{script}{reset}{side_effect}\n'.format(
|
||||
prefix=const.USER_COMMAND_MARK,
|
||||
script=corrected_command.script,
|
||||
side_effect=u' (+side effect)' if corrected_command.side_effect else u'',
|
||||
bold=color(colorama.Style.BRIGHT),
|
||||
@ -48,9 +58,10 @@ def show_corrected_command(corrected_command):
|
||||
|
||||
def confirm_text(corrected_command):
|
||||
sys.stderr.write(
|
||||
(u'{clear}{bold}{script}{reset}{side_effect} '
|
||||
(u'{prefix}{clear}{bold}{script}{reset}{side_effect} '
|
||||
u'[{green}enter{reset}/{blue}↑{reset}/{blue}↓{reset}'
|
||||
u'/{red}ctrl+c{reset}]').format(
|
||||
prefix=const.USER_COMMAND_MARK,
|
||||
script=corrected_command.script,
|
||||
side_effect=' (+side effect)' if corrected_command.side_effect else '',
|
||||
clear='\033[1K\r',
|
||||
|
@ -5,6 +5,7 @@ init_output()
|
||||
|
||||
from pprint import pformat # noqa: E402
|
||||
import sys # noqa: E402
|
||||
import six # noqa: E402
|
||||
from . import logs, types # noqa: E402
|
||||
from .shells import shell # noqa: E402
|
||||
from .conf import settings # noqa: E402
|
||||
@ -13,6 +14,7 @@ from .exceptions import EmptyCommand # noqa: E402
|
||||
from .ui import select_command # noqa: E402
|
||||
from .argument_parser import Parser # noqa: E402
|
||||
from .utils import get_installation_info # noqa: E402
|
||||
from .logs import warn # noqa: E402
|
||||
|
||||
|
||||
def fix_command(known_args):
|
||||
@ -50,6 +52,15 @@ def main():
|
||||
elif known_args.command:
|
||||
fix_command(known_args)
|
||||
elif known_args.alias:
|
||||
print(shell.app_alias(known_args.alias))
|
||||
if known_args.enable_experimental_instant_mode:
|
||||
if six.PY2:
|
||||
warn("Instant mode not supported with Python 2")
|
||||
alias = shell.app_alias(known_args.alias)
|
||||
else:
|
||||
alias = shell.instant_mode_alias(known_args.alias)
|
||||
else:
|
||||
alias = shell.app_alias(known_args.alias)
|
||||
|
||||
print(alias)
|
||||
else:
|
||||
parser.print_usage()
|
||||
|
18
thefuck/output_readers/__init__.py
Normal file
18
thefuck/output_readers/__init__.py
Normal file
@ -0,0 +1,18 @@
|
||||
from ..conf import settings
|
||||
from . import read_log, rerun
|
||||
|
||||
|
||||
def get_output(script, expanded):
|
||||
"""Get output of the script.
|
||||
|
||||
:param script: Console script.
|
||||
:type script: str
|
||||
:param expanded: Console script with expanded aliases.
|
||||
:type expanded: str
|
||||
:rtype: (str, str)
|
||||
|
||||
"""
|
||||
if settings.instant_mode:
|
||||
return read_log.get_output(script)
|
||||
else:
|
||||
return rerun.get_output(script, expanded)
|
82
thefuck/output_readers/read_log.py
Normal file
82
thefuck/output_readers/read_log.py
Normal file
@ -0,0 +1,82 @@
|
||||
import os
|
||||
import shlex
|
||||
try:
|
||||
from shutil import get_terminal_size
|
||||
except ImportError:
|
||||
from backports.shutil_get_terminal_size import get_terminal_size
|
||||
import six
|
||||
import pyte
|
||||
from ..exceptions import ScriptNotInLog
|
||||
from ..logs import warn
|
||||
from .. import const
|
||||
|
||||
|
||||
def _group_by_calls(log):
|
||||
script_line = None
|
||||
lines = []
|
||||
for line in log:
|
||||
try:
|
||||
line = line.decode()
|
||||
except UnicodeDecodeError:
|
||||
continue
|
||||
|
||||
if const.USER_COMMAND_MARK in line:
|
||||
if script_line:
|
||||
yield script_line, lines
|
||||
|
||||
script_line = line
|
||||
lines = [line]
|
||||
elif script_line is not None:
|
||||
lines.append(line)
|
||||
|
||||
if script_line:
|
||||
yield script_line, lines
|
||||
|
||||
|
||||
def _get_script_group_lines(grouped, script):
|
||||
parts = shlex.split(script)
|
||||
|
||||
for script_line, lines in reversed(grouped):
|
||||
if all(part in script_line for part in parts):
|
||||
return lines
|
||||
|
||||
raise ScriptNotInLog
|
||||
|
||||
|
||||
def _get_output_lines(script, log_file):
|
||||
lines = log_file.readlines()[-const.LOG_SIZE:]
|
||||
grouped = list(_group_by_calls(lines))
|
||||
script_lines = _get_script_group_lines(grouped, script)
|
||||
|
||||
screen = pyte.Screen(get_terminal_size().columns, len(script_lines))
|
||||
stream = pyte.Stream(screen)
|
||||
stream.feed(''.join(script_lines))
|
||||
return screen.display
|
||||
|
||||
|
||||
def get_output(script):
|
||||
"""Reads script output from log.
|
||||
|
||||
:type script: str
|
||||
:rtype: (str, str)
|
||||
|
||||
"""
|
||||
if six.PY2:
|
||||
warn('Experimental instant mode is Python 3+ only')
|
||||
return None, None
|
||||
|
||||
if 'THEFUCK_OUTPUT_LOG' not in os.environ:
|
||||
warn("Output log isn't specified")
|
||||
return None, None
|
||||
|
||||
try:
|
||||
with open(os.environ['THEFUCK_OUTPUT_LOG'], 'rb') as log_file:
|
||||
lines = _get_output_lines(script, log_file)
|
||||
output = '\n'.join(lines).strip()
|
||||
return output, output
|
||||
except OSError:
|
||||
warn("Can't read output log")
|
||||
return None, None
|
||||
except ScriptNotInLog:
|
||||
warn("Script not found in output log")
|
||||
return None, None
|
57
thefuck/output_readers/rerun.py
Normal file
57
thefuck/output_readers/rerun.py
Normal file
@ -0,0 +1,57 @@
|
||||
import os
|
||||
import shlex
|
||||
from subprocess import Popen, PIPE
|
||||
from psutil import Process, TimeoutExpired
|
||||
from .. import logs
|
||||
from ..conf import settings
|
||||
|
||||
|
||||
def _wait_output(popen, is_slow):
|
||||
"""Returns `True` if we can get output of the command in the
|
||||
`settings.wait_command` time.
|
||||
|
||||
Command will be killed if it wasn't finished in the time.
|
||||
|
||||
:type popen: Popen
|
||||
:rtype: bool
|
||||
|
||||
"""
|
||||
proc = Process(popen.pid)
|
||||
try:
|
||||
proc.wait(settings.wait_slow_command if is_slow
|
||||
else settings.wait_command)
|
||||
return True
|
||||
except TimeoutExpired:
|
||||
for child in proc.children(recursive=True):
|
||||
child.kill()
|
||||
proc.kill()
|
||||
return False
|
||||
|
||||
|
||||
def get_output(script, expanded):
|
||||
"""Runs the script and obtains stdin/stderr.
|
||||
|
||||
:type script: str
|
||||
:type expanded: str
|
||||
:rtype: (str, str)
|
||||
|
||||
"""
|
||||
env = dict(os.environ)
|
||||
env.update(settings.env)
|
||||
|
||||
is_slow = shlex.split(expanded) in settings.slow_commands
|
||||
with logs.debug_time(u'Call: {}; with env: {}; is slow: '.format(
|
||||
script, env, is_slow)):
|
||||
result = Popen(expanded, shell=True, stdin=PIPE,
|
||||
stdout=PIPE, stderr=PIPE, env=env)
|
||||
if _wait_output(result, is_slow):
|
||||
stdout = result.stdout.read().decode('utf-8')
|
||||
stderr = result.stderr.read().decode('utf-8')
|
||||
|
||||
logs.debug(u'Received stdout: {}'.format(stdout))
|
||||
logs.debug(u'Received stderr: {}'.format(stderr))
|
||||
|
||||
return stdout, stderr
|
||||
else:
|
||||
logs.debug(u'Execution timed out!')
|
||||
return None, None
|
@ -1,3 +1,4 @@
|
||||
import re
|
||||
from thefuck.utils import replace_argument
|
||||
from thefuck.specific.git import git_support
|
||||
|
||||
@ -32,5 +33,6 @@ def get_new_command(command):
|
||||
if len(command_parts) > upstream_option_index:
|
||||
command_parts.pop(upstream_option_index)
|
||||
|
||||
push_upstream = command.stderr.split('\n')[-3].strip().partition('git ')[2]
|
||||
return replace_argument(" ".join(command_parts), 'push', push_upstream)
|
||||
arguments = re.findall(r'git push (.*)', command.stderr)[0].strip()
|
||||
return replace_argument(" ".join(command_parts), 'push',
|
||||
'push {}'.format(arguments))
|
||||
|
@ -1,6 +1,7 @@
|
||||
import os
|
||||
from uuid import uuid4
|
||||
from ..conf import settings
|
||||
from ..const import ARGUMENT_PLACEHOLDER
|
||||
from ..const import ARGUMENT_PLACEHOLDER, USER_COMMAND_MARK
|
||||
from ..utils import memoize
|
||||
from .generic import Generic
|
||||
|
||||
@ -27,6 +28,21 @@ class Bash(Generic):
|
||||
alter_history=('history -s $TF_CMD;'
|
||||
if settings.alter_history else ''))
|
||||
|
||||
def instant_mode_alias(self, alias_name):
|
||||
if os.environ.get('THEFUCK_INSTANT_MODE', '').lower() == 'true':
|
||||
return '''
|
||||
export PS1="{user_command_mark}$PS1";
|
||||
{app_alias}
|
||||
'''.format(user_command_mark=USER_COMMAND_MARK,
|
||||
app_alias=self.app_alias(alias_name))
|
||||
else:
|
||||
return '''
|
||||
export THEFUCK_INSTANT_MODE=True;
|
||||
export THEFUCK_OUTPUT_LOG={log};
|
||||
script -feq {log};
|
||||
exit
|
||||
'''.format(log='/tmp/thefuck-script-log-{}'.format(uuid4().hex))
|
||||
|
||||
def _parse_alias(self, alias):
|
||||
name, value = alias.replace('alias ', '', 1).split('=', 1)
|
||||
if value[0] == value[-1] == '"' or value[0] == value[-1] == "'":
|
||||
|
@ -18,7 +18,7 @@ class Fish(Generic):
|
||||
default.add(alias.strip())
|
||||
return default
|
||||
|
||||
def app_alias(self, fuck):
|
||||
def app_alias(self, alias_name):
|
||||
if settings.alter_history:
|
||||
alter_history = (' builtin history delete --exact'
|
||||
' --case-sensitive -- $fucked_up_command\n'
|
||||
@ -33,7 +33,7 @@ class Fish(Generic):
|
||||
' if [ "$unfucked_command" != "" ]\n'
|
||||
' eval $unfucked_command\n{1}'
|
||||
' end\n'
|
||||
'end').format(fuck, alter_history)
|
||||
'end').format(alias_name, alter_history)
|
||||
|
||||
@memoize
|
||||
@cache('.config/fish/config.fish', '.config/fish/functions')
|
||||
|
@ -3,6 +3,7 @@ import os
|
||||
import shlex
|
||||
import six
|
||||
from collections import namedtuple
|
||||
from ..logs import warn
|
||||
from ..utils import memoize
|
||||
from ..conf import settings
|
||||
from ..system import Path
|
||||
@ -32,9 +33,13 @@ class Generic(object):
|
||||
"""Prepares command for running in shell."""
|
||||
return command_script
|
||||
|
||||
def app_alias(self, fuck):
|
||||
def app_alias(self, alias_name):
|
||||
return "alias {0}='eval $(TF_ALIAS={0} PYTHONIOENCODING=utf-8 " \
|
||||
"thefuck $(fc -ln -1))'".format(fuck)
|
||||
"thefuck $(fc -ln -1))'".format(alias_name)
|
||||
|
||||
def instant_mode_alias(self, alias_name):
|
||||
warn("Instant mode not supported by your shell")
|
||||
return self.app_alias(alias_name)
|
||||
|
||||
def _get_history_file_name(self):
|
||||
return ''
|
||||
|
@ -2,8 +2,8 @@ from .generic import Generic, ShellConfiguration
|
||||
|
||||
|
||||
class Powershell(Generic):
|
||||
def app_alias(self, fuck):
|
||||
return 'function ' + fuck + ' {\n' \
|
||||
def app_alias(self, alias_name):
|
||||
return 'function ' + alias_name + ' {\n' \
|
||||
' $history = (Get-History -Count 1).CommandLine;\n' \
|
||||
' if (-not [string]::IsNullOrWhiteSpace($history)) {\n' \
|
||||
' $fuck = $(thefuck $history);\n' \
|
||||
|
@ -6,10 +6,10 @@ from .generic import Generic
|
||||
|
||||
|
||||
class Tcsh(Generic):
|
||||
def app_alias(self, fuck):
|
||||
def app_alias(self, alias_name):
|
||||
return ("alias {0} 'setenv TF_ALIAS {0} && "
|
||||
"set fucked_cmd=`history -h 2 | head -n 1` && "
|
||||
"eval `thefuck ${{fucked_cmd}}`'").format(fuck)
|
||||
"eval `thefuck ${{fucked_cmd}}`'").format(alias_name)
|
||||
|
||||
def _parse_alias(self, alias):
|
||||
name, value = alias.split("\t", 1)
|
||||
|
@ -1,7 +1,8 @@
|
||||
from time import time
|
||||
import os
|
||||
from uuid import uuid4
|
||||
from ..conf import settings
|
||||
from ..const import ARGUMENT_PLACEHOLDER
|
||||
from ..const import ARGUMENT_PLACEHOLDER, USER_COMMAND_MARK
|
||||
from ..utils import memoize
|
||||
from .generic import Generic
|
||||
|
||||
@ -26,6 +27,21 @@ class Zsh(Generic):
|
||||
alter_history=('test -n "$TF_CMD" && print -s $TF_CMD'
|
||||
if settings.alter_history else ''))
|
||||
|
||||
def instant_mode_alias(self, alias_name):
|
||||
if os.environ.get('THEFUCK_INSTANT_MODE', '').lower() == 'true':
|
||||
return '''
|
||||
export PS1="{user_command_mark}$PS1";
|
||||
{app_alias}
|
||||
'''.format(user_command_mark=USER_COMMAND_MARK,
|
||||
app_alias=self.app_alias(alias_name))
|
||||
else:
|
||||
return '''
|
||||
export THEFUCK_INSTANT_MODE=True;
|
||||
export THEFUCK_OUTPUT_LOG={log};
|
||||
script -feq {log};
|
||||
exit
|
||||
'''.format(log='/tmp/thefuck-script-log-{}'.format(uuid4().hex))
|
||||
|
||||
def _parse_alias(self, alias):
|
||||
name, value = alias.split('=', 1)
|
||||
if value[0] == value[-1] == '"' or value[0] == value[-1] == "'":
|
||||
|
@ -1,15 +1,13 @@
|
||||
from imp import load_source
|
||||
from subprocess import Popen, PIPE
|
||||
import os
|
||||
import sys
|
||||
import six
|
||||
from psutil import Process, TimeoutExpired
|
||||
from . import logs
|
||||
from .shells import shell
|
||||
from .conf import settings
|
||||
from .const import DEFAULT_PRIORITY, ALL_ENABLED
|
||||
from .exceptions import EmptyCommand
|
||||
from .utils import get_alias
|
||||
from .utils import get_alias, format_raw_script
|
||||
from .output_readers import get_output
|
||||
|
||||
|
||||
class Command(object):
|
||||
@ -61,44 +59,6 @@ class Command(object):
|
||||
kwargs.setdefault('stderr', self.stderr)
|
||||
return Command(**kwargs)
|
||||
|
||||
@staticmethod
|
||||
def _wait_output(popen, is_slow):
|
||||
"""Returns `True` if we can get output of the command in the
|
||||
`settings.wait_command` time.
|
||||
|
||||
Command will be killed if it wasn't finished in the time.
|
||||
|
||||
:type popen: Popen
|
||||
:rtype: bool
|
||||
|
||||
"""
|
||||
proc = Process(popen.pid)
|
||||
try:
|
||||
proc.wait(settings.wait_slow_command if is_slow
|
||||
else settings.wait_command)
|
||||
return True
|
||||
except TimeoutExpired:
|
||||
for child in proc.children(recursive=True):
|
||||
child.kill()
|
||||
proc.kill()
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def _prepare_script(raw_script):
|
||||
"""Creates single script from a list of script parts.
|
||||
|
||||
:type raw_script: [basestring]
|
||||
:rtype: basestring
|
||||
|
||||
"""
|
||||
if six.PY2:
|
||||
script = ' '.join(arg.decode('utf-8') for arg in raw_script)
|
||||
else:
|
||||
script = ' '.join(raw_script)
|
||||
|
||||
script = script.strip()
|
||||
return shell.from_shell(script)
|
||||
|
||||
@classmethod
|
||||
def from_raw_script(cls, raw_script):
|
||||
"""Creates instance of `Command` from a list of script parts.
|
||||
@ -108,29 +68,13 @@ class Command(object):
|
||||
:raises: EmptyCommand
|
||||
|
||||
"""
|
||||
script = cls._prepare_script(raw_script)
|
||||
script = format_raw_script(raw_script)
|
||||
if not script:
|
||||
raise EmptyCommand
|
||||
|
||||
env = dict(os.environ)
|
||||
env.update(settings.env)
|
||||
|
||||
is_slow = script.split(' ')[0] in settings.slow_commands
|
||||
with logs.debug_time(u'Call: {}; with env: {}; is slow: '.format(
|
||||
script, env, is_slow)):
|
||||
result = Popen(script, shell=True, stdin=PIPE,
|
||||
stdout=PIPE, stderr=PIPE, env=env)
|
||||
if cls._wait_output(result, is_slow):
|
||||
stdout = result.stdout.read().decode('utf-8')
|
||||
stderr = result.stderr.read().decode('utf-8')
|
||||
|
||||
logs.debug(u'Received stdout: {}'.format(stdout))
|
||||
logs.debug(u'Received stderr: {}'.format(stderr))
|
||||
|
||||
return cls(script, stdout, stderr)
|
||||
else:
|
||||
logs.debug(u'Execution timed out!')
|
||||
return cls(script, None, None)
|
||||
expanded = shell.from_shell(script)
|
||||
stdout, stderr = get_output(script, expanded)
|
||||
return cls(expanded, stdout, stderr)
|
||||
|
||||
|
||||
class Rule(object):
|
||||
|
@ -7,7 +7,7 @@ from contextlib import closing
|
||||
from decorator import decorator
|
||||
from difflib import get_close_matches
|
||||
from functools import wraps
|
||||
from warnings import warn
|
||||
from .logs import warn
|
||||
from .conf import settings
|
||||
from .system import Path
|
||||
|
||||
@ -282,3 +282,18 @@ def get_valid_history_without_current(command):
|
||||
return [line for line in _not_corrected(history, tf_alias)
|
||||
if not line.startswith(tf_alias) and not line == command.script
|
||||
and line.split(' ')[0] in executables]
|
||||
|
||||
|
||||
def format_raw_script(raw_script):
|
||||
"""Creates single script from a list of script parts.
|
||||
|
||||
:type raw_script: [basestring]
|
||||
:rtype: basestring
|
||||
|
||||
"""
|
||||
if six.PY2:
|
||||
script = ' '.join(arg.decode('utf-8') for arg in raw_script)
|
||||
else:
|
||||
script = ' '.join(raw_script)
|
||||
|
||||
return script.strip()
|
||||
|
Loading…
x
Reference in New Issue
Block a user