This commit is contained in:
a-kenji
2025-10-08 00:20:29 +02:00
parent 841a8edbdd
commit cb2054ff10

View File

@@ -74,6 +74,9 @@ def handle_io(
) # wlist is a list of file descriptors to be monitored for write events
stdout_buf = b""
stderr_buf = b""
# Buffers for incomplete lines (no trailing newline yet)
stdout_line_buf = ""
stderr_line_buf = ""
start = time.time()
# Function to handle file descriptors
@@ -85,6 +88,40 @@ def handle_io(
rlist.remove(fd)
return b""
# Function to process output with proper carriage return handling
def process_output(
chunk: bytes, line_buf: str, extra: dict[str, str], cmdlog_func: Any
) -> str:
"""Process output chunk, handling carriage returns properly.
Returns the updated line buffer (incomplete lines).
"""
if not chunk:
return line_buf
# Decode the chunk and append to line buffer
decoded = chunk.decode("utf-8", "replace")
line_buf += decoded
# Split by newlines to get complete lines
lines = line_buf.split("\n")
# The last element might be an incomplete line
line_buf = lines[-1]
complete_lines = lines[:-1]
# Process each complete line
for line in complete_lines:
if "\r" in line:
# Handle carriage return: only keep the last segment after final \r
# This is what would be visible on a terminal
visible_line = line.split("\r")[-1]
if visible_line: # Only log non-empty lines
cmdlog_func(visible_line, extra=extra)
elif line: # Only log non-empty lines
cmdlog_func(line, extra=extra)
return line_buf
# Extra information passed to the logger
stdout_extra = {}
stderr_extra = {}
@@ -126,19 +163,9 @@ def handle_io(
# If Log.STDOUT is set, log the stdout output
if ret and log in [Log.STDOUT, Log.BOTH]:
decoded = ret.decode("utf-8", "replace").rstrip("\n").rstrip()
# Handle carriage returns: split by \r and only keep the last segment of each line
# This prevents progress indicators that use \r from creating multiple log lines
lines = []
for line in decoded.split("\n"):
if "\r" in line:
# Only keep the last segment after the final \r (what would be visible)
lines.append(line.split("\r")[-1])
else:
lines.append(line)
for line in lines:
if line: # Only log non-empty lines
cmdlog.info(line, extra=stdout_extra)
stdout_line_buf = process_output(
ret, stdout_line_buf, stdout_extra, cmdlog.info
)
# If stdout file is set, stream the stdout output
if ret and stdout:
@@ -153,19 +180,9 @@ def handle_io(
# If Log.STDERR is set, log the stderr output
if ret and log in [Log.STDERR, Log.BOTH]:
decoded = ret.decode("utf-8", "replace").rstrip("\n").rstrip()
# Handle carriage returns: split by \r and only keep the last segment of each line
# This prevents progress indicators that use \r from creating multiple log lines
lines = []
for line in decoded.split("\n"):
if "\r" in line:
# Only keep the last segment after the final \r (what would be visible)
lines.append(line.split("\r")[-1])
else:
lines.append(line)
for line in lines:
if line: # Only log non-empty lines
cmdlog.info(line, extra=stderr_extra)
stderr_line_buf = process_output(
ret, stderr_line_buf, stderr_extra, cmdlog.info
)
# If stderr file is set, stream the stderr output
if ret and stderr:
@@ -193,6 +210,24 @@ def handle_io(
process.stdin.close()
else:
wlist.remove(process.stdin)
# Flush any remaining buffered lines at the end
if stdout_line_buf and log in [Log.STDOUT, Log.BOTH]:
if "\r" in stdout_line_buf:
visible_line = stdout_line_buf.split("\r")[-1]
if visible_line:
cmdlog.info(visible_line, extra=stdout_extra)
elif stdout_line_buf:
cmdlog.info(stdout_line_buf, extra=stdout_extra)
if stderr_line_buf and log in [Log.STDERR, Log.BOTH]:
if "\r" in stderr_line_buf:
visible_line = stderr_line_buf.split("\r")[-1]
if visible_line:
cmdlog.info(visible_line, extra=stderr_extra)
elif stderr_line_buf:
cmdlog.info(stderr_line_buf, extra=stderr_extra)
return stdout_buf.decode("utf-8", "replace"), stderr_buf.decode("utf-8", "replace")