docs: write headings in sentence case

The gaps page and the Advanced usage, Verification modes, Release process and Testing guide pages titled in Title Case beside sentence-case pages. A test now reads every wiki heading.
This commit is contained in:
Abdessamad Derraz committed 2026-10-20 06:16:00 +02:00
1 parent bb19a6d5cf
commit 23b69d185c
7 files changed
+73 -31

No files matched your search

+12 -12
View File
@@ -417,7 +417,7 @@ def generate_home(
"[Systems](systems/index.md){ .md-button } "
"[Emulators](emulators/index.md){ .md-button } "
"[Cross-reference](cross-reference.md){ .md-button } "
"[Gap Analysis](gaps.md){ .md-button } "
"[Gap analysis](gaps.md){ .md-button } "
"[Dump provenance](provenance.md){ .md-button } "
"[Data & API](data.md){ .md-button } "
"[Contributing](contributing.md){ .md-button .md-button--primary }",
@@ -3334,7 +3334,7 @@ def _render_problem_files(
if total_problems > 0:
lines.extend([
"## Problem Files",
"## Problem files",
"",
f"{len(missing_files)} missing, {len(untested_files)} untested, "
f"{len(mismatch_files)} hash mismatch.",
@@ -3374,7 +3374,7 @@ def _render_problem_files(
if mismatch_files:
lines.extend([
f'### Hash Mismatch <span class="rb-badge rb-badge-warning">'
f'### Hash mismatch <span class="rb-badge rb-badge-warning">'
f"{_count(len(mismatch_files), 'file')}</span>",
"",
"Platform says OK but emulator validation disagrees.",
@@ -3476,7 +3476,7 @@ def generate_gap_analysis(
)
lines = [
"# Gap Analysis",
"# Gap analysis",
"",
"Unified view of BIOS verification, file provenance, and coverage gaps.",
"",
@@ -3513,7 +3513,7 @@ def generate_gap_analysis(
# ---- Verification per platform ----
lines.extend([
"## What Each Pack Contains",
"## What each pack contains",
"",
"| Platform | On its BIOS list | Files its emulators load | Checked by |",
"|----------|-----------------:|-------------------------:|------------|",
@@ -3559,7 +3559,7 @@ def generate_gap_analysis(
"from its source code "
"([how each one works](wiki/verification-modes.md)).",
"",
"## Corroboration Against Emulator Source",
"## Corroboration against emulator source",
"",
"| Platform | On its BIOS list | Documented in a profile | Content the code checks |",
"|----------|-----------------:|------------------------:|------------------------:|",
@@ -3623,7 +3623,7 @@ def generate_gap_analysis(
)
lines.extend([
"## Platform Lists vs Emulator Source",
"## Platform lists vs emulator source",
"",
"Platform file lists are scraped as-is from each upstream project. "
"Emulator profiles are read from source code. The two do not always "
@@ -3691,7 +3691,7 @@ def generate_gap_analysis(
)
lines.extend([
"## Core Complement",
"## Core complement",
"",
f"Files loaded by emulators but not declared by any platform. "
f"{total_undeclared:,} files across {len(emulator_gaps)} emulators, "
@@ -3714,7 +3714,7 @@ def generate_gap_analysis(
"| GitHub release assets (large files) |",
f"| missing | {src_totals['missing']} | Not available, needs sourcing |",
"",
"### Per Emulator",
"### Per emulator",
"",
"| Emulator | Undeclared | bios | data | release | Missing |",
"|----------|----------:|-----:|-----:|--------:|--------:|",
@@ -3792,7 +3792,7 @@ def generate_gap_analysis(
if src_missing_details:
req_src = [m for m in src_missing_details if m["required"]]
lines.extend([
f"### Files to Source ({_count(len(src_missing_details), 'file')}, "
f"### Files to source ({_count(len(src_missing_details), 'file')}, "
f"{len(req_src)} required"
+ (
f", {_count(entries, 'entry', 'entries')}: "
@@ -3856,7 +3856,7 @@ def generate_gap_analysis(
if all_unsourceable:
lines.extend([
"## Acknowledged Gaps",
"## Acknowledged gaps",
"",
f"{len(all_unsourceable)} files documented as unsourceable "
"(verified from source code).",
@@ -4494,7 +4494,7 @@ def generate_mkdocs_nav(
{"Systems": system_nav},
{"Emulators": emu_nav},
{"Cross-reference": "cross-reference.md"},
{"Gap Analysis": "gaps.md"},
{"Gap analysis": "gaps.md"},
{"Dump provenance": "provenance.md"},
{"Data & API": "data.md"},
{"Wiki": wiki_nav},
+42
View File
@@ -111,6 +111,37 @@ def _yaml_prose(text: str) -> str:
return "\n".join(parts)
# Words a heading keeps capitalized: names of platforms, systems and programs.
_HEADING_KEEP = set("""Batocera Recalbox Lakka Dolphin Redump Linux Windows Android Python Steam Deck
macOS Termux Flatpak Switch Raspberry Pi Mega Drive Genesis Saturn Dreamcast Amiga Atari Sega
Nintendo Sony Neo Geo Lynx Jaguar Commodore Apple Mac Spectrum Sinclair Amstrad MSX GameCube Wii
Game Boy Advance Famicom Disk System Super Master PlayStation Vita Xbox Arcade English French
Spanish I Lua Unity Qt Kodi Discord Pages Actions Docker Windows Mac Ubuntu Debian Arch
SteamOS Chihiro NAOMI Triforce Lindbergh Model PinMAME ScummVM Dinothawr Cemu Citra Yuzu Ryujinx
Mednafen Beetle Stella Hatari Caprice Vice Fuse Daphne Hypseus Singe Supermodel Flycast Redream
DuckStation SwanStation Opera Gearsystem Picodrive Snes9x Nestopia Mesen Gambatte SameBoy
mGBA melonDS DeSmuME Azahar Eden Sudachi Torzu Xemu Cxbx PCSX2 AetherSX2 NetherSX2 ARMSX2
RPCS3 Vita3K Zinc Raine FBNeo FinalBurn Neo MAME Clrmamepro RomVault""".split())
def _sentence_case(title: str) -> str:
words = title.split(" ")
out = []
starts = True
for w in words:
core = w.strip("()[],:.")
if starts:
# The first word of a heading or of a clause after ":", "." or " - ".
head, sep, tail = core.partition("-")
if sep and re.fullmatch(r"[A-Z][a-z]+", tail) and tail not in _HEADING_KEEP:
w = w.replace(core, f"{head}-{tail.lower()}", 1)
elif re.fullmatch(r"[A-Z][a-z]+(-[A-Z]?[a-z]+)*", core) and core not in _HEADING_KEEP:
w = w.replace(core, core.lower(), 1)
out.append(w)
starts = w.endswith((":", ".")) or w == "-" or re.fullmatch(r"\d+[a-z]?\.?", w) is not None
return " ".join(out)
class NoGeneratedTypography(unittest.TestCase):
def test_no_dash_arrow_ellipsis_or_banner_glyphs(self):
offenders = []
@@ -187,6 +218,17 @@ class NoClauseSemicolons(unittest.TestCase):
]
self.assertEqual(offenders, [], "\n".join(offenders[:20]))
def test_wiki_headings_use_sentence_case(self):
"""The gaps page and two wiki pages titled in Title Case ("What Each
Pack Contains") beside sentence-case pages ("Quick install")."""
offenders = []
for page in sorted((ROOT / "wiki").glob("*.md")):
text = FENCED.sub("", page.read_text(encoding="utf-8"))
for title in re.findall(r"(?m)^#{1,4} (.+)$", text):
if _sentence_case(title) != title:
offenders.append(f"{page.name}: {title}")
self.assertEqual(offenders, [], "\n".join(offenders))
def test_yaml_prose_and_comments(self):
files = [p for p in _tracked() if p.suffix in (".yml", ".yaml")]
self._check(files, _yaml_prose)
+7 -7
View File
@@ -1,8 +1,8 @@
# Advanced Usage
# Advanced usage
Fine-grained control over pack generation, hardware filtering, truth analysis, and verification.
## Custom Packs
## Custom packs
### Build from hash
@@ -188,7 +188,7 @@ under the name the platform files the target by, whichever alias was typed:
never overwrite the full one.
## Pack Source Variants
## Pack source variants
A pack is built from two file sources: the platform's own declared list
(layer 1) and the requirements of the emulator profiles that apply to it
@@ -223,7 +223,7 @@ visible.
Variants compose with `--split`, `--target` and `--manifest`.
## Hardware Target Filtering
## Hardware target filtering
### What targets are
@@ -291,7 +291,7 @@ treat `--target <their-only-target>` as a no-op: the output is identical to runn
without `--target`.
## Truth Generation and Diffing
## Truth generation and diffing
### What truth is
@@ -360,7 +360,7 @@ makes md5 and core required on every element, and RomM compares the file
size before any hash, so an entry missing either could never verify.
## Emulator-Level Verification
## Emulator-level verification
### Per-emulator checks
@@ -410,7 +410,7 @@ emulator profiles. Files without ground truth rely solely on platform-level veri
JSON output (`--json`) always includes the full per-emulator detail regardless of verbosity.
## Offline Workflow
## Offline workflow
### Full offline pipeline
+1 -1
View File
@@ -4,7 +4,7 @@ Projects built on RetroBIOS data.
---
## BIOS Preservation Tool
## BIOS preservation tool
**Author:** [monster-penguin](https://github.com/monster-penguin)
**Repository:** [github.com/monster-penguin/BIOS-Preservation-Tool](https://github.com/monster-penguin/BIOS-Preservation-Tool)
+2 -2
View File
@@ -1,4 +1,4 @@
# Release Process
# Release process
This page documents the CI/CD pipeline: what each workflow does, how releases
are built, and how to run the process manually.
@@ -23,7 +23,7 @@ maintainer's machine and uploaded with `gh`, see
[cutting a release](#cutting-a-release): the packs weigh 25 GB, more than a
hosted runner should rebuild and re-upload.
## deploy-site.yml - Deploy Documentation Site
## deploy-site.yml - Deploy documentation site
**Trigger.** Push to `main` when any of these paths change: `platforms/`,
`emulators/`, `provenance/`, `wiki/`, `schemas/`, `tests/`, `docs_assets/`,
+1 -1
View File
@@ -1,4 +1,4 @@
# Testing Guide
# Testing guide
This page covers how to run, understand, and extend the test suite.
+8 -8
View File
@@ -1,10 +1,10 @@
# Verification Modes
# Verification modes
Each platform verifies BIOS files differently. `verify.py` replicates the native behavior
of each platform so that verification results match what the platform itself would report.
## Existence Mode
## Existence mode
**Platforms**: RetroArch, Lakka, RetroPie
@@ -26,7 +26,7 @@ Lakka and RetroPie inherit this behavior through platform config inheritance
(`inherits: retroarch` in the platform YAML).
## MD5 Mode
## MD5 mode
**Platforms**: Batocera, RetroBat, Recalbox, EmuDeck, RetroDECK, RomM, ROCKNIX, MiSTer FPGA
@@ -127,7 +127,7 @@ The 208 `games/` files shipped by the `Distribution_MiSTer` repository carry no
URL and install themselves, so they are not part of the pack.
## SHA1 Mode
## SHA1 mode
**Platforms**: BizHawk
@@ -141,7 +141,7 @@ via `compute_hashes()` and compares case-insensitively.
| File missing | MISSING | CRITICAL | WARNING |
## Emulator-Level Validation
## Emulator-level validation
Independent of platform verification mode, `verify.py` runs emulator-level validation
from `validation.py`. This layer uses data from emulator profiles (YAML files in
@@ -199,7 +199,7 @@ This catches cases where a file has the right name but wrong content, which exis
platforms cannot detect.
## Severity Matrix
## Severity matrix
`compute_severity()` maps the combination of status, required flag, verification mode,
and HLE fallback to a severity level.
@@ -221,7 +221,7 @@ is still included in packs (better accuracy with the real BIOS), but its absence
actionable.
## File Resolution Chain
## File resolution chain
Before verification, each file entry is resolved to a local path by `resolve_local_file()`.
The function tries these steps in order, returning the first
@@ -259,7 +259,7 @@ the divergence is printed as a discrepancy: an error in an upstream BIOS list
must not remove a file the frontend would have loaded.
## Discrepancy Detection
## Discrepancy detection
When platform verification passes but emulator validation fails, the file has a discrepancy.
This happens most often in existence-mode platforms where any file with the right name is