#!/usr/bin/env python3 """Describe the Gitea request that creates or resynchronizes a release.""" from __future__ import annotations import argparse import json from pathlib import Path import re import sys from typing import Any _SEMVER = r"(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)" class ReleaseRequestError(ValueError): """A release request could not be prepared safely.""" def build_release_request( version: str, body: str, existing_release: dict[str, Any] | None ) -> dict[str, Any]: """Return the observable POST or PATCH request for a Gitea release.""" if not re.fullmatch(_SEMVER, version): raise ReleaseRequestError(f"version is not bare semver: {version!r}") if existing_release is None: return { "method": "POST", "path": "/releases", "payload": {"tag_name": f"v{version}", "name": f"v{version}", "body": body}, } release_id = existing_release.get("id") if not isinstance(release_id, int): raise ReleaseRequestError("existing release does not contain an integer id") return { "method": "PATCH", "path": f"/releases/{release_id}", "payload": {"body": body}, } def _read_json(path: Path) -> dict[str, Any]: try: value = json.loads(path.read_text(encoding="utf-8")) except (OSError, json.JSONDecodeError) as error: raise ReleaseRequestError(f"cannot read existing release {path}: {error}") from error if not isinstance(value, dict): raise ReleaseRequestError("existing release must be a JSON object") return value def main(argv: list[str] | None = None) -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--version", required=True) parser.add_argument("--body-file", type=Path, required=True) parser.add_argument("--existing-release", type=Path) args = parser.parse_args(argv) try: body = args.body_file.read_text(encoding="utf-8") existing = _read_json(args.existing_release) if args.existing_release else None print(json.dumps(build_release_request(args.version, body, existing))) except (OSError, ReleaseRequestError) as error: print(f"::error::{error}", file=sys.stderr) return 1 return 0 if __name__ == "__main__": raise SystemExit(main())