Codex skill for development added. Tested by using it to set up Play Palace.
This commit is contained in:
@@ -0,0 +1,202 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Audit Linux Game Manager catalog consistency."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def read_first_line(file_path: Path) -> str:
|
||||
try:
|
||||
with file_path.open("r", encoding="utf-8") as handle:
|
||||
return handle.readline().strip()
|
||||
except OSError:
|
||||
return ""
|
||||
|
||||
|
||||
def has_run_update(file_path: Path) -> bool:
|
||||
try:
|
||||
content = file_path.read_text(encoding="utf-8")
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
return "run_update()" in content or "run_update ()" in content
|
||||
|
||||
|
||||
def collect_names(file_paths: list[Path], suffix: str) -> list[str]:
|
||||
return sorted(file_path.name[: -len(suffix)] for file_path in file_paths)
|
||||
|
||||
|
||||
def count_disabled(file_paths: list[Path]) -> list[str]:
|
||||
disabledNames = []
|
||||
for file_path in file_paths:
|
||||
if read_first_line(file_path).startswith("#//"):
|
||||
disabledNames.append(file_path.name)
|
||||
return sorted(disabledNames)
|
||||
|
||||
|
||||
def build_report(repo_root: Path) -> dict:
|
||||
installDir = repo_root / ".install"
|
||||
launchDir = repo_root / ".launch"
|
||||
updateDir = repo_root / ".update"
|
||||
|
||||
missingDirs = [
|
||||
str(path.relative_to(repo_root))
|
||||
for path in [installDir, launchDir, updateDir]
|
||||
if not path.exists()
|
||||
]
|
||||
if missingDirs:
|
||||
raise FileNotFoundError(
|
||||
f"Expected directories not found: {', '.join(missingDirs)}"
|
||||
)
|
||||
|
||||
installPaths = sorted(installDir.glob("*.sh"))
|
||||
launchGamePaths = sorted(launchDir.glob("*.game"))
|
||||
launchShPaths = sorted(launchDir.glob("*.sh"))
|
||||
updatePaths = sorted(updateDir.glob("*.sh"))
|
||||
|
||||
installerNames = collect_names(installPaths, ".sh")
|
||||
launcherGameNames = collect_names(launchGamePaths, ".game")
|
||||
launcherShNames = collect_names(launchShPaths, ".sh")
|
||||
|
||||
launcherSymlinkCount = sum(1 for path in launchShPaths if path.is_symlink())
|
||||
launcherFileCount = len(launchShPaths) - launcherSymlinkCount
|
||||
|
||||
missingLauncherGameForInstaller = sorted(
|
||||
set(installerNames) - set(launcherGameNames)
|
||||
)
|
||||
missingInstallerForLauncherGame = sorted(
|
||||
set(launcherGameNames) - set(installerNames)
|
||||
)
|
||||
missingLauncherShForLauncherGame = sorted(
|
||||
set(launcherGameNames) - set(launcherShNames)
|
||||
)
|
||||
orphanLauncherSh = sorted(set(launcherShNames) - set(launcherGameNames))
|
||||
|
||||
updateWithoutRunFunction = sorted(
|
||||
path.name for path in updatePaths if not has_run_update(path)
|
||||
)
|
||||
|
||||
report = {
|
||||
"repoRoot": str(repo_root),
|
||||
"criticalMismatches": [
|
||||
"missingLauncherGameForInstaller",
|
||||
"updateScriptsMissingRunUpdate",
|
||||
],
|
||||
"warningMismatches": [
|
||||
"missingInstallerForLauncherGame",
|
||||
"missingLauncherShForLauncherGame",
|
||||
"orphanLauncherSh",
|
||||
],
|
||||
"counts": {
|
||||
"installers": len(installPaths),
|
||||
"launcherGameDefinitions": len(launchGamePaths),
|
||||
"launcherRunnableEntries": len(launchShPaths),
|
||||
"launcherRunnableSymlinks": launcherSymlinkCount,
|
||||
"launcherRunnableRegularFiles": launcherFileCount,
|
||||
"updateScripts": len(updatePaths),
|
||||
},
|
||||
"disabledEntries": {
|
||||
"installers": count_disabled(installPaths),
|
||||
"launchers": count_disabled(launchShPaths),
|
||||
},
|
||||
"mismatches": {
|
||||
"missingLauncherGameForInstaller": missingLauncherGameForInstaller,
|
||||
"missingInstallerForLauncherGame": missingInstallerForLauncherGame,
|
||||
"missingLauncherShForLauncherGame": missingLauncherShForLauncherGame,
|
||||
"orphanLauncherSh": orphanLauncherSh,
|
||||
"updateScriptsMissingRunUpdate": updateWithoutRunFunction,
|
||||
},
|
||||
}
|
||||
return report
|
||||
|
||||
|
||||
def print_human_report(report: dict) -> None:
|
||||
counts = report["counts"]
|
||||
mismatches = report["mismatches"]
|
||||
criticalMismatches = report["criticalMismatches"]
|
||||
warningMismatches = report["warningMismatches"]
|
||||
disabledEntries = report["disabledEntries"]
|
||||
|
||||
print(f"Repository: {report['repoRoot']}")
|
||||
print("Counts:")
|
||||
print(f" Installers: {counts['installers']}")
|
||||
print(f" Launcher .game files: {counts['launcherGameDefinitions']}")
|
||||
print(f" Launcher .sh entries: {counts['launcherRunnableEntries']}")
|
||||
print(f" Symlinks: {counts['launcherRunnableSymlinks']}")
|
||||
print(f" Regular files: {counts['launcherRunnableRegularFiles']}")
|
||||
print(f" Update scripts: {counts['updateScripts']}")
|
||||
|
||||
print("Disabled entries:")
|
||||
print(f" Installers: {len(disabledEntries['installers'])}")
|
||||
if disabledEntries["installers"]:
|
||||
print(" " + ", ".join(disabledEntries["installers"]))
|
||||
print(f" Launchers: {len(disabledEntries['launchers'])}")
|
||||
if disabledEntries["launchers"]:
|
||||
print(" " + ", ".join(disabledEntries["launchers"]))
|
||||
|
||||
print("Critical checks:")
|
||||
for key in criticalMismatches:
|
||||
values = mismatches[key]
|
||||
print(f" {key}: {len(values)}")
|
||||
if values:
|
||||
print(" " + ", ".join(values))
|
||||
|
||||
print("Warnings:")
|
||||
for key in warningMismatches:
|
||||
values = mismatches[key]
|
||||
print(f" {key}: {len(values)}")
|
||||
if values:
|
||||
print(" " + ", ".join(values))
|
||||
|
||||
criticalTotal = sum(len(mismatches[key]) for key in criticalMismatches)
|
||||
if criticalTotal == 0:
|
||||
print("Result: OK (no critical mismatches)")
|
||||
else:
|
||||
print("Result: ATTENTION REQUIRED (critical mismatches detected)")
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
defaultRepoRoot = Path(__file__).resolve().parents[4]
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Audit installer/launcher/update consistency for linux-game-manager."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--repo-root",
|
||||
default=str(defaultRepoRoot),
|
||||
help=f"Repository root path (default: {defaultRepoRoot})",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--json",
|
||||
action="store_true",
|
||||
help="Emit JSON instead of human-readable text",
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def main() -> int:
|
||||
args = parse_args()
|
||||
repoRoot = Path(args.repo_root).resolve()
|
||||
|
||||
try:
|
||||
report = build_report(repoRoot)
|
||||
except FileNotFoundError as error:
|
||||
print(str(error), file=sys.stderr)
|
||||
return 2
|
||||
|
||||
if args.json:
|
||||
print(json.dumps(report, indent=2, sort_keys=True))
|
||||
else:
|
||||
print_human_report(report)
|
||||
|
||||
criticalTotal = sum(
|
||||
len(report["mismatches"][bucket]) for bucket in report["criticalMismatches"]
|
||||
)
|
||||
return 1 if criticalTotal else 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user