Bene, ho scritto il codice a mano. Lascerò una spiegazione per riferimento futuro.
Requisiti
import sys, tty, termios, codecs, unicodedata
from contextlib import contextmanager
disabilitazione buffering di riga
Il primo problema che sorge quando semplicemente leggendo stdin è linea buffer. Vogliamo singoli personaggi per raggiungere il nostro programma senza una nuova riga richiesta, e questo non è il modo predefinito in cui il terminale opera.
Per questo, ho scritto un manager contesto che gestisce tty
configurazione:
@contextmanager
def cbreak():
old_attrs = termios.tcgetattr(sys.stdin)
tty.setcbreak(sys.stdin)
try:
yield
finally:
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_attrs)
Questo Manager consente il seguente linguaggio:
with cbreak():
single_char_no_newline = sys.stdin.read(1)
E 'importante eseguire la pulizia quando abbiamo finito oppure il terminale potrebbe richiedere un reset
.
decodifica stdin
Il secondo problema con solo leggendo stdin è la codifica. I caratteri unicode non-ascii ci raggiungeranno byte per byte, il che è completamente indesiderabile.
Per decodificare correttamente stdin, ho scritto un generatore che possiamo iterare per i caratteri unicode:
def uinput():
reader = codecs.getreader(sys.stdin.encoding)(sys.stdin)
with cbreak():
while True:
yield reader.read(1)
Questo può venire a mancare sopra i tubi. Non ne sono sicuro. Per il mio caso d'uso, tuttavia, raccoglie la giusta codifica e genera un flusso di caratteri.
Manipolazione caratteri speciali
Prima di tutto, dovremmo essere in grado di dire caratteri stampabili oltre a quelli di controllo:
def is_printable(c):
return not unicodedata.category(c).startswith('C')
parte stampabili, per ora, voglio solo per gestire ← backspace e la CtrlD sequenza:
def is_backspace(c):
return c in ('\x08','\x7F')
def is_interrupt(c):
return c == '\x04'
Mettere insieme: xinput()
Ora è tutto a posto. Il contratto originale per la funzione che volevo era input di lettura, gestire caratteri speciali, richiamare la callback. L'implementazione riflette proprio questo:
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
provarlo
def test(text):
print 'Buffer now holds', text
xinput(test)
L'esecuzione e digitando Hellx← backspaceo mondiale spettacoli:
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