1
0
mirror of https://github.com/nvbn/thefuck.git synced 2025-02-21 20:38:54 +00:00

#414: Move system-dependent utils in system module

This commit is contained in:
nvbn 2015-12-03 20:03:27 +08:00
parent 0560f4ba8e
commit 29c1d1efcf
7 changed files with 120 additions and 89 deletions

View File

@ -4,37 +4,32 @@ import pytest
from itertools import islice from itertools import islice
from thefuck import ui from thefuck import ui
from thefuck.types import CorrectedCommand from thefuck.types import CorrectedCommand
from thefuck import const
@pytest.fixture @pytest.fixture
def patch_getch(monkeypatch): def patch_get_key(monkeypatch):
def patch(vals): def patch(vals):
def getch(): vals = iter(vals)
for val in vals: monkeypatch.setattr('thefuck.ui.get_key', lambda: next(vals))
if val == KeyboardInterrupt:
raise val
else:
yield val
getch_gen = getch()
monkeypatch.setattr('thefuck.ui.getch', lambda: next(getch_gen))
return patch return patch
def test_read_actions(patch_getch): def test_read_actions(patch_get_key):
patch_getch([ # Enter: patch_get_key([
'\n', # Enter:
# Enter: '\n',
'\r', # Enter:
# Ignored: '\r',
'x', 'y', # Ignored:
# Up: 'x', 'y',
'\x1b', '[', 'A', # Up:
# Down: const.KEY_UP,
'\x1b', '[', 'B', # Down:
# Ctrl+C: const.KEY_DOWN,
KeyboardInterrupt], ) # Ctrl+C:
const.KEY_CTRL_C])
assert list(islice(ui.read_actions(), 5)) \ assert list(islice(ui.read_actions(), 5)) \
== [ui.SELECT, ui.SELECT, ui.PREVIOUS, ui.NEXT, ui.ABORT] == [ui.SELECT, ui.SELECT, ui.PREVIOUS, ui.NEXT, ui.ABORT]
@ -80,25 +75,25 @@ class TestSelectCommand(object):
== commands_with_side_effect[0] == commands_with_side_effect[0]
assert capsys.readouterr() == ('', 'ls (+side effect)\n') assert capsys.readouterr() == ('', 'ls (+side effect)\n')
def test_with_confirmation(self, capsys, patch_getch, commands): def test_with_confirmation(self, capsys, patch_get_key, commands):
patch_getch(['\n']) patch_get_key(['\n'])
assert ui.select_command(iter(commands)) == commands[0] assert ui.select_command(iter(commands)) == commands[0]
assert capsys.readouterr() == ('', u'\x1b[1K\rls [enter/↑/↓/ctrl+c]\n') assert capsys.readouterr() == ('', u'\x1b[1K\rls [enter/↑/↓/ctrl+c]\n')
def test_with_confirmation_abort(self, capsys, patch_getch, commands): def test_with_confirmation_abort(self, capsys, patch_get_key, commands):
patch_getch([KeyboardInterrupt]) patch_get_key([const.KEY_CTRL_C])
assert ui.select_command(iter(commands)) is None assert ui.select_command(iter(commands)) is None
assert capsys.readouterr() == ('', u'\x1b[1K\rls [enter/↑/↓/ctrl+c]\nAborted\n') assert capsys.readouterr() == ('', u'\x1b[1K\rls [enter/↑/↓/ctrl+c]\nAborted\n')
def test_with_confirmation_with_side_effct(self, capsys, patch_getch, def test_with_confirmation_with_side_effct(self, capsys, patch_get_key,
commands_with_side_effect): commands_with_side_effect):
patch_getch(['\n']) patch_get_key(['\n'])
assert ui.select_command(iter(commands_with_side_effect))\ assert ui.select_command(iter(commands_with_side_effect)) \
== commands_with_side_effect[0] == commands_with_side_effect[0]
assert capsys.readouterr() == ('', u'\x1b[1K\rls (+side effect) [enter/↑/↓/ctrl+c]\n') assert capsys.readouterr() == ('', u'\x1b[1K\rls (+side effect) [enter/↑/↓/ctrl+c]\n')
def test_with_confirmation_select_second(self, capsys, patch_getch, commands): def test_with_confirmation_select_second(self, capsys, patch_get_key, commands):
patch_getch(['\x1b', '[', 'B', '\n']) patch_get_key([const.KEY_DOWN, '\n'])
assert ui.select_command(iter(commands)) == commands[1] assert ui.select_command(iter(commands)) == commands[1]
assert capsys.readouterr() == ( assert capsys.readouterr() == (
'', u'\x1b[1K\rls [enter/↑/↓/ctrl+c]\x1b[1K\rcd [enter/↑/↓/ctrl+c]\n') '', u'\x1b[1K\rls [enter/↑/↓/ctrl+c]\x1b[1K\rcd [enter/↑/↓/ctrl+c]\n')

14
thefuck/const.py Normal file
View File

@ -0,0 +1,14 @@
# -*- encoding: utf-8 -*-
class _GenConst(object):
def __init__(self, name):
self._name = name
def __repr__(self):
return u'<const: {}>'.format(self._name)
KEY_UP = _GenConst('')
KEY_DOWN = _GenConst('')
KEY_CTRL_C = _GenConst('Ctrl+C')

View File

@ -2,27 +2,18 @@ from argparse import ArgumentParser
from warnings import warn from warnings import warn
from pprint import pformat from pprint import pformat
import sys import sys
import colorama
from . import logs, types, shells from . import logs, types, shells
from .conf import settings from .conf import settings
from .corrector import get_corrected_commands from .corrector import get_corrected_commands
from .exceptions import EmptyCommand from .exceptions import EmptyCommand
from .utils import get_installation_info from .utils import get_installation_info
from .ui import select_command from .ui import select_command
from .system import init_output
def init_colorama():
if sys.platform == 'win32':
# https://github.com/tartley/colorama/issues/32
import win_unicode_console
win_unicode_console.enable()
colorama.init()
def fix_command(): def fix_command():
"""Fixes previous command. Used when `thefuck` called without arguments.""" """Fixes previous command. Used when `thefuck` called without arguments."""
init_colorama() init_output()
settings.init() settings.init()
with logs.debug_time('Total'): with logs.debug_time('Total'):
logs.debug(u'Run with settings: {}'.format(pformat(settings))) logs.debug(u'Run with settings: {}'.format(pformat(settings)))
@ -60,7 +51,7 @@ def how_to_configure_alias():
It'll be only visible when user type fuck and when alias isn't configured. It'll be only visible when user type fuck and when alias isn't configured.
""" """
init_colorama() init_output()
settings.init() settings.init()
logs.how_to_configure_alias(shells.how_to_configure()) logs.how_to_configure_alias(shells.how_to_configure())

View File

@ -0,0 +1,7 @@
import sys
if sys.platform == 'win32':
from .win32 import *
else:
from .unix import *

35
thefuck/system/unix.py Normal file
View File

@ -0,0 +1,35 @@
import sys
import tty
import termios
import colorama
from .. import const
init_output = colorama.init
def getch():
fd = sys.stdin.fileno()
old = termios.tcgetattr(fd)
try:
tty.setraw(fd)
return sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old)
def get_key():
ch = getch()
if ch == '\x03':
return const.KEY_CTRL_C
elif ch == '\x1b':
next_ch = getch()
if next_ch == '[':
last_ch = getch()
if last_ch == 'A':
return const.KEY_UP
elif last_ch == 'B':
return const.KEY_DOWN
return ch

25
thefuck/system/win32.py Normal file
View File

@ -0,0 +1,25 @@
import sys
import msvcrt
import colorama
import win_unicode_console
from .. import const
def init_output():
win_unicode_console.enable()
colorama.init()
def get_key():
ch = msvcrt.getch()
if ch in (b'\x00', b'\xe0'): # arrow or function key prefix?
ch = msvcrt.getch() # second call returns the actual key code
if ch == b'\x03':
raise const.KEY_CTRL_C
if ch == b'H':
return const.KEY_UP
if ch == b'P':
return const.KEY_DOWN
return ch.decode(sys.stdout.encoding)

View File

@ -3,38 +3,8 @@
import sys import sys
from .conf import settings from .conf import settings
from .exceptions import NoRuleMatched from .exceptions import NoRuleMatched
from . import logs from .system import get_key
from . import logs, const
try:
import msvcrt
def getch():
ch = msvcrt.getch()
if ch in (b'\x00', b'\xe0'): # arrow or function key prefix?
ch = msvcrt.getch() # second call returns the actual key code
if ch == b'\x03':
raise KeyboardInterrupt
if ch == b'H':
return 'k'
if ch == b'P':
return 'j'
return ch.decode(sys.stdout.encoding)
except ImportError:
def getch():
import tty
import termios
fd = sys.stdin.fileno()
old = termios.tcgetattr(fd)
try:
tty.setraw(fd)
ch = sys.stdin.read(1)
if ch == '\x03': # For compatibility with msvcrt.getch
raise KeyboardInterrupt
return ch
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old)
SELECT = 0 SELECT = 0
ABORT = 1 ABORT = 1
@ -44,23 +14,17 @@ NEXT = 3
def read_actions(): def read_actions():
"""Yields actions for pressed keys.""" """Yields actions for pressed keys."""
buffer = []
while True: while True:
try: key = get_key()
ch = getch()
except KeyboardInterrupt: # Ctrl+C
yield ABORT
if ch in ('\n', '\r'): # Enter if key in (const.KEY_UP, 'k'):
yield SELECT
buffer.append(ch)
buffer = buffer[-3:]
if buffer == ['\x1b', '[', 'A'] or ch == 'k': # ↑
yield PREVIOUS yield PREVIOUS
elif buffer == ['\x1b', '[', 'B'] or ch == 'j': # ↓ elif key in (const.KEY_DOWN, 'j'):
yield NEXT yield NEXT
elif key == const.KEY_CTRL_C:
yield ABORT
elif key in ('\n', '\r'):
yield SELECT
class CommandSelector(object): class CommandSelector(object):