patternpythonMinor
Read stdin like a dictator
Viewed 0 times
stdinlikedictatorread
Problem
Rags.
Introduction
All too often I find myself wanting to allow only a certain list of characters to be written to
Essentially, this module provides a few APIs that allow a very tailored approach to reading characters from a terminal. By intercepting individual keypresses at the instant they occur, we can make cool decisions about closing the input stream whenever we want -- we dictate who says what in our terminal!
The standard input stream, after being opened, can be closed after a number of characters, or, at caller's option, any combination of a number of characters and allowed inputs.
API Overview
Outward interface
-
func
-
func
-
func
-
func
-
func
Inward interface
-
class
-
class
-
class
-
func
-
func
Introduction
All too often I find myself wanting to allow only a certain list of characters to be written to
stdin, and only recently did I actually bother to implement it. In Python, of all languages!Essentially, this module provides a few APIs that allow a very tailored approach to reading characters from a terminal. By intercepting individual keypresses at the instant they occur, we can make cool decisions about closing the input stream whenever we want -- we dictate who says what in our terminal!
The standard input stream, after being opened, can be closed after a number of characters, or, at caller's option, any combination of a number of characters and allowed inputs.
API Overview
Outward interface
-
func
read_single_keypress() -> string — cross-platform function that gets exactly one keypress, and returns it after processing.-
func
thismany(count: int = -1) -> string — get exactly count characters from stdin. if count is -1, then sys.maxsize chars will be read.-
func
until(chars: string || list, count: int = -1) -> string — get characters from stdin until char is read, or until count chars have been read. if count is -1, sys.maxsize chars will be read.-
func
until_not(chars: string || list, count: int = -1) -> string — get characters from stdin until any of chars is not read. if count is -1, then sys.maxsize chars will be read.-
func
pretty_press() -> string — read a char from stdin, and send it through the same processing as the other functions here do — write it, then if it's a backspace write a backspace, etcInward interface
-
class
_Getch: determines system platform and calls one of _GetchUnix or _GetchWindows appropriately-
class
_GetchUnix: get a raw character from stdin, on any *nx box-
class
_GetchWindows: get a raw character from stdin, on any Windows box-
func
nbsp: do stuff accordingly for certain chars of input; handles backspace, etc-
func
parsenum: returSolution
I really don't like that you detect which version of
If that level of repetition is also upsetting, move the
More generally, however, I don't understand why this is a class. Just make it a function.
Why is this a lambda?
Just make a normal function, for readability if nothing else.
General thoughts on the rest of your code:
Why are you explicitly calling
You mentioned in the comments that you assign the result of
_GetchX to use via an ImportError - that isn't obvious to me at all. I also don't like that you keep importing things locally. I think you can solve this like so:import platform
system = platform.system()
import sys
if system == "Windows":
import msvcrt
class _Getch:
"""Gets a single character from standard input."""
def __call__(self):
return msvcrt.getch()
else:
import tty, termios
class _Getch:
"""Gets a single character from standard input."""
def __call__(self):
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(sys.stdin.fileno())
ch = sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return chIf that level of repetition is also upsetting, move the
if inside of the __call__ implementation, but that seems like too much. You could try this instead:class _Getch:
"""Gets a single character from standard input."""
if system == "Windows":
import msvcrt
def _call(self):
return msvcrt.getch()
else:
import tty, termios
def _call(self):
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(sys.stdin.fileno())
ch = sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return ch
_Getch.__call__ = _callMore generally, however, I don't understand why this is a class. Just make it a function.
if system == "Windows":
import msvcrt
def getch():
return msvcrt.getch()
else:
import tty, termios
def getch():
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(sys.stdin.fileno())
ch = sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return chWhy is this a lambda?
parsenum = (lambda num:
(sys.maxsize if 0 > num else num))Just make a normal function, for readability if nothing else.
def parsenum(num):
return sys.maxsize if 0 > num else numGeneral thoughts on the rest of your code:
Why are you explicitly calling
__call__()? Just call it normally (or just use the function, as above). You also have a lot of magic numbers, which to someone who is not immediately familiar with their ASCII codes (I have literally never been able to remember any ASCII code off the top of my head) isn't helpful - prefer named constants here. Instead of ox == 27 or ox == 127 just do ox in [27, 127] (and replace that with some constant). You shouldn't need to explicitly turn chars into a list, unless you expect that it is some bizarre iterable type that doesn't implement __getitem__.You mentioned in the comments that you assign the result of
sys.stdout.write(i) because it will append the result to the output otherwise. I had no idea that happens, and it seems pretty odd, but it looks like a code smell to anyone (like myself) who doesn't know that it happens. You could put a comment everywhere you do that, but that is annoying and you might forget if you add it in the future. I'd write a simple helper methoddef write_to_stdout(i):
# Have to assign or it will append the result to the output
_ = sys.stdout.write(i)
sys.stdout.flush()until and until_not are basically identical - they could be condensed to this:def _until_condition(chars, condition, count) -> str:
y = []
count = parsenum(count)
while len(y) str:
"""get chars of stdin until any of chars is read,
or until count chars have been read, whichever comes first"""
return _until_condition(chars, lambda i, chars: i in chars, count)
def until_not(chars, count=-1) -> str:
"""read stdin until any of chars stop being read,
or until count chars have been read; whichever comes first"""
return _until_condition(chars, lambda i, chars: i not in chars, count)Code Snippets
import platform
system = platform.system()
import sys
if system == "Windows":
import msvcrt
class _Getch:
"""Gets a single character from standard input."""
def __call__(self):
return msvcrt.getch()
else:
import tty, termios
class _Getch:
"""Gets a single character from standard input."""
def __call__(self):
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(sys.stdin.fileno())
ch = sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return chclass _Getch:
"""Gets a single character from standard input."""
if system == "Windows":
import msvcrt
def _call(self):
return msvcrt.getch()
else:
import tty, termios
def _call(self):
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(sys.stdin.fileno())
ch = sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return ch
_Getch.__call__ = _callif system == "Windows":
import msvcrt
def getch():
return msvcrt.getch()
else:
import tty, termios
def getch():
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(sys.stdin.fileno())
ch = sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return chparsenum = (lambda num:
(sys.maxsize if 0 > num else num))def parsenum(num):
return sys.maxsize if 0 > num else numContext
StackExchange Code Review Q#118721, answer score: 8
Revisions (0)
No revisions yet.