Skip to content

Commit 1308cb5

Browse files
committed
run_before_script: Refactor
1 parent 425bf3c commit 1308cb5

File tree

1 file changed

+64
-34
lines changed

1 file changed

+64
-34
lines changed

src/tmuxp/util.py

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

6797

6898
def oh_my_zsh_auto_title() -> None:

0 commit comments

Comments
 (0)