Files
gitea/tools/wiki-contents.sh
T
jiantw83 c40b561589 feat(wiki): 目錄頁專用存取庫、HASH 去除截短與 H 前綴
What:CONTENTS 成為第 15 種頁面類型,解析鏈為 JSC_WIKI_REPO_CONTENTS 到
JSC_WIKI_REPO,刻意不退回型別變數。hash-id 拿掉 8 碼截短與 H 前綴改寫,只留大寫
轉換,輸出完整 40 碼;空輸入改成用法錯誤。新增 page-name.sh、wiki-contents.sh、
migrate-wiki.sh 與 wiki-delete 子命令。

Why:H 前綴會命中 16 個首碼裡的 13 個,還丟掉第 8 碼,把有效熵壓到 28 位元,而且
全庫查不到任何理由紀錄。目錄頁的整列 upsert 原本 14 處只有一處寫成程式,同一段判斷
做 14 次,錯一次就少一筆紀錄。

How:頁名樣式仍收 H 加 7 碼的舊頁,遷移期間讀得到舊頁。wiki-contents.sh 建新頁時
剝掉範本的示範列,否則每個目錄頁第一次建立都會留一列佔位死連結。migrate-wiki.sh
預設只印對照表,--apply 先寫新頁、確認寫成、才刪舊頁;孤兒頁只列不猜,因為 SHA-1
不可逆,新頁名只能靠候選鍵正推。

Who:jsc-gitea
2026-09-02 11:02:07 +08:00

179 lines
6.0 KiB
Bash
Executable File
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.
#!/usr/bin/env sh
# wiki-contents.sh — 目錄頁(*_CONTENTS)的表格列 upsert。
#
# 為什麼要有這支腳本:目錄頁的「找同一列就取代、找不到就附加」原本靠模型照
# SKILL.md 手工做,十四個目錄頁只有一處寫成程式。同一段判斷做十四次,錯一次
# 就少一筆紀錄。抽成一支,讀舊頁、比對鍵、整頁寫回只有一種做法。
#
# 用法:
# wiki-contents.sh upsert <TYPE> <key-col> <key> <row-file> [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 <TYPE> <key-col> <key> <row-file> [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