From cb2054ff10f396b576508029073a7206070c6f2d Mon Sep 17 00:00:00 2001 From: a-kenji Date: Wed, 8 Oct 2025 00:20:29 +0200 Subject: [PATCH] s --- pkgs/clan-cli/clan_lib/cmd/__init__.py | 87 ++++++++++++++++++-------- 1 file changed, 61 insertions(+), 26 deletions(-) diff --git a/pkgs/clan-cli/clan_lib/cmd/__init__.py b/pkgs/clan-cli/clan_lib/cmd/__init__.py index c83e5ecad..435b575d4 100644 --- a/pkgs/clan-cli/clan_lib/cmd/__init__.py +++ b/pkgs/clan-cli/clan_lib/cmd/__init__.py @@ -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")