feat: write the options glance from the parser

The glance on the installer page is written from install.py's argument parser when the page is built, grouped by GLANCE_GROUPS. An option no group names lands under Other, so a new option cannot be left off the page.
This commit is contained in:
Abdessamad Derraz committed 2026-10-23 20:19:26 +02:00
1 parent db0065e4f5
commit 8a8931e4c6
5 files changed
+83 -11

No files matched your search

+52 -1
View File
@@ -861,6 +861,57 @@ def options_table(markdown: str) -> list[str]:
return rows
# The installer page's glance groups its options by task. The list is
# written from the parser when the page is built: an option the parser
# gains and no group names lands under "Other", never off the page.
GLANCE_HEADING = "## Options at a glance"
GLANCE_GROUPS: tuple[tuple[str, tuple[str, ...]], ...] = (
("Where and for what", ("--platform", "--dest", "--target", "--every-board", "--save-dir",
"--romm-firmware", "--standalone-copies")),
("Fewer files", ("--system", "--core", "--emulator", "--region", "--no-game-data",
"--declared-only", "--installed-cores", "--cores-dir")),
("Look first", ("--check", "--dry-run", "--list-platforms", "--list-targets",
"--list-systems", "--list-cores", "--list-emulators")),
("From files you have", ("--from", "--from-pack", "--manifest", "--offline", "--restore")),
("How the run talks", ("--tui", "--plain", "--no-input", "--yes", "--quiet", "--verbose",
"--events", "--jobs", "--version", "--help")),
)
GLANCE_OTHER = "Other"
def installer_flags() -> list[str]:
"""Each option's first flag, in the order --help lists them."""
return [usage.split()[0].rstrip(",") for usage, _help in installer_options()]
def options_glance() -> list[str]:
"""One line per group of the glance, each option a link to its row."""
flags = installer_flags()
groups = [(label, [flag for flag in members if flag in flags]) for label, members in GLANCE_GROUPS]
named = {flag for _label, members in GLANCE_GROUPS for flag in members}
groups.append((GLANCE_OTHER, [flag for flag in flags if flag not in named]))
return [
f"- **{label}**: " + ", ".join(f"[`{flag}`](#option-{flag.lstrip('-')})" for flag in members)
for label, members in groups if members
]
def expand_glance(markdown: str) -> str:
"""The glance's list, after the sentence that opens the section."""
start = markdown.find(f"\n{GLANCE_HEADING}\n")
if start == -1:
return markdown
body_start = start + len(GLANCE_HEADING) + 2
end = markdown.find("\n## ", body_start)
end = len(markdown) if end == -1 else end
body = markdown[body_start:end].strip("\n")
if any(line.startswith("- ") for line in body.splitlines()):
raise ValueError(f"{GLANCE_HEADING}: the list is written from install.py's parser, "
"not by hand")
return (markdown[:body_start] + "\n" + body + "\n\n" + "\n".join(options_glance())
+ "\n" + markdown[end:])
def expand_options(markdown: str) -> str:
if "installer-options" not in markdown:
return markdown
@@ -898,7 +949,7 @@ def hide_hashes(markdown: str) -> str:
def prepare_page(markdown: str, page: Path) -> str:
"""Everything this module does to a page before it is decorated."""
markdown = expand_options(expand_commands(resolve_images(localize_images(markdown, page))))
markdown = expand_glance(expand_options(expand_commands(resolve_images(localize_images(markdown, page)))))
if page.parts[:1] == ("wiki",):
markdown = hide_hashes(markdown)
return markdown
+1 -3
View File
@@ -202,9 +202,7 @@ class BuiltSite(unittest.TestCase):
def test_search_fields_hold_no_text(self) -> None:
texts = []
for path in (DIST / "systems").rglob("index.html"):
# The title meta reads the visible h1 (PageTitle.astro). The
# search fields are attributes on empty spans.
for match in re.finditer(r"<(\w+)[^>]*data-pagefind-meta=\"(?!title\")[^>]*>([^<]*)</\1>",
for match in re.finditer(r"<(\w+)[^>]*data-pagefind-meta=[^>]*>([^<]*)</\1>",
path.read_text(encoding="utf-8")):
if match[2].strip():
texts.append(str(path.relative_to(DIST)))
+28
View File
@@ -15,6 +15,7 @@ import re
import sys
import unittest
from pathlib import Path
from unittest import mock
import yaml
@@ -23,6 +24,7 @@ sys.path.insert(0, str(REPO_ROOT / "scripts"))
import check_freshness # noqa: E402
import generate_site as gs # noqa: E402
import sitediagrams # noqa: E402
import siterender # noqa: E402
DIST = REPO_ROOT / "website" / "dist"
@@ -122,6 +124,32 @@ class SourcePathsFreshness(unittest.TestCase):
self.assertIn("sitesources.py --fetch", finding.detail)
class OptionsGlanceFromTheParser(unittest.TestCase):
"""--declared-only and --offline reached install.py and its Options
table, and not the hand-written glance above it."""
def test_every_parser_option_has_a_group(self):
named = {flag for _label, flags in sitediagrams.GLANCE_GROUPS for flag in flags}
self.assertEqual(set(sitediagrams.installer_flags()) - named, set())
self.assertEqual(named - set(sitediagrams.installer_flags()), set())
def test_an_option_no_group_names_still_lands(self):
with mock.patch.object(sitediagrams, "installer_flags",
return_value=["--platform", "--brand-new"]):
lines = sitediagrams.options_glance()
self.assertEqual(lines, [
"- **Where and for what**: [`--platform`](#option-platform)",
"- **Other**: [`--brand-new`](#option-brand-new)",
])
def test_the_source_holds_no_list(self):
text = (REPO_ROOT / "wiki" / "installer.md").read_text(encoding="utf-8")
section = text[text.index(sitediagrams.GLANCE_HEADING):text.index("\n## One line")]
self.assertNotIn("\n- ", section)
with self.assertRaises(ValueError):
sitediagrams.expand_glance(f"# x\n\n{sitediagrams.GLANCE_HEADING}\n\n- a\n\n## y\n")
@unittest.skipUnless((DIST / "systems" / "arcade-acclaim-zn-1" / "index.html").is_file(),
"site not built")
class BuiltCrumbs(unittest.TestCase):
+2 -1
View File
@@ -323,7 +323,8 @@ class OneOptionsTable(unittest.TestCase):
self.text = _wiki("installer.md")
def test_each_option_links_to_its_row(self):
glance = _section(self.text, "Options at a glance")
# The glance is written from the parser when the page is built.
glance = _section(sitediagrams.expand_glance(self.text), "Options at a glance")
table = _section(self.text, "Options")
self.assertNotIn("installer-options", glance)
self.assertNotIn("|", glance)
-6
View File
@@ -25,12 +25,6 @@ How the command protects your machine is under
Each links to its row in the [Options](#options) table.
- **Where and for what**: [`--platform`](#option-platform), [`--dest`](#option-dest), [`--target`](#option-target), [`--every-board`](#option-every-board), [`--save-dir`](#option-save-dir), [`--romm-firmware`](#option-romm-firmware), [`--standalone-copies`](#option-standalone-copies)
- **Fewer files**: [`--system`](#option-system), [`--core`](#option-core), [`--emulator`](#option-emulator), [`--region`](#option-region), [`--no-game-data`](#option-no-game-data), [`--declared-only`](#option-declared-only), [`--installed-cores`](#option-installed-cores), [`--cores-dir`](#option-cores-dir)
- **Look first**: [`--check`](#option-check), [`--dry-run`](#option-dry-run), [`--list-platforms`](#option-list-platforms), [`--list-targets`](#option-list-targets), [`--list-systems`](#option-list-systems), [`--list-cores`](#option-list-cores), [`--list-emulators`](#option-list-emulators)
- **From files you have**: [`--from`](#option-from), [`--from-pack`](#option-from-pack), [`--manifest`](#option-manifest), [`--offline`](#option-offline), [`--restore`](#option-restore)
- **How the run talks**: [`--tui`](#option-tui), [`--plain`](#option-plain), [`--no-input`](#option-no-input), [`--yes`](#option-yes), [`--quiet`](#option-quiet), [`--verbose`](#option-verbose), [`--events`](#option-events), [`--jobs`](#option-jobs), [`--version`](#option-version), [`--help`](#option-help)
## One line
```bash