gitlink-cli/examples/workflows/gitlink-flow/src/cli.py

97 lines
3.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""gitlink-cli 命令封装层。
本模块把 `gitlink-cli` 的子命令issue +list、pr +list、repo +info、
release +list 等)封装为 Python 函数,是 gitlink-flow 工作流的**主调用链**。
设计依据赛题要求——工作流应优先组合 gitlink-cli 已有命令,而非自行直连
平台 HTTP API。因此 flow.py 默认通过本模块调用 gitlink-cli仅当本机
未安装 gitlink-cli 时,才回退到 glapi.py 的直连方式(见 GitLinkClient
gitlink-cli 的 JSON 输出统一为 {"ok": bool, "data": {...}},本模块负责
解包 data 并归一化为与 glapi 相同的返回结构,使上层步骤无感知。
"""
from __future__ import annotations
import json
import shutil
import subprocess
from typing import Any
class CliError(RuntimeError):
"""gitlink-cli 调用失败。"""
def cli_available() -> bool:
"""检测本机是否安装了 gitlink-cli。"""
return shutil.which("gitlink-cli") is not None
def _run(args: list[str], timeout: int = 60) -> Any:
"""执行 gitlink-cli 子命令,返回解包后的 data。
统一追加 --format json解析 {"ok":..., "data":...} 信封。
"""
cmd = ["gitlink-cli", *args, "--format", "json"]
try:
proc = subprocess.run(cmd, capture_output=True, text=True,
encoding="utf-8", timeout=timeout)
except FileNotFoundError as exc:
raise CliError("未找到 gitlink-cli请先安装npm i -g @gitlink-ai/cli") from exc
except subprocess.TimeoutExpired as exc:
raise CliError(f"gitlink-cli 调用超时:{' '.join(args)}") from exc
out = (proc.stdout or "").strip()
# 跳过可能的环境横幅行,定位 JSON 起始
brace = out.find("{")
if brace > 0:
out = out[brace:]
if not out:
raise CliError(f"gitlink-cli 无输出:{' '.join(args)}stderr={proc.stderr.strip()}")
try:
payload = json.loads(out)
except json.JSONDecodeError as exc:
raise CliError(f"gitlink-cli 输出非 JSON{' '.join(args)}") from exc
if isinstance(payload, dict) and payload.get("ok") is False:
raise CliError(f"gitlink-cli 返回失败:{payload.get('message') or payload}")
if isinstance(payload, dict) and "data" in payload:
return payload["data"]
return payload
def _extract_list(data: Any, keys: tuple[str, ...]) -> list[dict[str, Any]]:
if isinstance(data, list):
return data
if isinstance(data, dict):
for k in keys:
v = data.get(k)
if isinstance(v, list):
return v
return []
# ---------------------------------------------------------------------------
# 高层封装——签名与 glapi.GitLinkClient 对齐
# ---------------------------------------------------------------------------
def repo_info(owner: str, repo: str) -> dict[str, Any]:
data = _run(["repo", "+info", "--owner", owner, "--repo", repo])
return data if isinstance(data, dict) else {}
def issues(owner: str, repo: str, limit: int = 50) -> list[dict[str, Any]]:
data = _run(["issue", "+list", "--owner", owner, "--repo", repo])
return _extract_list(data, ("issues",))
def pulls(owner: str, repo: str, limit: int = 50) -> list[dict[str, Any]]:
data = _run(["pr", "+list", "--owner", owner, "--repo", repo])
return _extract_list(data, ("issues", "pulls"))
def releases(owner: str, repo: str) -> list[dict[str, Any]]:
data = _run(["release", "+list", "--owner", owner, "--repo", repo])
return _extract_list(data, ("releases",))