mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-11-11 04:23:47 -06:00
fix: count what each export format writes
This commit is contained in:
1 parent
bb789872b2
commit
95d10ee812
7 files changed
+319
-28
No files matched your search
@@ -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] = {}
|
||||
|
||||
|
||||
Reference in new issue
Block a user