1
0
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:
Sameet Sapra 2018-10-02 20:17:10 +00:00 committed by GitHub
commit c2c632ccc8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 150 additions and 2 deletions

View File

@ -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

View 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()

View File

@ -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)),

View File

@ -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',

View File

@ -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(

View File

@ -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,

View File

@ -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)

View File

@ -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()

View 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

View File

@ -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"