Files
pokemon/scripts/standardize-names.py
ginnoirandClaude Opus 4.8 ae56d101b7 refactor: standardize hack filenames to [Hack] and flatten to platform root
Switch the library naming convention from "Pokemon - <Name> (Hack)" to
"Pokemon - <Name> [Hack][version][tags]", and place hacks in the platform
root (roms/<plat>/) instead of a Hacks/ subfolder. Applied across the live
valhalla library (272 hack/fan-game files) via scripts/standardize-names.py.

- romhack-import.py / romhack-fetch.py: emit [Hack], write to roms/<plat>/
- romhack-pc-migrate.py: emit [Hack]
- build-romhack-vault.py (archived): update documented library_path
- standardize-names.py: new one-shot, self-fetching standardizer (idempotent;
  pipes \n-only bytes over SSH to avoid Windows CRLF corrupting filenames)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 18:08:31 -05:00

94 lines
3.3 KiB
Python

#!/usr/bin/env python3
"""
Standardize valhalla Hack-library filenames to:
Pokemon - <Name> [Hack][<version>][<functional tags>].<ext>
Rule-based + self-fetching: queries valhalla for every file still tagged
"(Hack)" anywhere under the roms tree and rewrites it.
- mechanical: " (Hack)" -> " [Hack]" (preserves other (...) like (FRLG+))
- OVERRIDES: platform-port fan-games whose stem carries "-<ver>-<os>"
get the version pulled into [ver] and the OS kept as a tag.
- SKIP: non-game patcher utilities are left untouched.
Dry-run prints OLD -> NEW; --apply runs `mv -n` over SSH.
Idempotent: already-[Hack] files don't match the (Hack) query, so re-runs
are no-ops.
"""
import subprocess, sys, shlex, os
APPLY = "--apply" in sys.argv
HOST = "valhalla"
ROOT = "/storage1/Emulation/roms"
# Non-game utilities — leave alone.
SKIP = {
"Pokemon - DeltaPatcherLite (Hack) [Installer].zip",
"Pokemon - Patcher (Hack) [Installer].zip",
}
# Stem-encoded version/OS ports -> canonical [Hack][ver][OS].
OVERRIDES = {
"Pokemon - Reborn-19.5.0-linux (Hack) [Linux].zip": "Pokemon - Reborn [Hack][19.5.0][Linux].zip",
"Pokemon - Rejuvenation-13.5.0-linux (Hack) [Linux].zip": "Pokemon - Rejuvenation [Hack][13.5.0][Linux].zip",
"Pokemon - Reborn-19.5.0-macos (Hack) [macOS].zip": "Pokemon - Reborn [Hack][19.5.0][macOS].zip",
"Pokemon - Rejuvenation-13.5.0-macos (Hack) [macOS].zip": "Pokemon - Rejuvenation [Hack][13.5.0][macOS].zip",
}
def new_name(base):
if base in SKIP:
return None
if base in OVERRIDES:
return OVERRIDES[base]
if " (Hack)" in base:
return base.replace(" (Hack)", " [Hack]")
return None
def fetch():
cmd = f"find {shlex.quote(ROOT)} -type f -name '*(Hack)*'"
out = subprocess.run(["ssh", HOST, cmd], capture_output=True, text=True, check=True).stdout
return sorted(p for p in out.splitlines() if p.strip())
def main():
paths = fetch()
cmds, special, mech, skipped = [], [], 0, []
for full in paths:
d, base = os.path.dirname(full), os.path.basename(full)
nn = new_name(base)
if nn is None:
skipped.append(base)
continue
cmds.append(f"mv -n -- {shlex.quote(full)} {shlex.quote(d + '/' + nn)}")
if base in OVERRIDES:
special.append((base, nn))
else:
mech += 1
print(f"Found {len(paths)} files still tagged (Hack).\n")
print(f"Mechanical (Hack)->[Hack] swaps : {mech}")
print(f"Special version/OS overrides : {len(special)}")
for o, n in special:
print(f" {o}\n -> {n}")
print(f"Skipped (non-game utilities) : {len(skipped)}")
for s in skipped:
print(f" {s}")
if not APPLY:
print("\n(dry-run — pass --apply to execute over SSH)")
return
# NB: pipe bytes with explicit \n line endings. On Windows, text=True wraps
# stdin in a TextIOWrapper that rewrites \n -> \r\n, which bash would append
# as a stray CR onto every destination filename. Encoding ourselves avoids it.
script = "set -e\n" + "\n".join(cmds) + "\n"
r = subprocess.run(["ssh", HOST, "bash -s"], input=script.encode("utf-8"))
print(f"\napplied {len(cmds)} renames (exit {r.returncode})")
sys.exit(r.returncode)
if __name__ == "__main__":
main()