範本種類以前由模型每次重推。底線怎麼切、標籤照什麼順序試,都是固定的 輸入輸出,卻放在技能內文裡由模型自己來,有人切錯底線,也有人跳過標籤 順序。現在把規則寫進腳本,輸出固定一行,還附上是哪個字首或哪個標籤產生 的依據。已經有標籤名單時可以直接帶進來,省掉一次 API 呼叫。 技能盤點頁是新的頁面類型。加進 wiki 存取庫的允許清單與規則驗證腳本, 這種頁才有地方可放,解析規則也才有人驗。
228 lines
8.9 KiB
Bash
Executable File
228 lines
8.9 KiB
Bash
Executable File
#!/usr/bin/env sh
|
||
# html-style.sh — 「哪一種 wiki 頁或議題,用哪一種版型與風格出 HTML」的設定(供 jsc-gitea:html-style、html-export 使用)。
|
||
#
|
||
# 設定格式(一行一筆):{種類}={版型},{風格}
|
||
# 種類:WIKI:{頁名前綴}(例 WIKI:PLAN)、ISSUE:{標籤名}(例 ISSUE:bug)、DEFAULT(都對不到時用)
|
||
# 「#」開頭為註解,空白行忽略。同一種類出現多行時取最後一行。
|
||
# 解析順序:
|
||
# 1. 目前工作目錄的 ./.jsc/html-styles(專案覆寫)
|
||
# 2. $JSC_HOME/html-styles.conf(JSC_HOME 預設 ~/.jsc)
|
||
# 3. 種類對不到就退 DEFAULT,DEFAULT 也沒有才用內建預設 report,minimal
|
||
#
|
||
# 用法:
|
||
# html-style.sh get <種類> 印出 版型<TAB>風格<TAB>來源(project/global/default/builtin)
|
||
# html-style.sh set <種類> <版型> <風格> [--project|--global] 寫入設定(預設 --global)
|
||
# html-style.sh unset <種類> [--project|--global] 移除設定
|
||
# html-style.sh list 印出合併後的所有設定:種類<TAB>版型<TAB>風格<TAB>來源
|
||
# html-style.sh layouts 列出可用版型:名稱<TAB>繁中說明
|
||
# html-style.sh styles 列出可用風格:名稱<TAB>繁中說明
|
||
# html-style.sh key wiki <頁名> 推導 wiki 頁的種類:取第一個底線前的字首
|
||
# html-style.sh key issue <owner>/<repo> <編號> [--labels <名稱>[,<名稱>]]
|
||
# 推導議題的種類:依序試每個標籤,第一個讓 get 回 project 或 global 的標籤勝出;
|
||
# 都對不到就回 ISSUE:DEFAULT。已經有標籤名單時用 --labels 帶進來,省掉一次 API 呼叫。
|
||
# key 的輸出固定一行:{種類}<TAB>{依據}
|
||
# 依據 = prefix:{字首}、label:{標籤名}、fallback:no-labels、fallback:no-configured-label
|
||
#
|
||
# 結束碼: 0=成功 1=取不到議題標籤,或設定檔的目錄建不出來 2=用法錯誤 4=版型或風格沒有對應範本檔
|
||
#
|
||
# 陷阱:
|
||
# - get 永遠印得出一組值:對不到就退 DEFAULT,再對不到就退內建預設。呼叫端不必自己準備退路,
|
||
# 但要看第三欄,才知道這組值是使用者設的還是撿來的。
|
||
# - key 的推導規則放在腳本裡,不放技能內文:字首怎麼切、標籤怎麼試,是固定的輸入輸出,
|
||
# 每次由模型重推就會有人切錯底線或跳過標籤順序。
|
||
# - set 會先確認範本檔真的存在,擋掉打錯字的版型或風格;設定寫得進去、出圖卻失敗最難查。
|
||
set -u
|
||
|
||
script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||
plugin_root="${CLAUDE_PLUGIN_ROOT:-$script_dir/..}"
|
||
TPL="$plugin_root/templates/html"
|
||
JSC_HOME="${JSC_HOME:-$HOME/.jsc}"
|
||
PROJECT_FILE=./.jsc/html-styles
|
||
GLOBAL_FILE="$JSC_HOME/html-styles.conf"
|
||
BUILTIN_LAYOUT=report
|
||
BUILTIN_STYLE=minimal
|
||
|
||
usage() {
|
||
cat >&2 <<'EOF'
|
||
用法:
|
||
html-style.sh get <種類>
|
||
html-style.sh set <種類> <版型> <風格> [--project|--global]
|
||
html-style.sh unset <種類> [--project|--global]
|
||
html-style.sh list | layouts | styles
|
||
html-style.sh key wiki <頁名>
|
||
html-style.sh key issue <owner>/<repo> <編號> [--labels <名稱>[,<名稱>]]
|
||
種類: WIKI:{頁名前綴}、ISSUE:{標籤名}、DEFAULT
|
||
結束碼: 0=成功 1=取不到議題標籤或建不出目錄 2=用法錯誤 4=版型或風格沒有對應範本檔
|
||
EOF
|
||
exit 2
|
||
}
|
||
|
||
read_value() { # $1=設定檔 $2=種類 -> 「版型,風格」
|
||
[ -f "$1" ] || return 0
|
||
awk -F= -v want="$2" '
|
||
{ sub(/\r$/, "") }
|
||
/^[ \t]*#/ { next }
|
||
/^[ \t]*$/ { next }
|
||
index($0, "=") == 0 { next }
|
||
{
|
||
key = $1
|
||
gsub(/[ \t]/, "", key)
|
||
if (key != want) next
|
||
val = substr($0, index($0, "=") + 1)
|
||
gsub(/[ \t]/, "", val)
|
||
if (val != "") v = val
|
||
}
|
||
END { if (v != "") print v }
|
||
' "$1"
|
||
}
|
||
|
||
resolve() { # $1=種類 -> 版型<TAB>風格<TAB>來源
|
||
for _f in "$PROJECT_FILE:project" "$GLOBAL_FILE:global"; do
|
||
_file=${_f%:*}; _src=${_f##*:}
|
||
_v=$(read_value "$_file" "$1")
|
||
if [ -n "$_v" ]; then
|
||
printf '%s\t%s\t%s\n' "${_v%%,*}" "${_v##*,}" "$_src"
|
||
return 0
|
||
fi
|
||
done
|
||
if [ "$1" != DEFAULT ]; then
|
||
_d=$(resolve DEFAULT)
|
||
_src=$(printf '%s' "$_d" | cut -f3)
|
||
# DEFAULT 自己也沒設定時,來源照實說是 builtin,不要蓋成 default
|
||
[ "$_src" = builtin ] || _src=default
|
||
printf '%s\t%s\n' "$(printf '%s' "$_d" | cut -f1,2)" "$_src"
|
||
return 0
|
||
fi
|
||
printf '%s\t%s\tbuiltin\n' "$BUILTIN_LAYOUT" "$BUILTIN_STYLE"
|
||
}
|
||
|
||
# 範本檔第一行註解就是繁中說明:版型放在 <!-- 說明 -->,風格放在 /* 說明 */。
|
||
describe() { # $1=檔案
|
||
head -n1 "$1" 2>/dev/null | sed 's/<!--[[:space:]]*//; s/[[:space:]]*-->//; s|/\*[[:space:]]*||; s|[[:space:]]*\*/||'
|
||
}
|
||
|
||
list_layouts() {
|
||
for f in "$TPL"/layout/*.html; do
|
||
[ -f "$f" ] || continue
|
||
name=$(basename "$f" .html)
|
||
printf '%s\t%s\n' "$name" "$(describe "$f")"
|
||
done
|
||
}
|
||
|
||
list_styles() {
|
||
for f in "$TPL"/style/*.css; do
|
||
[ -f "$f" ] || continue
|
||
name=$(basename "$f" .css)
|
||
printf '%s\t%s\n' "$name" "$(describe "$f")"
|
||
done
|
||
}
|
||
|
||
write_kv() { # $1=檔案 $2=種類 $3=值
|
||
dir=$(dirname "$1")
|
||
mkdir -p "$dir" || { echo "[jsc][HTML 設定][ERR]:建不出目錄「$dir」。" >&2; exit 1; }
|
||
tmp="$1.tmp.$$"
|
||
{ [ -f "$1" ] && grep -v "^[[:space:]]*$2[[:space:]]*=" "$1" || true; } > "$tmp"
|
||
[ -n "$3" ] && printf '%s=%s\n' "$2" "$3" >> "$tmp"
|
||
mv "$tmp" "$1"
|
||
}
|
||
|
||
cmd="${1:-}"; [ -n "$cmd" ] || usage
|
||
shift || true
|
||
|
||
case "$cmd" in
|
||
layouts) list_layouts; exit 0 ;;
|
||
styles) list_styles; exit 0 ;;
|
||
list)
|
||
keys=$( { [ -f "$PROJECT_FILE" ] && cut -d= -f1 "$PROJECT_FILE" || true
|
||
[ -f "$GLOBAL_FILE" ] && cut -d= -f1 "$GLOBAL_FILE" || true; } \
|
||
| sed 's/^[[:space:]]*//; s/[[:space:]]*$//' | grep -v '^#' | grep -v '^$' | sort -u)
|
||
[ -n "$keys" ] || { printf 'DEFAULT\t%s\t%s\tbuiltin\n' "$BUILTIN_LAYOUT" "$BUILTIN_STYLE"; exit 0; }
|
||
printf '%s\n' "$keys" | while IFS= read -r k; do
|
||
printf '%s\t%s\n' "$k" "$(resolve "$k")"
|
||
done
|
||
exit 0 ;;
|
||
get)
|
||
key="${1:-}"; [ -n "$key" ] || usage
|
||
resolve "$key"
|
||
exit 0 ;;
|
||
key)
|
||
kind="${1:-}"; [ -n "$kind" ] || usage
|
||
shift
|
||
case "$kind" in
|
||
wiki)
|
||
page="${1:-}"; [ -n "$page" ] || usage
|
||
# 頁名前綴 = 第一個底線之前那一段;沒有底線就是整個頁名。
|
||
prefix=${page%%_*}
|
||
printf 'WIKI:%s\tprefix:%s\n' "$prefix" "$prefix"
|
||
exit 0 ;;
|
||
issue)
|
||
repo="${1:-}"; idx="${2:-}"
|
||
[ -n "$repo" ] && [ -n "$idx" ] || usage
|
||
shift 2
|
||
labels=''
|
||
have_labels=0
|
||
case "${1:-}" in
|
||
--labels) [ "$#" -ge 2 ] || usage; labels="$2"; have_labels=1 ;;
|
||
'') ;;
|
||
*) usage ;;
|
||
esac
|
||
if [ "$have_labels" = 0 ]; then
|
||
raw=$("$script_dir/issue.sh" labels-of "$repo" "$idx") || {
|
||
echo "[jsc][HTML 設定][ERR]:取不到議題 $repo#$idx 的標籤。" >&2; exit 1; }
|
||
labels=$(printf '%s' "$raw" | tr '\n' ',')
|
||
fi
|
||
matched=''
|
||
old_ifs=$IFS
|
||
IFS=','
|
||
for l in $labels; do
|
||
[ -n "$l" ] || continue
|
||
# 標籤順序就是優先序:第一個使用者真的設過的標籤勝出,
|
||
# 撿來的 DEFAULT 不算數,否則第一個標籤永遠贏,後面的設定形同虛設。
|
||
case "$(resolve "ISSUE:$l" | cut -f3)" in
|
||
project|global) matched="$l"; break ;;
|
||
esac
|
||
done
|
||
IFS=$old_ifs
|
||
if [ -n "$matched" ]; then
|
||
printf 'ISSUE:%s\tlabel:%s\n' "$matched" "$matched"
|
||
elif [ -z "$(printf '%s' "$labels" | tr -d ',')" ]; then
|
||
printf 'ISSUE:DEFAULT\tfallback:no-labels\n'
|
||
else
|
||
printf 'ISSUE:DEFAULT\tfallback:no-configured-label\n'
|
||
fi
|
||
exit 0 ;;
|
||
*) usage ;;
|
||
esac ;;
|
||
set)
|
||
key="${1:-}"; layout="${2:-}"; style="${3:-}"
|
||
[ -n "$key" ] && [ -n "$layout" ] && [ -n "$style" ] || usage
|
||
shift 3
|
||
target="$GLOBAL_FILE"; scope=global
|
||
case "${1:-}" in
|
||
--project) target="$PROJECT_FILE"; scope=project ;;
|
||
--global|'') ;;
|
||
*) usage ;;
|
||
esac
|
||
[ -f "$TPL/layout/$layout.html" ] || {
|
||
echo "[jsc][HTML 設定][ERR]:沒有版型「$layout」。可用:$(list_layouts | cut -f1 | tr '\n' ' ')" >&2; exit 4; }
|
||
[ -f "$TPL/style/$style.css" ] || {
|
||
echo "[jsc][HTML 設定][ERR]:沒有風格「$style」。可用:$(list_styles | cut -f1 | tr '\n' ' ')" >&2; exit 4; }
|
||
write_kv "$target" "$key" "$layout,$style"
|
||
printf '已寫入 %s:%s=%s,%s(%s)\n' "$target" "$key" "$layout" "$style" "$scope"
|
||
exit 0 ;;
|
||
unset)
|
||
key="${1:-}"; [ -n "$key" ] || usage
|
||
shift
|
||
target="$GLOBAL_FILE"
|
||
case "${1:-}" in
|
||
--project) target="$PROJECT_FILE" ;;
|
||
--global|'') ;;
|
||
*) usage ;;
|
||
esac
|
||
[ -f "$target" ] || { echo "找不到設定檔:$target" >&2; exit 0; }
|
||
write_kv "$target" "$key" ''
|
||
printf '已移除 %s 的 %s\n' "$target" "$key"
|
||
exit 0 ;;
|
||
*) usage ;;
|
||
esac
|