Cóż, napisałem kod ręcznie. Zostawię wyjaśnienie na przyszłość.
Wymagania
import sys, tty, termios, codecs, unicodedata
from contextlib import contextmanager
Wyłączenie bufor linii
Pierwszy problem, który pojawia się, gdy tylko odczytu standardowe wejście jest bufor linii. Chcemy, aby pojedyncze postacie dotarły do naszego programu bez wymaganego znaku nowej linii, a to nie jest domyślny sposób działania terminala.
W tym pisałem menedżera kontekstowego, który obsługuje tty
konfigurację:
@contextmanager
def cbreak():
old_attrs = termios.tcgetattr(sys.stdin)
tty.setcbreak(sys.stdin)
try:
yield
finally:
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_attrs)
Menadżer umożliwia następujące idiom:
with cbreak():
single_char_no_newline = sys.stdin.read(1)
ważne jest, aby wykonać czyste, kiedy skończymy lub terminal może potrzebować reset
.
dekodowania standardowe wejście
Drugi problem tylko czytać standardowe wejście jest kodowanie. Znaki Unicode w formacie innym niż ascii docierają do nas bajt po bajcie, co jest całkowicie niepożądane.
Aby poprawnie dekodować stdin pisałem generator, który możemy iterację dla znaków Unicode:
def uinput():
reader = codecs.getreader(sys.stdin.encoding)(sys.stdin)
with cbreak():
while True:
yield reader.read(1)
Może to fail over rur. Nie jestem pewny. Jednak w moim przypadku używa prawidłowego kodowania i generuje strumień znaków.
Handling znaki specjalne
Po pierwsze, powinniśmy być w stanie powiedzieć znaki drukowalne oprócz tych kontroli:
def is_printable(c):
return not unicodedata.category(c).startswith('C')
Oprócz printables, na razie, tylko chcę obsłużyć ← Backspace i CtrlD sekwencji:
def is_backspace(c):
return c in ('\x08','\x7F')
def is_interrupt(c):
return c == '\x04'
Składając razem: xinput()
Wszystko jest już gotowe. Oryginalna umowa dla funkcji, którą chciałem, to odczytanie danych wejściowych, obsługa znaków specjalnych, wywołanie oddzwonienia. Realizacja odzwierciedla tylko, że:
def xinput(callback):
text = ''
for c in uinput():
if is_printable(c): text += c
elif is_backspace(c): text = text[:-1]
elif is_interrupt(c): break
callback(text)
return text
próbuje ją
def test(text):
print 'Buffer now holds', text
xinput(test)
Running go i wpisując Hellx← Backspaceo Świat pokazuje:
Buffer now holds H
Buffer now holds He
Buffer now holds Hel
Buffer now holds Hell
Buffer now holds Hellx
Buffer now holds Hell
Buffer now holds Hello
Buffer now holds Hello
Buffer now holds Hello w
Buffer now holds Hello wo
Buffer now holds Hello wor
Buffer now holds Hello worl
Buffer now holds Hello world