#!/usr/bin/env sh # issue.sh — Gitea 議題的讀取與建立(供 jsc-gitea:wiki-to-issue、html-export 使用)。 # # 為什麼要有這支腳本:建議題要組 JSON、要把標籤名換成標籤 id、要把回應裡的編號與網址挑出來, # 全是固定的輸入輸出。交給模型每次自己拼 curl,跳脫字元一錯就把議題內容寫壞,而議題是對外的。 # # 用法: # issue.sh labels / 列出既有標籤:idname # issue.sh label-ids / [,] 標籤名換成 id(逗號分隔);有名字對不到就 exit 4 # issue.sh projects / 列出專案看板:idtitle;站台沒有這個 API 就 exit 3 # issue.sh title / 印出議題標題 # issue.sh body / 印出議題正文(markdown) # issue.sh labels-of / 印出議題目前的標籤名,一行一個 # issue.sh create / <body-file> [--labels <id>[,<id>]] [--milestone <id>] # 建立議題。輸出兩行: index=<編號>、url=<議題網址> # # 環境變數: 同 gitea.sh(GITEA_HOST、GITEA_TOKEN;token 缺或被拒時退回 tea 登入金鑰)。 # 結束碼: 0=成功 1=API 失敗 2=用法錯誤 3=站台不支援該 API 4=名稱對不到 id # # 陷阱: # - 正文一律用檔案傳進來,內容維持 UTF-8 與真實換行;不要在參數裡塞 \n。 # - Gitea 1.27 沒有公開的專案看板 API,projects 會 exit 3。呼叫端要據實回報「請手動把議題拖到看板」, # 不要假裝已經關聯好——關聯不上跟關聯成功看起來一樣,是最容易被當成完成的一種失敗。 set -eu script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) GITEA="$script_dir/gitea.sh" usage() { cat >&2 <<'EOF' 用法: issue.sh labels <owner>/<repo> 列出既有標籤:id<TAB>name issue.sh label-ids <owner>/<repo> <name>[,<name>] 標籤名換成 id issue.sh projects <owner>/<repo> 列出專案看板;站台不支援就 exit 3 issue.sh title|body|labels-of <owner>/<repo> <index> issue.sh create <owner>/<repo> <title> <body-file> [--labels <id>[,<id>]] [--milestone <id>] 結束碼: 0=成功 1=API 失敗 2=用法錯誤 3=站台不支援 4=名稱對不到 id EOF exit 2 } [ -f "$GITEA" ] || { echo "[jsc][議題][ERR]:找不到 $GITEA。" >&2; exit 1; } valid_repo() { case "${1:-}" in */*/*|/*|*/) return 1 ;; */*) return 0 ;; *) return 1 ;; esac } cmd="${1:-}"; [ -n "$cmd" ] || usage shift || true repo="${1:-}" valid_repo "$repo" || { echo "[jsc][議題][ERR]:存取庫須為 {owner}/{repo},收到「${repo:-空值}」。" >&2; exit 2; } shift case "$cmd" in labels) "$GITEA" api GET "/repos/$repo/labels?limit=100" | python3 -c ' import json,sys for l in json.load(sys.stdin): print("%s\t%s" % (l["id"], l["name"])) ' ;; label-ids) names="${1:-}"; [ -n "$names" ] || usage "$GITEA" api GET "/repos/$repo/labels?limit=100" | python3 -c ' import json,sys want=[n.strip() for n in sys.argv[1].split(",") if n.strip()] have={l["name"]: l["id"] for l in json.load(sys.stdin)} miss=[n for n in want if n not in have] if miss: sys.stderr.write("[jsc][議題][ERR]:這些標籤在存取庫裡找不到:" + "、".join(miss) + "\n") sys.exit(4) print(",".join(str(have[n]) for n in want)) ' "$names" ;; projects) if out=$("$GITEA" api GET "/repos/$repo/projects" 2>/dev/null); then printf '%s' "$out" | python3 -c ' import json,sys try: d=json.load(sys.stdin) except Exception: sys.exit(3) if not isinstance(d, list): sys.exit(3) for p in d: print("%s\t%s" % (p.get("id",""), p.get("title") or p.get("name",""))) ' || exit 3 else host="${GITEA_HOST:-}" case "$host" in http://*|https://*) : ;; *) host="https://$host" ;; esac echo "[jsc][議題][WARN]:這個 Gitea 站台沒有專案看板 API,程式關聯不了。請開 ${host%/}/$repo/projects 手動把議題拖進看板。" >&2 exit 3 fi ;; title) idx="${1:-}"; [ -n "$idx" ] || usage "$GITEA" api GET "/repos/$repo/issues/$idx" | python3 -c ' import json,sys print(json.load(sys.stdin).get("title","")) ' ;; body) idx="${1:-}"; [ -n "$idx" ] || usage "$GITEA" api GET "/repos/$repo/issues/$idx" | python3 -c ' import json,sys sys.stdout.write(json.load(sys.stdin).get("body","") or "") ' ;; labels-of) idx="${1:-}"; [ -n "$idx" ] || usage "$GITEA" api GET "/repos/$repo/issues/$idx" | python3 -c ' import json,sys for l in json.load(sys.stdin).get("labels") or []: print(l.get("name","")) ' ;; create) title="${1:-}"; body_file="${2:-}" [ -n "$title" ] || usage [ -n "$body_file" ] || usage [ -f "$body_file" ] || { echo "[jsc][議題][ERR]:找不到正文檔「$body_file」。" >&2; exit 2; } shift 2 labels='' milestone='' while [ "$#" -gt 0 ]; do case "$1" in --labels) [ "$#" -ge 2 ] || usage; labels="$2"; shift 2 ;; --milestone) [ "$#" -ge 2 ] || usage; milestone="$2"; shift 2 ;; *) echo "[jsc][議題][ERR]:不認得的選項「$1」。" >&2; usage ;; esac done confirm_target="議題「$title」" [ -n "$labels" ] && confirm_target="$confirm_target,標籤:$labels" [ -n "$milestone" ] && confirm_target="$confirm_target,里程碑:$milestone" sh "$script_dir/write-confirm.sh" "建立議題" "$confirm_target" payload=$(mktemp) trap 'rm -f "$payload"' EXIT python3 -c ' import json,sys title, body_file, labels, milestone, out = sys.argv[1:6] d={"title": title, "body": open(body_file, encoding="utf-8").read()} if labels: d["labels"]=[int(x) for x in labels.split(",") if x.strip()] if milestone: d["milestone"]=int(milestone) with open(out, "w", encoding="utf-8") as f: json.dump(d, f, ensure_ascii=False) ' "$title" "$body_file" "$labels" "$milestone" "$payload" "$GITEA" api POST "/repos/$repo/issues" "$payload" | python3 -c ' import json,sys d=json.load(sys.stdin) print("index=%s" % (d.get("number") or d.get("index") or "")) print("url=%s" % (d.get("html_url") or "")) ' ;; *) usage ;; esac