mirror of
https://github.com/nvbn/thefuck.git
synced 2025-03-19 17:18:42 +00:00
Merge 0ddcb15c96f8500e1217dcf2237c1ebd7e1cbdb6 into a0286b402a8a22b912b30e21042e08f4f26d9886
This commit is contained in:
commit
c2c632ccc8
@ -141,6 +141,12 @@ eval $(thefuck --alias)
|
|||||||
eval $(thefuck --alias FUCK)
|
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)
|
[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
|
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`);
|
* `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.
|
* `rm_root` – adds `--no-preserve-root` to `rm -rf /` command.
|
||||||
|
* `smart_rule`; returns recommended commands based on user corrected commands.
|
||||||
|
|
||||||
## Creating your own rules
|
## 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,
|
'help': False, 'version': False, 'debug': False,
|
||||||
'force_command': None, 'repeat': False,
|
'force_command': None, 'repeat': False,
|
||||||
'enable_experimental_instant_mode': False,
|
'enable_experimental_instant_mode': False,
|
||||||
|
'enable_experimental_shell_history': False,
|
||||||
'shell_logger': None}
|
'shell_logger': None}
|
||||||
args.update(override)
|
args.update(override)
|
||||||
return args
|
return args
|
||||||
@ -18,6 +19,8 @@ def _args(**override):
|
|||||||
(['thefuck', '-a'], _args(alias='fuck')),
|
(['thefuck', '-a'], _args(alias='fuck')),
|
||||||
(['thefuck', '--alias', '--enable-experimental-instant-mode'],
|
(['thefuck', '--alias', '--enable-experimental-instant-mode'],
|
||||||
_args(alias='fuck', enable_experimental_instant_mode=True)),
|
_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', '-a', 'fix'], _args(alias='fix')),
|
||||||
(['thefuck', 'git', 'branch', ARGUMENT_PLACEHOLDER, '-y'],
|
(['thefuck', 'git', 'branch', ARGUMENT_PLACEHOLDER, '-y'],
|
||||||
_args(command=['git', 'branch'], yes=True)),
|
_args(command=['git', 'branch'], yes=True)),
|
||||||
|
@ -33,6 +33,10 @@ class Parser(object):
|
|||||||
'--enable-experimental-instant-mode',
|
'--enable-experimental-instant-mode',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help='enable experimental instant mode, use on your own risk')
|
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(
|
self._parser.add_argument(
|
||||||
'-h', '--help',
|
'-h', '--help',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
|
@ -19,6 +19,7 @@ class Settings(dict):
|
|||||||
from .logs import exception
|
from .logs import exception
|
||||||
|
|
||||||
self._setup_user_dir()
|
self._setup_user_dir()
|
||||||
|
self._setup_data_dir()
|
||||||
self._init_settings_file()
|
self._init_settings_file()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -64,6 +65,18 @@ class Settings(dict):
|
|||||||
rules_dir.mkdir(parents=True)
|
rules_dir.mkdir(parents=True)
|
||||||
self.user_dir = user_dir
|
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):
|
def _settings_from_file(self):
|
||||||
"""Loads settings from file."""
|
"""Loads settings from file."""
|
||||||
settings = load_source(
|
settings = load_source(
|
||||||
|
@ -28,6 +28,12 @@ ALL_ENABLED = _GenConst('All rules enabled')
|
|||||||
DEFAULT_RULES = [ALL_ENABLED]
|
DEFAULT_RULES = [ALL_ENABLED]
|
||||||
DEFAULT_PRIORITY = 1000
|
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,
|
DEFAULT_SETTINGS = {'rules': DEFAULT_RULES,
|
||||||
'exclude_rules': [],
|
'exclude_rules': [],
|
||||||
'wait_command': 3,
|
'wait_command': 3,
|
||||||
|
@ -1,5 +1,17 @@
|
|||||||
|
import subprocess
|
||||||
|
import urllib.request
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
import six
|
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 ..shells import shell
|
||||||
from ..utils import which
|
from ..utils import which
|
||||||
|
|
||||||
@ -24,3 +36,30 @@ def _get_alias(known_args):
|
|||||||
|
|
||||||
def print_alias(known_args):
|
def print_alias(known_args):
|
||||||
print(_get_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 .. import logs # noqa: E402
|
||||||
from ..argument_parser import Parser # noqa: E402
|
from ..argument_parser import Parser # noqa: E402
|
||||||
from ..utils import get_installation_info # 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
|
from .fix_command import fix_command # noqa: E402
|
||||||
|
|
||||||
|
|
||||||
@ -32,5 +32,8 @@ def main():
|
|||||||
logs.warn('Shell logger supports only Linux and macOS')
|
logs.warn('Shell logger supports only Linux and macOS')
|
||||||
else:
|
else:
|
||||||
shell_logger(known_args.shell_logger)
|
shell_logger(known_args.shell_logger)
|
||||||
|
elif known_args.enable_experimental_shell_history:
|
||||||
|
print_experimental_shell_history(known_args)
|
||||||
else:
|
else:
|
||||||
parser.print_usage()
|
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
|
from .win32 import * # noqa: F401,F403
|
||||||
else:
|
else:
|
||||||
from .unix import * # noqa: F401,F403
|
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