Ctrl K
ComunidadGuías…

scripts/sync_provider_catalog.py

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#!/usr/bin/env python3
"""Compare Sublarr's catalog providers with a checkout of the Bazarr+ catalog.

The bundles in backend/providers/hub/bundles are reviewed copies, never
fetched at runtime. This script is how they are updated: it reports what
changed upstream and copies a bundle across only when asked to by name, so
every update passes through a person reading the diff first.

    git clone https://github.com/LavX/bazarr-provider-catalog /tmp/catalog
    python3 scripts/sync_provider_catalog.py /tmp/catalog            # report
    python3 scripts/sync_provider_catalog.py /tmp/catalog --diff subhd
    python3 scripts/sync_provider_catalog.py /tmp/catalog --apply subhd titulky

Adopting a provider that is not vendored yet works the same way with
--apply; check its dependencies and the exclusion notes in
backend/providers/hub/__init__.py first.
"""

from __future__ import annotations

import argparse
import difflib
import json
import shutil
import subprocess
import sys
from pathlib import Path

BUNDLES = Path(__file__).resolve().parent.parent / "backend" / "providers" / "hub" / "bundles"
SOURCES = BUNDLES / "SOURCES.json"


def _bundle_files(bundle: Path) -> dict[str, bytes]:
    manifest = json.loads((bundle / "provider.json").read_text(encoding="utf-8"))
    names = ["provider.json", *manifest.get("files", {})]
    return {name: (bundle / name).read_bytes() for name in names if (bundle / name).is_file()}


def _catalog_commit(catalog: Path) -> str:
    out = subprocess.run(
        ["git", "-C", str(catalog), "rev-parse", "HEAD"], capture_output=True, text=True, check=True
    )
    return out.stdout.strip()


def report(catalog: Path) -> int:
    upstream = {
        p.name for p in (catalog / "providers").iterdir() if (p / "provider.json").is_file()
    }
    vendored = {p.name for p in BUNDLES.iterdir() if (p / "provider.json").is_file()}
    changed = sorted(
        name
        for name in vendored & upstream
        if _bundle_files(BUNDLES / name) != _bundle_files(catalog / "providers" / name)
    )
    print(f"catalog at {_catalog_commit(catalog)[:12]}")
    print(f"changed upstream ({len(changed)}): {', '.join(changed) or '-'}")
    print(f"removed upstream: {', '.join(sorted(vendored - upstream)) or '-'}")
    print(
        f"not vendored ({len(upstream - vendored)}): {', '.join(sorted(upstream - vendored)) or '-'}"
    )
    return 0


def diff(catalog: Path, name: str) -> int:
    ours = _bundle_files(BUNDLES / name) if (BUNDLES / name).is_dir() else {}
    theirs = _bundle_files(catalog / "providers" / name)
    for file in sorted(set(ours) | set(theirs)):
        a = ours.get(file, b"").decode("utf-8", "replace").splitlines(keepends=True)
        b = theirs.get(file, b"").decode("utf-8", "replace").splitlines(keepends=True)
        sys.stdout.writelines(
            difflib.unified_diff(a, b, f"sublarr/{name}/{file}", f"catalog/{name}/{file}")
        )
    return 0


def apply(catalog: Path, names: list[str]) -> int:
    commit = _catalog_commit(catalog)
    sources = json.loads(SOURCES.read_text(encoding="utf-8"))
    for name in names:
        source = catalog / "providers" / name
        if not (source / "provider.json").is_file():
            print(f"{name}: not in the catalog", file=sys.stderr)
            return 1
        target = BUNDLES / name
        if target.exists():
            shutil.rmtree(target)
        target.mkdir()
        for file, data in _bundle_files(source).items():
            (target / file).write_bytes(data)
        sources["bundles"][name] = commit
        print(f"{name}: copied from {commit[:12]}")
    sources["bundles"] = dict(sorted(sources["bundles"].items()))
    SOURCES.write_text(json.dumps(sources, indent=2) + "\n", encoding="utf-8")
    return 0


def main() -> int:
    parser = argparse.ArgumentParser(
        description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter
    )
    parser.add_argument("catalog", type=Path, help="path to a bazarr-provider-catalog checkout")
    group = parser.add_mutually_exclusive_group()
    group.add_argument("--diff", metavar="ID", help="show the upstream diff of one bundle")
    group.add_argument(
        "--apply", nargs="+", metavar="ID", help="copy these bundles from the catalog"
    )
    args = parser.parse_args()
    if args.diff:
        return diff(args.catalog, args.diff)
    if args.apply:
        return apply(args.catalog, args.apply)
    return report(args.catalog)


if __name__ == "__main__":
    sys.exit(main())