fix(render): improve file-change paths

This commit is contained in:
banteg
2026-01-01 02:20:17 +04:00
parent ac35ba3594
commit 77e0bbfb1d
2 changed files with 55 additions and 5 deletions
+31 -4
View File
@@ -4,6 +4,7 @@ from __future__ import annotations
import textwrap import textwrap
from collections import deque from collections import deque
from pathlib import Path
from typing import Callable from typing import Callable
from .model import Action, ActionEvent, ResumeToken, StartedEvent, TakopiEvent from .model import Action, ActionEvent, ResumeToken, StartedEvent, TakopiEvent
@@ -18,7 +19,33 @@ HARD_BREAK = " \n"
MAX_PROGRESS_CMD_LEN = 300 MAX_PROGRESS_CMD_LEN = 300
MAX_FILE_CHANGES_INLINE = 3 MAX_FILE_CHANGES_INLINE = 3
FILE_CHANGE_PREFIX = {"add": "+", "delete": "-", "update": "~"} FILE_CHANGE_VERB = {"add": "added", "delete": "deleted", "update": "updated"}
_ABSOLUTE_PATH_PREFIXES = ("Users/", "home/", "private/", "var/", "etc/", "opt/", "usr/")
def format_changed_file_path(path: str, *, base_dir: Path | None = None) -> str:
raw = path.strip()
if raw.startswith("./"):
raw = raw[2:]
# Some clients may accidentally prefix "~" onto an absolute path (e.g. "~" + "/Users/…").
if raw.startswith("~/") and raw[2:].startswith(_ABSOLUTE_PATH_PREFIXES):
raw = "/" + raw[2:]
base = Path.cwd() if base_dir is None else base_dir
try:
raw_path = Path(raw)
except Exception:
return f"`{raw}`"
if raw_path.is_absolute():
try:
raw_path = raw_path.relative_to(base)
raw = raw_path.as_posix()
except Exception:
pass
return f"`{raw}`"
def format_elapsed(elapsed_s: float) -> str: def format_elapsed(elapsed_s: float) -> str:
@@ -82,13 +109,13 @@ def format_file_change_title(action: Action, *, command_width: int | None) -> st
if not isinstance(path, str) or not path: if not isinstance(path, str) or not path:
continue continue
kind = raw.get("kind") kind = raw.get("kind")
prefix = FILE_CHANGE_PREFIX.get(kind, "~") if isinstance(kind, str) else "~" verb = FILE_CHANGE_VERB.get(kind, "updated") if isinstance(kind, str) else "updated"
rendered.append(f"{prefix}{path}") rendered.append(f"{verb} {format_changed_file_path(path)}")
if rendered: if rendered:
if len(rendered) > MAX_FILE_CHANGES_INLINE: if len(rendered) > MAX_FILE_CHANGES_INLINE:
remaining = len(rendered) - MAX_FILE_CHANGES_INLINE remaining = len(rendered) - MAX_FILE_CHANGES_INLINE
rendered = rendered[:MAX_FILE_CHANGES_INLINE] + [f"…(+{remaining})"] rendered = rendered[:MAX_FILE_CHANGES_INLINE] + [f"…({remaining} more)"]
inline = shorten(", ".join(rendered), command_width) inline = shorten(", ".join(rendered), command_width)
return f"files: {inline}" return f"files: {inline}"
+24 -1
View File
@@ -1,5 +1,6 @@
from typing import cast from typing import cast
from types import SimpleNamespace from types import SimpleNamespace
from pathlib import Path
from takopi.markdown import render_markdown from takopi.markdown import render_markdown
from takopi.model import TakopiEvent from takopi.model import TakopiEvent
@@ -79,10 +80,32 @@ def test_render_event_cli_handles_action_kinds() -> None:
for line in out for line in out
) )
assert any("tool: github.search_issues" in line for line in out) assert any("tool: github.search_issues" in line for line in out)
assert any("files: +README.md, ~src/compute_answer.py" in line for line in out) assert any(
"files: added `README.md`, updated `src/compute_answer.py`" in line for line in out
)
assert any(line.startswith("✗ stream error") for line in out) assert any(line.startswith("✗ stream error") for line in out)
def test_file_change_renders_relative_paths_inside_cwd() -> None:
readme_abs = str(Path.cwd() / "README.md")
weird_abs = "~" + readme_abs
out = render_event_cli(
action_completed(
"f-abs",
"file_change",
"README.md",
ok=True,
detail={
"changes": [
{"path": readme_abs, "kind": "update"},
{"path": weird_abs, "kind": "update"},
]
},
)
)
assert any("files: updated `README.md`, updated `README.md`" in line for line in out)
def test_progress_renderer_renders_progress_and_final() -> None: def test_progress_renderer_renders_progress_and_final() -> None:
r = ExecProgressRenderer(max_actions=5, resume_formatter=_format_resume) r = ExecProgressRenderer(max_actions=5, resume_formatter=_format_resume)
for evt in SAMPLE_EVENTS: for evt in SAMPLE_EVENTS: