feat(docs): add CLASSIFICATION function-header field, rename history shadow

Rename the C1 history() shadow to pretty-history so it never collides
with the fish builtin -- every function expecting stock history
semantics (search, --max, merge, ...) would otherwise silently break.
hist.fish, which relied on the shadow's timestamp formatting, now
requests it explicitly via builtin history --show-time.

Add a CLASSIFICATION doc-header label so a function can declare its
interaction with C1-shadowed commands (uses-shadow/bypasses-shadow)
and general hazards (destructive, network, blocking-prompt) for
anyone deciding to disable an opinionated category or call the
function from automation. Wired into the manual/site build pipeline
(manualtools.py, build-manual.py) and the C1 shadow doc gets a new
"For function authors" bypass-mechanism reference table
(command/builtin/__original_help, and which shadows have no real
bypass target at all).
This commit is contained in:
2026-09-21 21:26:40 -04:00
parent 392e6593ac
commit aef686af86
5 changed files with 82 additions and 6 deletions
+25
View File
@@ -703,6 +703,29 @@ ENTRY_HEADS = {
}
def _classification_tags(raw: list[str]) -> list[str]:
"""Split a CLASSIFICATION body into its comma-separated tags.
A plain comma split (as `names()` uses for DEPENDENCIES) would break on
the commas inside `uses-shadow(rm, cp)`-style tags, so this only splits
on commas at paren depth 0.
"""
text = " ".join(raw)
tags: list[str] = []
depth = 0
start = 0
for i, ch in enumerate(text):
if ch == "(":
depth += 1
elif ch == ")":
depth = max(0, depth - 1)
elif ch == "," and depth == 0:
tags.append(text[start:i].strip())
start = i + 1
tags.append(text[start:].strip())
return [t for t in tags if t]
def render_entry(fn: dict[str, list[str]], used_by: list[str], link=None) -> str:
"""Render one parsed function header as a manual entry body.
@@ -739,6 +762,7 @@ def render_entry(fn: dict[str, list[str]], used_by: list[str], link=None) -> str
refs = []
for label, values in (
("Dependencies", names(fn.get("DEPENDENCIES", []))),
("Classification", _classification_tags(fn.get("CLASSIFICATION", []))),
("Used by", sorted(used_by)),
):
if values:
@@ -884,6 +908,7 @@ def render_entry_site(fn: dict[str, list[str]], used_by: list[str], link=None) -
refs = []
for label, values in (
("Dependencies", names(fn.get("DEPENDENCIES", []))),
("Classification", _classification_tags(fn.get("CLASSIFICATION", []))),
("Used by", sorted(used_by)),
):
if values: