191 lines
6.2 KiB
Python
191 lines
6.2 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
tea_helper.py — Python utility for Gitea tea CLI automation.
|
|
|
|
Usage:
|
|
python tea_helper.py issues [--state open|closed|all] [--repo owner/repo]
|
|
python tea_helper.py prs [--state open|closed|all] [--repo owner/repo]
|
|
python tea_helper.py releases [--repo owner/repo]
|
|
python tea_helper.py close-issue <index> [--repo owner/repo]
|
|
python tea_helper.py close-pr <index> [--repo owner/repo]
|
|
python tea_helper.py create-issue --title TITLE [--desc DESC] [--labels L1,L2]
|
|
python tea_helper.py triage [--repo owner/repo]
|
|
"""
|
|
|
|
import argparse
|
|
import json
|
|
import subprocess
|
|
import sys
|
|
from typing import Any
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Core helpers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def run_tea(*args: str, capture: bool = True) -> subprocess.CompletedProcess:
|
|
cmd = ["tea", *args]
|
|
result = subprocess.run(cmd, capture_output=capture, text=True)
|
|
if result.returncode != 0 and capture:
|
|
print(f"[tea error] {result.stderr.strip()}", file=sys.stderr)
|
|
return result
|
|
|
|
|
|
def tea_json(*args: str) -> Any:
|
|
result = run_tea(*args, "-o", "json")
|
|
if result.returncode != 0:
|
|
return []
|
|
try:
|
|
return json.loads(result.stdout)
|
|
except json.JSONDecodeError:
|
|
return []
|
|
|
|
|
|
def repo_flags(repo: str | None) -> list[str]:
|
|
return ["-r", repo] if repo else []
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Commands
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def cmd_issues(args: argparse.Namespace) -> None:
|
|
flags = ["issues", "list", "--state", args.state, *repo_flags(args.repo)]
|
|
issues = tea_json(*flags)
|
|
if not issues:
|
|
print("No issues found.")
|
|
return
|
|
print(f"{'#':<6} {'State':<8} {'Title'}")
|
|
print("-" * 60)
|
|
for i in issues:
|
|
print(f"#{i.get('number', i.get('index','?')):<5} {i.get('state',''):<8} {i.get('title','')}")
|
|
|
|
|
|
def cmd_prs(args: argparse.Namespace) -> None:
|
|
flags = ["pulls", "list", "--state", args.state,
|
|
"--fields", "index,title,state,author,milestone,updated,labels,ci",
|
|
*repo_flags(args.repo)]
|
|
prs = tea_json(*flags)
|
|
if not prs:
|
|
print("No pull requests found.")
|
|
return
|
|
print(f"{'#':<6} {'State':<8} {'Title'}")
|
|
print("-" * 60)
|
|
for pr in prs:
|
|
print(f"#{pr.get('number', pr.get('index','?')):<5} {pr.get('state',''):<8} {pr.get('title','')}")
|
|
|
|
|
|
def cmd_releases(args: argparse.Namespace) -> None:
|
|
flags = ["releases", "list", *repo_flags(args.repo)]
|
|
releases = tea_json(*flags)
|
|
if not releases:
|
|
print("No releases found.")
|
|
return
|
|
print(f"{'Tag':<20} {'Name':<30} {'Draft':<6} {'Pre':<5}")
|
|
print("-" * 65)
|
|
for r in releases:
|
|
print(f"{r.get('tag_name',''):<20} {r.get('name',''):<30} "
|
|
f"{str(r.get('draft', False)):<6} {str(r.get('prerelease', False)):<5}")
|
|
|
|
|
|
def cmd_close_issue(args: argparse.Namespace) -> None:
|
|
result = run_tea("issues", "close", str(args.index), *repo_flags(args.repo))
|
|
if result.returncode == 0:
|
|
print(f"Issue #{args.index} closed.")
|
|
|
|
|
|
def cmd_close_pr(args: argparse.Namespace) -> None:
|
|
result = run_tea("pulls", "close", str(args.index), *repo_flags(args.repo))
|
|
if result.returncode == 0:
|
|
print(f"PR #{args.index} closed.")
|
|
|
|
|
|
def cmd_create_issue(args: argparse.Namespace) -> None:
|
|
flags = ["issues", "create", "--title", args.title, *repo_flags(args.repo)]
|
|
if args.desc:
|
|
flags += ["--description", args.desc]
|
|
if args.labels:
|
|
flags += ["--labels", args.labels]
|
|
result = run_tea(*flags, capture=False)
|
|
sys.exit(result.returncode)
|
|
|
|
|
|
def cmd_triage(args: argparse.Namespace) -> None:
|
|
"""Group open issues by their first label."""
|
|
flags = ["issues", "list", "--state", "open", *repo_flags(args.repo)]
|
|
issues = tea_json(*flags)
|
|
if not issues:
|
|
print("No open issues.")
|
|
return
|
|
|
|
groups: dict[str, list] = {}
|
|
for issue in issues:
|
|
labels = issue.get("labels") or []
|
|
label = labels[0].get("name", "unlabeled") if labels else "unlabeled"
|
|
groups.setdefault(label, []).append(issue)
|
|
|
|
for label, items in sorted(groups.items()):
|
|
print(f"\n[{label}] ({len(items)})")
|
|
for i in items:
|
|
print(f" #{i.get('number', i.get('index','?'))} {i.get('title','')}")
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# CLI
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def build_parser() -> argparse.ArgumentParser:
|
|
p = argparse.ArgumentParser(description="tea CLI helper (Python wrapper)")
|
|
p.add_argument("--repo", "-r", help="owner/repo slug (overrides auto-detect)")
|
|
sub = p.add_subparsers(dest="command", required=True)
|
|
|
|
# issues
|
|
pi = sub.add_parser("issues", help="List issues")
|
|
pi.add_argument("--state", default="open", choices=["open", "closed", "all"])
|
|
|
|
# prs
|
|
pp = sub.add_parser("prs", help="List pull requests")
|
|
pp.add_argument("--state", default="open", choices=["open", "closed", "all"])
|
|
|
|
# releases
|
|
sub.add_parser("releases", help="List releases")
|
|
|
|
# close-issue
|
|
pci = sub.add_parser("close-issue", help="Close an issue")
|
|
pci.add_argument("index", type=int, help="Issue index/number")
|
|
|
|
# close-pr
|
|
pcp = sub.add_parser("close-pr", help="Close a pull request")
|
|
pcp.add_argument("index", type=int, help="PR index/number")
|
|
|
|
# create-issue
|
|
pcr = sub.add_parser("create-issue", help="Create a new issue")
|
|
pcr.add_argument("--title", "-t", required=True)
|
|
pcr.add_argument("--desc", "-d", default="")
|
|
pcr.add_argument("--labels", "-L", default="", help="Comma-separated labels")
|
|
|
|
# triage
|
|
sub.add_parser("triage", help="Group open issues by label")
|
|
|
|
return p
|
|
|
|
|
|
def main() -> None:
|
|
parser = build_parser()
|
|
args = parser.parse_args()
|
|
|
|
dispatch = {
|
|
"issues": cmd_issues,
|
|
"prs": cmd_prs,
|
|
"releases": cmd_releases,
|
|
"close-issue": cmd_close_issue,
|
|
"close-pr": cmd_close_pr,
|
|
"create-issue": cmd_create_issue,
|
|
"triage": cmd_triage,
|
|
}
|
|
dispatch[args.command](args)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|