mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-11-13 13:33:52 -06:00
chore: merge origin/main
This commit is contained in:
commit
768cc9fd6f
14 files changed
+290
-10
No files matched your search
@@ -83,6 +83,17 @@ def load_ledger(path: Path = ARCHIVES_FILE) -> dict[str, dict]:
|
||||
return {record["path"]: record for record in data.get("archives", [])}
|
||||
|
||||
|
||||
def rebuild_counts(path: Path = ARCHIVES_FILE) -> dict[str, int]:
|
||||
"""How many recipes rebuild ``exact`` and ``canonical``, and ``total``:
|
||||
the figures the README and the site print."""
|
||||
records = load_ledger(path).values()
|
||||
return {
|
||||
"total": len(records),
|
||||
**{kind: sum(1 for r in records if r.get("rebuild") == kind)
|
||||
for kind in ("exact", "canonical")},
|
||||
}
|
||||
|
||||
|
||||
def ledger_text(records: dict[str, dict]) -> str:
|
||||
"""One record per line, so a diff names the archive that changed."""
|
||||
lines = [
|
||||
|
||||
@@ -38,6 +38,7 @@ from common import (
|
||||
write_if_changed,
|
||||
)
|
||||
from nativemode import check_label
|
||||
import archive_build
|
||||
import release_record
|
||||
from siteguides import PACKAGE_COMMAND, install_command
|
||||
from validate_pr import LARGE_FILE_ISSUE_URL
|
||||
@@ -563,6 +564,32 @@ def _catalog_matched_line(db: dict) -> list[str]:
|
||||
]
|
||||
|
||||
|
||||
# The badge row under the links: (alt text, image, link).
|
||||
BADGES = (
|
||||
("PyPI version", "https://img.shields.io/pypi/v/retrobios", "https://pypi.org/project/retrobios/"),
|
||||
("Python versions", "https://img.shields.io/pypi/pyversions/retrobios",
|
||||
"https://pypi.org/project/retrobios/"),
|
||||
("Validation",
|
||||
"https://github.com/Abdess/retrobios/actions/workflows/validate.yml/badge.svg?branch=main",
|
||||
"https://github.com/Abdess/retrobios/actions/workflows/validate.yml"),
|
||||
("License", "https://img.shields.io/github/license/Abdess/retrobios", "LICENSE"),
|
||||
)
|
||||
|
||||
|
||||
def badge_row() -> str:
|
||||
badges = " ".join(f'<a href="{link}"><img src="{image}" alt="{alt}"></a>'
|
||||
for alt, image, link in BADGES)
|
||||
return f'<p align="center">{badges}</p>'
|
||||
|
||||
|
||||
def archive_line(counts: dict[str, int]) -> str:
|
||||
"""The README's line on archive recipes, counted from recipes/archives.json."""
|
||||
return (f"- **{counts['total']:,} archives**, each with a recipe that rebuilds it from the"
|
||||
f" ROMs the collection holds: {counts['exact']:,} byte for byte,"
|
||||
f" {counts['canonical']:,} with the same files inside"
|
||||
f" ([rebuilding an archive]({SITE_URL}wiki/archive-recipes/))")
|
||||
|
||||
|
||||
def generate_readme(db: dict, platforms_dir: str, db_path: str = "database.json") -> str:
|
||||
total_files = db.get("total_files", 0)
|
||||
total_size = db.get("total_size", 0)
|
||||
@@ -629,6 +656,8 @@ def generate_readme(db: dict, platforms_dir: str, db_path: str = "database.json"
|
||||
f'<a href="{SITE_URL}wiki/faq/">Questions</a>'
|
||||
"</p>",
|
||||
"",
|
||||
badge_row(),
|
||||
"",
|
||||
"Some consoles need a small program, the **BIOS**, to start their games: PlayStation 1"
|
||||
" and 2, Saturn, Neo Geo, Dreamcast and more. RetroBIOS installs the BIOS and firmware"
|
||||
f" files for {_names_list(coverages)}, checked the way each one checks them.",
|
||||
@@ -715,6 +744,7 @@ def generate_readme(db: dict, platforms_dir: str, db_path: str = "database.json"
|
||||
" the file name for RetroArch, as their own code does",
|
||||
f"- **{len(system_ids)} systems**, {total_files:,} files in the collection,"
|
||||
f" [what is still missing]({SITE_URL}gaps/) named file by file",
|
||||
archive_line(archive_build.rebuild_counts()),
|
||||
f"- Public data: [JSON, CSV and SQLite]({SITE_URL}data/), the metadata under MIT",
|
||||
"",
|
||||
"<details>",
|
||||
|
||||
@@ -124,6 +124,7 @@ from siteguides import (
|
||||
terminal_tabs,
|
||||
trust_box,
|
||||
)
|
||||
import archive_build
|
||||
import release_record
|
||||
import sitebootstrap
|
||||
import sitemeasurements
|
||||
@@ -8789,6 +8790,30 @@ def with_romset_table(page: str, profiles: dict) -> str:
|
||||
return _ROMSET_BLOCK.sub(lambda m: f"{m.group(1)}{table}{m.group(3)}", page, count=1)
|
||||
|
||||
|
||||
ARCHIVE_RECIPES_PAGE = Path(WIKI_SRC_DIR) / "archive-recipes.md"
|
||||
_ARCHIVE_BLOCK = re.compile(
|
||||
r"(<!-- generated: archive-recipes -->\n)(.*?)(<!-- /generated: archive-recipes -->)", re.S)
|
||||
|
||||
|
||||
def archive_recipe_lines(counts: dict[str, int]) -> list[str]:
|
||||
"""What an exact and a canonical rebuild are, each with its count."""
|
||||
total = f"{counts['total']:,}"
|
||||
return [
|
||||
f"- **Exact**: the same bytes as the archive in the collection."
|
||||
f" {counts['exact']:,} of {total} archives.",
|
||||
f"- **Canonical**: the same files inside, packed another way."
|
||||
f" {counts['canonical']:,} of {total} archives.",
|
||||
]
|
||||
|
||||
|
||||
def with_archive_counts(page: str, counts: dict[str, int]) -> str:
|
||||
"""The archive recipes page with its counts read from recipes/archives.json."""
|
||||
block = "\n".join(archive_recipe_lines(counts)) + "\n"
|
||||
if not _ARCHIVE_BLOCK.search(page):
|
||||
raise ValueError(f"{ARCHIVE_RECIPES_PAGE}: no archive-recipes block to write the counts in")
|
||||
return _ARCHIVE_BLOCK.sub(lambda m: f"{m.group(1)}{block}{m.group(3)}", page, count=1)
|
||||
|
||||
|
||||
def _render_metadata_rows(profile: dict) -> list[str]:
|
||||
"""Scalar profile fields, one table row each.
|
||||
|
||||
@@ -12678,6 +12703,10 @@ def _generate(args: argparse.Namespace) -> None:
|
||||
faq = _REPO_ROOT / ROMSET_FAQ_PAGE
|
||||
if faq.is_file():
|
||||
write_if_changed(str(faq), with_romset_table(faq.read_text(encoding="utf-8"), profiles))
|
||||
recipes_page = _REPO_ROOT / ARCHIVE_RECIPES_PAGE
|
||||
if recipes_page.is_file():
|
||||
write_if_changed(str(recipes_page), with_archive_counts(
|
||||
recipes_page.read_text(encoding="utf-8"), archive_build.rebuild_counts()))
|
||||
if wiki_src.is_dir():
|
||||
for src_file in wiki_src.glob("*.md"):
|
||||
shutil.copy2(src_file, wiki_dest / src_file.name)
|
||||
|
||||
@@ -19,6 +19,8 @@ import json
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
|
||||
from siteguides import PACKAGE_COMMAND
|
||||
|
||||
SITE = "https://abdess.github.io/retrobios/"
|
||||
PUBLISHER = "abdess.github.io"
|
||||
SH = "curl -fsSL https://abdess.github.io/retrobios/i | sh -s --"
|
||||
@@ -31,6 +33,22 @@ QUOTING = (f"A folder goes in single quotes, attached to its option. One that ho
|
||||
"command. On a page of the site, the `device_setup_command` WebMCP tool writes the "
|
||||
"command and refuses such a folder.")
|
||||
|
||||
UVX = PACKAGE_COMMAND
|
||||
# EVENT_SCHEMA of install.py, and the schema generate_event_schema.py writes
|
||||
# from EVENT_KEYS, as the site publishes it. tests/test_site_agents.py holds
|
||||
# both to install.py.
|
||||
EVENT_SCHEMA = 1
|
||||
EVENTS_SCHEMA_PATH = "api/v1/schemas/install-events.schema.json"
|
||||
# Every code of the Exit status table of wiki/installer.md, in short.
|
||||
EXIT_CODES = (
|
||||
(0, "everything asked for is installed or already in place"),
|
||||
(1, "a failure: a download, a file list or a folder that cannot be used"),
|
||||
(2, "nothing was done: an option refused, every file failed, or the user quit"),
|
||||
(3, "`--check` found files missing or wrong, nothing having failed"),
|
||||
(130, "stopped with Ctrl+C"),
|
||||
(141, "the output went to a reader that closed early"),
|
||||
)
|
||||
|
||||
START_PAGES = (
|
||||
("Which pack is needed", "which-pack/", "pick a device or setup, get the folder its BIOS files go in, "
|
||||
"one command that installs and checks them, or a ZIP pack"),
|
||||
@@ -108,6 +126,24 @@ def llms_txt(platforms: list[tuple[str, str]]) -> str:
|
||||
"- A game the installer cannot identify gets every BIOS of its system, and the run names it.",
|
||||
f"- The same steps as an Agent Skill: [SKILL.md]({SITE}{SKILL_PATH}).",
|
||||
"",
|
||||
"## Driving the installer for a user",
|
||||
"",
|
||||
f"With Python 3.8 or newer, the installer is also a package: `{UVX} --for-games "
|
||||
f"--required-only --dry-run`, or `pipx run retrobios` with the same options.",
|
||||
"",
|
||||
"1. Plan: run it with `--dry-run --events`. Nothing is written, and standard output carries "
|
||||
f"one JSON object per line, event schema {EVENT_SCHEMA} "
|
||||
f"([JSON Schema]({SITE}{EVENTS_SCHEMA_PATH})): a `file` event per file with its "
|
||||
"`state`, `check` with the totals and `bytes_needed`, then `summary` with `pending`.",
|
||||
"2. Consent: show the user what will be installed, where, and the size, and ask.",
|
||||
"3. Act, only on a yes: the same command with `--yes` in place of `--dry-run`.",
|
||||
"",
|
||||
"- `--yes` asks nothing: use it in a run without a terminal. A run that needs an "
|
||||
"answer (two setups found, none found) stops and names the option to give.",
|
||||
"- The exit code, also the `code` of the last `end` event. An option refused at once "
|
||||
"writes no event at all:",
|
||||
*(f" - `{code}`: {meaning}" for code, meaning in EXIT_CODES),
|
||||
"",
|
||||
"## Start",
|
||||
"",
|
||||
*(f"- [{title}]({SITE}{path}): {what}" for title, path, what in START_PAGES),
|
||||
|
||||
Reference in new issue
Block a user