-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathuiev.pyx
367 lines (340 loc) · 13.7 KB
/
uiev.pyx
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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
cimport cython
import cython
from libcpp.string cimport string
##
# The following extern block wraps the C++ class ShellProcessManager from
# "nonblockingsubprocess.hpp", exposing its methods to Cython.
# Each method is briefly described below:
#
# ShellProcessManager(
# string shell_command,
# size_t buffer_size,
# size_t stdout_max_len,
# size_t stderr_max_len,
# string exit_command,
# int print_stdout,
# int print_stderr
# )
# Constructor that initializes the shell process manager with the desired
# parameters, such as buffer sizes, command to exit the shell, etc.
#
# bint start_shell(
# unsigned long creationFlag,
# unsigned long creationFlags,
# unsigned short wShowWindow,
# char * lpReserved,
# char * lpDesktop,
# char * lpTitle,
# unsigned long dwX,
# unsigned long dwY,
# unsigned long dwXSize,
# unsigned long dwYSize,
# unsigned long dwXCountChars,
# unsigned long dwYCountChars,
# unsigned long dwFillAttribute,
# unsigned long dwFlags,
# unsigned long cbReserved2,
# unsigned char * lpReserved2
# )
# Starts the shell process with the specified window creation parameters and
# environment details (ignored on Posix)
#
# bint stdin_write(string)
# Writes a string to the process's standard input. Returns boolean success/failure.
#
# string get_stdout()
# Retrieves all available standard output data from the shell process.
#
# string get_stderr()
# Retrieves all available standard error data from the shell process.
#
# void stop_shell()
# Stops the shell process if it is running.
##
cdef extern from "nonblockingsubprocess.hpp" nogil :
cdef cppclass ShellProcessManager:
void ShellProcessManager(
string shell_command,
size_t buffer_size,
size_t stdout_max_len,
size_t stderr_max_len,
string exit_command,
int print_stdout,
int print_stderr)
bint start_shell(unsigned long creationFlag,
unsigned long creationFlags,
unsigned short wShowWindow,
char * lpReserved,
char * lpDesktop,
char * lpTitle,
unsigned long dwX,
unsigned long dwY,
unsigned long dwXSize,
unsigned long dwYSize,
unsigned long dwXCountChars,
unsigned long dwYCountChars,
unsigned long dwFillAttribute,
unsigned long dwFlags,
unsigned long cbReserved2,
unsigned char * lpReserved2,)
bint stdin_write(string)
string get_stdout()
string get_stderr()
void stop_shell()
cdef class CySubProc:
"""
CySubProc is a Cython class that provides a Python interface to a C++ ShellProcessManager.
It allows you to start a shell process, write commands to stdin, and read stdout/stderr output without deadlocking.
The 2 two threads that read from stdout/stderr run in nogil mode
"""
cdef ShellProcessManager*subproc
def __init__(self,
object shell_command,
size_t buffer_size=4096,
size_t stdout_max_len=4096,
size_t stderr_max_len=4096,
object exit_command=b"exit",
bint print_stdout=False,
bint print_stderr=False,
):
r"""
Initialize the CySubProc object.
Parameters
----------
shell_command : bytes or str
The command used to start the shell or interpreter (e.g., b'/bin/bash' or "C:\\Windows\\System32\\cmd.exe").
buffer_size : int, optional
The size of the buffer used when reading output, by default 4096.
stdout_max_len : int, optional
The maximum length of stdout before truncation or buffering logic, by default 4096.
It treats the C++ vector that stores the output data like collections.deque
stderr_max_len : int, optional
The maximum length of stderr before truncation or buffering logic, by default 4096.
It treats the C++ vector that stores the output data like collections.deque
exit_command : bytes or str, optional
The command sent to gracefully exit the shell, by default b"exit".
print_stdout : bool, optional
Whether to print the shell's stdout directly, by default False.
Stdout is saved nevertheless, it is not lost after printing!
print_stderr : bool, optional
Whether to print the shell's stderr directly, by default False.
Stderr is saved nevertheless, it is not lost after printing!
Returns
-------
None
"""
cdef:
string cpp_shell_command
string cpp_exit_command
if isinstance(shell_command,bytes):
cpp_shell_command=<string>shell_command
else:
cpp_shell_command=<string>(str(shell_command).encode())
if isinstance(exit_command,bytes):
cpp_exit_command=<string>exit_command
else:
cpp_exit_command=<string>(str(exit_command).encode())
self.subproc= new ShellProcessManager(
shell_command=cpp_shell_command,
buffer_size=buffer_size,
stdout_max_len=stdout_max_len,
stderr_max_len=stderr_max_len,
exit_command=cpp_exit_command,
print_stdout=print_stdout,
print_stderr=print_stderr
)
cpdef start_shell(
self,
unsigned long creationFlag=0,
unsigned long creationFlags=0x08000000,
unsigned short wShowWindow=1,
object lpReserved=None,
object lpDesktop=None,
object lpTitle=None,
unsigned long dwX=0,
unsigned long dwY=0,
unsigned long dwXSize=0,
unsigned long dwYSize=0,
unsigned long dwXCountChars=0,
unsigned long dwYCountChars=0,
unsigned long dwFillAttribute=0,
unsigned long dwFlags=0,
unsigned long cbReserved2=0,
object lpReserved2=None,
):
r"""
Start the shell process with specific creation flags and environment parameters.
On Posix, all arguments are ignored!
Detailed information can be found on Microsoft's website https://learn.microsoft.com/en-us/windows/win32/procthread/process-creation-flags
Parameters
----------
creationFlag : int, optional
A custom flag for process creation, by default 0.
creationFlags : int, optional
Additional flags controlling process creation, by default 0x08000000.
wShowWindow : int, optional
Flags controlling how the window is shown (e.g., hidden, normal, minimized),
by default 1 (SW_SHOWNORMAL).
lpReserved : bytes or str, optional
Reserved parameter for process creation, by default None.
lpDesktop : bytes or str, optional
The name of the desktop for the process, by default None.
lpTitle : bytes or str, optional
The title for the new console window, by default None.
dwX : int, optional
X-coordinate for the upper-left corner of the window, by default 0.
dwY : int, optional
Y-coordinate for the upper-left corner of the window, by default 0.
dwXSize : int, optional
Width of the window, by default 0.
dwYSize : int, optional
Height of the window, by default 0.
dwXCountChars : int, optional
Screen buffer width in character columns, by default 0.
dwYCountChars : int, optional
Screen buffer height in character rows, by default 0.
dwFillAttribute : int, optional
Initial text and background colors if used in a console, by default 0.
dwFlags : int, optional
Flags that control how the creationFlags are used, by default 0.
cbReserved2 : int, optional
Reserved for C runtime initialization, by default 0.
lpReserved2 : bytes or str, optional
Reserved for C runtime initialization, by default None.
Returns
-------
None
"""
cdef:
string cpp_lpReserved, cpp_lpDesktop, cpp_lpTitle, cpp_lpReserved2
unsigned char* ptr_lpReserved2
char* ptr_lpReserved
char* ptr_lpDesktop
char* ptr_lpTitle
size_t addr_cpp_lpReserved, addr_cpp_lpDesktop, addr_cpp_lpTitle, addr_cpp_lpReserved2
if not lpReserved:
lpReserved=b'\x00'
if not lpDesktop:
lpDesktop=b'\x00'
if not lpTitle:
lpTitle=b'\x00'
if not lpReserved2:
lpReserved2=b'\x00'
if isinstance(lpReserved, bytes):
cpp_lpReserved=<string>lpReserved
else:
cpp_lpReserved=<string>(str(lpReserved).encode())
if isinstance(lpDesktop, bytes):
cpp_lpDesktop=<string>lpDesktop
else:
cpp_lpDesktop=<string>(str(lpDesktop).encode())
if isinstance(lpTitle, bytes):
cpp_lpTitle=<string>lpTitle
else:
cpp_lpTitle=<string>(str(lpTitle).encode())
if isinstance(lpReserved2, bytes):
cpp_lpReserved2=<string>lpReserved2
else:
cpp_lpReserved2=<string>(str(lpReserved2).encode())
# Obtain raw pointers to the underlying string data.
# Cython is nagging when assigning or casting directly to char pointers of .data() (temporary object ... unsafe ... blah blah blah)
# But the data pointer of the string doesn't not change as long as the size of the string doesn't change which is the case here.
# It works perfectly on Windows, and on Linux, the pointers and all other arguments are ignored anyways, so they could even be dangling on Posix, it wouldn't matter at all.
addr_cpp_lpReserved=<size_t>(&(cpp_lpReserved.data()[0]))
addr_cpp_lpDesktop=<size_t>(&(cpp_lpDesktop.data()[0]))
addr_cpp_lpTitle=<size_t>(&(cpp_lpTitle.data()[0]))
addr_cpp_lpReserved2=<size_t>(&(cpp_lpReserved2.data()[0]))
ptr_lpReserved=<char*>addr_cpp_lpReserved
ptr_lpDesktop=<char*>addr_cpp_lpDesktop
ptr_lpTitle=<char*>addr_cpp_lpTitle
ptr_lpReserved2=<unsigned char*>(addr_cpp_lpReserved2)
self.subproc.start_shell(
creationFlag=creationFlag,
creationFlags=creationFlags,
wShowWindow=wShowWindow,
lpReserved=ptr_lpReserved,
lpDesktop=ptr_lpDesktop,
lpTitle=ptr_lpTitle,
dwX=dwX,
dwY=dwY,
dwXSize=dwXSize,
dwYSize=dwYSize,
dwXCountChars=dwXCountChars,
dwYCountChars=dwYCountChars,
dwFillAttribute=dwFillAttribute,
dwFlags=dwFlags,
cbReserved2=cbReserved2,
lpReserved2=ptr_lpReserved2,
)
cpdef stdin_write(self, object cmd):
"""
Write a command or input data to the shell process's stdin.
Parameters
----------
cmd : bytes or str
The command or data to send to the process via stdin.
Returns
-------
None
"""
cdef:
string cpp_cmd
if isinstance(cmd,bytes):
cpp_cmd=<string>cmd
elif isinstance(cmd,str):
cpp_cmd=<string>(cmd.encode())
else:
cpp_cmd=<string>str(cmd).encode()
self.subproc.stdin_write(cpp_cmd)
cpdef bytes get_stdout(self):
"""
Retrieve the current contents of the shell's standard output as bytes, and clears the C++ vector
Returns
-------
bytes
The raw bytes from the shell's stdout.
"""
return self.subproc.get_stdout()
cpdef bytes get_stderr(self):
"""
Retrieve the current contents of the shell's standard error as bytes, and clears the C++ vector.
Returns
-------
bytes
The raw bytes from the shell's stderr.
"""
return self.subproc.get_stderr()
cpdef stop_shell(self):
"""
Stop the running shell process gracefully (stops the 2 background threads and writes the exit command 5 times to the shell - in case there are subshells running).
Returns
-------
None
"""
self.subproc.stop_shell()
cdef string read_stdout(self):
"""
Read the current contents of the shell's standard output as a C++ string, and clears the C++ vector.
This function is Cython only, and has the advantage that it does not convert the data to a Python object
Returns
-------
string
The raw C++ string from the shell's stdout.
"""
return self.subproc.get_stdout()
cdef string read_stderr(self):
"""
Read the current contents of the shell's standard error as a C++ string, and clears the C++ vector.
This function is Cython only, and has the advantage that it does not convert the data to a Python object
Returns
-------
string
The raw C++ string from the shell's stderr.
"""
return self.subproc.get_stderr()
def __dealloc__(self):
"""
Calls the C++ destructor, which executes the C++ stop_shell function
It deallocates the underlying ShellProcessManager pointer when this object is garbage collected.
"""
del self.subproc