fix(docs): title distro code blocks and fix missed shell highlighting #140
+51
-10
@@ -334,7 +334,16 @@ def _is_prose(para: list[str]) -> bool:
|
||||
|
||||
|
||||
def _is_shell(para: list[str], entry_name: str | None) -> bool:
|
||||
"""True when every line of a paragraph looks like a shell command."""
|
||||
"""True when every line of a paragraph looks like a shell command.
|
||||
|
||||
Recognised command names are `SHELL_HEADS` plus the same code
|
||||
vocabulary `codespans` wraps in backticks (repo function names and
|
||||
`fish-deps` catalog entries included) — one shared list instead of a
|
||||
second hand-maintained one that silently drifts, which is how
|
||||
`fish-deps`/`config-settings`-style custom commands used to fall
|
||||
through to an unhighlighted block.
|
||||
"""
|
||||
vocab = SHELL_HEADS | _code_vocabulary().full
|
||||
name_re = (
|
||||
re.compile(rf"(?<![\w-]){re.escape(entry_name)}(?![\w-])")
|
||||
if entry_name
|
||||
@@ -346,7 +355,7 @@ def _is_shell(para: list[str], entry_name: str | None) -> bool:
|
||||
continue
|
||||
if name_re and name_re.search(stripped):
|
||||
continue
|
||||
if stripped.split()[0].lstrip("$").rstrip(";") not in SHELL_HEADS:
|
||||
if stripped.split()[0].lstrip("$").rstrip(";") not in vocab:
|
||||
return False
|
||||
return True
|
||||
|
||||
@@ -361,6 +370,30 @@ PATH_LINE_RE = re.compile(r"^[~$][\w./{}-]*\.\w+$")
|
||||
# as a literal comment inside the code.
|
||||
FILENAME_COMMENT_RE = re.compile(r"^#\s*(?:in\s+)?([$~\w./-]+\.\w+)\s*$")
|
||||
|
||||
# A leading comment that isn't a filename can still name what the block is
|
||||
# about (e.g. "# Arch / AUR" heading a distro's install command) rather
|
||||
# than explain a step ("# Turn it off:") — the trailing-punctuation and
|
||||
# length checks in `_label_title` are what tell the two apart.
|
||||
LABEL_COMMENT_RE = re.compile(r"^#\s*(.+)$")
|
||||
|
||||
|
||||
def _label_title(line: str) -> str | None:
|
||||
"""A short, label-shaped leading comment, promoted to a fence title.
|
||||
|
||||
Anything that reads as a sentence — trailing `.`/`!`/`?`/`;`/`:`/`,`,
|
||||
or just long — is left as a literal comment instead: it's explaining a
|
||||
step, not naming the block.
|
||||
"""
|
||||
m = LABEL_COMMENT_RE.match(line)
|
||||
if not m:
|
||||
return None
|
||||
text = m.group(1).strip()
|
||||
if not text or text[-1] in ".!?;:,":
|
||||
return None
|
||||
if len(text) > 48 or len(text.split()) > 8:
|
||||
return None
|
||||
return text
|
||||
|
||||
CELL_SPLIT = re.compile(r"\s{2,}")
|
||||
|
||||
# A rule line under a header row — the "Component Reference" tables'
|
||||
@@ -505,14 +538,22 @@ def _render_para(para: list[str], entry_name: str | None, deeper: bool) -> str:
|
||||
path = para[0].strip()
|
||||
name = path.rsplit("/", 1)[-1]
|
||||
return f'```fish title="{name}"\n{path}\n```'
|
||||
if _is_shell(para, entry_name):
|
||||
body = para
|
||||
title = None
|
||||
m = FILENAME_COMMENT_RE.match(para[0].strip())
|
||||
if m:
|
||||
title, body = m.group(1), para[1:]
|
||||
info = f'fish title="{title}"' if title else "fish"
|
||||
return f"```{info}\n" + "\n".join(body) + "\n```"
|
||||
# Checked even when `deeper`: a shell paragraph's own nested indentation
|
||||
# (a for/if/while body) must not be mistaken for a table's alignment —
|
||||
# `_is_shell` only looks at each line's first word, so it stays safe to
|
||||
# try before falling through to the table/text fallbacks below.
|
||||
if _is_shell(para, entry_name):
|
||||
body = para
|
||||
title = None
|
||||
m = FILENAME_COMMENT_RE.match(para[0].strip())
|
||||
if m:
|
||||
title, body = m.group(1), para[1:]
|
||||
else:
|
||||
label = _label_title(para[0].strip())
|
||||
if label:
|
||||
title, body = label, 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) or _as_file_tree(para)
|
||||
if table is not None:
|
||||
return table
|
||||
|
||||
+4
-4
@@ -3465,9 +3465,9 @@ category variable.
|
||||
C5 Logging and Capture — Session logs, command duration
|
||||
C6 Greeting & First-Run UI — Custom startup banner
|
||||
|
||||
Each category further sub-divides into two to six sub-categories (25 in
|
||||
total) with their own `__fish_config_op_<category>_<subcategory>` toggles
|
||||
-- see that category's page for its sub-category list.
|
||||
Each category further sub-divides into several sub-categories, each with
|
||||
its own `__fish_config_op_<category>_<subcategory>` toggle -- see that
|
||||
category's page for its sub-category list.
|
||||
|
||||
## Per-function overrides: `C0`/`always`
|
||||
|
||||
@@ -4317,7 +4317,7 @@ Re-enable everything:
|
||||
|
||||
set -Ue __fish_config_opinionated
|
||||
|
||||
Each category also has two to six sub-categories (e.g.
|
||||
Each category can also have several sub-categories (e.g.
|
||||
`__fish_config_op_aliases_filesystem`) that can be checked, disabled, or
|
||||
reset the same way — `set -U __fish_config_op_<category>_<subcategory> off`
|
||||
and `set -Ue __fish_config_op_<category>_<subcategory>` work identically to
|
||||
|
||||
@@ -19,9 +19,9 @@ category variable.
|
||||
C5 [Logging and Capture](/08-components-reference/05-c5-logging-and-capture/) — Session logs, command duration
|
||||
C6 [Greeting & First-Run UI](/08-components-reference/06-c6-greeting-and-first-run-ui/) — Custom startup banner
|
||||
|
||||
Each category further sub-divides into two to six sub-categories (25 in
|
||||
total) with their own `__fish_config_op_<category>_<subcategory>` toggles
|
||||
-- see that category's page for its sub-category list.
|
||||
Each category further sub-divides into several sub-categories, each with
|
||||
its own `__fish_config_op_<category>_<subcategory>` toggle -- see that
|
||||
category's page for its sub-category list.
|
||||
|
||||
## Per-function overrides: `C0`/`always`
|
||||
|
||||
|
||||
@@ -237,7 +237,7 @@ Re-enable everything:
|
||||
|
||||
set -Ue __fish_config_opinionated
|
||||
|
||||
Each category also has two to six sub-categories (e.g.
|
||||
Each category can also have several sub-categories (e.g.
|
||||
`__fish_config_op_aliases_filesystem`) that can be checked, disabled, or
|
||||
reset the same way — `set -U __fish_config_op_<category>_<subcategory> off`
|
||||
and `set -Ue __fish_config_op_<category>_<subcategory>` work identically to
|
||||
|
||||
@@ -681,6 +681,43 @@ def test_prettify_titles_paths_and_commented_examples():
|
||||
)
|
||||
|
||||
|
||||
def test_prettify_titles_label_comments_but_not_explanations():
|
||||
"""A short leading comment titles a shell block; a sentence stays a comment."""
|
||||
import build_manual
|
||||
|
||||
distro = "\n".join([" # Arch / AUR", " pacman -S fish"])
|
||||
out = build_manual.prettify(distro)
|
||||
assert '```fish title="Arch / AUR"\npacman -S fish\n```' in out, (
|
||||
f"a distro label comment was not promoted to the fence title:\n{out}"
|
||||
)
|
||||
|
||||
explanation = "\n".join(
|
||||
[" # Turn it off:", " set -U __fish_config_op_logging off"]
|
||||
)
|
||||
out = build_manual.prettify(explanation)
|
||||
assert 'title=' not in out and "# Turn it off:" in out, (
|
||||
f"a sentence-shaped comment was wrongly promoted to a title:\n{out}"
|
||||
)
|
||||
|
||||
|
||||
def test_prettify_highlights_nested_shell_and_custom_commands():
|
||||
"""A for-loop's indented body and a repo-only command still get shell highlighting."""
|
||||
import build_manual
|
||||
|
||||
loop = "\n".join(
|
||||
[" for v in (set -Un | string match 'x*')", " set -Ue $v", " end"]
|
||||
)
|
||||
out = build_manual.prettify(loop)
|
||||
assert out.startswith("```fish\n") and "```text" not in out, (
|
||||
f"a nested for-loop body lost shell highlighting:\n{out}"
|
||||
)
|
||||
|
||||
out = build_manual.prettify(" fish-deps sync")
|
||||
assert out == '```fish\nfish-deps sync\n```', (
|
||||
f"a repo function name was not recognised as a shell command:\n{out}"
|
||||
)
|
||||
|
||||
|
||||
def test_as_aside_converts_a_single_line_label():
|
||||
"""A `LABEL: text` line becomes a titled <Aside> with the label's type."""
|
||||
import build_manual
|
||||
|
||||
Reference in New Issue
Block a user