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
179 lines
6.0 KiB
Bash
Executable File
179 lines
6.0 KiB
Bash
Executable File
#!/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
|