2222
2323'''Process-related system utilities'''
2424
25+ from __future__ import annotations
26+
2527import ctypes
26- import logging
2728import locale
29+ import logging
2830import os
2931import os .path
3032import struct
3133import subprocess
3234import threading
3335import time
36+ from enum import Enum
37+ from typing import TYPE_CHECKING
3438
3539import nimp .sys .platform
3640
41+ if TYPE_CHECKING :
42+ from typing import Callable
43+
44+
45+ class ProcessOutputStream (Enum ):
46+ """
47+ process output stream identifiers
48+ """
49+
50+ STDOUT = 1
51+ STDERR = 2
52+ STDDBG = 3
53+
3754
3855def call (
3956 command ,
4057 cwd = '.' ,
4158 heartbeat = 0 ,
4259 stdin = None ,
4360 encoding = 'utf-8' ,
44- capture_output = False ,
61+ capture_output : bool | Callable [[ ProcessOutputStream , str ], None ] = False ,
4562 capture_debug = False ,
4663 hide_output = False ,
4764 dry_run = False ,
4865 timeout = None ,
66+ ** popen_kwargs ,
4967):
5068 '''Calls a process redirecting its output to nimp's output'''
5169 command = _sanitize_command (command )
@@ -61,6 +79,16 @@ def call(
6179 else :
6280 debug_pipe = None
6381
82+ for reserved_popen_kwargs in [
83+ 'stdout' ,
84+ 'stderr' ,
85+ 'stdin' ,
86+ 'bufsize' ,
87+ ]:
88+ if reserved_popen_kwargs in popen_kwargs :
89+ value = popen_kwargs .pop (reserved_popen_kwargs )
90+ logging .debug ("Ignoring reserved subprocess.Popen kwarg '%s=%s'" , reserved_popen_kwargs , value )
91+
6492 # The bufsize = -1 is important; if we don’t bufferise the output, we’re
6593 # going to make the callee lag a lot. In Python 3.3.1 this is now the
6694 # default behaviour, but it used to default to 0.
@@ -82,9 +110,26 @@ def call(
82110 debug_pipe .start ()
83111
84112 # FIXME: put all this in a class instead!
85- all_pipes = [process .stdout , process .stderr , debug_pipe .output if debug_pipe else None ]
86-
87- all_captures = [[] if capture_output else None , [] if capture_output else None , None ]
113+ all_pipes = {
114+ ProcessOutputStream .STDOUT : process .stdout ,
115+ ProcessOutputStream .STDERR : process .stderr ,
116+ ProcessOutputStream .STDDBG : debug_pipe .output if debug_pipe else None ,
117+ }
118+
119+ all_captures = {
120+ ProcessOutputStream .STDOUT : [],
121+ ProcessOutputStream .STDERR : [],
122+ }
123+
124+ def _capture_output_default (stream : ProcessOutputStream , value : str ):
125+ if (capture_array := all_captures .get (stream )) is not None :
126+ capture_array .append (value )
127+
128+ capture_processor = None
129+ if capture_output is True :
130+ capture_processor = _capture_output_default
131+ elif callable (capture_output ):
132+ capture_processor = capture_output
88133
89134 debug_info = [False ]
90135
@@ -100,9 +145,8 @@ def _input_worker(in_pipe, data):
100145 in_pipe .write (data )
101146 in_pipe .close ()
102147
103- def _output_worker (index , decoding_format ):
148+ def _output_worker (index : ProcessOutputStream , decoding_format ):
104149 in_pipe = all_pipes [index ]
105- capture_array = all_captures [index ]
106150 if in_pipe is None :
107151 return
108152 force_ascii = locale .getpreferredencoding ().lower () != 'utf-8'
@@ -125,15 +169,15 @@ def _output_worker(index, decoding_format):
125169 except UnicodeError :
126170 pass
127171
128- if capture_array is not None :
129- capture_array . append (line )
172+ if capture_processor is not None :
173+ capture_processor (line )
130174
131175 # Stop reading data from stdout if data has arrived on OutputDebugString
132176 if index == 2 :
133177 debug_info [0 ] = True
134178 elif index == 0 and debug_info [0 ]:
135179 logging .info ('Stopping stdout monitoring (OutputDebugString is active)' )
136- all_pipes [0 ].close ()
180+ all_pipes [ProcessOutputStream . STDOUT ].close ()
137181 return
138182
139183 if not hide_output :
@@ -144,7 +188,14 @@ def _output_worker(index, decoding_format):
144188 time .sleep (0.010 )
145189
146190 # Default threads
147- all_workers = [threading .Thread (target = _output_worker , args = (i , encoding )) for i in range (3 )]
191+ all_workers = [
192+ threading .Thread (target = _output_worker , args = (i , encoding ))
193+ for i in [
194+ ProcessOutputStream .STDOUT ,
195+ ProcessOutputStream .STDERR ,
196+ ProcessOutputStream .STDDBG ,
197+ ]
198+ ]
148199
149200 # Thread to feed stdin data if necessary
150201 if stdin is not None :
@@ -172,8 +223,12 @@ def _output_worker(index, decoding_format):
172223 if not hide_output :
173224 logging .info ('Finished with exit code %d (0x%08x)' , exit_code , exit_code )
174225
175- if capture_output :
176- return exit_code , '' .join (all_captures [0 ]), '' .join (all_captures [1 ])
226+ if capture_output is True :
227+ return (
228+ exit_code ,
229+ '' .join (all_captures [ProcessOutputStream .STDOUT ]),
230+ '' .join (all_captures [ProcessOutputStream .STDERR ]),
231+ )
177232 return exit_code
178233
179234
0 commit comments