#!/usr/bin/env python3 """Read a Gitea issue and print a compact Markdown summary.""" from __future__ import annotations import argparse import json import urllib.error import urllib.parse import urllib.request from pathlib import Path from typing import Any from gitea_common import DEFAULT_ENV_FILE, config def api_url(base_url: str, owner: str, repo: str, suffix: str) -> str: encoded_owner = urllib.parse.quote(owner, safe="") encoded_repo = urllib.parse.quote(repo, safe="") return f"{base_url.rstrip('/')}/api/v1/repos/{encoded_owner}/{encoded_repo}/{suffix.lstrip('/')}" def request_json(method: str, url: str, token: str) -> Any: request = urllib.request.Request(url, method=method) request.add_header("Authorization", f"token {token}") request.add_header("Accept", "application/json") try: with urllib.request.urlopen(request, timeout=30) as response: body = response.read().decode("utf-8") except urllib.error.HTTPError as exc: error_body = exc.read().decode("utf-8", errors="replace") raise SystemExit(f"Gitea API request failed: HTTP {exc.code}\n{error_body}") from exc except urllib.error.URLError as exc: raise SystemExit(f"Gitea API request failed: {exc.reason}") from exc return json.loads(body) if body else None def issue_line(issue: dict[str, Any]) -> str: return f"#{issue['number']} {issue['title']} ({issue['state']}) - {issue['html_url']}" def print_issue(issue: dict[str, Any], dependencies: list[dict[str, Any]], comments: list[dict[str, Any]] | None) -> None: labels = [label["name"] for label in issue.get("labels", [])] print(f"# Issue #{issue['number']}: {issue['title']}") print() print(f"- URL: {issue['html_url']}") print(f"- State: {issue['state']}") print(f"- Labels: {', '.join(labels) if labels else '(none)'}") print(f"- Created: {issue.get('created_at')}") print(f"- Updated: {issue.get('updated_at')}") print() print("## Body") print() print(issue.get("body") or "(empty)") print() print("## Dependencies") print() if dependencies: for dependency in sorted(dependencies, key=lambda item: item["number"]): print(f"- {issue_line(dependency)}") else: print("(none)") if comments is not None: print() print("## Comments") print() if comments: for comment in comments: user = comment.get("user", {}).get("login", "unknown") print(f"### Comment by {user} at {comment.get('created_at')}") print() print(comment.get("body") or "(empty)") print() else: print("(none)") def main() -> int: parser = argparse.ArgumentParser(description="Read an issue in the configured Gitea repo.") parser.add_argument("--issue", type=int, required=True) parser.add_argument("--comments", action="store_true", help="Include issue comments.") parser.add_argument("--env-file", type=Path, default=DEFAULT_ENV_FILE) args = parser.parse_args() cfg = config(args.env_file.expanduser()) issue = request_json( "GET", api_url(cfg["GITEA_BASE_URL"], cfg["GITEA_OWNER"], cfg["GITEA_REPO"], f"issues/{args.issue}"), cfg["GITEA_TOKEN"], ) dependencies = request_json( "GET", api_url( cfg["GITEA_BASE_URL"], cfg["GITEA_OWNER"], cfg["GITEA_REPO"], f"issues/{args.issue}/dependencies?limit=1000", ), cfg["GITEA_TOKEN"], ) comments = None if args.comments: comments = request_json( "GET", api_url( cfg["GITEA_BASE_URL"], cfg["GITEA_OWNER"], cfg["GITEA_REPO"], f"issues/{args.issue}/comments?limit=1000", ), cfg["GITEA_TOKEN"], ) print_issue(issue, dependencies, comments) return 0 if __name__ == "__main__": raise SystemExit(main())