cli: pretty printed summary for fetch with HTTP status coloring

This commit is contained in:
2026-08-15 18:09:05 +08:00
parent 259dd87c43
commit aeae99a496
2 changed files with 93 additions and 0 deletions
+54
View File
@@ -43,6 +43,7 @@ _PRETTY_TOOLS = frozenset(
"grep_files", "grep_files",
"run_argv", "run_argv",
"run_argv_batch", "run_argv_batch",
"fetch",
} }
) )
@@ -331,6 +332,58 @@ def _run_argv_batch_pretty(_args_json: str, result: str) -> str:
return _pretty_line("* Batch ", (f"({ran}/{steps} steps ran, {tail})", fg)) return _pretty_line("* Batch ", (f"({ran}/{steps} steps ran, {tail})", fg))
def _human_bytes(value: str) -> str:
"""Compact byte-count rendering: ``900B`` / ``1.2KB`` / ``3.4MB``."""
kib = 1024
try:
n = int(value)
except ValueError:
return ""
if n < kib:
return f"{n}B"
kb = n / kib
if kb < kib:
return f"{kb:.1f}KB"
mb = kb / kib
if mb < kib:
return f"{mb:.1f}MB"
return f"{mb / kib:.1f}GB"
def _http_status_fg(status: str) -> str | None:
"""Foreground color by HTTP status class (2xx green, 3xx yellow, 4xx/5xx red)."""
if status and status[0] in {"2", "3", "4", "5"}:
return {"2": "green", "3": "yellow", "4": "red", "5": "red"}[status[0]]
return None
def _fetch_pretty(args_json: str, result: str) -> str:
"""One-line summary for a ``fetch`` call: ``* GET 200 https://… (json, 1.2KB)``."""
method = _json_str_arg(args_json, "method") or "GET"
if result.startswith("error:"):
return _pretty_line(f"* {method} ", (f"({result})", "red"))
fields: dict[str, str] = {}
for line in result.splitlines():
if line.startswith("--- "):
break
if "=" in line:
key, _, value = line.partition("=")
fields[key] = value
status = fields.get("status", "?")
url = fields.get("final_url", "?")
detail_parts: list[str] = []
kind = fields.get("body_kind", "")
if kind:
detail_parts.append(kind)
size = _human_bytes(fields.get("bytes", ""))
if size:
detail_parts.append(size)
if fields.get("truncated") == "true":
detail_parts.append("truncated")
detail = f" ({', '.join(detail_parts)})" if detail_parts else ""
return _pretty_line(f"* {method} {status} {url}", (detail, _http_status_fg(status)))
_PRETTY_BUILDERS: dict[str, Callable[[str, str], str]] = { _PRETTY_BUILDERS: dict[str, Callable[[str, str], str]] = {
"read_file": _read_file_pretty, "read_file": _read_file_pretty,
"tree": _tree_pretty, "tree": _tree_pretty,
@@ -341,6 +394,7 @@ _PRETTY_BUILDERS: dict[str, Callable[[str, str], str]] = {
"grep_files": _grep_pretty, "grep_files": _grep_pretty,
"run_argv": _run_argv_pretty, "run_argv": _run_argv_pretty,
"run_argv_batch": _run_argv_batch_pretty, "run_argv_batch": _run_argv_batch_pretty,
"fetch": _fetch_pretty,
} }
+39
View File
@@ -410,3 +410,42 @@ async def test_pretty_run_argv_batch_complete(capsys: pytest.CaptureFixture[str]
await render_events(_aiter([_pretty_call("run_argv_batch", '{"steps": []}'), _result(result)])) await render_events(_aiter([_pretty_call("run_argv_batch", '{"steps": []}'), _result(result)]))
out = capsys.readouterr().out out = capsys.readouterr().out
assert "* Batch (2/2 steps ran, last exit 0)" in out assert "* Batch (2/2 steps ran, last exit 0)" in out
async def test_pretty_fetch(capsys: pytest.CaptureFixture[str]) -> None:
result = (
"status=200\nmethod=GET\nfinal_url=https://example.com\ncontent_type=application/json\n"
"body_kind=json\nbytes=1229\ntruncated=false\nredirects=0\nsecurity=public\n--- body ---\n{...}\n"
)
await render_events(_aiter([_pretty_call("fetch", '{"url": "https://example.com"}'), _result(result)]))
out = capsys.readouterr().out
assert "* GET 200 https://example.com (json, 1.2KB)" in out
assert "[tool]" not in out
async def test_pretty_fetch_error_status(capsys: pytest.CaptureFixture[str]) -> None:
result = (
"status=404\nmethod=GET\nfinal_url=https://example.com/nope\ncontent_type=text/html\n"
"body_kind=html\nbytes=45\ntruncated=false\nredirects=0\nsecurity=public\n--- body ---\nNot found\n"
)
await render_events(_aiter([_pretty_call("fetch", '{"url": "https://example.com/nope"}'), _result(result)]))
out = capsys.readouterr().out
assert "* GET 404 https://example.com/nope (html, 45B)" in out
async def test_pretty_fetch_truncated(capsys: pytest.CaptureFixture[str]) -> None:
result = (
"status=200\nmethod=GET\nfinal_url=https://example.com\ncontent_type=text/plain\n"
"body_kind=text\nbytes=50000\ntruncated=true\nredirects=0\nsecurity=public\n--- body ---\n..."
)
await render_events(_aiter([_pretty_call("fetch", '{"url": "https://example.com"}'), _result(result)]))
out = capsys.readouterr().out
assert "* GET 200 https://example.com (text, 48.8KB, truncated)" in out
async def test_pretty_fetch_error(capsys: pytest.CaptureFixture[str]) -> None:
await render_events(
_aiter([_pretty_call("fetch", '{"url": "https://example.com"}'), _result("error: fetch failed: boom")])
)
out = capsys.readouterr().out
assert "* GET (error: fetch failed: boom)" in out