From 29c1d1efcf076f4ae6840a912f5cad1e77bb5d08 Mon Sep 17 00:00:00 2001 From: nvbn Date: Thu, 3 Dec 2015 20:03:27 +0800 Subject: [PATCH] #414: Move system-dependent utils in `system` module --- tests/test_ui.py | 59 +++++++++++++++++--------------------- thefuck/const.py | 14 +++++++++ thefuck/main.py | 15 ++-------- thefuck/system/__init__.py | 7 +++++ thefuck/system/unix.py | 35 ++++++++++++++++++++++ thefuck/system/win32.py | 25 ++++++++++++++++ thefuck/ui.py | 54 ++++++---------------------------- 7 files changed, 120 insertions(+), 89 deletions(-) create mode 100644 thefuck/const.py create mode 100644 thefuck/system/__init__.py create mode 100644 thefuck/system/unix.py create mode 100644 thefuck/system/win32.py diff --git a/tests/test_ui.py b/tests/test_ui.py index 731171cc..310dd376 100644 --- a/tests/test_ui.py +++ b/tests/test_ui.py @@ -4,37 +4,32 @@ import pytest from itertools import islice from thefuck import ui from thefuck.types import CorrectedCommand +from thefuck import const @pytest.fixture -def patch_getch(monkeypatch): +def patch_get_key(monkeypatch): def patch(vals): - def getch(): - for val in vals: - if val == KeyboardInterrupt: - raise val - else: - yield val - - getch_gen = getch() - monkeypatch.setattr('thefuck.ui.getch', lambda: next(getch_gen)) + vals = iter(vals) + monkeypatch.setattr('thefuck.ui.get_key', lambda: next(vals)) return patch -def test_read_actions(patch_getch): - patch_getch([ # Enter: - '\n', - # Enter: - '\r', - # Ignored: - 'x', 'y', - # Up: - '\x1b', '[', 'A', - # Down: - '\x1b', '[', 'B', - # Ctrl+C: - KeyboardInterrupt], ) +def test_read_actions(patch_get_key): + patch_get_key([ + # Enter: + '\n', + # Enter: + '\r', + # Ignored: + 'x', 'y', + # Up: + const.KEY_UP, + # Down: + const.KEY_DOWN, + # Ctrl+C: + const.KEY_CTRL_C]) assert list(islice(ui.read_actions(), 5)) \ == [ui.SELECT, ui.SELECT, ui.PREVIOUS, ui.NEXT, ui.ABORT] @@ -80,25 +75,25 @@ class TestSelectCommand(object): == commands_with_side_effect[0] assert capsys.readouterr() == ('', 'ls (+side effect)\n') - def test_with_confirmation(self, capsys, patch_getch, commands): - patch_getch(['\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') - def test_with_confirmation_abort(self, capsys, patch_getch, commands): - patch_getch([KeyboardInterrupt]) + 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') - 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): - patch_getch(['\n']) - assert ui.select_command(iter(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') - def test_with_confirmation_select_second(self, capsys, patch_getch, commands): - patch_getch(['\x1b', '[', 'B', '\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') diff --git a/thefuck/const.py b/thefuck/const.py new file mode 100644 index 00000000..c7ffaa0e --- /dev/null +++ b/thefuck/const.py @@ -0,0 +1,14 @@ +# -*- encoding: utf-8 -*- + + +class _GenConst(object): + def __init__(self, name): + self._name = name + + def __repr__(self): + return u''.format(self._name) + + +KEY_UP = _GenConst('↑') +KEY_DOWN = _GenConst('↓') +KEY_CTRL_C = _GenConst('Ctrl+C') diff --git a/thefuck/main.py b/thefuck/main.py index 5f52f53c..1d298c08 100644 --- a/thefuck/main.py +++ b/thefuck/main.py @@ -2,27 +2,18 @@ from argparse import ArgumentParser from warnings import warn from pprint import pformat import sys -import colorama from . import logs, types, shells from .conf import settings from .corrector import get_corrected_commands from .exceptions import EmptyCommand from .utils import get_installation_info from .ui import select_command - - -def init_colorama(): - if sys.platform == 'win32': - # https://github.com/tartley/colorama/issues/32 - import win_unicode_console - - win_unicode_console.enable() - colorama.init() +from .system import init_output def fix_command(): """Fixes previous command. Used when `thefuck` called without arguments.""" - init_colorama() + init_output() settings.init() with logs.debug_time('Total'): 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. """ - init_colorama() + init_output() settings.init() logs.how_to_configure_alias(shells.how_to_configure()) diff --git a/thefuck/system/__init__.py b/thefuck/system/__init__.py new file mode 100644 index 00000000..cc4698ac --- /dev/null +++ b/thefuck/system/__init__.py @@ -0,0 +1,7 @@ +import sys + + +if sys.platform == 'win32': + from .win32 import * +else: + from .unix import * diff --git a/thefuck/system/unix.py b/thefuck/system/unix.py new file mode 100644 index 00000000..4d0b8555 --- /dev/null +++ b/thefuck/system/unix.py @@ -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 diff --git a/thefuck/system/win32.py b/thefuck/system/win32.py new file mode 100644 index 00000000..8132f235 --- /dev/null +++ b/thefuck/system/win32.py @@ -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) \ No newline at end of file diff --git a/thefuck/ui.py b/thefuck/ui.py index bea45e96..e2eb920f 100644 --- a/thefuck/ui.py +++ b/thefuck/ui.py @@ -3,38 +3,8 @@ import sys from .conf import settings from .exceptions import NoRuleMatched -from . import logs - -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) +from .system import get_key +from . import logs, const SELECT = 0 ABORT = 1 @@ -44,23 +14,17 @@ NEXT = 3 def read_actions(): """Yields actions for pressed keys.""" - buffer = [] while True: - try: - ch = getch() - except KeyboardInterrupt: # Ctrl+C - yield ABORT + key = get_key() - if ch in ('\n', '\r'): # Enter - yield SELECT - - buffer.append(ch) - buffer = buffer[-3:] - - if buffer == ['\x1b', '[', 'A'] or ch == 'k': # ↑ + if key in (const.KEY_UP, 'k'): yield PREVIOUS - elif buffer == ['\x1b', '[', 'B'] or ch == 'j': # ↓ + elif key in (const.KEY_DOWN, 'j'): yield NEXT + elif key == const.KEY_CTRL_C: + yield ABORT + elif key in ('\n', '\r'): + yield SELECT class CommandSelector(object):