diff --git a/scripts/send.py b/scripts/send.py index 0b92cc6..09be414 100644 --- a/scripts/send.py +++ b/scripts/send.py @@ -168,6 +168,71 @@ def fetch_failing_log( return "\n".join(cleaned[-log_lines:]).strip() +# --- diff rendering ------------------------------------------------------- +# When `details` is a unified diff (the network-configs notify feeds +# `git diff HEAD~1`), colour each line the way a terminal pager would: +# additions green, deletions red, hunk headers violet, file/meta headers dim. +# Non-diff details (e.g. auto-log output) fall through to a single muted block. +DIFF_ADD = OK_FG +DIFF_DEL = ERROR_FG +DIFF_HUNK = ACCENT_VIOLET +DIFF_META = MUTED +DIFF_CONTEXT = MUTED_STRONG + +_HUNK_RE = re.compile(r"^@@ -\d+(?:,\d+)? \+\d+(?:,\d+)? @@") +# Lines that start with +/- but are file headers, not content changes. +_META_PREFIXES = ( + "diff --git ", + "index ", + "--- ", + "+++ ", + "new file mode ", + "deleted file mode ", + "old mode ", + "new mode ", + "rename ", + "similarity index ", + "copy ", + "Binary files ", +) + + +def _looks_like_diff(text: str) -> bool: + """True if `text` reads as a unified diff (has a git header or a hunk).""" + for ln in text.splitlines(): + if ln.startswith("diff --git ") or _HUNK_RE.match(ln): + return True + return False + + +def _diff_line_color(line: str) -> str: + if _HUNK_RE.match(line): + return DIFF_HUNK + if line.startswith(_META_PREFIXES): + return DIFF_META + if line.startswith("+"): + return DIFF_ADD + if line.startswith("-"): + return DIFF_DEL + return DIFF_CONTEXT + + +def colorize_diff_html(details: str) -> str: + """Return inner HTML for the details
: one coloured span per line."""
+    spans = [
+        f'{escape(line)}'
+        for line in details.split("\n")
+    ]
+    return "\n".join(spans)
+
+
+def details_inner_html(details: str) -> str:
+    """Colour-highlight diffs; otherwise escape as a plain muted block."""
+    if _looks_like_diff(details):
+        return colorize_diff_html(details)
+    return escape(details)
+
+
 def fact_rows(items: Iterable[tuple[str, str, str]]) -> tuple[str, str]:
     """Build the HTML  rows and plain-text lines for the fact list.
 
@@ -222,8 +287,8 @@ def render(
           
{escape(details)}
+ font-size:12px;line-height:1.55;white-space:pre; + overflow-x:auto;">{details_inner_html(details)} """ @@ -240,8 +305,8 @@ def render(
-