fix: count what each export format writes

This commit is contained in:
Abdessamad Derraz committed 2026-10-06 01:22:56 +02:00
1 parent bb789872b2
commit 95d10ee812
7 files changed
+319 -28

No files matched your search

+74 -13
View File
@@ -19,6 +19,10 @@ COMPONENTS_REPO = "RetroDECK/components"
COMPONENTS_BRANCH = "main"
RAW_BASE = f"https://raw.githubusercontent.com/{COMPONENTS_REPO}/{COMPONENTS_BRANCH}"
MANIFEST = "component_manifest.json"
# Labels that only say yes or no, so a corrected requirement can be written
# over them. Every other label is a sentence ("At least one BIOS file
# required", "Required for some Japanese games.") and is the platform's own.
PLAIN_LABELS = ("Required", "Optional")
class Exporter(BaseExporter):
@@ -42,6 +46,42 @@ class Exporter(BaseExporter):
# one from BIOS data alone would throw the component away.
return True
@classmethod
def writable(cls, fe: NativeFile, require: str = "") -> bool:
"""An addition lands only in a manifest it can be placed in."""
if fe.platform is None and not fe.native("component", ""):
return False
return super().writable(fe, require)
def states(self, fe: NativeFile, field_name: str) -> bool:
if field_name == "required":
label = str(fe.native("required_label", ""))
return not label or label in PLAIN_LABELS
return super().states(fe, field_name)
@staticmethod
def _place_additions(systems: dict[str, NativeSystem]) -> None:
"""Give each addition the component its system already lives in.
A component is one emulator's manifest, and only the platform's
entries name it. A system whose entries all sit in one component
takes the addition there; one spread over several, or with none of
its own, cannot say which manifest is meant, and the addition is
left out and counted as such.
"""
for system in systems.values():
components = {
str(fe.native("component", ""))
for fe in system.files
if fe.platform is not None and fe.native("component", "")
}
if len(components) != 1:
continue
component = components.pop()
for fe in system.files:
if fe.platform is None and fe.truth is not None:
fe.truth = {**fe.truth, "component": component}
@staticmethod
def component_url(component: str) -> str:
return f"{RAW_BASE}/{component}/{MANIFEST}"
@@ -73,11 +113,13 @@ class Exporter(BaseExporter):
# RetroDECK words the requirement in prose ("Required", "At least one
# BIOS file required"), so the platform's own wording is kept and a
# boolean is only rendered when there is none to keep.
label = fe.native("required_label", "")
if label:
entry["required"] = str(label)
label = str(fe.native("required_label", ""))
if label and label not in PLAIN_LABELS:
entry["required"] = label
elif fe.required:
entry["required"] = "Required"
elif label:
entry["required"] = "Optional"
destination = fe.destination
if destination and destination not in (fe.name, f"bios/{fe.name}"):
directory = destination.rsplit("/", 1)[0]
@@ -116,29 +158,47 @@ class Exporter(BaseExporter):
truth has something to say and left alone where it does not, and what
the platform does not declare is appended.
"""
by_name: OrderedDict[str, OrderedDict] = OrderedDict()
# Keyed by name AND system: the retroarch manifest declares
# ATARIOSB.ROM for atari5200 and atari800 with different md5 lists,
# and a name-only key wrote the first of ours over both.
by_key: OrderedDict[tuple[str, str], OrderedDict] = OrderedDict()
for entry in ours:
name = str(entry.get("filename", ""))
if name and name not in by_name:
by_name[name] = entry
key = (str(entry.get("filename", "")), str(entry.get("system", "")))
if key[0] and key not in by_key:
by_key[key] = entry
merged: list[OrderedDict] = []
corrected: set[str] = set()
corrected: set[tuple[str, str]] = set()
for entry in existing if isinstance(existing, list) else []:
if not isinstance(entry, dict):
continue
name = str(entry.get("filename", ""))
ours_entry = by_name.get(name)
if ours_entry is None:
declared = entry.get("system")
# One entry can serve several systems: neogeo.zip is declared
# once for neogeo, fbneo and arcade.
systems = (
[str(s) for s in declared] if isinstance(declared, list)
else [str(declared)] if declared else []
)
keys = [(name, s) for s in systems if (name, s) in by_key]
if not systems:
# An entry without a system matches ours only when one
# system alone declares the name.
named = [k for k in by_key if k[0] == name]
keys = named if len(named) == 1 else []
if not keys:
merged.append(OrderedDict(entry))
continue
combined = OrderedDict(entry)
combined.update(ours_entry)
combined.update(
(field, value) for field, value in by_key[keys[0]].items()
if not (field == "system" and declared)
)
merged.append(combined)
corrected.add(name)
corrected.update(keys)
merged.extend(
entry for name, entry in by_name.items() if name not in corrected
entry for key, entry in by_key.items() if key not in corrected
)
return merged
@@ -149,6 +209,7 @@ class Exporter(BaseExporter):
originals: dict[str, str],
scraped: dict | None = None,
) -> dict[str, str]:
self._place_additions(systems)
grouped = self._by_component(systems)
produced: dict[str, str] = {}