@@ -28,41 +28,72 @@ def run_before_script(
28
28
script_file : str | pathlib .Path ,
29
29
cwd : pathlib .Path | None = None ,
30
30
) -> int :
31
- """Execute a shell script, wraps :meth:`subprocess.check_call()` in a try/catch."""
31
+ """Execute shell script, ``tee``-ing output to both terminal (if TTY) and buffer."""
32
+ script_cmd = shlex .split (str (script_file ))
33
+
32
34
try :
33
35
proc = subprocess .Popen (
34
- shlex .split (str (script_file )),
35
- stderr = subprocess .PIPE ,
36
- stdout = subprocess .PIPE ,
36
+ script_cmd ,
37
37
cwd = cwd ,
38
- text = True ,
38
+ stdout = subprocess .PIPE ,
39
+ stderr = subprocess .PIPE ,
40
+ text = True , # decode to str
39
41
errors = "backslashreplace" ,
40
- encoding = "utf-8" ,
41
42
)
42
- if proc .stdout is not None :
43
- for line in iter (proc .stdout .readline , "" ):
44
- sys .stdout .write (line )
45
- proc .wait ()
46
-
47
- if proc .returncode and proc .stderr is not None :
48
- stderr = proc .stderr .read ()
49
- proc .stderr .close ()
50
- stderr_strlist = stderr .split ("\n " )
51
- stderr_str = "\n " .join (list (filter (None , stderr_strlist ))) # filter empty
52
-
53
- raise exc .BeforeLoadScriptError (
54
- proc .returncode ,
55
- os .path .abspath (script_file ), # NOQA: PTH100
56
- stderr_str ,
57
- )
58
- except OSError as e :
59
- if e .errno == 2 :
60
- raise exc .BeforeLoadScriptNotExists (
61
- e ,
62
- os .path .abspath (script_file ), # NOQA: PTH100
63
- ) from e
64
- raise
65
- return proc .returncode
43
+ except FileNotFoundError as e :
44
+ raise exc .BeforeLoadScriptNotExists (
45
+ e ,
46
+ os .path .abspath (script_file ), # NOQA: PTH100
47
+ ) from e
48
+
49
+ out_buffer = []
50
+ err_buffer = []
51
+
52
+ # While process is running, read lines from stdout/stderr
53
+ # and write them to this process's stdout/stderr if isatty
54
+ is_out_tty = sys .stdout .isatty ()
55
+ is_err_tty = sys .stderr .isatty ()
56
+
57
+ # You can do a simple loop reading in real-time:
58
+ while True :
59
+ # Use .poll() to check if the child has exited
60
+ return_code = proc .poll ()
61
+
62
+ # Read one line from stdout, if available
63
+ line_out = proc .stdout .readline () if proc .stdout else ""
64
+
65
+ # Read one line from stderr, if available
66
+ line_err = proc .stderr .readline () if proc .stderr else ""
67
+
68
+ if line_out :
69
+ out_buffer .append (line_out )
70
+ if is_out_tty :
71
+ sys .stdout .write (line_out )
72
+ sys .stdout .flush ()
73
+
74
+ if line_err :
75
+ err_buffer .append (line_err )
76
+ if is_err_tty :
77
+ sys .stderr .write (line_err )
78
+ sys .stderr .flush ()
79
+
80
+ # If no more data from pipes and process ended, break
81
+ if not line_out and not line_err and return_code is not None :
82
+ break
83
+
84
+ # At this point, the process has finished
85
+ return_code = proc .wait ()
86
+
87
+ if return_code != 0 :
88
+ # Join captured stderr lines for your exception
89
+ stderr_str = "" .join (err_buffer ).strip ()
90
+ raise exc .BeforeLoadScriptError (
91
+ return_code ,
92
+ os .path .abspath (script_file ), # NOQA: PTH100
93
+ stderr_str ,
94
+ )
95
+
96
+ return return_code
66
97
67
98
68
99
def oh_my_zsh_auto_title () -> None :
0 commit comments