#!/usr/bin/env sh # wiki-contents.sh — 目錄頁(*_CONTENTS)的表格列 upsert。 # # 為什麼要有這支腳本:目錄頁的「找同一列就取代、找不到就附加」原本靠模型照 # SKILL.md 手工做,十四個目錄頁只有一處寫成程式。同一段判斷做十四次,錯一次 # 就少一筆紀錄。抽成一支,讀舊頁、比對鍵、整頁寫回只有一種做法。 # # 用法: # wiki-contents.sh upsert [template-file] # TYPE 頁型,決定頁名 {TYPE}_CONTENTS # key-col 鍵在表格第幾欄,1 起算 # key 鍵值,用來找既有列 # row-file 整列 markdown 表格列的檔案 # template-file 選用。頁不存在時用它建新頁 # # 規則: # 目錄頁一律住在 CONTENTS 專用存取庫,所以存取庫走 wiki-repo CONTENTS, # 不走各自的頁型。 # 只有 wiki-get 回 4 才准建新頁。回 7 或 8 一律中止:把金鑰失效讀成 # 「頁面不存在」,就會拿新範本蓋掉活著的頁,舊紀錄整份沒了。 # 這條規則的正本在 skills/wiki/SKILL.md 的 Rules 第 4 條。 # 結束碼: 0=已更新或已新增 1=寫入失敗 2=用法錯誤 3=CONTENTS 存取庫未設定 # 4=頁不存在且沒給範本 7=金鑰失效或權限不足 8=其他 API 失敗 set -eu dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) gitea="$dir/gitea.sh" page_name="$dir/page-name.sh" usage() { echo 'usage: wiki-contents.sh upsert [template-file]' >&2 exit 2 } [ "${1-}" = upsert ] || usage shift [ "$#" -ge 4 ] && [ "$#" -le 5 ] || usage type=$(printf '%s' "${1-}" | tr a-z A-Z) keycol="${2-}" key="${3-}" rowfile="${4-}" template="${5-}" [ -n "$type" ] || usage page="${type}_CONTENTS" # 頁型合不合法交給 page-name.sh 判:型別清單只留一份正本。 # 它連 CONTENTS 一起擋掉——CONTENTS 只用來解存取庫,沒有 CONTENTS_CONTENTS 這一頁。 sh "$page_name" check "$page" >/dev/null 2>&1 || { echo "不能用來組目錄頁頁名的頁型: $type" >&2; usage; } case "$keycol" in ''|*[!0-9]*) echo "key-col must be a positive integer: $keycol" >&2; exit 2 ;; esac [ "$keycol" -ge 1 ] || { echo "key-col must be a positive integer: $keycol" >&2; exit 2; } [ -n "$key" ] || { echo 'key required' >&2; exit 2; } [ -f "$rowfile" ] || { echo "找不到列檔案: $rowfile" >&2; exit 2; } [ -z "$template" ] || [ -f "$template" ] || { echo "找不到範本檔: $template" >&2; exit 2; } # 存取庫解析失敗照原碼傳出去:3 是「沒設定」,2 是型別不認得,兩者處置不同。 rc=0 repo=$(sh "$gitea" wiki-repo CONTENTS) || rc=$? [ "$rc" -eq 0 ] || exit "$rc" old=$(mktemp) new=$(mktemp) trap 'rm -f "$old" "$new"' EXIT fresh=0 rc=0 sh "$gitea" wiki-get "$repo" "$page" > "$old" 2>/dev/null || rc=$? case "$rc" in 0) ;; 4) # 頁不存在。只有這一碼准許建新頁。 [ -n "$template" ] || { echo "[jsc][gitea][ERR]:$repo/$page 不存在,也沒有給範本,不建新頁。" >&2 exit 4 } cat "$template" > "$old" fresh=1 ;; 7) echo "[jsc][gitea][ERR]:讀 $repo/$page 遇金鑰失效或權限不足,整個動作中止。" >&2 exit 7 ;; *) echo "[jsc][gitea][ERR]:讀 $repo/$page 失敗(結束碼 $rc),整個動作中止。" >&2 exit 8 ;; esac rc=0 action=$(python3 - "$old" "$rowfile" "$keycol" "$key" "$new" "$fresh" <<'PY' import sys old_path, row_path, keycol, key, new_path, fresh = sys.argv[1:7] keycol = int(keycol) fresh = fresh == '1' lines = open(old_path, encoding='utf-8').read().split('\n') row = open(row_path, encoding='utf-8').read().strip('\n') def cells(line): s = line.strip() if not s.startswith('|'): return None s = s[1:] if s.endswith('|'): s = s[:-1] return [c.strip() for c in s.split('|')] def is_sep(cs): return bool(cs) and all(c and set(c) <= set('-: ') for c in cs) # 範本表格的示範列落在分隔列之後,會被當成真的資料列。照抄進新頁,那一列就永遠留著, # 之後每次 upsert 都比不到它的鍵而跳過,正式頁上多出一條指向不存在的頁的死連結。 # 所以建新頁時剝掉分隔列之後的所有資料列,只留標題、說明、表頭與分隔列。 if fresh: kept = [] passed_sep = False for line in lines: cs = cells(line) if cs is None: kept.append(line) continue if is_sep(cs): passed_sep = True kept.append(line) continue if passed_sep: continue kept.append(line) lines = kept # 分隔列之前的都是表頭。從分隔列之後才開始比對鍵,表頭第一欄剛好等於鍵時才不會被改掉。 seen_sep = False hit = -1 last_row = -1 for i, line in enumerate(lines): cs = cells(line) if cs is None: continue if is_sep(cs): seen_sep = True last_row = i continue if not seen_sep: continue last_row = i if hit < 0 and len(cs) >= keycol and cs[keycol - 1] == key: hit = i if hit >= 0: lines[hit] = row action = 'updated' elif last_row >= 0: lines.insert(last_row + 1, row) action = 'added' else: sys.stderr.write('[jsc][gitea][ERR]:頁面裡找不到 markdown 表格,無處可放這一列。\n') raise SystemExit(1) open(new_path, 'w', encoding='utf-8').write('\n'.join(lines)) print(action) PY ) || rc=$? # 整不出正確的頁就不要送出去。組不出內容跟送出失敗一樣寫不進去,共用結束碼 1。 [ "$rc" -eq 0 ] || exit 1 rc=0 sh "$gitea" wiki-put "$repo" "$page" "$new" >/dev/null || rc=$? case "$rc" in 0) printf '%s %s/%s\n' "$action" "$repo" "$page" ;; 7) echo "[jsc][gitea][ERR]:寫入 $repo/$page 遇金鑰失效或權限不足。" >&2; exit 7 ;; 8) echo "[jsc][gitea][ERR]:寫入 $repo/$page 遇 API 失敗。" >&2; exit 8 ;; *) echo "[jsc][gitea][ERR]:寫入 $repo/$page 失敗(結束碼 $rc)。" >&2; exit 1 ;; esac