-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathstdout_capture.py
91 lines (75 loc) · 2.87 KB
/
stdout_capture.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
"""
Solution was found at
https://stackoverflow.com/questions/35745541/how-to-get-printed-output-from-ctypes-c-functions-into-jupyter-ipython-notebook
and adopted for MacOS by changing stdin and other symbols name to __stdinp and so on
"""
import io
import os
import sys
import tempfile
from contextlib import contextmanager
import ctypes
from sys import platform
# use_errno parameter is optional, because I'm not checking errno anyway.
libc = ctypes.CDLL(ctypes.util.find_library('c'), use_errno=True)
class FILE(ctypes.Structure):
pass
FILE_p = ctypes.POINTER(FILE)
# Alternatively, we can just use:
# FILE_p = ctypes.c_void_p
# These variables, defined inside the C library, are readonly.
if platform == "darwin":
cstdin = FILE_p.in_dll(libc, '__stdinp')
cstdout = FILE_p.in_dll(libc, '__stdoutp')
cstderr = FILE_p.in_dll(libc, '__stderrp')
elif platform in ("linux", 'liunx2'):
cstdin = FILE_p.in_dll(libc, 'stdin')
cstdout = FILE_p.in_dll(libc, 'stdout')
cstderr = FILE_p.in_dll(libc, 'stderr')
# C function to disable buffering.
csetbuf = libc.setbuf
csetbuf.argtypes = (FILE_p, ctypes.c_char_p)
csetbuf.restype = None
# C function to flush the C library buffer.
cfflush = libc.fflush
cfflush.argtypes = (FILE_p,)
cfflush.restype = ctypes.c_int
@contextmanager
def capture_c_stdout(encoding='utf8'):
# Flushing, it's a good practice.
sys.stdout.flush()
cfflush(cstdout)
# We need to use a actual file because we need the file descriptor number.
with tempfile.TemporaryFile(buffering=0) as temp:
# Saving a copy of the original stdout.
prev_sys_stdout = sys.stdout
prev_stdout_fd = os.dup(1)
os.close(1)
# Duplicating the temporary file fd into the stdout fd.
# In other words, replacing the stdout.
os.dup2(temp.fileno(), 1)
# Replacing sys.stdout for Python code.
#
# IPython Notebook version of sys.stdout is actually an
# in-memory OutStream, so it does not have a file descriptor.
# We need to replace sys.stdout so that interleaved Python
# and C output gets captured in the correct order.
#
# We enable line_buffering to force a flush after each line.
# And write_through to force all data to be passed through the
# wrapper directly into the binary temporary file.
temp_wrapper = io.TextIOWrapper(
temp, encoding=encoding, line_buffering=True, write_through=True)
sys.stdout = temp_wrapper
# Disabling buffering of C stdout.
csetbuf(cstdout, None)
yield
# Must flush to clear the C library buffer.
cfflush(cstdout)
# Restoring stdout.
os.dup2(prev_stdout_fd, 1)
os.close(prev_stdout_fd)
sys.stdout = prev_sys_stdout
# Printing the captured output.
temp_wrapper.seek(0)
print(temp_wrapper.read(), end='')