mirror of
https://github.com/NCBM/plyngent.git
synced 2026-11-07 23:47:54 +08:00
cli: pretty printed summary for fetch with HTTP status coloring
This commit is contained in:
@@ -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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user