mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-10-24 04:39:06 -05:00
feat: name each system once, from its own section
siterender.system_name reads the heading of the console section the id spells, then SYSTEM_NAMES, and raises for an id in neither. The emulator intro and the picker titles read it instead of the folder a system's files were voted onto.
This commit is contained in:
1 parent
2a50bc21f7
commit
72e7507ff9
4 files changed
+695
-10
No files matched your search
@@ -4656,12 +4656,9 @@ def _emulator_intro(emu_name: str, profile: dict) -> str:
|
||||
The page opened on a twelve-row table: "Type", "Version", "Checked by".
|
||||
DuckStation needs one PlayStation BIOS of any name, and the reader had
|
||||
to infer it from the "Files found by" row."""
|
||||
import siterender # noqa: PLC0415
|
||||
|
||||
names: list[str] = []
|
||||
for sys_id in profile.get("systems") or []:
|
||||
page = siterender._system_page_map.get(sys_id) or siterender._page_by_norm(sys_id)
|
||||
label = page[1] if page else str(sys_id)
|
||||
label = system_name(sys_id)
|
||||
if label not in names:
|
||||
names.append(label)
|
||||
files = [f for f in profile.get("files") or [] if isinstance(f, dict)]
|
||||
@@ -6675,6 +6672,7 @@ def _generate(args: argparse.Namespace) -> None:
|
||||
_system_pages_from_data(manufacturers, coverages, db, profiles)
|
||||
)
|
||||
print(f" {len(_system_page_map)} system IDs mapped to pages")
|
||||
set_console_sections(manufacturers, program_names(profiles))
|
||||
|
||||
print("Generating system pages...")
|
||||
|
||||
@@ -6864,11 +6862,10 @@ def _generate(args: argparse.Namespace) -> None:
|
||||
json.dumps(sitestarlight.sidebar(nav, present), indent=1) + "\n")
|
||||
write_if_changed(str(generated / "lastmod.json"),
|
||||
json.dumps(dict(sorted(lastmod.items())), indent=1) + "\n")
|
||||
mfr_names = {mfr.lower().replace(" ", "-"): mfr for mfr in manufacturers}
|
||||
titles = {}
|
||||
for sys_id, (slug, console) in _system_page_map.items():
|
||||
mfr = mfr_names.get(slug, "")
|
||||
titles[sys_id] = console if console.startswith(mfr) else f"{mfr} {console}"
|
||||
system_ids = set(_system_page_map) | {
|
||||
sys_id for cov in coverages.values() for sys_id in cov["config"].get("systems", {})
|
||||
}
|
||||
titles = {sys_id: system_name(sys_id) for sys_id in sorted(system_ids)}
|
||||
sitestarlight.picker_data(_REPO_ROOT / "install", public / "data" / "picker", titles)
|
||||
total_pages = len(present)
|
||||
print(f"\nGenerated {total_pages} pages in {args.docs_dir}/")
|
||||
@@ -6903,6 +6900,9 @@ from siterender import ( # noqa: E402,F401
|
||||
_system_page_map,
|
||||
_system_link,
|
||||
set_system_page_map,
|
||||
set_console_sections,
|
||||
program_names,
|
||||
system_name,
|
||||
card_page,
|
||||
console_href,
|
||||
record_console_pages,
|
||||
|
||||
@@ -306,3 +306,568 @@ def _by_mode(value: str | dict, mode: str) -> str:
|
||||
if isinstance(value, dict):
|
||||
return str(value.get(mode) or "") if mode else ""
|
||||
return str(value or "")
|
||||
|
||||
|
||||
# System display names. A system is named by the heading of the console
|
||||
# section that is its own: the folder its id spells, under the maker the id
|
||||
# names when two makers file a console of that name. The page a system's
|
||||
# files were voted onto is not its section and never names it. That vote
|
||||
# called nintendo-gba "Nintendo Game Boy" and camplynx "Arcade". A system
|
||||
# with no section of its own is named in SYSTEM_NAMES, and a system in
|
||||
# neither stops the build.
|
||||
|
||||
# Folders that file copies by the emulator or board that loads them. Their
|
||||
# headings name a program, not a system.
|
||||
_PROGRAM_TOPS = frozenset({"Other", "Arcade"})
|
||||
|
||||
# Normalized id -> the console sections spelled that way.
|
||||
_console_sections: dict[str, set[tuple[str, str]]] = {}
|
||||
# Emulator and engine names. A heading spelled like one (Game Engines >
|
||||
# Solarus, ZC > ZC210) files that program's copies and names no system.
|
||||
_program_names: set[str] = set()
|
||||
|
||||
|
||||
def program_names(profiles: dict[str, dict]) -> set[str]:
|
||||
"""Every name an emulator profile goes by: its key, its emulator and
|
||||
display names."""
|
||||
names = {str(key) for key in profiles}
|
||||
for profile in profiles.values():
|
||||
names.update(
|
||||
str(profile[field]) for field in ("emulator", "display_name")
|
||||
if profile.get(field)
|
||||
)
|
||||
return names
|
||||
|
||||
|
||||
def set_console_sections(
|
||||
sections: dict[str, dict] | set[tuple[str, str]],
|
||||
programs: set[str] | frozenset[str] = frozenset(),
|
||||
) -> None:
|
||||
"""Install the console sections the system pages carry.
|
||||
|
||||
``sections`` maps each manufacturer to its consoles, as the system pages
|
||||
group them, or is a set of (manufacturer, console) pairs. ``programs``
|
||||
are the emulator names, which never name a system.
|
||||
"""
|
||||
from common import _norm_system_id
|
||||
|
||||
pairs = (
|
||||
{(mfr, console) for mfr, consoles in sections.items() for console in consoles}
|
||||
if isinstance(sections, dict) else set(sections)
|
||||
)
|
||||
_console_sections.clear()
|
||||
for mfr, console in pairs:
|
||||
spelled = console.lower().replace(" ", "-")
|
||||
slug = mfr.lower().replace(" ", "-")
|
||||
for key in {_norm_system_id(f"{slug}-{spelled}"), _norm_system_id(spelled)}:
|
||||
_console_sections.setdefault(key, set()).add((mfr, console))
|
||||
_program_names.clear()
|
||||
_program_names.update(name.casefold() for name in programs)
|
||||
_sections_installed[0] = True
|
||||
|
||||
|
||||
_sections_installed = [False]
|
||||
_REPO_ROOT = Path(__file__).resolve().parent.parent
|
||||
|
||||
|
||||
def _ensure_console_sections() -> None:
|
||||
"""Read the sections from the repository when no generator installed
|
||||
them, so a page rendered on its own names systems as the site does."""
|
||||
if _sections_installed[0]:
|
||||
return
|
||||
import json
|
||||
|
||||
from common import load_emulator_profiles
|
||||
from generate_site import _group_by_manufacturer
|
||||
|
||||
with open(_REPO_ROOT / "database.json", encoding="utf-8") as handle:
|
||||
db = json.load(handle)
|
||||
profiles = load_emulator_profiles(str(_REPO_ROOT / "emulators"), skip_aliases=False)
|
||||
set_console_sections(_group_by_manufacturer(db), program_names(profiles))
|
||||
|
||||
|
||||
def _own_section(sys_id: str) -> tuple[str, str] | None:
|
||||
"""The console section that is this id's own, or None.
|
||||
|
||||
Of two sections spelled alike (Atari and Camputers both file a Lynx),
|
||||
the one under the maker the id names is kept. Two left is no answer,
|
||||
and neither is a section that files a program's copies.
|
||||
"""
|
||||
from common import _norm_system_id
|
||||
|
||||
_ensure_console_sections()
|
||||
places = _console_sections.get(_norm_system_id(sys_id), set())
|
||||
if len(places) > 1:
|
||||
places = {
|
||||
(mfr, console) for mfr, console in places
|
||||
if sys_id.lower().startswith(mfr.lower().replace(" ", "-") + "-")
|
||||
}
|
||||
if len(places) != 1:
|
||||
return None
|
||||
mfr, console = next(iter(places))
|
||||
# A folder under a program's top, named like its own page (Dinothawr) or
|
||||
# like an emulator holds a program's files, not a system's.
|
||||
if (
|
||||
mfr in _PROGRAM_TOPS
|
||||
or console.casefold() == mfr.casefold()
|
||||
or _heading_name(mfr, console).casefold() in _program_names
|
||||
):
|
||||
return None
|
||||
return mfr, console
|
||||
|
||||
|
||||
def _heading_name(mfr: str, console: str) -> str:
|
||||
"""A console heading read out of its page: the maker in front, unless
|
||||
the heading already starts with it or the page is a game publisher's."""
|
||||
from common import GAME_DATA_TOPS
|
||||
|
||||
if mfr in GAME_DATA_TOPS:
|
||||
return console
|
||||
if console.casefold().startswith(mfr.split()[0].casefold()):
|
||||
return console
|
||||
return f"{mfr} {console}"
|
||||
|
||||
|
||||
def system_name(sys_id: str) -> str:
|
||||
"""The display name of a system id, the only one a page prints."""
|
||||
sys_id = str(sys_id)
|
||||
section = _own_section(sys_id)
|
||||
if section is not None:
|
||||
return _heading_name(*section)
|
||||
name = SYSTEM_NAMES.get(sys_id)
|
||||
if name is None:
|
||||
raise KeyError(
|
||||
f"system {sys_id!r} has no console section of its own and no "
|
||||
"entry in siterender.SYSTEM_NAMES"
|
||||
)
|
||||
return name
|
||||
|
||||
|
||||
# Systems without a console section of their own.
|
||||
SYSTEM_NAMES: dict[str, str] = {
|
||||
"2ship": "The Legend of Zelda: Majora's Mask",
|
||||
"64dd": "Nintendo 64DD",
|
||||
"aarch64": "ARM 64-bit (AArch64)",
|
||||
"acclaim-zn1": "Acclaim ZN-1",
|
||||
"adrift": "ADRIFT",
|
||||
"advsys": "AdvSys",
|
||||
"ags": "Adventure Game Studio games",
|
||||
"agt": "Adventure Game Toolkit",
|
||||
"alan": "Alan",
|
||||
"alg-laserdisc": "American Laser Games laserdisc",
|
||||
"allied-leisure-pinball": "Allied Leisure pinball",
|
||||
"alpha": "DEC Alpha",
|
||||
"andamiro-pumpitup": "Andamiro Pump It Up",
|
||||
"apf-m1000": "APF M-1000",
|
||||
"apfm1000": "APF M-1000",
|
||||
"apple-iie": "Apple IIe",
|
||||
"apple-ios": "Apple iOS",
|
||||
"apple2": "Apple II",
|
||||
"apple2gs": "Apple IIGS",
|
||||
"arcade": "Arcade games",
|
||||
"arcade-laserdisc": "Laserdisc arcade games",
|
||||
"arcadia-systems": "Arcadia Systems Multi Select",
|
||||
"arduboy": "Arduboy",
|
||||
"aristocrat-mk5": "Aristocrat MK5",
|
||||
"aristocrat-mk6": "Aristocrat MK6",
|
||||
"aristocrat-mk7": "Aristocrat MK7",
|
||||
"arm": "ARM",
|
||||
"astrocde": "Bally Astrocade",
|
||||
"atari-falcon": "Atari Falcon",
|
||||
"atari-jaguarcd": "Atari Jaguar CD",
|
||||
"atari-ste": "Atari STE",
|
||||
"atari-system1": "Atari System 1",
|
||||
"atari-tt": "Atari TT",
|
||||
"atari-zn1": "Atari ZN-1",
|
||||
"atlus-zn1": "Atlus ZN-1",
|
||||
"atom": "Acorn Atom",
|
||||
"bandai-playdia": "Bandai Playdia",
|
||||
"bandai-wswan": "Bandai WonderSwan",
|
||||
"bandai-wswanc": "Bandai WonderSwan Color",
|
||||
"barcrest-mpu4": "Barcrest MPU4",
|
||||
"bbc-master-128": "Acorn BBC Master 128",
|
||||
"bbc-master-compact": "Acorn BBC Master Compact",
|
||||
"bbc-master-et": "Acorn BBC Master ET",
|
||||
"bbc-micro-b": "Acorn BBC Micro Model B",
|
||||
"bbc-micro-bplus": "Acorn BBC Micro Model B+",
|
||||
"bbk-longman-4980": "BBK Longman A4980",
|
||||
"bbk-longman-4988": "BBK Longman A4988",
|
||||
"bennugd": "BennuGD games",
|
||||
"bermuda-syndrome": "Bermuda Syndrome game",
|
||||
"brezzasoft-crystal": "BrezzaSoft Crystal System",
|
||||
"bshift": "Half-Life: Blue Shift",
|
||||
"bsyndrome": "Bermuda Syndrome game",
|
||||
"caesar3": "Caesar III",
|
||||
"cambridge-z88": "Cambridge Computer Z88",
|
||||
"camplynx": "Camputers Lynx",
|
||||
"capcom-cps1": "Capcom CPS-1",
|
||||
"capcom-cps2": "Capcom CPS-2",
|
||||
"capcom-cps3": "Capcom CPS-3",
|
||||
"capcom-zn1": "Capcom ZN-1",
|
||||
"capcom-zn2": "Capcom ZN-2",
|
||||
"casio-pv1000": "Casio PV-1000",
|
||||
"catacomb-3d": "Catacomb 3-D",
|
||||
"catacomb-adventure": "Catacomb Adventure Series",
|
||||
"cave-cv1000": "Cave CV1000",
|
||||
"cave-story": "Cave Story",
|
||||
"cavestory": "Cave Story",
|
||||
"celeste-classic": "Celeste Classic",
|
||||
"century-cvs": "Century CVS",
|
||||
"ces-galaxy-games": "CES Galaxy Games",
|
||||
"cgenie": "EACA Colour Genie",
|
||||
"chex": "Chex Quest",
|
||||
"chihiro": "Sega Chihiro",
|
||||
"chip8": "CHIP-8",
|
||||
"coco3": "Tandy Color Computer 3",
|
||||
"commanderx16": "Commander X16",
|
||||
"commodore-c64-supercpu": "Commodore 64 with SuperCPU",
|
||||
"commodore-cbm-ii": "Commodore CBM-II",
|
||||
"commodore-cd32": "Commodore Amiga CD32",
|
||||
"commodore-cdtv": "Commodore CDTV",
|
||||
"commodore-cubo": "CD Express Cubo CD32",
|
||||
"commodore-pet": "Commodore PET",
|
||||
"coolbar": "Coolbar feature phones",
|
||||
"cps1": "Capcom CPS-1",
|
||||
"cps2": "Capcom CPS-2",
|
||||
"cps3": "Capcom CPS-3",
|
||||
"crvision": "VTech CreatiVision",
|
||||
"cstrike": "Counter-Strike",
|
||||
"ctf": "Quake II: Capture the Flag",
|
||||
"czero": "Counter-Strike: Condition Zero",
|
||||
"czeror": "Counter-Strike: Condition Zero Deleted Scenes",
|
||||
"dark-forces": "Star Wars: Dark Forces",
|
||||
"dark-forces-ii": "Star Wars Jedi Knight: Dark Forces II",
|
||||
"deco-cassette": "Data East DECO Cassette System",
|
||||
"descent2": "Descent II",
|
||||
"dingoo-a320": "Dingoo A320",
|
||||
"dinothawr": "Dinothawr game",
|
||||
"discrete-arcade": "Discrete logic arcade games",
|
||||
"dmc": "Deathmatch Classic",
|
||||
"doom": "Doom",
|
||||
"doom3-bfg": "Doom 3: BFG Edition",
|
||||
"dos": "MS-DOS",
|
||||
"dragon32": "Dragon 32",
|
||||
"dragon64": "Dragon 64",
|
||||
"droidworks": "Star Wars: DroidWorks",
|
||||
"duke-nukem-2": "Duke Nukem II",
|
||||
"duke3d": "Duke Nukem 3D",
|
||||
"dusklight": "The Legend of Zelda: Twilight Princess",
|
||||
"efo-cedar-magnet": "EFO Cedar Magnet System",
|
||||
"eighting-zn1": "Eighting ZN-1",
|
||||
"elektor-tv-games-computer": "Elektor TV Games Computer",
|
||||
"emerson-arcadia-2001": "Emerson Arcadia 2001",
|
||||
"enemy-territory-quake-wars": "Enemy Territory: Quake Wars",
|
||||
"enemyterritory": "Wolfenstein: Enemy Territory",
|
||||
"enterprise-128": "Enterprise 128",
|
||||
"enterprise-64": "Enterprise 64",
|
||||
"entex-advision": "Entex Adventure Vision",
|
||||
"epoch-scv": "Epoch Super Cassette Vision",
|
||||
"epochcv": "Epoch Cassette Vision",
|
||||
"eternal-computer": "Eternal Software Initiative computer",
|
||||
"exelvision-exl100": "Exelvision EXL 100",
|
||||
"exhumed": "PowerSlave",
|
||||
"exidy-maxaflex": "Exidy Max-A-Flex",
|
||||
"fallout": "Fallout",
|
||||
"famicom": "Nintendo Famicom",
|
||||
"flash": "Adobe Flash",
|
||||
"flashback": "Flashback",
|
||||
"fm77av": "Fujitsu FM77AV",
|
||||
"fpinball": "Future Pinball tables",
|
||||
"fruitmachine": "Fruit machines",
|
||||
"fury": "Ion Fury game",
|
||||
"gaelco-powervr": "Gaelco PowerVR boards",
|
||||
"galaksija": "Galaksija computer",
|
||||
"game": "Picross puzzle (libretro sample)",
|
||||
"gamecom": "Tiger Game.com",
|
||||
"gameplan-mpu1": "Game Plan MPU-1",
|
||||
"gamepock": "Epoch Game Pocket Computer",
|
||||
"gametank": "GameTank console",
|
||||
"gba": "Nintendo Game Boy Advance",
|
||||
"gearbox": "Half-Life: Opposing Force",
|
||||
"ghostship": "Super Mario 64",
|
||||
"glulx": "Glulx",
|
||||
"gottlieb-system1": "Gottlieb System 1",
|
||||
"half-life-2": "Half-Life 2",
|
||||
"handheld-electronic": "Nintendo Game & Watch",
|
||||
"heart-of-darkness": "Heart of Darkness",
|
||||
"heretic": "Heretic",
|
||||
"heroes-of-might-and-magic-2": "Heroes of Might and Magic II",
|
||||
"hexen": "Hexen",
|
||||
"hexen2": "Hexen II",
|
||||
"hppa": "HP PA-RISC",
|
||||
"hugo": "Hugo",
|
||||
"hyper-neogeo64": "SNK Hyper Neo Geo 64",
|
||||
"ibm-at": "IBM PC/AT",
|
||||
"ibm-pcjr": "IBM PCjr",
|
||||
"ibm-ps1": "IBM PS/1",
|
||||
"ibm-xt": "IBM PC XT",
|
||||
"icarus-starship-command-simulator": "Icarus Starship Command Simulator",
|
||||
"igs-pgm": "IGS PolyGame Master",
|
||||
"imax-macs": "I'Max MACS",
|
||||
"interact": "Interact Home Computer",
|
||||
"interton-vc4000": "Interton VC 4000",
|
||||
"itech-eagle": "Incredible Technologies Eagle",
|
||||
"jacl": "JACL",
|
||||
"jedi-academy": "Star Wars Jedi Knight: Jedi Academy",
|
||||
"jedi-outcast": "Star Wars Jedi Knight II: Jedi Outcast",
|
||||
"jediacademy": "Star Wars Jedi Knight: Jedi Academy",
|
||||
"jediknight2": "Star Wars Jedi Knight II: Jedi Outcast",
|
||||
"jupiter-ace": "Jupiter Cantab Jupiter Ace",
|
||||
"konami-bubble-system": "Konami Bubble System",
|
||||
"konami-bubblesystem": "Konami Bubble System",
|
||||
"konami-bubsys": "Konami Bubble System",
|
||||
"konami-endeavour": "Konami Endeavour",
|
||||
"konami-gq": "Konami GQ",
|
||||
"konami-gv": "Konami GV",
|
||||
"konami-gx": "Konami GX",
|
||||
"konami-python": "Konami Python",
|
||||
"konami-python2": "Konami Python 2",
|
||||
"konami-twinkle": "Konami Twinkle",
|
||||
"konami-viper": "Konami Viper",
|
||||
"lambda-8300": "Lambda 8300",
|
||||
"laserdisc-arcade": "Laserdisc arcade games",
|
||||
"level9": "Level 9",
|
||||
"lowresnx": "LowRes NX programs",
|
||||
"lynx": "Atari Lynx",
|
||||
"m68k": "Motorola 68000",
|
||||
"magnetic-scrolls": "Magnetic Scrolls",
|
||||
"marty": "Fujitsu FM Towns Marty",
|
||||
"mega-cd": "Sega Mega-CD",
|
||||
"mega-cd-32x": "Sega Mega-CD 32X",
|
||||
"mega-duck": "Welback Mega Duck",
|
||||
"mega-ld": "Sega Mega LD",
|
||||
"megacd": "Sega Mega-CD",
|
||||
"microdigital-tk85": "Microdigital TK85",
|
||||
"microsoft-msx2": "MSX2",
|
||||
"microsoft-xbox-360": "Microsoft Xbox 360",
|
||||
"midway-mcr": "Midway MCR",
|
||||
"mips": "MIPS",
|
||||
"mips64": "MIPS64",
|
||||
"miuchiz": "Miuchiz",
|
||||
"mohaa": "Medal of Honor: Allied Assault",
|
||||
"mohaa-breakthrough": "Medal of Honor: Allied Assault Breakthrough",
|
||||
"mohaa-spearhead": "Medal of Honor: Allied Assault Spearhead",
|
||||
"msx1": "MSX",
|
||||
"msx2": "MSX2",
|
||||
"msx2+": "MSX2+",
|
||||
"msx2plus": "MSX2+",
|
||||
"msxturboR": "MSX turbo R",
|
||||
"msxturbor": "MSX turbo R",
|
||||
"mysteries-of-the-sith": "Star Wars Jedi Knight: Mysteries of the Sith",
|
||||
"myvision": "Nichibutsu My Vision",
|
||||
"mz2000": "Sharp MZ-2000",
|
||||
"mz2500": "Sharp MZ-2500",
|
||||
"mz700": "Sharp MZ-700",
|
||||
"mz800": "Sharp MZ-800",
|
||||
"mz80k": "Sharp MZ-80K",
|
||||
"nam": "NAM",
|
||||
"namco-na1": "Namco NA-1",
|
||||
"namco-nb1": "Namco NB-1",
|
||||
"namco-system11": "Namco System 11",
|
||||
"namco-system246": "Namco System 246",
|
||||
"namco-system256": "Namco System 256",
|
||||
"namco-system357": "Namco System 357",
|
||||
"namco-system573": "Konami System 573",
|
||||
"naomi2": "Sega NAOMI 2",
|
||||
"napalm": "NAPALM",
|
||||
"nec-pc-80": "NEC PC-8001",
|
||||
"nec-supergrafx": "NEC SuperGrafx",
|
||||
"neo-geo-pocket-color": "SNK Neo Geo Pocket Color",
|
||||
"neogeo64": "SNK Hyper Neo Geo 64",
|
||||
"ngp": "SNK Neo Geo Pocket",
|
||||
"ngpc": "SNK Neo Geo Pocket Color",
|
||||
"nichibutsu-hrdvd": "Nichibutsu High Rate DVD",
|
||||
"nintendo-64dd": "Nintendo 64DD",
|
||||
"nintendo-aleck64": "Nintendo Aleck64",
|
||||
"nintendo-ds": "Nintendo DS",
|
||||
"nintendo-dsi": "Nintendo DSi",
|
||||
"nintendo-fds": "Nintendo Famicom Disk System",
|
||||
"nintendo-gb": "Nintendo Game Boy",
|
||||
"nintendo-gba": "Nintendo Game Boy Advance",
|
||||
"nintendo-gbc": "Nintendo Game Boy Color",
|
||||
"nintendo-nss": "Nintendo Super System",
|
||||
"nintendo-playch10": "Nintendo PlayChoice-10",
|
||||
"nintendo-sfcbox": "Nintendo Super Famicom Box",
|
||||
"nintendo-sgb": "Nintendo Super Game Boy",
|
||||
"nintendo-virtualboy": "Nintendo Virtual Boy",
|
||||
"nintendo-wii-arcade": "Nintendo Wii arcade boards",
|
||||
"nmk16": "NMK16",
|
||||
"oberon": "Project Oberon",
|
||||
"olivetti-pc128": "Olivetti Prodest PC 128",
|
||||
"omicron": "Omicron cartridges",
|
||||
"onscripter": "ONScripter visual novels",
|
||||
"openbor": "OpenBOR games",
|
||||
"opengoal": "Jak and Daxter",
|
||||
"oric": "Tangerine Oric",
|
||||
"oricatmos": "Oric Atmos",
|
||||
"outlaws": "Outlaws",
|
||||
"palm-os": "Palm OS",
|
||||
"pc-engine-ld": "NEC PC Engine LD-ROM2",
|
||||
"pc60": "NEC PC-6001",
|
||||
"pc80": "NEC PC-8001",
|
||||
"pce-tourvision": "TourVision",
|
||||
"pdark": "Perfect Dark",
|
||||
"pegasus": "Aamber Pegasus",
|
||||
"pgm": "IGS PolyGame Master",
|
||||
"philips-p2000t": "Philips P2000T",
|
||||
"philips-vg5k": "Philips VG 5000",
|
||||
"picocomputer-6502": "Picocomputer 6502 programs",
|
||||
"pocket-challenge-v2": "Benesse Pocket Challenge V2",
|
||||
"powerslave": "PowerSlave",
|
||||
"ppc": "PowerPC",
|
||||
"ppc64": "PowerPC 64-bit",
|
||||
"prey": "Prey",
|
||||
"prince-of-persia": "Prince of Persia",
|
||||
"ps4": "Sony PlayStation 4",
|
||||
"pv1000": "Casio PV-1000",
|
||||
"quadrilateral-cowboy": "Quadrilateral Cowboy",
|
||||
"quake2": "Quake II",
|
||||
"quake3": "Quake III Arena",
|
||||
"quake4": "Quake 4",
|
||||
"realrtcw": "Return to Castle Wolfenstein (RealRTCW)",
|
||||
"recel-system3": "Recel System III",
|
||||
"redneck": "Redneck Rampage",
|
||||
"ricochet": "Ricochet",
|
||||
"ringo-r470": "Ringo R470",
|
||||
"riscv": "RISC-V",
|
||||
"riscv32": "RISC-V 32-bit",
|
||||
"riscv64": "RISC-V 64-bit",
|
||||
"rogue": "Quake II: Ground Zero",
|
||||
"rott": "Rise of the Triad",
|
||||
"rpgmaker": "RPG Maker games",
|
||||
"s390x": "IBM z/Architecture (s390x)",
|
||||
"sam-coupe": "MGT SAM Coupe",
|
||||
"sammy-atomiswave": "Sammy Atomiswave",
|
||||
"sammy-medal": "Sammy medal games",
|
||||
"science-of-cambridge-mk14": "Science of Cambridge MK14",
|
||||
"scott-adams": "Scott Adams adventures",
|
||||
"scott-adams-plus": "Scott Adams Plus adventures",
|
||||
"scummvm": "ScummVM games",
|
||||
"scv": "Epoch Super Cassette Vision",
|
||||
"sdlpal": "The Legend of Sword and Fairy",
|
||||
"sega-atomiswave": "Sammy Atomiswave",
|
||||
"sega-dreamcast-arcade": "Sega Dreamcast-based arcade boards",
|
||||
"sega-genesis": "Sega Genesis",
|
||||
"sega-isgsm": "Sega ISG Selection Master",
|
||||
"sega-mega-cd": "Sega Mega-CD",
|
||||
"sega-megacd": "Sega Mega-CD",
|
||||
"sega-megaplay": "Sega Mega Play",
|
||||
"sega-megatech": "Sega Mega-Tech",
|
||||
"sega-model2": "Sega Model 2",
|
||||
"sega-naomi": "Sega NAOMI",
|
||||
"sega-naomi2": "Sega NAOMI 2",
|
||||
"sega-naomigd": "Sega NAOMI GD-ROM",
|
||||
"sega-outrun": "OutRun",
|
||||
"sega-pico": "Sega Pico",
|
||||
"sega-sg1000": "Sega SG-1000",
|
||||
"sega-shootingzone": "Sega Shooting Zone",
|
||||
"sega-system-sp": "Sega System SP",
|
||||
"sega-system16b": "Sega System 16B",
|
||||
"sega-systemsp": "Sega System SP",
|
||||
"sega-vmu": "Sega Dreamcast VMU",
|
||||
"serious-sam": "Serious Sam",
|
||||
"sg-1000": "Sega SG-1000",
|
||||
"sielectronics-y2": "SiElectronics System Y2",
|
||||
"sinclair-ql": "Sinclair QL",
|
||||
"sinclair-spectrum": "Sinclair ZX Spectrum",
|
||||
"sinclair-zx-spectrum": "Sinclair ZX Spectrum",
|
||||
"sinclair-zx-spectrum-next": "ZX Spectrum Next",
|
||||
"sinclair-zx80-81": "Sinclair ZX80 and ZX81",
|
||||
"sinclair-zxspectrum": "Sinclair ZX Spectrum",
|
||||
"skin-deep": "Skin Deep",
|
||||
"smbx": "Super Mario Bros. X",
|
||||
"sms": "Sega Master System",
|
||||
"snk-neogeo-aes": "SNK Neo Geo AES",
|
||||
"snk-neogeo-mvs": "SNK Neo Geo MVS",
|
||||
"snk-ngp": "SNK Neo Geo Pocket",
|
||||
"snk-ngpc": "SNK Neo Geo Pocket Color",
|
||||
"soh": "The Legend of Zelda: Ocarina of Time",
|
||||
"sonic-1-2013": "Sonic the Hedgehog (2013)",
|
||||
"sonic-2-2013": "Sonic the Hedgehog 2 (2013)",
|
||||
"sonic-mania": "Sonic Mania",
|
||||
"soniccd": "Sonic CD",
|
||||
"sony-psp": "Sony PlayStation Portable",
|
||||
"sparc": "SPARC",
|
||||
"sparc64": "SPARC64",
|
||||
"spectravideo": "Spectravideo",
|
||||
"star-control-2": "Star Control II",
|
||||
"starship": "Star Fox 64",
|
||||
"strife": "Strife",
|
||||
"sunplus-native32": "Sunplus Native32",
|
||||
"sunplus-spmp8000": "Sunplus SPMP8000",
|
||||
"super-famicom": "Nintendo Super Famicom",
|
||||
"super-kaneko-nova": "Kaneko Super Nova System",
|
||||
"super-mario-war": "Super Mario War game",
|
||||
"superacan": "Funtech Super A'Can",
|
||||
"supergrafx": "NEC SuperGrafx",
|
||||
"supergrafx-cd": "NEC SuperGrafx CD",
|
||||
"solarus": "Solarus games",
|
||||
"supertux": "SuperTux game",
|
||||
"supertuxkart": "SuperTuxKart game",
|
||||
"supracan": "Funtech Super A'Can",
|
||||
"symbian": "Symbian OS",
|
||||
"tab-silverball": "TAB Austria Silverball",
|
||||
"tads": "TADS",
|
||||
"taito-cchip": "Taito C-Chip",
|
||||
"taito-fx1": "Taito FX-1",
|
||||
"taito-gnet": "Taito G-NET",
|
||||
"taito-type-x": "Taito Type X",
|
||||
"taito-typezero": "Taito Type Zero",
|
||||
"tamagotchi-p1": "Bandai Tamagotchi P1",
|
||||
"tandy-1000": "Tandy 1000",
|
||||
"taylormade": "TaylorMade",
|
||||
"teamarena": "Quake III: Team Arena",
|
||||
"tecmo-tps": "Tecmo TPS",
|
||||
"tfc": "Team Fortress Classic",
|
||||
"the-dark-mod": "The Dark Mod",
|
||||
"thomson-mo6": "Thomson MO6",
|
||||
"thomson-to7": "Thomson TO7",
|
||||
"thomson-to770": "Thomson TO7/70",
|
||||
"thomson-to8": "Thomson TO8",
|
||||
"thomson-to8d": "Thomson TO8D",
|
||||
"thomson-to9": "Thomson TO9",
|
||||
"thomson-to9plus": "Thomson TO9+",
|
||||
"tic80": "TIC-80 cartridges",
|
||||
"tiger-game-com": "Tiger Game.com",
|
||||
"timex-tc2048": "Timex Computer 2048",
|
||||
"timex-ts1500": "Timex Sinclair 1500",
|
||||
"timex-ts2068": "Timex Sinclair 2068",
|
||||
"tomb-raider": "Tomb Raider",
|
||||
"tomb-raider-1": "Tomb Raider",
|
||||
"tomb-raider-2": "Tomb Raider II",
|
||||
"transport-tycoon-deluxe": "Transport Tycoon Deluxe",
|
||||
"tyrian": "Tyrian",
|
||||
"ur-quan-masters": "Star Control II",
|
||||
"urban-terror": "Urban Terror",
|
||||
"uzebox": "Uzebox",
|
||||
"vaporspec": "VaporSpec cartridges",
|
||||
"vc4000": "Interton VC 4000",
|
||||
"vg5k": "Philips VG 5000",
|
||||
"vgmplay": "Video game music files",
|
||||
"videobrain": "Umtech VideoBrain",
|
||||
"videosystem-zn1": "Video System ZN-1",
|
||||
"vircon32": "Vircon32 cartridges",
|
||||
"virtuality-su2000": "Virtuality SU2000",
|
||||
"vm-labs-nuon": "VM Labs Nuon",
|
||||
"vpinball": "Visual Pinball tables",
|
||||
"vvvvvv": "VVVVVV game",
|
||||
"watara-supervision": "Watara Supervision",
|
||||
"wonderswan-color": "Bandai WonderSwan Color",
|
||||
"ww2gi": "World War II GI",
|
||||
"x86": "x86 PC",
|
||||
"x86-64": "x86-64 PC",
|
||||
"xatrix": "Quake II: The Reckoning",
|
||||
"xrick": "Rick Dangerous",
|
||||
"ym2608": "Yamaha YM2608",
|
||||
"zc210": "Zelda Classic",
|
||||
"zeldaclassic": "Zelda Classic",
|
||||
"zn1-acclaim": "Acclaim ZN-1",
|
||||
"zn1-atlus": "Atlus ZN-1",
|
||||
"zn1-capcom": "Capcom ZN-1",
|
||||
"zn1-taito": "Taito ZN-1",
|
||||
"zn1-tecmo": "Tecmo ZN-1",
|
||||
"zn1-video-system": "Video System ZN-1",
|
||||
"zn2-capcom": "Capcom ZN-2",
|
||||
"zx-spectrum": "Sinclair ZX Spectrum",
|
||||
"zx-spectrum-128": "Sinclair ZX Spectrum 128",
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
"""One display name per system id (round 2, package P0).
|
||||
|
||||
An emulator page listed the folders a system's files were voted onto:
|
||||
nintendo-gba read "Nintendo Game Boy", camplynx "Arcade", and MAME
|
||||
"emulated FBNeo". siterender.system_name names a system from the console
|
||||
section that is its own, then from SYSTEM_NAMES, and raises otherwise.
|
||||
database.json carries no system id: its files reach a page through their
|
||||
console folders, so the ids to name are the platforms' and the profiles'.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import sys
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(REPO_ROOT / "scripts"))
|
||||
|
||||
import siterender # noqa: E402
|
||||
from common import ( # noqa: E402
|
||||
list_registered_platforms,
|
||||
load_emulator_profiles,
|
||||
load_platform_config,
|
||||
)
|
||||
from generate_site import _group_by_manufacturer # noqa: E402
|
||||
|
||||
|
||||
def _profile_system_ids(profiles: dict[str, dict]) -> set[str]:
|
||||
ids: set[str] = set()
|
||||
for profile in profiles.values():
|
||||
ids.update(str(sys_id) for sys_id in profile.get("systems") or [])
|
||||
for entry in profile.get("files") or []:
|
||||
if not isinstance(entry, dict):
|
||||
continue
|
||||
if entry.get("system"):
|
||||
ids.add(str(entry["system"]))
|
||||
ids.update(str(sys_id) for sys_id in entry.get("systems") or [])
|
||||
return ids
|
||||
|
||||
|
||||
class SystemNames(unittest.TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls) -> None:
|
||||
with open(REPO_ROOT / "database.json", encoding="utf-8") as handle:
|
||||
db = json.load(handle)
|
||||
cls.manufacturers = _group_by_manufacturer(db)
|
||||
cls.profiles = load_emulator_profiles(
|
||||
str(REPO_ROOT / "emulators"), skip_aliases=False)
|
||||
siterender.set_console_sections(
|
||||
cls.manufacturers, siterender.program_names(cls.profiles))
|
||||
platforms_dir = str(REPO_ROOT / "platforms")
|
||||
cls.platform_ids = {
|
||||
sys_id
|
||||
for name in list_registered_platforms(platforms_dir, include_archived=True)
|
||||
for sys_id in load_platform_config(name, platforms_dir).get("systems", {})
|
||||
}
|
||||
cls.profile_ids = _profile_system_ids(cls.profiles)
|
||||
|
||||
def _all_ids(self) -> set[str]:
|
||||
return self.platform_ids | self.profile_ids
|
||||
|
||||
def test_every_system_id_has_a_name(self) -> None:
|
||||
unnamed = []
|
||||
for sys_id in sorted(self._all_ids()):
|
||||
try:
|
||||
name = siterender.system_name(sys_id)
|
||||
except KeyError:
|
||||
unnamed.append(sys_id)
|
||||
continue
|
||||
self.assertTrue(name.strip(), sys_id)
|
||||
self.assertEqual(unnamed, [], "add these to siterender.SYSTEM_NAMES")
|
||||
|
||||
def test_no_name_is_a_manufacturer_page_or_an_emulator(self) -> None:
|
||||
taken = {name.casefold() for name in self.manufacturers}
|
||||
taken |= {name.casefold() for name in siterender.program_names(self.profiles)}
|
||||
clashes = sorted(
|
||||
(sys_id, siterender.system_name(sys_id))
|
||||
for sys_id in self._all_ids()
|
||||
if siterender.system_name(sys_id).casefold() in taken
|
||||
)
|
||||
self.assertEqual(clashes, [])
|
||||
|
||||
def test_table_entries_are_reached(self) -> None:
|
||||
"""A table entry an own section shadows is never read, and an entry
|
||||
no platform or profile names is a leftover."""
|
||||
shadowed = sorted(
|
||||
sys_id for sys_id in siterender.SYSTEM_NAMES
|
||||
if siterender._own_section(sys_id) is not None
|
||||
)
|
||||
self.assertEqual(shadowed, [])
|
||||
unused = sorted(set(siterender.SYSTEM_NAMES) - self._all_ids())
|
||||
self.assertEqual(unused, [])
|
||||
|
||||
def test_names_that_read_a_folder_vote(self) -> None:
|
||||
self.assertEqual(siterender.system_name("nintendo-gba"), "Nintendo Game Boy Advance")
|
||||
self.assertEqual(siterender.system_name("camplynx"), "Camputers Lynx")
|
||||
self.assertEqual(siterender.system_name("atari-lynx"), "Atari Lynx")
|
||||
self.assertEqual(siterender.system_name("arcade"), "Arcade games")
|
||||
self.assertEqual(siterender.system_name("sega-naomi"), "Sega NAOMI")
|
||||
|
||||
def test_own_section_names_from_its_heading(self) -> None:
|
||||
self.assertEqual(siterender.system_name("nintendo-snes"), "Nintendo SNES")
|
||||
self.assertEqual(siterender.system_name("sony-playstation"), "Sony PlayStation")
|
||||
|
||||
def test_an_unknown_id_raises(self) -> None:
|
||||
with self.assertRaises(KeyError):
|
||||
siterender.system_name("no-such-system-anywhere")
|
||||
|
||||
def test_mame_names_systems_not_emulators(self) -> None:
|
||||
names = {siterender.system_name(s) for s in self.profiles["mame"].get("systems") or []}
|
||||
self.assertNotIn("FBNeo", names)
|
||||
self.assertNotIn("Arcade", names)
|
||||
self.assertFalse(names & siterender.program_names(self.profiles))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -56,7 +56,7 @@ Every module in `tests/`, by the area it guards.
|
||||
| Handheld port | stubbed PortMaster dialogs, fake installer | `test_port` |
|
||||
| Truth and native export | synthetic + cached originals | `test_export_counts`, `test_export_native_layer`, `test_export_report`, `test_exporters`, `test_truth_data_dir_readers`, `test_truth_diff`, `test_truth_layout` |
|
||||
| Scrapers, upstream and provenance | inline sources, synthetic | `test_check_freshness`, `test_dedup`, `test_elec_scrapers`, `test_fbneo_parser`, `test_find_pinned_revision`, `test_hash_merge`, `test_launchers`, `test_launchers_emudeck`, `test_launchers_recalbox`, `test_launchers_retrobat`, `test_launchers_retrodeck`, `test_launchers_retropie`, `test_launchers_rocknix`, `test_mame_devices`, `test_mame_parser`, `test_mame_ref_audit`, `test_profile_sync`, `test_provenance`, `test_refresh_data_dirs`, `test_refresh_failures`, `test_refresh_stale`, `test_retrodeck_scraper_errors`, `test_romset_recipes`, `test_scraper_contract`, `test_upstream`, `test_upstream_index` |
|
||||
| Site, docs and repository contracts | rendered pages, sources | `test_contributing_text`, `test_coverage_cache`, `test_data_dir_refs`, `test_dataclass_defaults`, `test_doc_pages`, `test_doc_surfaces`, `test_optional_dependencies`, `test_prose_style`, `test_schema_semantic_gate`, `test_site_availability`, `test_site_content_data`, `test_site_copy`, `test_site_cores`, `test_site_emulator_page`, `test_site_exports`, `test_site_funnel`, `test_site_images`, `test_site_picker`, `test_site_platform_hashes`, `test_site_render`, `test_site_system_links`, `test_site_validation`, `test_site_weight`, `test_site_write`, `test_validate_pr`, `test_validate_pr_flow`, `test_validate_pr_hashes`, `test_workflow_paths` |
|
||||
| Site, docs and repository contracts | rendered pages, sources | `test_contributing_text`, `test_coverage_cache`, `test_data_dir_refs`, `test_dataclass_defaults`, `test_doc_pages`, `test_doc_surfaces`, `test_optional_dependencies`, `test_prose_style`, `test_schema_semantic_gate`, `test_site_availability`, `test_site_content_data`, `test_site_copy`, `test_site_cores`, `test_site_emulator_page`, `test_site_exports`, `test_site_funnel`, `test_site_images`, `test_site_picker`, `test_site_platform_hashes`, `test_site_r2_p0`, `test_site_render`, `test_site_system_links`, `test_site_validation`, `test_site_weight`, `test_site_write`, `test_validate_pr`, `test_validate_pr_flow`, `test_validate_pr_hashes`, `test_workflow_paths` |
|
||||
|
||||
## Test architecture
|
||||
|
||||
|
||||
Reference in new issue
Block a user