feat: offer the one-liner where users land

The download page had no Android line and no Windows form with
options, and platform and system pages offered only ZIP parts. One
generator now writes the Linux, Windows and Android commands for the
download page and the platform, emulator and system pages. A system
section lists each platform's command under the system id its install
manifest records, the only names --system accepts.
This commit is contained in:
Abdessamad Derraz committed 2026-10-21 10:20:19 +02:00
1 parent ccd1ff3b0b
commit f15fa46d85
2 files changed
+167 -25

No files matched your search

+123 -25
View File
@@ -20,6 +20,7 @@ import io
import json
import os
import re
import shlex
import shutil
import sqlite3
import sys
@@ -2157,6 +2158,7 @@ def generate_platform_page(
f"| Contributed by | [@{username}](https://github.com/{username}):"
f" {contribution}{pr_link} |"
)
lines.extend(["", *_platform_install_block(name, display)])
lines.extend(
[
"",
@@ -2842,11 +2844,46 @@ def _card_title(text: str, anchor: str) -> str:
return f"#### {text} {{ {attributes} }}"
def system_install_choices(coverages: dict) -> dict[str, dict[str, list[tuple[str, str]]]]:
"""slug -> console -> the (platform, system id) pairs linked to it.
Read through the same table as every system link, so a command sits in
the section the platform's system links to. Only a system the
platform's install manifest records is offered: --system refuses any
other name.
"""
from siterender import _page_by_norm # noqa: PLC0415
choices: dict[str, dict[str, list[tuple[str, str]]]] = {}
for name, cov in sorted(coverages.items()):
manifest = _REPO_ROOT / "install" / f"{name}.json"
if not manifest.is_file():
continue
document = json.loads(manifest.read_text(encoding="utf-8"))
recorded = {
str(system)
for entry in (document.get("files") or []) + (document.get("omitted_files") or [])
for system in entry.get("systems") or []
}
for sys_id in sorted(set(cov["config"].get("systems") or {}) & recorded):
page = _system_page_map.get(sys_id) or _page_by_norm(sys_id)
if page:
slug, console = page
choices.setdefault(slug, {}).setdefault(console, []).append((name, sys_id))
return choices
def generate_system_page(
manufacturer: str,
consoles: dict[str, list],
consumers: FileConsumers,
installs: dict[str, list[tuple[str, str]]] | None = None,
) -> str:
"""One manufacturer's page, a section per console.
``installs`` gives, per console, the (platform, system id) pairs whose
install command the section offers.
"""
slug = manufacturer.lower().replace(" ", "-")
lines = [
f"# {manufacturer}",
@@ -2862,6 +2899,7 @@ def generate_system_page(
icon_md = system_icon_markdown(manufacturer, console_name)
lines.append(f"## {icon_md}{console_name}")
lines.append("")
lines.extend(_console_install_block(console_name, (installs or {}).get(console_name, [])))
main_files = [f for f in files if "/.variants/" not in f["path"]]
variant_files = [f for f in files if "/.variants/" in f["path"]]
@@ -3477,6 +3515,37 @@ def _render_metadata_rows(profile: dict) -> list[str]:
return lines
INSTALL_BASE = "https://raw.githubusercontent.com/Abdess/retrobios/main"
def install_command(shell: str, args: str = "") -> str:
"""The one-liner for a shell, with installer options when given.
PowerShell passes options only through a scriptblock: `irm ... | iex`
takes none.
"""
if shell == "sh":
return f"curl -fsSL {INSTALL_BASE}/install.sh | sh" + (f" -s -- {args}" if args else "")
if args:
return f"&([scriptblock]::Create((irm {INSTALL_BASE}/install.ps1))) {args}"
return f"irm {INSTALL_BASE}/install.ps1 | iex"
def install_commands(args: str = "", indent: str = "") -> list[str]:
"""The same install for each place a user runs it: one generator for the
download page and the platform, system and emulator pages, so none of
them loses the Android or Windows form."""
lines = []
for label, shell in (
("Linux, macOS, Steam Deck:", "sh"),
("Windows (PowerShell):", "powershell"),
("Android (Termux, after `pkg install python` and `termux-setup-storage`):", "sh"),
):
lines += [f"{indent}{label}", "", f"{indent}```{shell}",
f"{indent}{install_command(shell, args)}", f"{indent}```", ""]
return lines
def _install_block(name: str, profile: dict) -> list[str]:
"""One command that installs this emulator's files where it reads them.
@@ -3486,26 +3555,49 @@ def _install_block(name: str, profile: dict) -> list[str]:
"""
if profile.get("type") in ("alias", "launcher", "test"):
return []
base = "https://raw.githubusercontent.com/Abdess/retrobios/main"
return [
'??? tip "Install this emulator\'s files"',
"",
" The installer finds your platform and its BIOS folder, then downloads "
"only the files this emulator reads there and checks each one.",
"",
" Linux, macOS, Steam Deck:",
*install_commands(f"--emulator {name}", " "),
]
def _platform_install_block(name: str, display: str) -> list[str]:
"""The platform's pack, installed file by file: what the ZIP holds, with
resume, verification and only what is missing."""
return [
'???+ tip "Install with one command"',
"",
f" The installer downloads the files of the {display} pack that are "
"missing or differ from it, checks each one, and resumes where it "
"stopped. Add `--system` with the ids of the systems overview below "
"to install fewer systems.",
"",
*install_commands(f"--platform {name}", " "),
]
def _console_install_block(console: str, declared: list[tuple[str, str]]) -> list[str]:
"""Each platform's command for one console, by the system id it gives it."""
if not declared:
return []
lines = [
f'??? tip "Install the {console} files"',
"",
" Each platform names the system its own way. On Windows, run the "
"same options through `&([scriptblock]::Create((irm "
f"{INSTALL_BASE}/install.ps1)))`.",
"",
" ```sh",
f" curl -fsSL {base}/install.sh | sh -s -- --emulator {name}",
" ```",
"",
" Windows (PowerShell):",
"",
" ```powershell",
f" &([scriptblock]::Create((irm {base}/install.ps1))) --emulator {name}",
" ```",
"",
]
lines += [
f" {install_command('sh', f'--platform {platform} --system {shlex.quote(system)}')}"
for platform, system in declared
]
return lines + [" ```", ""]
def _render_platform_details(profile: dict) -> list[str]:
@@ -5045,6 +5137,10 @@ def _mkdocs_config(docs: Path, text: str) -> tuple[Path, str]:
def generate_which_pack() -> str:
"""Generate the 'Which pack?' decision page."""
rel = RELEASE_URL
quick = "\n".join(install_commands())
check_sh, check_ps = install_command("sh", "--check"), install_command("powershell", "--check")
sd_card = install_command(
"sh", "--platform retroarch --dest /path/to/sdcard/retroarch/cores/system")
return _link_pack_cells(f"""\
# Download
@@ -5063,18 +5159,7 @@ incorrect files, verifies their hashes, and installs them atomically. The small
bootstrap checks the Python installer against an embedded SHA-256 before running
it, and the installer reads its file list from that same revision.
**Linux / Mac / Steam Deck:**
```sh
curl -fsSL https://raw.githubusercontent.com/Abdess/retrobios/main/install.sh | sh
```
**Windows (PowerShell):**
```powershell
irm https://raw.githubusercontent.com/Abdess/retrobios/main/install.ps1 | iex
```
{quick}
That is the complete default flow. Extra copies into detected standalone
emulator directories are deliberately opt-in with `--standalone-copies`, so
automatic setup never writes outside the selected platform tree unexpectedly.
@@ -5082,7 +5167,19 @@ Adding `--check` reads the BIOS folder, hashes what is there and writes
nothing:
```sh
curl -fsSL https://raw.githubusercontent.com/Abdess/retrobios/main/install.sh | sh -s -- --check
{check_sh}
```
```powershell
{check_ps}
```
An SD card mounted on a PC is installed with `--platform` and `--dest`, the
BIOS folder on the card (RetroArch on Switch: `retroarch/cores/system`, on
Android: `RetroArch/system`):
```sh
{sd_card}
```
Options, environment overrides, how each platform is detected and what the
@@ -5488,11 +5585,12 @@ def _generate(args: argparse.Namespace) -> None:
write_if_changed(
str(docs / "systems" / "index.md"), generate_systems_index(manufacturers)
)
console_installs = system_install_choices(coverages)
(docs / "files").mkdir(parents=True, exist_ok=True)
write_if_changed(str(docs / "files" / "index.md"), generate_files_page())
for mfr, consoles in manufacturers.items():
slug = mfr.lower().replace(" ", "-")
page = generate_system_page(mfr, consoles, consumers)
page = generate_system_page(mfr, consoles, consumers, console_installs.get(slug))
PAGE_DATA[f"systems/{slug}.md"] = system_structured_data(slug, consoles)
write_single_item(
docs / "api" / "v1" / "systems" / f"{slug}.json", "systems",
+44
View File
@@ -882,6 +882,50 @@ class NestedProfileBlocksRenderAsLists(unittest.TestCase):
self.assertIn("name: a.bin, size: 2", html)
class TheOneLinerIsWhereUsersLand(unittest.TestCase):
"""The download page had no Android line and no Windows form with
options, and the platform and system pages offered only ZIP parts."""
def test_download_page(self):
import generate_site # noqa: PLC0415
page = generate_site.generate_which_pack()
self.assertIn("Android (Termux", page)
self.assertIn("install.ps1))) --check", page)
self.assertIn("--platform retroarch --dest", page)
def test_platform_and_system_pages(self):
import generate_site # noqa: PLC0415
from cross_reference import FileConsumers # noqa: PLC0415
cov = {"platform": "Batocera", "mode": "md5", "present": 0, "total": 0,
"verified": 0, "missing": 0, "details": [], "config": {"systems": {}}}
page = generate_site.generate_platform_page("batocera", cov)
self.assertIn("install.sh | sh -s -- --platform batocera", page)
self.assertIn("install.ps1))) --platform batocera", page)
self.assertLess(page.index("--platform batocera"), page.index("md-button"))
entry = {"name": "a.bin", "sha1": "a" * 40, "md5": "b" * 32, "size": 3,
"path": "bios/Recalbox/MSX/a.bin"}
page = generate_site.generate_system_page(
"Microsoft", {"MSX": [entry]}, FileConsumers({"files": {}}),
{"MSX": [("recalbox", "msx1,msx2,msxturbor"), ("batocera", "msx1")]})
self.assertIn("--platform recalbox --system msx1,msx2,msxturbor\n", page)
self.assertIn("--platform batocera --system msx1", page)
def test_a_system_is_offered_only_where_the_manifest_records_it(self):
import generate_site # noqa: PLC0415
import siterender # noqa: PLC0415
saved = dict(siterender._system_page_map)
self.addCleanup(siterender.set_system_page_map, saved)
siterender.set_system_page_map({"sony-playstation": ("sony", "PlayStation"),
"nowhere": ("sony", "PlayStation")})
cov = {"retroarch": {"config": {"systems": {"sony-playstation": {}, "nowhere": {}}}}}
choices = generate_site.system_install_choices(cov)
self.assertEqual(choices["sony"]["PlayStation"], [("retroarch", "sony-playstation")])
class TheGapPageOpensOnCounts(unittest.TestCase):
"""The page opened on a slogan and on tiles reading "Verified (100%)"
for 5,437 of 5,438 files and "Missing 0", above a table of red cells."""