From 690bfd64d0f90c5e82822c7c8f21e4f93759ad4a Mon Sep 17 00:00:00 2001 From: rootiest Date: Sun, 26 Jul 2026 20:29:42 -0400 Subject: [PATCH] feat(docs): add _as_file_tree detector for box-drawing trees --- docs/build-manual.py | 22 +++++++++++++++++++++- docs/verify-manual.py | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/docs/build-manual.py b/docs/build-manual.py index 12a8dfa..d43571c 100644 --- a/docs/build-manual.py +++ b/docs/build-manual.py @@ -314,6 +314,26 @@ def _as_ruled_table(para: list[str]) -> str | None: return "\n".join(out) +TREE_ROOT_RE = re.compile(r"^[~$][\w./{}-]*/$") +TREE_BRANCH_RE = re.compile(r"^[│ ]*[├└]──\s*(\S+)\s*(.*)$") + + +def _as_file_tree(para: list[str]) -> str | None: + """Render a hand-drawn box-drawing tree as a Starlight , else None.""" + if len(para) < 2 or not TREE_ROOT_RE.match(para[0].strip()): + return None + branches = [] + for line in para[1:]: + m = TREE_BRANCH_RE.match(line) + if not m: + return None + branches.append(m.groups()) + out = ["", f"- {para[0].strip()}"] + out += [f" - {name} {desc}".rstrip() for name, desc in branches] + out.append("") + return "\n".join(out) + + def _render_para(para: list[str], entry_name: str | None, deeper: bool) -> str: """Render one paragraph of a former indented block. @@ -335,7 +355,7 @@ def _render_para(para: list[str], entry_name: str | None, deeper: bool) -> str: title, body = m.group(1), para[1:] info = f'fish title="{title}"' if title else "fish" return f"```{info}\n" + "\n".join(body) + "\n```" - table = _as_ruled_table(para) or _as_table(para) + table = _as_ruled_table(para) or _as_table(para) or _as_file_tree(para) if table is not None: return table return "\n".join(INDENT + line for line in para) diff --git a/docs/verify-manual.py b/docs/verify-manual.py index 4f3035d..b210da8 100644 --- a/docs/verify-manual.py +++ b/docs/verify-manual.py @@ -633,6 +633,38 @@ def test_prettify_converts_a_flat_note_paragraph_to_an_aside(): ), f"note paragraph was not converted:\n{out}" +def test_as_file_tree_converts_a_box_drawing_tree(): + """A root path plus ├──/└── branches becomes a Starlight .""" + import build_manual + + para = [ + "$__fish_user_dots_path/", + "├── secrets.fish API keys, tokens, passwords, personal identifiers", + "└── local.fish Machine-specific paths, env vars, and sourcing secrets", + ] + out = build_manual._as_file_tree(para) + assert out == ( + "\n" + "- $__fish_user_dots_path/\n" + " - secrets.fish API keys, tokens, passwords, personal identifiers\n" + " - local.fish Machine-specific paths, env vars, and sourcing secrets\n" + "" + ), f"unexpected file tree output:\n{out}" + + +def test_as_file_tree_rejects_non_trees(): + """Returning None is always safe for anything that isn't a root+branches shape.""" + import build_manual + + cases = { + "no root slash": ["$__fish_user_dots_path", "├── secrets.fish"], + "single line": ["$__fish_user_dots_path/"], + "non-branch second line": ["$__fish_user_dots_path/", "secrets.fish"], + } + for label, para in cases.items(): + assert build_manual._as_file_tree(para) is None, f"{label} was wrongly treed" + + def test_prettify_is_site_only(): """The SSOT keeps the indented form the man-page pipeline depends on.""" import build_manual