Merge pull request 'clan-cli: Improve CmdOut output' (#2207) from Qubasa/clan-core:Qubasa-main into main

This commit is contained in:
clan-bot
2024-10-06 23:44:12 +00:00
2 changed files with 104 additions and 17 deletions

View File

@@ -63,7 +63,7 @@ def handle_output(process: subprocess.Popen, log: Log) -> tuple[str, str]:
class TimeTable: class TimeTable:
""" """
This class is used to store the time taken by each command This class is used to store the time taken by each command
and print it at the end of the program if env PERF=1 is set. and print it at the end of the program if env CLAN_CLI_PERF=1 is set.
""" """
def __init__(self) -> None: def __init__(self) -> None:
@@ -144,7 +144,8 @@ def run(
stdout=stdout_buf, stdout=stdout_buf,
stderr=stderr_buf, stderr=stderr_buf,
cwd=cwd, cwd=cwd,
command=shlex.join(cmd), env=env,
command_list=cmd,
returncode=process.returncode, returncode=process.returncode,
msg=error_msg, msg=error_msg,
) )

View File

@@ -1,42 +1,128 @@
import contextlib
import json
import os
import shlex
import shutil import shutil
from dataclasses import dataclass from dataclasses import dataclass
from math import floor from math import floor
from pathlib import Path from pathlib import Path
from typing import cast
def get_term_filler(name: str) -> int: def get_term_filler(name: str) -> tuple[int, int]:
width, height = shutil.get_terminal_size() width, _ = shutil.get_terminal_size()
filler = floor((width - len(name)) / 2) filler = floor((width - len(name)) / 2)
return filler - 1 return (filler - 1, width)
def text_heading(heading: str) -> str: def text_heading(heading: str) -> str:
filler = get_term_filler(heading) filler, total = get_term_filler(heading)
return f"{'=' * filler} {heading} {'=' * filler}" msg = f"{'=' * filler} {heading} {'=' * filler}"
if len(msg) < total:
msg += "="
return msg
def optional_text(heading: str, text: str | None) -> str:
if text is None or text.strip() == "":
return ""
with contextlib.suppress(json.JSONDecodeError):
text = json.dumps(json.loads(text), indent=4)
return f"{text_heading(heading)}\n{text}"
@dataclass
class DictDiff:
added: dict[str, str]
removed: dict[str, str]
changed: dict[str, dict[str, str]]
def diff_dicts(dict1: dict[str, str], dict2: dict[str, str]) -> DictDiff:
"""
Compare two dictionaries and report additions, deletions, and changes.
:param dict1: The first dictionary (baseline).
:param dict2: The second dictionary (to compare).
:return: A dictionary with keys 'added', 'removed', and 'changed', each containing
the respective differences. 'changed' is a nested dictionary with keys 'old' and 'new'.
"""
added = {k: dict2[k] for k in dict2 if k not in dict1}
removed = {k: dict1[k] for k in dict1 if k not in dict2}
changed = {
k: {"old": dict1[k], "new": dict2[k]}
for k in dict1
if k in dict2 and dict1[k] != dict2[k]
}
return DictDiff(added=added, removed=removed, changed=changed)
def indent_command(command_list: list[str]) -> str:
formatted_command = []
i = 0
while i < len(command_list):
arg = command_list[i]
formatted_command.append(shlex.quote(arg))
if i < len(command_list) - 1:
# Check if the current argument is an option
if arg.startswith("-"):
# Indent after the next argument
formatted_command.append(" ")
i += 1
formatted_command.append(shlex.quote(command_list[i]))
if i < len(command_list) - 1:
# Add line continuation only if it's not the last argument
formatted_command.append(" \\\n ")
i += 1
# Join the list into a single string
final_command = "".join(formatted_command)
# Remove the trailing continuation if it exists
if final_command.endswith(" \\ \n "):
final_command = final_command.rsplit(" \\ \n ", 1)[0]
return final_command
@dataclass @dataclass
class CmdOut: class CmdOut:
stdout: str stdout: str
stderr: str stderr: str
env: dict[str, str] | None
cwd: Path cwd: Path
command: str command_list: list[str]
returncode: int returncode: int
msg: str | None msg: str | None
@property
def command(self) -> str:
return indent_command(self.command_list)
def __str__(self) -> str: def __str__(self) -> str:
# Set a common indentation level, assuming a reasonable spacing
label_width = max(len("Return Code"), len("Work Dir"), len("Error Msg"))
diffed_dict = (
diff_dicts(cast(dict[str, str], os.environ), self.env) if self.env else None
)
diffed_dict_str = (
json.dumps(diffed_dict.__dict__, indent=4) if diffed_dict else None
)
error_str = f""" error_str = f"""
{text_heading(heading="Command")} {optional_text("Stdout", self.stdout)}
{self.command} {optional_text("Environment", diffed_dict_str)}
{text_heading(heading="Stderr")} {optional_text("Stderr", self.stderr)}
{self.stderr} {optional_text("Command", self.command)}
{text_heading(heading="Stdout")}
{self.stdout}
{text_heading(heading="Metadata")} {text_heading(heading="Metadata")}
Message: {self.msg} {'Return Code:':<{label_width}} {self.returncode}
Working Directory: '{self.cwd}' {'Work Dir:':<{label_width}} '{self.cwd}'
Return Code: {self.returncode} {'Error Msg:':<{label_width}} {self.msg.capitalize() if self.msg else ""}
""" """
return error_str return error_str