Merge pull request 'clan-cli: Improve CmdOut output' (#2207) from Qubasa/clan-core:Qubasa-main into main
This commit is contained in:
@@ -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,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user