mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-10-29 07:07:20 -05:00
feat: cite terminals, shares and hardware
This commit is contained in:
1 parent
4b7cf6501f
commit
235001babb
3 files changed
+242
-49
No files matched your search
+70
-10
@@ -38,7 +38,12 @@ class Guide:
|
||||
and open the Reference part of the page. `download_hint` is one more
|
||||
bullet under the pack download. `pack_manual` is the manual part of the
|
||||
README inside the pack. `logo_owner` and `logo_licence` credit the
|
||||
logo the registry links.
|
||||
logo the registry links. `runs_on` names the hardware, in the project's
|
||||
own words, for its tile and its row. `terminal` replaces the shared
|
||||
"On the device" text with how this system opens a terminal. `lead` comes
|
||||
before the folder table, `finish` closes the guide. `{folders}` in
|
||||
`pack_manual` is the folder list of the page, so the README and the page
|
||||
cannot name two folders for one device.
|
||||
"""
|
||||
|
||||
summary: str
|
||||
@@ -56,6 +61,10 @@ class Guide:
|
||||
pack_manual: str = ""
|
||||
logo_owner: str = ""
|
||||
logo_licence: str = ""
|
||||
runs_on: str = ""
|
||||
terminal: str = ""
|
||||
lead: str = ""
|
||||
finish: str = ""
|
||||
|
||||
|
||||
_GUIDE_KEYS = {f.name for f in fields(Guide)}
|
||||
@@ -130,7 +139,7 @@ def _guide(name: str, data: object, extract_to: str, terminals: dict) -> Guide:
|
||||
if key in data:
|
||||
values[key] = _texts(data[key], f"{where}.{key}")
|
||||
for key in ("dest", "download_hint", "more_folders_title", "more_folders_note",
|
||||
"logo_owner", "logo_licence"):
|
||||
"logo_owner", "logo_licence", "runs_on", "terminal", "lead", "finish"):
|
||||
if key in data:
|
||||
values[key] = _text(data[key], f"{where}.{key}")
|
||||
for key in ("on_card", "more_folders_detected"):
|
||||
@@ -213,6 +222,7 @@ def load_guides(path: Path = GUIDES_FILE, registry: dict | None = None) -> dict:
|
||||
# The order the file is written in, which the pages do not depend on.
|
||||
guides = {name: guides[name] for name in raw_guides}
|
||||
return {
|
||||
"registry": registry,
|
||||
"install_base": _text(data.get("install_base"), f"{path.name}: install_base"),
|
||||
"trust_items": _texts(data.get("trust_items"), f"{path.name}: trust_items"),
|
||||
"device_groups": groups,
|
||||
@@ -231,6 +241,43 @@ ALSO_RUNS: dict[str, tuple[tuple[str, str, str, str, str], ...]] = _LOADED["also
|
||||
TERMINALS: dict[str, tuple[str, str, str]] = _LOADED["terminals"]
|
||||
TRUST_ITEMS: tuple[str, ...] = _LOADED["trust_items"]
|
||||
GUIDES: dict[str, Guide] = _LOADED["guides"]
|
||||
_REGISTRY: dict = _LOADED["registry"]
|
||||
|
||||
|
||||
def detected_systems(registry: dict, platform: str) -> list[tuple[str, str]]:
|
||||
"""Named systems a platform runs on, handhelds that run RetroArch for one,
|
||||
with the BIOS folder the installer writes on each, read from the
|
||||
detection rules of the registry."""
|
||||
seen: dict[str, str] = {}
|
||||
for rule in (registry.get(platform, {}).get("install") or {}).get("detect") or []:
|
||||
name, folder = rule.get("name"), rule.get("bios_path")
|
||||
if name and folder and rule.get("installs", True) and name not in seen:
|
||||
seen[name] = folder
|
||||
return sorted(seen.items(), key=lambda item: item[0].casefold())
|
||||
|
||||
|
||||
def folder_list(name: str, registry: dict | None = None) -> list[tuple[str, str]]:
|
||||
"""Every folder a guide names, as (where, path): the common ones, the
|
||||
rarer builds, and the systems the registry detects when the guide says so."""
|
||||
guide = GUIDES[name]
|
||||
rows = [*guide.folders, *guide.more_folders]
|
||||
if guide.more_folders_detected:
|
||||
rows += [(system, f"`{folder}`")
|
||||
for system, folder in detected_systems(registry or _REGISTRY, name)]
|
||||
return rows
|
||||
|
||||
|
||||
def _plain_folders(name: str) -> str:
|
||||
rows = folder_list(name)
|
||||
width = max(len(where) for where, _path in rows) + 1
|
||||
return "\n".join(f"{where + ':':<{width}} {path.replace('`', '')}" for where, path in rows)
|
||||
|
||||
|
||||
def setup_count() -> int:
|
||||
"""Setups the site covers: every registered platform, archived ones
|
||||
included, since each keeps its page, its pack and its command. The one
|
||||
count every page states."""
|
||||
return len(GUIDES)
|
||||
|
||||
|
||||
def pack_manual(name: str) -> str:
|
||||
@@ -238,7 +285,12 @@ def pack_manual(name: str) -> str:
|
||||
guide = GUIDES.get(name)
|
||||
if not guide or not guide.pack_manual:
|
||||
return ""
|
||||
return textwrap.indent(guide.pack_manual.rstrip("\n"), " ") + "\n\n"
|
||||
text = guide.pack_manual.rstrip("\n")
|
||||
if "{folders}" in text:
|
||||
indent = next(line[: len(line) - len(line.lstrip())]
|
||||
for line in text.splitlines() if "{folders}" in line)
|
||||
text = text.replace("{folders}", textwrap.indent(_plain_folders(name), indent).lstrip())
|
||||
return textwrap.indent(text, " ") + "\n\n"
|
||||
|
||||
|
||||
def install_command(shell: str, args: str = "") -> str:
|
||||
@@ -254,21 +306,29 @@ def install_command(shell: str, args: str = "") -> str:
|
||||
return f"irm {INSTALL_BASE}/install.ps1 | iex"
|
||||
|
||||
|
||||
def _fence(shell: str, command: str, indent: str) -> list[str]:
|
||||
return [f"{indent}```{shell}", f"{indent}{command}", f"{indent}```"]
|
||||
# The title over each command block, by the shell it is written for.
|
||||
FENCE_TITLES = {"sh": "Terminal", "powershell": "PowerShell"}
|
||||
|
||||
|
||||
def fence(shell: str, command: str, indent: str = "") -> list[str]:
|
||||
"""A command block with its title, as every command on the site is shown."""
|
||||
return [f'{indent}```{shell} title="{FENCE_TITLES[shell]}"', f"{indent}{command}", f"{indent}```"]
|
||||
|
||||
|
||||
def terminal_tabs(
|
||||
tabs: tuple[str, ...], args: dict[str, str], indent: str = "", steps: bool = True,
|
||||
how: dict[str, str] | None = None,
|
||||
) -> list[str]:
|
||||
"""Content tabs, one per place the command runs, each with how to open
|
||||
the terminal there. Tab labels are shared across the site, so the reader's
|
||||
choice carries from page to page."""
|
||||
choice carries from page to page. *how* replaces the shared text of a tab
|
||||
for one setup."""
|
||||
items = []
|
||||
for key in tabs:
|
||||
label, how, shell = TERMINALS[key]
|
||||
body = [how, ""] if steps else []
|
||||
items.append((label, body + _fence(shell, install_command(shell, args.get(key, "")), "")))
|
||||
label, shared, shell = TERMINALS[key]
|
||||
text = (how or {}).get(key) or shared
|
||||
body = [text, ""] if steps else []
|
||||
items.append((label, body + fence(shell, install_command(shell, args.get(key, "")))))
|
||||
return markup.tabs(items, indent)
|
||||
|
||||
|
||||
@@ -287,7 +347,7 @@ def platform_args(name: str, guide: Guide) -> dict[str, str]:
|
||||
|
||||
|
||||
def trust_box(prefix: str, indent: str = "") -> list[str]:
|
||||
"""Is it safe, under every command on a guide page: five plain facts."""
|
||||
"""Is it safe, under every command on a guide page: the trust items."""
|
||||
return markup.details("note", "Is it safe?", [
|
||||
*(f"- {item}" for item in TRUST_ITEMS),
|
||||
"",
|
||||
|
||||
@@ -184,6 +184,37 @@ def card_grid(cards: list[tuple[str, list[str]]]) -> list[str]:
|
||||
return [*lines, "</div>", ""]
|
||||
|
||||
|
||||
def initials(name: str) -> str:
|
||||
"""Up to two capitals of a name, for a setup that has no logo: MyMinUI is MM."""
|
||||
capitals = [c for c in name if c.isupper()]
|
||||
return "".join(capitals[:2]) or name[:2].upper()
|
||||
|
||||
|
||||
def _tile_mark(name: str, logo: str) -> str:
|
||||
if logo:
|
||||
return f'<img class="rb-card-logo" src="{html.escape(logo)}" alt="" width="40" height="40">'
|
||||
# The same square as a logo, drawn on white like the logos are.
|
||||
return ('<svg class="rb-card-logo" viewBox="0 0 40 40" width="40" height="40" aria-hidden="true">'
|
||||
'<text x="20" y="25" text-anchor="middle" font-size="14" font-weight="700" '
|
||||
f'fill="#2b2e5c">{html.escape(initials(name))}</text></svg>')
|
||||
|
||||
|
||||
def platform_tiles(tiles: list[tuple[str, str, str, str]]) -> list[str]:
|
||||
"""One link per setup: its logo (or its initials), its name and where it
|
||||
runs. *tiles* are (name, target, logo, runs_on).
|
||||
|
||||
One HTML line: a grid of links is a menu, not prose, and the Markdown
|
||||
around it reads none of it."""
|
||||
links = "".join(
|
||||
f'<a class="rb-card rb-tile" href="{html.escape(target)}">'
|
||||
f"{_tile_mark(name, logo)}"
|
||||
f'<strong class="rb-tile-name">{html.escape(name)}</strong>'
|
||||
f'<span class="rb-tile-runs">{inline_html(runs_on)}</span></a>'
|
||||
for name, target, logo, runs_on in tiles
|
||||
)
|
||||
return [f'<div class="rb-cards rb-tiles">{links}</div>', ""]
|
||||
|
||||
|
||||
def steps(items: list[str]) -> list[str]:
|
||||
"""An ordered list drawn as numbered steps."""
|
||||
return [block_open("div", 'class="rb-steps"'), "",
|
||||
|
||||
Reference in new issue
Block a user