Created
October 1, 2014 17:20
-
-
Save embray/3dc1dcb0d264bce912fb to your computer and use it in GitHub Desktop.
That time I reimplemented/replaced the raw_input builtin so I could log stdin.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
PY3K = sys.version_info[:2] >= (3, 0) | |
# The global_logging system replaces the raw_input builtin (input on Python 3) | |
# for two reasons: | |
# | |
# 1) It's the easiest way to capture the raw_input prompt and subsequent user | |
# input to the log. | |
# | |
# 2) On Python 2.x raw_input() does not play nicely with GUI toolkits if | |
# sys.stdout has been replaced by a non-file object (as global_logging | |
# does). The default raw_input() implementation first checks that | |
# sys.stdout and sys.stdin are connected to a terminal. If so it uses the | |
# PyOS_Readline() implementation, which allows a GUI's event loop to run | |
# while waiting for user input via PyOS_InputHook(). However, if | |
# sys.stdout is not attached to a terminal, raw_input() uses | |
# PyFile_GetLine(), which blocks until a line is entered on sys.stdin, | |
# thus preventing the GUI from updating. It doesn't matter if sys.stdin is | |
# still attached to the terminal even if sys.stdout isn't, nor does it | |
# automatically fall back on sys.__stdout__ and sys.__stdin__. | |
# | |
# This replacement raw_input() reimplements most of the built in | |
# raw_input(), but is aware that sys.stdout may have been replaced and | |
# knows how to find the real stdout if so. | |
# | |
# Note that this is a non-issue in Python 3 which has a new implementation | |
# in which it doesn't matter what sys.stdout points to--only that it has a | |
# fileno() method that returns the correct file descriptor for the | |
# console's stdout. | |
if not PY3K: | |
import os | |
import __builtin__ as builtins | |
from ctypes import pythonapi, py_object, c_void_p, c_char_p, c_int | |
# PyFile_AsFile returns a FILE * from a python file object. | |
# This is used later with pythonapi.PyOS_Readline to perform | |
# the readline. | |
pythonapi.PyFile_AsFile.argtypes = (py_object,) | |
pythonapi.PyFile_AsFile.restype = c_void_p | |
pythonapi.PyOS_Readline.argtypes = (c_void_p, c_void_p, c_char_p) | |
pythonapi.PyOS_Readline.restype = c_char_p | |
def global_logging_raw_input(prompt): | |
def get_stream(name): | |
if hasattr(sys, name): | |
stream = getattr(sys, name) | |
if isinstance(stream, file): | |
return stream | |
elif isinstance(stream, StreamTeeLogger): | |
return stream.stream | |
if hasattr(sys, '__%s__' % name): | |
stream = getattr(sys, '__%s__' % name) | |
if isinstance(stream, file): | |
return stream | |
return None | |
def check_interactive(stream, name): | |
try: | |
fd = stream.fileno() | |
except: | |
# Could be an AttributeError, an OSError, and IOError, or who | |
# knows what else... | |
return False | |
realfd = {'stdin': 0, 'stdout': 1, 'stderr': 2}[name] | |
return fd == realfd and os.isatty(fd) | |
stdout = get_stream('stdout') | |
stdin = get_stream('stdin') | |
stderr = get_stream('stderr') | |
if stdout is None: | |
raise RuntimeError('raw_input(): lost sys.stdout') | |
if stdin is None: | |
raise RuntimeError('raw_input(): lost sys.stdin') | |
if stderr is None: | |
raise RuntimeError('raw_input(): lost sys.stderr') | |
if (not check_interactive(stdin, 'stdin') or | |
not check_interactive(stdout, 'stdout')): | |
# Use the built-in raw_input(); this will repeat some of the checks | |
# we just did, but will save us from having to reimplement | |
# raw_input() in its entirety | |
retval = builtins._original_raw_input(prompt) | |
else: | |
stdout.flush() | |
infd = pythonapi.PyFile_AsFile(stdin) | |
outfd = pythonapi.PyFile_AsFile(stdout) | |
retval = pythonapi.PyOS_Readline(infd, outfd, str(prompt)) | |
retval = retval.rstrip('\n') | |
if isinstance(sys.stdout, StreamTeeLogger): | |
sys.stdout.log_orig(str(prompt) + retval, echo=False) | |
return retval | |
else: | |
import builtins | |
def global_logging_raw_input(prompt): | |
retval = builtins._original_raw_input(prompt) | |
if isinstance(sys.stdout, StreamTeeLogger): | |
sys.stdout.log_orig(str(prompt) + retval, echo=False) | |
return retval |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment