mirror of
https://github.com/nvbn/thefuck.git
synced 2025-03-14 14:48:49 +00:00
Merge 0ddcb15c96f8500e1217dcf2237c1ebd7e1cbdb6 into a0286b402a8a22b912b30e21042e08f4f26d9886
This commit is contained in:
commit
c2c632ccc8
@ -141,6 +141,12 @@ eval $(thefuck --alias)
|
||||
eval $(thefuck --alias FUCK)
|
||||
```
|
||||
|
||||
If you want to enable the experimental smart rule, place this command in your `.bash_profile`, `.bashrc`, `.zshrc` or other startup script:
|
||||
|
||||
```bash
|
||||
eval $(thefuck --enable-experimental-shell-history)
|
||||
```
|
||||
|
||||
[Or in your shell config (Bash, Zsh, Fish, Powershell, tcsh).](https://github.com/nvbn/thefuck/wiki/Shell-aliases)
|
||||
|
||||
Changes are only available in a new shell session. To make changes immediately
|
||||
@ -321,6 +327,7 @@ default:
|
||||
|
||||
* `git_push_force` – adds `--force-with-lease` to a `git push` (may conflict with `git_push_pull`);
|
||||
* `rm_root` – adds `--no-preserve-root` to `rm -rf /` command.
|
||||
* `smart_rule`; returns recommended commands based on user corrected commands.
|
||||
|
||||
## Creating your own rules
|
||||
|
||||
|
34
tests/rules/test_smart_rule.py
Normal file
34
tests/rules/test_smart_rule.py
Normal file
@ -0,0 +1,34 @@
|
||||
import pytest
|
||||
from mock import patch, MagicMock
|
||||
from thefuck.rules.smart_rule import match, get_new_command
|
||||
from thefuck.types import Command
|
||||
|
||||
|
||||
def test_match_simple():
|
||||
assert match('')
|
||||
|
||||
|
||||
@pytest.mark.parametrize('script, socket_response, new_command', [
|
||||
('git push', [b'\x03', b'\x25', b'git push --set-upstream origin master', b'\x16',
|
||||
b'git push origin master', b'\x17', b'git push origin develop'],
|
||||
['git push --set-upstream origin master', 'git push origin master', 'git push origin develop']),
|
||||
('ls', [b'\x01', b'\x06', b'ls -la'], ['ls -la'])
|
||||
])
|
||||
@patch('thefuck.rules.smart_rule.socket')
|
||||
def test_get_new_command(socket_mock, script, socket_response, new_command):
|
||||
sock_mock = MagicMock()
|
||||
recv_mock = MagicMock(side_effect=socket_response)
|
||||
socket_mock.socket.return_value = sock_mock
|
||||
sock_mock.recv = recv_mock
|
||||
returned_commands = get_new_command(Command(script, None))
|
||||
assert returned_commands == new_command
|
||||
|
||||
|
||||
@patch('thefuck.rules.smart_rule.socket')
|
||||
def test_socket_open_close_connect(socket_mock):
|
||||
sock_mock = MagicMock()
|
||||
socket_mock.socket.return_value = sock_mock
|
||||
get_new_command('')
|
||||
socket_mock.socket.assert_called_once()
|
||||
sock_mock.connect.assert_called_once()
|
||||
sock_mock.close.assert_called_once()
|
@ -8,6 +8,7 @@ def _args(**override):
|
||||
'help': False, 'version': False, 'debug': False,
|
||||
'force_command': None, 'repeat': False,
|
||||
'enable_experimental_instant_mode': False,
|
||||
'enable_experimental_shell_history': False,
|
||||
'shell_logger': None}
|
||||
args.update(override)
|
||||
return args
|
||||
@ -18,6 +19,8 @@ def _args(**override):
|
||||
(['thefuck', '-a'], _args(alias='fuck')),
|
||||
(['thefuck', '--alias', '--enable-experimental-instant-mode'],
|
||||
_args(alias='fuck', enable_experimental_instant_mode=True)),
|
||||
(['thefuck', '--enable-experimental-shell-history'],
|
||||
_args(enable_experimental_shell_history=True)),
|
||||
(['thefuck', '-a', 'fix'], _args(alias='fix')),
|
||||
(['thefuck', 'git', 'branch', ARGUMENT_PLACEHOLDER, '-y'],
|
||||
_args(command=['git', 'branch'], yes=True)),
|
||||
|
@ -33,6 +33,10 @@ class Parser(object):
|
||||
'--enable-experimental-instant-mode',
|
||||
action='store_true',
|
||||
help='enable experimental instant mode, use on your own risk')
|
||||
self._parser.add_argument(
|
||||
'--enable-experimental-shell-history',
|
||||
action='store_true',
|
||||
help='enable experimental shell history')
|
||||
self._parser.add_argument(
|
||||
'-h', '--help',
|
||||
action='store_true',
|
||||
|
@ -19,6 +19,7 @@ class Settings(dict):
|
||||
from .logs import exception
|
||||
|
||||
self._setup_user_dir()
|
||||
self._setup_data_dir()
|
||||
self._init_settings_file()
|
||||
|
||||
try:
|
||||
@ -64,6 +65,18 @@ class Settings(dict):
|
||||
rules_dir.mkdir(parents=True)
|
||||
self.user_dir = user_dir
|
||||
|
||||
def _get_user_data_path(self):
|
||||
"""Return Path object representing the user local resource"""
|
||||
xdg_config_home = os.environ.get('XDG_DATA_HOME', '~/.local/share')
|
||||
user_dir = Path(xdg_config_home, 'thefuck').expanduser()
|
||||
return user_dir
|
||||
|
||||
def _setup_data_dir(self):
|
||||
data_dir = self._get_user_data_path()
|
||||
if not data_dir.is_dir():
|
||||
data_dir.mkdir(parents=True)
|
||||
self.data_dir = data_dir
|
||||
|
||||
def _settings_from_file(self):
|
||||
"""Loads settings from file."""
|
||||
settings = load_source(
|
||||
|
@ -28,6 +28,12 @@ ALL_ENABLED = _GenConst('All rules enabled')
|
||||
DEFAULT_RULES = [ALL_ENABLED]
|
||||
DEFAULT_PRIORITY = 1000
|
||||
|
||||
SHELL_LOGGER_SOCKET_ENV_VAR = '__SHELL_LOGGER_SOCKET'
|
||||
SHELL_LOGGER_DB_ENV_VAR = '__SHELL_LOGGER_DB_PATH'
|
||||
SHELL_LOGGER_DB_FILENAME = 'my.db'
|
||||
SHELL_LOGGER_SOCKET_PATH = '/tmp/tf_socket'
|
||||
SHELL_LOGGER_BINARY_FILENAME = 'shell_logger'
|
||||
|
||||
DEFAULT_SETTINGS = {'rules': DEFAULT_RULES,
|
||||
'exclude_rules': [],
|
||||
'wait_command': 3,
|
||||
|
@ -1,5 +1,17 @@
|
||||
import subprocess
|
||||
import urllib.request
|
||||
|
||||
import os
|
||||
import sys
|
||||
import six
|
||||
from ..logs import warn
|
||||
import psutil
|
||||
from psutil import ZombieProcess
|
||||
|
||||
from ..conf import settings
|
||||
from ..system import get_shell_logger_bname_from_sys
|
||||
from ..const import SHELL_LOGGER_SOCKET_ENV_VAR, SHELL_LOGGER_SOCKET_PATH, \
|
||||
SHELL_LOGGER_BINARY_FILENAME, SHELL_LOGGER_DB_FILENAME, SHELL_LOGGER_DB_ENV_VAR
|
||||
from ..logs import warn, debug
|
||||
from ..shells import shell
|
||||
from ..utils import which
|
||||
|
||||
@ -24,3 +36,30 @@ def _get_alias(known_args):
|
||||
|
||||
def print_alias(known_args):
|
||||
print(_get_alias(known_args))
|
||||
|
||||
|
||||
def print_experimental_shell_history(known_args):
|
||||
settings.init(known_args)
|
||||
|
||||
filename_suffix = get_shell_logger_bname_from_sys()
|
||||
client_release = 'https://github.com/nvbn/shell_logger/releases/download/0.1.0a1/shell_logger_{}'\
|
||||
.format(filename_suffix)
|
||||
binary_path = '{}/{}'.format(settings.data_dir, SHELL_LOGGER_BINARY_FILENAME)
|
||||
db_path = '{}/{}'.format(settings.data_dir, SHELL_LOGGER_DB_FILENAME)
|
||||
|
||||
debug('Downloading the shell_logger release and putting it in the path ... ')
|
||||
urllib.request.urlretrieve(client_release, binary_path)
|
||||
|
||||
subprocess.Popen(['chmod', '+x', binary_path])
|
||||
|
||||
my_env = os.environ.copy()
|
||||
my_env[SHELL_LOGGER_DB_ENV_VAR] = db_path
|
||||
proc = subprocess.Popen([binary_path, '-mode', 'configure'], stdout=subprocess.PIPE,
|
||||
env=my_env)
|
||||
print(''.join([line.decode() for line in proc.stdout.readlines()]))
|
||||
# TODO seems like daemon returns something, so redirect stdout so eval doesn't hang
|
||||
subprocess.Popen(["{} -mode daemon &".format(binary_path)], shell=True,
|
||||
env={SHELL_LOGGER_SOCKET_ENV_VAR: SHELL_LOGGER_SOCKET_PATH,
|
||||
SHELL_LOGGER_DB_ENV_VAR: db_path}, stdout=2)
|
||||
|
||||
|
||||
|
@ -8,7 +8,7 @@ import sys # noqa: E402
|
||||
from .. import logs # noqa: E402
|
||||
from ..argument_parser import Parser # noqa: E402
|
||||
from ..utils import get_installation_info # noqa: E402
|
||||
from .alias import print_alias # noqa: E402
|
||||
from .alias import print_alias, print_experimental_shell_history # noqa: E402
|
||||
from .fix_command import fix_command # noqa: E402
|
||||
|
||||
|
||||
@ -32,5 +32,8 @@ def main():
|
||||
logs.warn('Shell logger supports only Linux and macOS')
|
||||
else:
|
||||
shell_logger(known_args.shell_logger)
|
||||
elif known_args.enable_experimental_shell_history:
|
||||
print_experimental_shell_history(known_args)
|
||||
else:
|
||||
parser.print_usage()
|
||||
|
||||
|
28
thefuck/rules/smart_rule.py
Normal file
28
thefuck/rules/smart_rule.py
Normal file
@ -0,0 +1,28 @@
|
||||
import sys
|
||||
import socket
|
||||
|
||||
from thefuck.logs import debug
|
||||
from thefuck.const import SHELL_LOGGER_SOCKET_PATH
|
||||
|
||||
|
||||
def match(command):
|
||||
return True
|
||||
|
||||
|
||||
def get_new_command(command):
|
||||
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
list_of_commands = []
|
||||
try:
|
||||
sock.connect(SHELL_LOGGER_SOCKET_PATH)
|
||||
sock.send(command.script.encode())
|
||||
number_of_strings = int.from_bytes(sock.recv(1), sys.byteorder)
|
||||
for i in range(number_of_strings):
|
||||
length_of_string = int.from_bytes(sock.recv(1), sys.byteorder)
|
||||
list_of_commands.append(sock.recv(length_of_string).decode())
|
||||
finally:
|
||||
sock.close()
|
||||
return list_of_commands
|
||||
|
||||
|
||||
# Make last priority
|
||||
priority = 10000
|
@ -5,3 +5,14 @@ if sys.platform == 'win32':
|
||||
from .win32 import * # noqa: F401,F403
|
||||
else:
|
||||
from .unix import * # noqa: F401,F403
|
||||
|
||||
|
||||
def get_shell_logger_bname_from_sys():
|
||||
"""Return the binary name associated with the current system"""
|
||||
platform = sys.platform
|
||||
if "darwin" in platform:
|
||||
return "darwin64"
|
||||
elif "linux" in platform:
|
||||
return "linux64"
|
||||
else:
|
||||
return "windows64.exe"
|
||||
|
Loading…
x
Reference in New Issue
Block a user