#!/usr/bin/env python3 """ sync_helpers.py - small helper used by sync.sh Two jobs, on purpose kept out of bash because reliable YAML parsing and XML escaping in pure bash/sed is a losing game: parse-yaml Reads the "sources:" list from the YAML file and prints it as JSON on stdout, so sync.sh can loop over it safely (no word-splitting issues, handles quoting etc.). gen-appstream entries_json is a JSON array of {"name":..., "summary":...} objects (only the *explicitly requested* top-level packages, never pulled-in dependencies). Prints a minimal AppStream "generic component" catalog on stdout, which sync.sh gzips and injects into the repo metadata via modifyrepo_c. NOTE: "generic" components (no icon/screenshots/.desktop file) make a package installable from GNOME Software / Discover and show up in search, but will NOT appear on the curated "Explore" front page - that's a GNOME Software behaviour for non-desktop-app components, not something fixable at the repo-metadata level. """ import sys import json import argparse from xml.sax.saxutils import escape def cmd_parse_yaml(path: str) -> None: try: import yaml except ImportError: sys.stderr.write( "ERROR: python3-pyyaml (PyYAML) is not installed.\n" "Install it with: dnf install -y python3-pyyaml\n" ) sys.exit(1) with open(path, "r", encoding="utf-8") as fh: data = yaml.safe_load(fh) or {} sources = data.get("sources") or [] if not isinstance(sources, list): sys.stderr.write("ERROR: 'sources' in YAML is not a list\n") sys.exit(1) for i, src in enumerate(sources): for field in ("name", "baseurl", "packages"): if field not in src: sys.stderr.write( f"ERROR: source #{i} is missing required field '{field}'\n" ) sys.exit(1) if not isinstance(src["packages"], list) or not src["packages"]: sys.stderr.write( f"ERROR: source '{src.get('name')}' has an empty/invalid packages list\n" ) sys.exit(1) json.dump(sources, sys.stdout) def cmd_gen_appstream(origin: str, entries_json: str) -> None: entries = json.loads(entries_json) print('') print(f'') for e in entries: name = e.get("name", "") summary = e.get("summary") or f"{name} (mirrored package)" cid = f"{name}.generic" print(' ') print(f" {escape(cid)}") print(f" {escape(name)}") print(f" {escape(name)}") print(f" {escape(summary)}") print(" CC0-1.0") print(" unknown") print(" ") print("") def main() -> None: ap = argparse.ArgumentParser() sub = ap.add_subparsers(dest="cmd", required=True) p1 = sub.add_parser("parse-yaml") p1.add_argument("path") p2 = sub.add_parser("gen-appstream") p2.add_argument("origin") p2.add_argument("entries_json") args = ap.parse_args() if args.cmd == "parse-yaml": cmd_parse_yaml(args.path) elif args.cmd == "gen-appstream": cmd_gen_appstream(args.origin, args.entries_json) if __name__ == "__main__": main()