feat(wiki): 目錄頁專用存取庫、HASH 去除截短與 H 前綴 #47
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "jsc-gitea",
|
"name": "jsc-gitea",
|
||||||
"version": "0.2.1",
|
"version": "0.2.2",
|
||||||
"description": "Gitea API 工具、Wiki 讀寫、議題轉換、HTML 匯出與存取庫批次同步",
|
"description": "Gitea API 工具、Wiki 讀寫、議題轉換、HTML 匯出與存取庫批次同步",
|
||||||
"skills": "./skills",
|
"skills": "./skills",
|
||||||
"author": {
|
"author": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "jsc-gitea",
|
"name": "jsc-gitea",
|
||||||
"version": "0.2.1",
|
"version": "0.2.2",
|
||||||
"description": "Gitea API 工具、Wiki 讀寫、議題轉換、HTML 匯出與存取庫批次同步",
|
"description": "Gitea API 工具、Wiki 讀寫、議題轉換、HTML 匯出與存取庫批次同步",
|
||||||
"skills": "./skills",
|
"skills": "./skills",
|
||||||
"jsc": {
|
"jsc": {
|
||||||
|
|||||||
@@ -27,11 +27,13 @@ gitea.sh owners # 列出可讀取的 owner
|
|||||||
gitea.sh repos <owner> # 列出 owner 的 repo 全名
|
gitea.sh repos <owner> # 列出 owner 的 repo 全名
|
||||||
gitea.sh default-branch <owner>/<repo>
|
gitea.sh default-branch <owner>/<repo>
|
||||||
gitea.sh clone-url <owner>/<repo>
|
gitea.sh clone-url <owner>/<repo>
|
||||||
gitea.sh hash-id <text> # 產生 8 碼大寫 SHA-1;首碼 0-9/A/B/C 時改成 Hxxxxxxx
|
gitea.sh hash-id <text> # 產生完整 40 碼大寫 SHA-1
|
||||||
gitea.sh wiki-repo <TYPE> # 解析頁面類型的 wiki 位置(TYPE = QUESTION / PLAN / ANALYZE / DELIVER / MAINTAIN / REPO / LOG / LEARN / ERROR / CHECK / REPORT / SKILLSET / TOOLING / MONITOR)
|
gitea.sh wiki-repo <TYPE> # 解析頁面類型的 wiki 位置(TYPE = QUESTION / PLAN / ANALYZE / DELIVER / MAINTAIN / REPO / LOG / LEARN / ERROR / CHECK / REPORT / SKILLSET / TOOLING / MONITOR / CONTENTS)
|
||||||
|
# 所有 *_CONTENTS 頁一律走 CONTENTS,內容頁走自己的型別
|
||||||
gitea.sh wiki-list <owner>/<repo>
|
gitea.sh wiki-list <owner>/<repo>
|
||||||
gitea.sh wiki-get <owner>/<repo> <page> # 不存在 exit 4
|
gitea.sh wiki-get <owner>/<repo> <page> # 不存在 exit 4
|
||||||
gitea.sh wiki-put <owner>/<repo> <page> <file> # 自動判斷新建或更新;會先要求確認
|
gitea.sh wiki-put <owner>/<repo> <page> <file> # 自動判斷新建或更新;會先要求確認
|
||||||
|
gitea.sh wiki-delete <owner>/<repo> <page> # 刪除 wiki 頁;與 wiki-put 走同一道確認,頁不存在 exit 4
|
||||||
gitea.sh wiki-url <owner>/<repo> <page> # 印出 wiki 頁絕對網址(取自 API 的 html_url);跨存取庫連結用
|
gitea.sh wiki-url <owner>/<repo> <page> # 印出 wiki 頁絕對網址(取自 API 的 html_url);跨存取庫連結用
|
||||||
gitea.sh pr-create <owner>/<repo> <head> <base> <title> <body-file> # 會先要求確認
|
gitea.sh pr-create <owner>/<repo> <head> <base> <title> <body-file> # 會先要求確認
|
||||||
gitea.sh pr-status <owner>/<repo> <pr-index> # 印出 {state} {merged} {mergeable}
|
gitea.sh pr-status <owner>/<repo> <pr-index> # 印出 {state} {merged} {mergeable}
|
||||||
@@ -57,9 +59,27 @@ gitea.sh api <METHOD> <path> [json-file]
|
|||||||
# 7 與 8 一定要跟 4 分開:認證失敗若被讀成「頁面不存在」,
|
# 7 與 8 一定要跟 4 分開:認證失敗若被讀成「頁面不存在」,
|
||||||
# 附加寫入就會變成整頁覆蓋,舊紀錄直接消失
|
# 附加寫入就會變成整頁覆蓋,舊紀錄直接消失
|
||||||
hash-id <text> # 與 gitea.sh hash-id 相同
|
hash-id <text> # 與 gitea.sh hash-id 相同
|
||||||
|
# 結束碼 0=成功、1=沒有 sha1sum 也沒有 shasum、2=沒給文字或給了空字串
|
||||||
|
page-name.sh regex | check <page> # 頁名樣式的唯一正本
|
||||||
|
# 合法頁名 {型別}_(CONTENTS|8 碼|H 加 7 碼|40 碼大寫十六進位)
|
||||||
|
# 8 碼與 H 加 7 碼留給尚未遷移的舊頁;舊演算法多數情況會加 H,兩種都要收
|
||||||
|
# 前綴只收十四種內容型別。CONTENTS 只解存取庫,沒有 CONTENTS_CONTENTS 這一頁
|
||||||
|
# 結束碼 0=合法、1=不合法、2=用法錯誤
|
||||||
|
wiki-contents.sh upsert <TYPE> <key-col> <key> <row-file> [template-file]
|
||||||
|
# 目錄頁的整列 upsert:解 CONTENTS 存取庫、讀舊頁、換掉鍵相同那一列或附加到表尾、整頁寫回
|
||||||
|
# key-col 是 1 起算的欄位序號,不是欄位名稱
|
||||||
|
# 用範本建新頁時剝掉分隔列之後的示範列,正式頁上不留佔位的死連結
|
||||||
|
# 結束碼 0=已更新或已新增、1=寫入失敗、2=用法錯誤、3=CONTENTS 存取庫未設定
|
||||||
|
# 4=頁不存在且沒給範本、7=金鑰失效或權限不足、8=其他 API 失敗
|
||||||
|
# 只有 4 才准建新頁;7 與 8 一律中止,不得當成「頁面不存在」
|
||||||
|
migrate-wiki.sh [--apply] [--key <候選鍵>]... # 把舊頁搬到新規則:目錄頁換存取庫、內容頁換頁名
|
||||||
|
# 只做正推配對,配不上的一律列成孤兒,不猜;不帶 --apply 只印對照表
|
||||||
|
# 寫目的地之前先讀:只有 4 才准寫,0 列成需人工確認且不覆蓋,7 與 8 中止
|
||||||
|
# 連結改寫只動被搬的頁;沒被搬的引用方另列一節,交給人改
|
||||||
|
# 結束碼 0=全部搬完、1=有頁搬移失敗、2=用法錯誤、3=有需人工處理的項目
|
||||||
repo-sync.sh <owner>/<repo> [target-dir] # 同步單一存取庫;印出 cloned、updated、dirty {分支} 或 failed {原因}
|
repo-sync.sh <owner>/<repo> [target-dir] # 同步單一存取庫;印出 cloned、updated、dirty {分支} 或 failed {原因}
|
||||||
# 基準分支的優先序只在這支腳本裡;dirty 會把解析好的分支帶出來當 PR 的 base
|
# 基準分支的優先序只在這支腳本裡;dirty 會把解析好的分支帶出來當 PR 的 base
|
||||||
check-wiki-rules.sh # 驗證 wiki repo 解析與 hash fallback 規則
|
check-wiki-rules.sh # 驗證 wiki repo 解析、hash-id 與頁名樣式規則
|
||||||
pr-watch.sh <owner>/<repo> <pr-index> [state-file]
|
pr-watch.sh <owner>/<repo> <pr-index> [state-file]
|
||||||
# 盯著一支 PR,直到它合併或關閉,不自動逾時退場
|
# 盯著一支 PR,直到它合併或關閉,不自動逾時退場
|
||||||
# 退出碼 0 = 已合併或關閉、10 = 有新留言要接手、2 = 參數錯誤、3 = 查不到該 PR
|
# 退出碼 0 = 已合併或關閉、10 = 有新留言要接手、2 = 參數錯誤、3 = 查不到該 PR
|
||||||
@@ -87,7 +107,7 @@ html-render.sh --markdown <檔案> --title <標題> --out <輸出檔> [--layout]
|
|||||||
|
|
||||||
## 參考資料
|
## 參考資料
|
||||||
|
|
||||||
- `references/wiki-links.md`:寫 wiki 頁才需要的連結規則。同類型用 `[[顯示文字|頁名]]`(顯示文字在左),跨類型用 `wiki-url` 給的絕對網址。
|
- `references/wiki-links.md`:寫 wiki 頁才需要的連結規則。目錄頁之間、同型別的內容頁之間用 `[[顯示文字|頁名]]`(顯示文字在左);目錄頁與內容頁之間以及跨型別,一律用 `wiki-url` 給的絕對網址。
|
||||||
|
|
||||||
## HTML 範本
|
## HTML 範本
|
||||||
|
|
||||||
@@ -122,7 +142,7 @@ html-render.sh --markdown <檔案> --title <標題> --out <輸出檔> [--layout]
|
|||||||
|
|
||||||
### `wiki`
|
### `wiki`
|
||||||
|
|
||||||
Gitea wiki 頁讀寫的統一入口:依頁面類型(QUESTION / PLAN / ANALYZE / DELIVER / MAINTAIN / REPO / LOG / LEARN / ERROR / CHECK / REPORT / SKILLSET / TOOLING / MONITOR)解析 wiki 所在的 `{owner}/{repo}`,先讀對應的 `JSC_WIKI_REPO_{TYPE}`,再退回 `JSC_WIKI_REPO`,不同類型不可互相代用。頁面內容以圖表優先(mermaid 圖、markdown 表格),純文字每節最多三句。
|
Gitea wiki 頁讀寫的統一入口:依頁面類型(QUESTION / PLAN / ANALYZE / DELIVER / MAINTAIN / REPO / LOG / LEARN / ERROR / CHECK / REPORT / SKILLSET / TOOLING / MONITOR / CONTENTS)解析 wiki 所在的 `{owner}/{repo}`,先讀對應的 `JSC_WIKI_REPO_{TYPE}`,再退回 `JSC_WIKI_REPO`,不同類型不可互相代用。所有 `*_CONTENTS` 頁一律走 `CONTENTS` 這個型別,內容頁走自己的型別;目錄頁的整列 upsert 交給 `tools/wiki-contents.sh`。頁面內容以圖表優先(mermaid 圖、markdown 表格),純文字每節最多三句。
|
||||||
|
|
||||||
### `repo-sync`
|
### `repo-sync`
|
||||||
|
|
||||||
@@ -148,17 +168,18 @@ Gitea wiki 頁讀寫的統一入口:依頁面類型(QUESTION / PLAN / ANALYZ
|
|||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `GITEA_HOST` | Gitea 站台(可省略 scheme,預設 https) | 詢問使用者 |
|
| `GITEA_HOST` | Gitea 站台(可省略 scheme,預設 https) | 詢問使用者 |
|
||||||
| `GITEA_TOKEN` | Gitea API token;缺少或遇 401/403 時自動退回 tea CLI 登入 token | 詢問使用者 |
|
| `GITEA_TOKEN` | Gitea API token;缺少或遇 401/403 時自動退回 tea CLI 登入 token | 詢問使用者 |
|
||||||
| `JSC_WIKI_REPO_{TYPE}` | 各類型 wiki 頁的 `{owner}/{repo}`;TYPE = QUESTION / PLAN / ANALYZE / DELIVER / MAINTAIN / REPO / LOG / LEARN / ERROR / CHECK / REPORT | 退回 `JSC_WIKI_REPO`,不做跨類型代用 |
|
| `JSC_WIKI_REPO_{TYPE}` | 各類型內容頁的 `{owner}/{repo}`;TYPE = QUESTION / PLAN / ANALYZE / DELIVER / MAINTAIN / REPO / LOG / LEARN / ERROR / CHECK / REPORT | 退回 `JSC_WIKI_REPO`,不做跨類型代用 |
|
||||||
| `JSC_WIKI_REPO_SKILLSET` | `SKILLSET_CONTENTS`、`SKILLSET_{HASH}`(技能組異動報告)的 `{owner}/{repo}` | 退回 `JSC_WIKI_REPO`,不做跨類型代用 |
|
| `JSC_WIKI_REPO_SKILLSET` | `SKILLSET_{HASH}`(技能組異動報告)的 `{owner}/{repo}` | 退回 `JSC_WIKI_REPO`,不做跨類型代用 |
|
||||||
| `JSC_WIKI_REPO_TOOLING` | `TOOLING_CONTENTS`、`TOOLING_{HASH}`(技能盤點)的 `{owner}/{repo}` | 退回 `JSC_WIKI_REPO`,不做跨類型代用 |
|
| `JSC_WIKI_REPO_TOOLING` | `TOOLING_{HASH}`(技能盤點)的 `{owner}/{repo}` | 退回 `JSC_WIKI_REPO`,不做跨類型代用 |
|
||||||
| `JSC_WIKI_REPO_MONITOR` | `MONITOR_CONTENTS`、`MONITOR_{HASH}`(助理巡檢監控)的 `{owner}/{repo}` | 退回 `JSC_WIKI_REPO`,不做跨類型代用 |
|
| `JSC_WIKI_REPO_MONITOR` | `MONITOR_{HASH}`(助理巡檢監控)的 `{owner}/{repo}` | 退回 `JSC_WIKI_REPO`,不做跨類型代用 |
|
||||||
|
| `JSC_WIKI_REPO_CONTENTS` | 全部目錄頁(各型別的 `*_CONTENTS`)共用的 `{owner}/{repo}` | 退回 `JSC_WIKI_REPO`,不退回各型別的變數 |
|
||||||
| `JSC_WIKI_REPO` | 共用預設的 wiki `{owner}/{repo}` | 詢問使用者 |
|
| `JSC_WIKI_REPO` | 共用預設的 wiki `{owner}/{repo}` | 詢問使用者 |
|
||||||
| `JSC_HOME` | `pr-watch.sh` 狀態檔的根目錄 | 預設 `~/.jsc` |
|
| `JSC_HOME` | `pr-watch.sh` 狀態檔的根目錄 | 預設 `~/.jsc` |
|
||||||
| `JSC_PR_WATCH_INTERVAL` | `pr-watch.sh` 的輪詢間隔秒數(正整數) | 預設 60 |
|
| `JSC_PR_WATCH_INTERVAL` | `pr-watch.sh` 的輪詢間隔秒數(正整數) | 預設 60 |
|
||||||
|
|
||||||
## Hash 規則
|
## Hash 規則
|
||||||
|
|
||||||
`{HASH}` 一律由 `tools/hash-id` 產生。長度、大小寫、`H` 前綴與各頁型的雜湊來源,唯一來源是 `jsc-meta` 的 [`references/guidelines.md`](https://gitea.jsc.idv.tw/plugins/meta/src/branch/master/references/guidelines.md)「Wiki 頁命名總表」,這裡不再複述一份。
|
`{HASH}` 一律由 `tools/hash-id` 產生:完整 40 碼大寫十六進位,不截短、不加前綴。頁名合不合規則交給 `tools/page-name.sh check` 判,不要另外寫一份樣式。各頁型的雜湊來源,唯一來源是 `jsc-meta` 的 [`references/guidelines.md`](https://gitea.jsc.idv.tw/plugins/meta/src/branch/master/references/guidelines.md)「Wiki 頁命名總表」,這裡不再複述一份。
|
||||||
|
|
||||||
## 相關 domain
|
## 相關 domain
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "jsc-gitea",
|
"name": "jsc-gitea",
|
||||||
"version": "0.2.1",
|
"version": "0.2.2",
|
||||||
"description": "Gitea API 工具、Wiki 讀寫、議題轉換、HTML 匯出與存取庫批次同步",
|
"description": "Gitea API 工具、Wiki 讀寫、議題轉換、HTML 匯出與存取庫批次同步",
|
||||||
"skills": "./skills/",
|
"skills": "./skills/",
|
||||||
"jsc": {
|
"jsc": {
|
||||||
|
|||||||
@@ -36,11 +36,11 @@
|
|||||||
|
|
||||||
| 項目 | 內容 |
|
| 項目 | 內容 |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| 觸發時機 | 技能組裡任何一次 wiki 頁的讀或寫,都經過這一支。呼叫端有 jsc-ask、jsc-sdlc、jsc-log、jsc-hooks 的 ERROR 頁、jsc-cli 的 CHECK 頁、jsc-meta 的 SKILLSET 與 TOOLING 頁、jsc-assist 的 MONITOR 頁。存放庫裡的程式碼檔案不歸這一支管。 |
|
| 觸發時機 | 技能組裡任何一次 wiki 頁的讀、寫、刪或搬移,都經過這一支。呼叫端有 jsc-ask、jsc-sdlc、jsc-log、jsc-hooks 的 ERROR 頁、jsc-cli 的 CHECK 頁、jsc-meta 的 SKILLSET 與 TOOLING 頁、jsc-assist 的 MONITOR 頁。存放庫裡的程式碼檔案不歸這一支管。 |
|
||||||
| 關鍵步驟 | 確認 GITEA_HOST 有值、用 tools/gitea.sh wiki-repo {TYPE} 依頁名前綴解出 {owner}/{repo}、用 tools/hash-id 算頁名要用的 HASH、再依動作跑 wiki-list、wiki-get、wiki-put 或 wiki-url、每一次呼叫都照結束碼表分流。存放庫的解法是先讀 JSC_WIKI_REPO_{TYPE}、再讀 JSC_WIKI_REPO、兩個都沒有才問使用者,而且不借用別的頁型的存放庫。寫入前一定先 wiki-get 讀回舊內容,把新內容接上去再整頁寫回;只有結束碼 4 才准用範本建新頁。 |
|
| 關鍵步驟 | 確認 GITEA_HOST 有值、用 tools/gitea.sh wiki-repo {TYPE} 解出 {owner}/{repo}、用 tools/hash-id 算頁名要用的 HASH、用 tools/page-name.sh check 驗過頁名、再依動作跑 wiki-list、wiki-get、wiki-put、wiki-delete 或 wiki-url,每一次呼叫都照結束碼表分流。所有 *_CONTENTS 頁一律走型別 CONTENTS,內容頁走自己的型別;存放庫的解法是先讀 JSC_WIKI_REPO_{TYPE}、再讀 JSC_WIKI_REPO、兩個都沒有才問使用者,而且不借用別的頁型的存放庫。目錄頁的整列 upsert 交給 tools/wiki-contents.sh,寫入前一定先 wiki-get 讀回舊內容,只有結束碼 4 才准用範本建新頁;舊頁搬到新規則走 tools/migrate-wiki.sh,不帶 --apply 只印對照表,寫每一個目的地之前也一樣先 wiki-get 讀一次。 |
|
||||||
| 外部呼叫 | tools/gitea.sh 的 wiki-repo、wiki-list、wiki-get、wiki-put、wiki-url、tools/hash-id、tools/write-confirm.sh、jsc-ask:ask、Gitea 的 wiki API。 |
|
| 外部呼叫 | tools/gitea.sh 的 wiki-repo、wiki-list、wiki-get、wiki-put、wiki-delete、wiki-url、tools/hash-id、tools/page-name.sh、tools/wiki-contents.sh、tools/migrate-wiki.sh、tools/write-confirm.sh、jsc-ask:ask、Gitea 的 wiki API。 |
|
||||||
| 完成條件 | 讀取動作拿到頁面內容,或拿到一個講得清楚的結束碼。寫入動作通過人工確認、wiki-put 回結束碼 0,而且送出去的是舊內容加上這次的異動,不是整頁覆蓋。結束碼 7 與 8 一律中止整個動作,不建頁、不寫入、不用原參數重試。 |
|
| 完成條件 | 讀取動作拿到頁面內容,或拿到一個講得清楚的結束碼。寫入動作通過人工確認、wiki-put 回結束碼 0,而且送出去的是舊內容加上這次的異動,不是整頁覆蓋。結束碼 7 與 8 一律中止整個動作,不建頁、不寫入、不用原參數重試。搬移動作要嘛全部搬完回 0,要嘛把失敗頁、孤兒頁、目的地已有內容的頁、指向被搬頁卻沒被搬的引用方逐條列出來,這四種一律交給人判斷。 |
|
||||||
| 可驗證跡象 | 目標 wiki 存放庫多一頁或改一頁,頁名照命名表,例如 PLAN_{HASH}、LOG_{HASH}、各種 *_CONTENTS。頁面內容是 UTF-8 繁體中文,以 mermaid 圖與 markdown 表格為主,散文每節最多三句。寫入前 tools/write-confirm.sh 會留下一次人工確認。只做讀取的呼叫無寫入跡象,只有回報內容。 |
|
| 可驗證跡象 | 目標 wiki 存放庫多一頁或改一頁。內容頁的頁名是 {型別}_{40 碼大寫十六進位},目錄頁是 {型別}_CONTENTS 且落在 JSC_WIKI_REPO_CONTENTS 指的那個存放庫。頁面內容是 UTF-8 繁體中文,以 mermaid 圖與 markdown 表格為主,散文每節最多三句;目錄頁與內容頁互指的連結是絕對網址,不是 [[...]]。寫入與刪除前 tools/write-confirm.sh 會各留下一次人工確認。只做讀取的呼叫無寫入跡象,只有回報內容。 |
|
||||||
|
|
||||||
## wiki-to-issue
|
## wiki-to-issue
|
||||||
|
|
||||||
|
|||||||
@@ -8,17 +8,20 @@ Gitea 採 GitHub/Gollum 慣例:`[[顯示文字|頁名]]`。方向與 MediaWi
|
|||||||
|
|
||||||
> MediaWiki uses [[link|text]], while GitHub uses [[text|link]] … we prefer GitHub syntax
|
> MediaWiki uses [[link|text]], while GitHub uses [[text|link]] … we prefer GitHub syntax
|
||||||
|
|
||||||
所以 `[[PLAN_H1234567|我的計畫]]` 會顯示成文字 `PLAN_H1234567`,連到一個叫「我的計畫」的頁——這是壞連結。要寫 `[[我的計畫|PLAN_H1234567]]`。
|
所以 `[[PLAN_CONTENTS|我的計畫]]` 會顯示成文字 `PLAN_CONTENTS`,連到一個叫「我的計畫」的頁——這是壞連結。要寫 `[[我的計畫|PLAN_CONTENTS]]`。
|
||||||
|
|
||||||
顯示文字與頁名相同時,用不帶豎線的 `[[PLAN_H1234567]]`,這種寫法不會寫錯。
|
顯示文字與頁名相同時,用不帶豎線的 `[[PLAN_CONTENTS]]`,這種寫法不會寫錯。
|
||||||
|
|
||||||
## 範圍:`[[...]]` 只在同一個 wiki 內解析
|
## 範圍:`[[...]]` 只在同一個 wiki 內解析
|
||||||
|
|
||||||
`[[...]]` 與 markdown 相對連結都只在目前這個 wiki 內解析。跨存取庫沒有 wiki 連結語法。
|
`[[...]]` 與 markdown 相對連結都只在目前這個 wiki 內解析。跨存取庫沒有 wiki 連結語法。
|
||||||
|
|
||||||
|
目錄頁全部住在 CONTENTS 專用存取庫,內容頁住在自己型別的存取庫。所以「同型別」不再等於「同存取庫」,判斷要看兩端各自解析到哪一個存取庫。
|
||||||
|
|
||||||
| 連結 | 同一個 wiki? | 寫法 |
|
| 連結 | 同一個 wiki? | 寫法 |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| 同頁面類型(例:`PLAN_CONTENTS` → `PLAN_{HASH}`) | 一定同一個:一個類型一個存取庫 | `[[顯示文字\|頁名]]` 或 `[[頁名]]` |
|
| 目錄頁之間(例:`PLAN_CONTENTS` → `LOG_CONTENTS`) | 一定同一個:目錄頁都在 CONTENTS 存取庫 | `[[顯示文字\|頁名]]` 或 `[[頁名]]` |
|
||||||
| 不同頁面類型(例:`LOG_{HASH}` → `PLAN_{HASH}`) | **只有兩個類型解析到同一個存取庫時才同一個** | `wiki-url` 給的絕對網址:`[顯示文字](https://…/wiki/PLAN_…)` |
|
| 同型別的內容頁之間(例:`PLAN_{HASH}` → 另一頁 `PLAN_{HASH}`) | 一定同一個:一個型別一個存取庫 | `[[顯示文字\|頁名]]` 或 `[[頁名]]` |
|
||||||
|
| 目錄頁與內容頁之間,以及跨型別(例:`PLAN_CONTENTS` → `PLAN_{HASH}`、`LOG_{HASH}` → `PLAN_{HASH}`) | **不保證**:兩端各自解析,可能不同 | `wiki-url` 給的絕對網址:`[顯示文字](https://…/wiki/PLAN_…)` |
|
||||||
|
|
||||||
每個類型各自解析自己的 `JSC_WIKI_REPO_{TYPE}`,所以跨類型連結**一律**用絕對網址:兩個類型剛好同存取庫也照樣正確,不必分兩種寫法。網址一律取自 `tools/gitea.sh wiki-url`,不要自己組路徑。
|
目錄頁解 `JSC_WIKI_REPO_CONTENTS`,內容頁解自己的 `JSC_WIKI_REPO_{TYPE}`,兩端各解各的。只設 `JSC_WIKI_REPO` 時兩端會落在同一個存取庫,多設一個型別變數就分開了。所以這兩種連結**一律**用絕對網址:兩邊剛好同存取庫也照樣正確,不必分兩種寫法,也不必跟著環境變數改寫法。網址一律取自 `tools/gitea.sh wiki-url`,不要自己組路徑。
|
||||||
|
|||||||
+38
-8
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: wiki
|
name: wiki
|
||||||
description: Read or write a Gitea wiki page through tools/gitea.sh and tools/hash-id. Resolve the wiki repo per page type with JSC_WIKI_REPO_{TYPE} first, then JSC_WIKI_REPO, and ask only when neither is set. Page content is chart-first - prefer mermaid diagrams and markdown tables over plain prose. Callers are jsc-ask, jsc-sdlc, jsc-log, jsc-hooks (ERROR), jsc-cli (CHECK), jsc-meta (SKILLSET, TOOLING) and jsc-assist (MONITOR). Use for any wiki page in the skill set; not for repo code files.
|
description: Read or write a Gitea wiki page through tools/gitea.sh, tools/hash-id and tools/page-name.sh. Resolve the wiki repo per page type with JSC_WIKI_REPO_{TYPE} first, then JSC_WIKI_REPO, and ask only when neither is set - every *_CONTENTS page resolves through type CONTENTS and is updated with tools/wiki-contents.sh, and {HASH} is the full 40-char uppercase SHA-1 from tools/hash-id. Page content is chart-first - prefer mermaid diagrams and markdown tables over plain prose. Callers are jsc-ask, jsc-sdlc, jsc-log, jsc-hooks (ERROR), jsc-cli (CHECK), jsc-meta (SKILLSET, TOOLING) and jsc-assist (MONITOR). Use for any wiki page in the skill set; not for repo code files.
|
||||||
---
|
---
|
||||||
|
|
||||||
# wiki — read and write Gitea wiki pages
|
# wiki — read and write Gitea wiki pages
|
||||||
@@ -12,8 +12,8 @@ Every wiki operation in the jsc skill set goes through this skill. One entry poi
|
|||||||
Different page types can live in different `{owner}/{repo}` repos, classified by the page-name prefix.
|
Different page types can live in different `{owner}/{repo}` repos, classified by the page-name prefix.
|
||||||
|
|
||||||
1. **Host gate.** Confirm `GITEA_HOST` holds a value in the current shell. When it is missing, ask for it per the `jsc-ask:ask` rules before any `tools/gitea.sh` call that reaches the API; otherwise the first thing the user sees is the script's `GITEA_HOST is required` line instead of a decision-tree question. `GITEA_TOKEN` needs no inventory here — the script resolves it, retries once with the tea CLI login token, and exits 7 when neither works. Done when `GITEA_HOST` holds a value.
|
1. **Host gate.** Confirm `GITEA_HOST` holds a value in the current shell. When it is missing, ask for it per the `jsc-ask:ask` rules before any `tools/gitea.sh` call that reaches the API; otherwise the first thing the user sees is the script's `GITEA_HOST is required` line instead of a decision-tree question. `GITEA_TOKEN` needs no inventory here — the script resolves it, retries once with the tea CLI login token, and exits 7 when neither works. Done when `GITEA_HOST` holds a value.
|
||||||
2. Run `tools/gitea.sh wiki-repo {TYPE}` (TYPE = the page-name prefix). Allowed types are `QUESTION`, `PLAN`, `ANALYZE`, `DELIVER`, `MAINTAIN`, `REPO`, `LOG`, `LEARN`, `ERROR`, `CHECK`, `REPORT`, `SKILLSET`, `TOOLING`, and `MONITOR`. The script reads `JSC_WIKI_REPO_{TYPE}` first and `JSC_WIKI_REPO` second, straight from the inherited environment, so take no separate inventory of those two variables. Never borrow another type's repo. Done when the command has printed exactly one `{owner}/{repo}`, or exited 3 and sent this page type to step 3, or exited 2 on a type outside the list above and stopped the run.
|
2. Run `tools/gitea.sh wiki-repo {TYPE}`. Allowed types are `QUESTION`, `PLAN`, `ANALYZE`, `DELIVER`, `MAINTAIN`, `REPO`, `LOG`, `LEARN`, `ERROR`, `CHECK`, `REPORT`, `SKILLSET`, `TOOLING`, `MONITOR`, and `CONTENTS`. **Every `*_CONTENTS` page resolves through `CONTENTS` — all contents pages live in one dedicated repo, so never pass a contents page its own prefix. A content page (`*_{HASH}`) resolves through its own type.** The script reads `JSC_WIKI_REPO_{TYPE}` first and `JSC_WIKI_REPO` second, straight from the inherited environment, so take no separate inventory of those two variables. Never borrow another type's repo. Done when the command has printed exactly one `{owner}/{repo}`, or exited 3 and sent this page type to step 3, or exited 2 on a type outside the list above and stopped the run.
|
||||||
3. On exit 3 (neither variable is set), ask the user for that page type's `{owner}/{repo}` per the `jsc-ask:ask` rules, and suggest setting `JSC_WIKI_REPO_{TYPE}` (can differ per type) or `JSC_WIKI_REPO` (shared default). Done when the user has supplied one `{owner}/{repo}` for that page type.
|
3. On exit 3 (neither variable is set), ask the user for that page type's `{owner}/{repo}` per the `jsc-ask:ask` rules, and suggest setting `JSC_WIKI_REPO_{TYPE}` (can differ per type, and `JSC_WIKI_REPO_CONTENTS` holds every contents page) or `JSC_WIKI_REPO` (shared default). Done when the user has supplied one `{owner}/{repo}` for that page type.
|
||||||
|
|
||||||
## Operations
|
## Operations
|
||||||
|
|
||||||
@@ -22,9 +22,13 @@ Different page types can live in different `{owner}/{repo}` repos, classified by
|
|||||||
| list pages | `tools/gitea.sh wiki-list {owner}/{repo}` |
|
| list pages | `tools/gitea.sh wiki-list {owner}/{repo}` |
|
||||||
| read page | `tools/gitea.sh wiki-get {owner}/{repo} {page}` |
|
| read page | `tools/gitea.sh wiki-get {owner}/{repo} {page}` |
|
||||||
| write page | write the content to a temp file first, then `tools/gitea.sh wiki-put {owner}/{repo} {page} {file}` (asks for confirmation first, then creates or updates) |
|
| write page | write the content to a temp file first, then `tools/gitea.sh wiki-put {owner}/{repo} {page} {file}` (asks for confirmation first, then creates or updates) |
|
||||||
|
| delete page | `tools/gitea.sh wiki-delete {owner}/{repo} {page}` — asks for the same confirmation as a write. Only a migration or an explicit user request may call it |
|
||||||
| page URL | `tools/gitea.sh wiki-url {owner}/{repo} {page}` — the page's absolute URL, taken from the API's `html_url` |
|
| page URL | `tools/gitea.sh wiki-url {owner}/{repo} {page}` — the page's absolute URL, taken from the API's `html_url` |
|
||||||
|
| update a contents page | `tools/wiki-contents.sh upsert {TYPE} {key-col} {key} {row-file} [template-file]` — resolves the CONTENTS repo, replaces the row whose key column matches, appends when none does, writes the whole page back. `{key-col}` is the column's 1-based position number, not the column name |
|
||||||
|
| check a page name | `tools/page-name.sh check {page}` — the single source of the page-name pattern; `tools/page-name.sh regex` prints it |
|
||||||
|
| move pages to the current rules | `tools/migrate-wiki.sh [--apply] [--key {key}]...` — prints the mapping table and the orphan list; writes only with `--apply` |
|
||||||
|
|
||||||
When writing a page, link same-type pages with the `[[display|page]]` form (display text on the LEFT) and cross-type pages with the absolute URL from `wiki-url`, because `[[...]]` resolves only inside one wiki. Full rules and the direction trap: `references/wiki-links.md`.
|
Contents pages and content pages no longer share a repo. Link between them — and between any two different types — with the absolute URL from `wiki-url`. Keep `[[display|page]]` (display text on the LEFT) for two pages that resolve to the same repo: contents page to contents page, or same-type content page to same-type content page. Full rules and the direction trap: `references/wiki-links.md`.
|
||||||
|
|
||||||
## Exit codes
|
## Exit codes
|
||||||
|
|
||||||
@@ -35,20 +39,46 @@ Route every `tools/gitea.sh` call in this skill on its exit code. A code with no
|
|||||||
| 0 | success | use the output |
|
| 0 | success | use the output |
|
||||||
| 2 | usage error, or a page type outside the allowed list | fix the arguments, then call again; never repeat the same call unchanged |
|
| 2 | usage error, or a page type outside the allowed list | fix the arguments, then call again; never repeat the same call unchanged |
|
||||||
| 3 | `wiki-repo`: neither `JSC_WIKI_REPO_{TYPE}` nor `JSC_WIKI_REPO` is set | go to step 3 and ask |
|
| 3 | `wiki-repo`: neither `JSC_WIKI_REPO_{TYPE}` nor `JSC_WIKI_REPO` is set | go to step 3 and ask |
|
||||||
| 4 | HTTP 404: `wiki-get` and `wiki-url` found no such page | for a read the caller expects to succeed, stop and report the page name; this is the **only** code that opens the create path of rule 4 — write the page from the template instead of appending |
|
| 4 | HTTP 404: `wiki-get` and `wiki-url` found no such page, or `wiki-delete` found nothing to delete | for a read the caller expects to succeed, stop and report the page name; this is the **only** code that opens the create path of rule 4 — write the page from the template instead of appending. For `wiki-delete` it means the page is already gone: report it and move on, do not retry |
|
||||||
| 5 | `wiki-url`: the page exists but the API returned no `html_url` | stop and report it. Link inside the same wiki with `[[display\|page]]`; a cross-repo link has no absolute URL to point at, so do not fabricate one |
|
| 5 | `wiki-url`: the page exists but the API returned no `html_url` | stop and report it. Link inside the same wiki with `[[display\|page]]`; a cross-repo link has no absolute URL to point at, so do not fabricate one |
|
||||||
| 7 | HTTP 401 or 403 after the tea-token retry: the key is invalid or lacks permission | **stop the whole operation and report the key problem.** Never read this as an empty or missing page, and never take the create path of rule 4: writing a fresh page over one you could not read destroys the record that is still there |
|
| 7 | HTTP 401 or 403 after the tea-token retry: the key is invalid or lacks permission | **stop the whole operation and report the key problem.** Never read this as an empty or missing page, and never take the create path of rule 4: writing a fresh page over one you could not read destroys the record that is still there |
|
||||||
| 8 | any other API failure, HTTP status in the message | stop and report that status; call again only after the cause is fixed |
|
| 8 | any other API failure, HTTP status in the message | stop and report that status; call again only after the cause is fixed |
|
||||||
|
|
||||||
|
### Helper scripts
|
||||||
|
|
||||||
|
Every code below gets its own branch. Nothing here is retried unchanged.
|
||||||
|
|
||||||
|
| Script | Code | Meaning | What this skill does |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `tools/hash-id` | 0 | the full 40-char uppercase hash | use it as `{HASH}` |
|
||||||
|
| | 1 | this machine has neither `sha1sum` nor `shasum` | stop, report that one of them has to be installed, compute no hash by hand |
|
||||||
|
| | 2 | no text given, or the text was empty | fix the key you passed, then call again; never fall back to a hand-made page name |
|
||||||
|
| `tools/page-name.sh` | 0 | the page name follows the pattern | continue with that page name |
|
||||||
|
| | 1 | the page name breaks the pattern | stop and report the name; build the correct one instead of writing to a wrong page |
|
||||||
|
| | 2 | usage error | fix the arguments, then call again |
|
||||||
|
| `tools/wiki-contents.sh` | 0 | the row was updated or added | report which of the two, and the page |
|
||||||
|
| | 1 | the write failed, or the page held no markdown table | stop and report; fix the page or the row before calling again |
|
||||||
|
| | 2 | usage error, or an unknown page type | fix the arguments, then call again |
|
||||||
|
| | 3 | the CONTENTS repo is not configured | go to step 3 and ask for `JSC_WIKI_REPO_CONTENTS` |
|
||||||
|
| | 4 | the page is not there and no template was given | supply the template for that page type, then call again |
|
||||||
|
| | 7 | the key is invalid or lacks permission | stop the whole operation and report the key problem; create no page |
|
||||||
|
| | 8 | any other API failure | stop and report the status |
|
||||||
|
| `tools/migrate-wiki.sh` | 0 | every page moved, or the preview found nothing to move | report the mapping table |
|
||||||
|
| | 1 | at least one page failed to move | report the failure list; the old pages of the failed entries stay in place |
|
||||||
|
| | 2 | usage error, including an unconfigured CONTENTS repo | fix the arguments or set `JSC_WIKI_REPO_CONTENTS`, then call again |
|
||||||
|
| | 3 | something needs manual handling: an orphan page, a page that links to a moved page without being moved itself, or a destination page that already holds content | report those lists and hand them to the user; guess no key, and rewrite no link the script left alone |
|
||||||
|
|
||||||
## Rules
|
## Rules
|
||||||
|
|
||||||
1. Page names must follow the wiki naming table in the skill guidelines (see `jsc-meta/references/guidelines.md`).
|
1. Page names must follow the wiki naming table in the skill guidelines (see `jsc-meta/references/guidelines.md`). Check any page name you build with `tools/page-name.sh check {page}` before it reaches an API call.
|
||||||
2. Use `tools/hash-id` for `{HASH}` values. It returns the first 8 uppercase SHA-1 hex chars, or `H` plus the first 7 chars when the raw hash starts with `0-9`, `A`, `B`, or `C`. Exit 1 means this machine has neither `sha1sum` nor `shasum`: stop, report that one of them has to be installed, and compute no hash by hand — a hand-made page name lands the content on a page nobody else reads.
|
2. Use `tools/hash-id` for `{HASH}` values. It returns the full 40 uppercase SHA-1 hex chars — no truncation, no prefix. Never compute a hash by hand: a hand-made page name lands the content on a page nobody else reads.
|
||||||
3. To update a contents page (`*_CONTENTS`): `wiki-get` it first, apply the template to append or modify, then `wiki-put` the whole page back — the write asks for confirmation before it goes out. Never overwrite entries owned by others. Whether the page may be created from the template instead is decided by rule 4, and by nothing else.
|
3. To update a contents page (`*_CONTENTS`), run `tools/wiki-contents.sh upsert {TYPE} {key-col} {key} {row-file} [template-file]`, where `{key-col}` is the column's 1-based position number, not the column name. It reads the page, replaces the row whose key column equals the key, appends the row when no line matches, and writes the whole page back through the same confirmation. Never overwrite entries owned by others. Whether the page may be created from the template instead is decided by rule 4, and by nothing else.
|
||||||
4. **Only exit 4 means the page is not there yet — this rule binds every "create it if it does not exist" path, without exception.** It is not limited to contents pages: a content page (`*_{HASH}`), a work log, an error page, a report, any page at all, follows the same branch.
|
4. **Only exit 4 means the page is not there yet — this rule binds every "create it if it does not exist" path, without exception.** It is not limited to contents pages: a content page (`*_{HASH}`), a work log, an error page, a report, any page at all, follows the same branch.
|
||||||
- **Correct branch.** Read the page with `wiki-get`. Exit 0 means the page exists, so append or modify the content that came back and `wiki-put` the whole page. Exit 4 (HTTP 404) is the one and only code that permits creating a new page from the template.
|
- **Correct branch.** Read the page with `wiki-get`. Exit 0 means the page exists, so append or modify the content that came back and `wiki-put` the whole page. Exit 4 (HTTP 404) is the one and only code that permits creating a new page from the template.
|
||||||
- **Exit 7 and exit 8 abort.** Exit 7 (HTTP 401 or 403) and exit 8 (any other API failure) both mean the old content is unknown, never that the page is missing. Stop the operation and report the exit code with its cause. Create no page, write nothing, and do not retry the same call unchanged.
|
- **Exit 7 and exit 8 abort.** Exit 7 (HTTP 401 or 403) and exit 8 (any other API failure) both mean the old content is unknown, never that the page is missing. Stop the operation and report the exit code with its cause. Create no page, write nothing, and do not retry the same call unchanged.
|
||||||
- **Why.** Wiki writes in this skill set are append-not-overwrite, and that semantics rests entirely on reading the old page back first. Reading a 401 as a 404 makes the caller believe it holds a brand-new page and `wiki-put` a fresh template over a live one, and the whole earlier record is gone — the write carries no merge and no backup.
|
- **Why.** Wiki writes in this skill set are append-not-overwrite, and that semantics rests entirely on reading the old page back first. Reading a 401 as a 404 makes the caller believe it holds a brand-new page and `wiki-put` a fresh template over a live one, and the whole earlier record is gone — the write carries no merge and no backup.
|
||||||
|
- `tools/wiki-contents.sh` implements exactly this branch for contents pages and reports the same codes.
|
||||||
5. Write all wiki content in UTF-8 Traditional Chinese, per the STE100 output rule.
|
5. Write all wiki content in UTF-8 Traditional Chinese, per the STE100 output rule.
|
||||||
6. Prefer visual forms for page content: use mermaid diagrams (flowchart, sequence, gantt, pie) and markdown tables wherever the information allows. Prose is capped at 3 sentences per section, and a sentence stays only when neither a mermaid diagram nor a markdown table can carry the same information.
|
6. Prefer visual forms for page content: use mermaid diagrams (flowchart, sequence, gantt, pie) and markdown tables wherever the information allows. Prose is capped at 3 sentences per section, and a sentence stays only when neither a mermaid diagram nor a markdown table can carry the same information.
|
||||||
7. `tools/gitea.sh` retries once with the tea CLI login token when `GITEA_TOKEN` is missing or the response is 401/403. Report a failure only after that retry also fails.
|
7. `tools/gitea.sh` retries once with the tea CLI login token when `GITEA_TOKEN` is missing or the response is 401/403. Report a failure only after that retry also fails.
|
||||||
|
8. `wiki-delete` removes a page for good. Call it only from `tools/migrate-wiki.sh --apply`, or when the user has asked for that exact page to go. In a migration the delete comes last: write the new page, read it back, then delete the old one.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env sh
|
#!/usr/bin/env sh
|
||||||
# check-wiki-rules — 驗證 wiki repo 解析與 hash-id 規則。
|
# check-wiki-rules — 驗證 wiki repo 解析、hash-id 與頁名樣式規則。
|
||||||
# 涵蓋 TYPES 列的每一種頁面類型,每種三項:專用變數優先、退回共用變數、不得跨類型代用。
|
# 涵蓋 TYPES 列的每一種頁面類型,每種三項:專用變數優先、退回共用變數、不得跨類型代用。
|
||||||
# 全部通過印 OK 並 exit 0;任一項不符印出差異並 exit 1。
|
# 全部通過印 OK 並 exit 0;任一項不符印出差異並 exit 1。
|
||||||
# 結束碼: 0=全部通過,stdout 印 OK
|
# 結束碼: 0=全部通過,stdout 印 OK
|
||||||
@@ -10,8 +10,9 @@ set -eu
|
|||||||
dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||||
gitea="$dir/gitea.sh"
|
gitea="$dir/gitea.sh"
|
||||||
hash_id="$dir/hash-id"
|
hash_id="$dir/hash-id"
|
||||||
|
page_name="$dir/page-name.sh"
|
||||||
|
|
||||||
TYPES='QUESTION PLAN ANALYZE DELIVER MAINTAIN REPO LOG LEARN ERROR CHECK REPORT SKILLSET TOOLING MONITOR'
|
TYPES='QUESTION PLAN ANALYZE DELIVER MAINTAIN REPO LOG LEARN ERROR CHECK REPORT SKILLSET TOOLING MONITOR CONTENTS'
|
||||||
|
|
||||||
fail() {
|
fail() {
|
||||||
printf '%s\n' "$1" >&2
|
printf '%s\n' "$1" >&2
|
||||||
@@ -84,12 +85,57 @@ fi
|
|||||||
[ "$code" -eq 2 ] || fail "unknown type: want exit=2 got exit=$code"
|
[ "$code" -eq 2 ] || fail "unknown type: want exit=2 got exit=$code"
|
||||||
[ -z "$got" ] || fail "unknown type: want no output got=$got"
|
[ -z "$got" ] || fail "unknown type: want no output got=$got"
|
||||||
|
|
||||||
# hash-id:首碼 0-9/A/B/C 改成 H 加前 7 碼;其餘首碼原樣輸出 8 碼大寫。
|
# hash-id:完整 40 碼大寫十六進位,不截短、不加前綴。
|
||||||
check_hash 'case-2' 'H5172CB7'
|
check_hash 'case-2' '5172CB7A273D9C64ADBAAC70F321F2EDCD49C495'
|
||||||
check_hash 'case-11' 'HA9A6662'
|
check_hash 'case-11' 'A9A6662D94D9CC4BE593F3846DABE9AA147333E8'
|
||||||
check_hash 'case-1' 'HB6EC7FD'
|
check_hash 'case-1' 'B6EC7FD12D959BFFB08AC7F8B279B5255D55AA4C'
|
||||||
check_hash 'case-12' 'HCCA8D42'
|
check_hash 'case-12' 'CCA8D4284394EBBA49F6493004ECAAE54FD74E0F'
|
||||||
check_hash 'case-3' 'D3E5AA27'
|
check_hash 'case-3' 'D3E5AA27999009B42908E5BB7C1636BC14087D9C'
|
||||||
check_hash 'case-7' 'D794C002'
|
check_hash 'case-7' 'D794C0028687E7F4F3AEFF703276964E618F2EE3'
|
||||||
|
|
||||||
|
# hash-id 空輸入回 2。靜靜回傳空字串的 SHA-1 會生出一個看起來合法的頁名,
|
||||||
|
# 內容就寫到沒有人讀的那一頁。
|
||||||
|
if got=$(printf '' | "$hash_id" 2>/dev/null); then
|
||||||
|
code=0
|
||||||
|
else
|
||||||
|
code=$?
|
||||||
|
fi
|
||||||
|
[ "$code" -eq 2 ] || fail "hash-id empty input: want exit=2 got exit=$code output=$got"
|
||||||
|
[ -z "$got" ] || fail "hash-id empty input: want no output got=$got"
|
||||||
|
|
||||||
|
# page-name.sh:四種合法尾段各一筆,不合法的一批。
|
||||||
|
check_page_ok() {
|
||||||
|
sh "$page_name" check "$1" >/dev/null 2>&1 || fail "page-name $1: want exit=0"
|
||||||
|
}
|
||||||
|
check_page_bad() {
|
||||||
|
if sh "$page_name" check "$1" >/dev/null 2>&1; then
|
||||||
|
code=0
|
||||||
|
else
|
||||||
|
code=$?
|
||||||
|
fi
|
||||||
|
[ "$code" -eq 1 ] || fail "page-name $1: want exit=1 got exit=$code"
|
||||||
|
}
|
||||||
|
|
||||||
|
check_page_ok 'CHECK_D3E5AA27999009B42908E5BB7C1636BC14087D9C'
|
||||||
|
check_page_ok 'LOG_FB8DF0B5'
|
||||||
|
check_page_ok 'PLAN_CONTENTS'
|
||||||
|
|
||||||
|
# H 開頭的舊頁名。舊演算法十六個首碼裡有十三個會改寫成 H 加前 7 碼,
|
||||||
|
# 既有舊頁名多數長這樣。判成不合法,先驗頁名的技能就讀不到它們。
|
||||||
|
check_page_ok 'LOG_H1A2B3C4'
|
||||||
|
check_page_ok 'CHECK_H87EC88F'
|
||||||
|
check_page_ok 'LOG_H87095D0'
|
||||||
|
|
||||||
|
# CONTENTS 只解存取庫,自己沒有頁。放行就會建出規格上不存在的那一頁。
|
||||||
|
check_page_bad 'CONTENTS_CONTENTS'
|
||||||
|
check_page_bad 'CONTENTS_D3E5AA27999009B42908E5BB7C1636BC14087D9C'
|
||||||
|
|
||||||
|
check_page_bad 'NOSUCH_CONTENTS'
|
||||||
|
check_page_bad 'PLAN_d3e5aa27'
|
||||||
|
check_page_bad 'PLAN_H87EC88'
|
||||||
|
check_page_bad 'PLAN'
|
||||||
|
|
||||||
|
# regex 子命令要印得出東西,頁名樣式的正本才只有這一份。
|
||||||
|
[ -n "$(sh "$page_name" regex)" ] || fail 'page-name regex: want a pattern'
|
||||||
|
|
||||||
printf '%s\n' 'OK'
|
printf '%s\n' 'OK'
|
||||||
|
|||||||
+18
-4
@@ -5,13 +5,14 @@
|
|||||||
# gitea.sh repos <owner> # 列出 owner 底下可讀取的 repo(全名)
|
# gitea.sh repos <owner> # 列出 owner 底下可讀取的 repo(全名)
|
||||||
# gitea.sh default-branch <owner>/<repo> # 印出預設分支
|
# gitea.sh default-branch <owner>/<repo> # 印出預設分支
|
||||||
# gitea.sh clone-url <owner>/<repo> # 印出 clone URL
|
# gitea.sh clone-url <owner>/<repo> # 印出 clone URL
|
||||||
# gitea.sh hash-id <text> # 產生 8 碼大寫 SHA-1 hash;首碼為 0-9/A/B/C 時改成 Hxxxxxxx
|
# gitea.sh hash-id <text> # 產生完整 40 碼大寫 SHA-1 hash
|
||||||
# gitea.sh wiki-repo <TYPE> # 解析頁面類型的 wiki 位置:
|
# gitea.sh wiki-repo <TYPE> # 解析頁面類型的 wiki 位置:
|
||||||
# TYPE = QUESTION|PLAN|ANALYZE|DELIVER|MAINTAIN|REPO|LOG|LEARN|ERROR|CHECK|REPORT|SKILLSET|TOOLING|MONITOR(即頁名前綴)
|
# TYPE = QUESTION|PLAN|ANALYZE|DELIVER|MAINTAIN|REPO|LOG|LEARN|ERROR|CHECK|REPORT|SKILLSET|TOOLING|MONITOR|CONTENTS(即頁名前綴)
|
||||||
# 依序取 JSC_WIKI_REPO_{TYPE} > JSC_WIKI_REPO;不得跨類型代用;都未設定 exit 3
|
# 依序取 JSC_WIKI_REPO_{TYPE} > JSC_WIKI_REPO;不得跨類型代用;都未設定 exit 3
|
||||||
# gitea.sh wiki-list <owner>/<repo> # 列出 wiki 頁名
|
# gitea.sh wiki-list <owner>/<repo> # 列出 wiki 頁名
|
||||||
# gitea.sh wiki-get <owner>/<repo> <page> # 印出 wiki 頁 markdown;不存在時 exit 4
|
# gitea.sh wiki-get <owner>/<repo> <page> # 印出 wiki 頁 markdown;不存在時 exit 4
|
||||||
# gitea.sh wiki-put <owner>/<repo> <page> <file> # 建立或更新 wiki 頁(內容取自檔案)
|
# gitea.sh wiki-put <owner>/<repo> <page> <file> # 建立或更新 wiki 頁(內容取自檔案)
|
||||||
|
# gitea.sh wiki-delete <owner>/<repo> <page> # 刪除 wiki 頁;與 wiki-put 走同一道人工確認
|
||||||
# gitea.sh wiki-url <owner>/<repo> <page> # 印出 wiki 頁絕對網址(取自 API 的 html_url);跨存取庫連結用
|
# gitea.sh wiki-url <owner>/<repo> <page> # 印出 wiki 頁絕對網址(取自 API 的 html_url);跨存取庫連結用
|
||||||
# gitea.sh pr-create <owner>/<repo> <head> <base> <title> <body-file> # 建立 PR,印出 PR URL
|
# gitea.sh pr-create <owner>/<repo> <head> <base> <title> <body-file> # 建立 PR,印出 PR URL
|
||||||
# gitea.sh pr-status <owner>/<repo> <pr-index> # 印出「{state} {merged} {mergeable}」
|
# gitea.sh pr-status <owner>/<repo> <pr-index> # 印出「{state} {merged} {mergeable}」
|
||||||
@@ -42,7 +43,7 @@
|
|||||||
# (~/.config/tea/config.yml,優先取 url 與 GITEA_HOST 同主機的登入,其次 default: true);兩者皆無才失敗。
|
# (~/.config/tea/config.yml,優先取 url 與 GITEA_HOST 同主機的登入,其次 default: true);兩者皆無才失敗。
|
||||||
# 結束碼: 0=成功 1=api 子命令的請求失敗 2=用法錯誤或不認得的指令
|
# 結束碼: 0=成功 1=api 子命令的請求失敗 2=用法錯誤或不認得的指令
|
||||||
# 3=wiki-repo 的該類型沒有設定存取庫、pr-of-branch 的該分支沒有開啟中的 PR
|
# 3=wiki-repo 的該類型沒有設定存取庫、pr-of-branch 的該分支沒有開啟中的 PR
|
||||||
# 4=找不到(HTTP 404,含 wiki 頁不存在)、PR 系列子命令的 API 失敗,
|
# 4=找不到(HTTP 404,含 wiki 頁不存在、wiki-delete 要刪的頁不存在)、PR 系列子命令的 API 失敗,
|
||||||
# 以及 pr-of-branch 翻過 50 頁上限仍沒結束(分頁沒有前進)
|
# 以及 pr-of-branch 翻過 50 頁上限仍沒結束(分頁沒有前進)
|
||||||
# 5=wiki 頁沒有 html_url
|
# 5=wiki 頁沒有 html_url
|
||||||
# 7=Gitea 金鑰失效或權限不足(HTTP 401/403)
|
# 7=Gitea 金鑰失效或權限不足(HTTP 401/403)
|
||||||
@@ -59,7 +60,7 @@ confirm_write() {
|
|||||||
resolve_wiki_repo() { # TYPE -> JSC_WIKI_REPO_{TYPE} -> JSC_WIKI_REPO
|
resolve_wiki_repo() { # TYPE -> JSC_WIKI_REPO_{TYPE} -> JSC_WIKI_REPO
|
||||||
type=$(printf '%s' "${1:?TYPE required}" | tr a-z A-Z)
|
type=$(printf '%s' "${1:?TYPE required}" | tr a-z A-Z)
|
||||||
case "$type" in
|
case "$type" in
|
||||||
QUESTION|PLAN|ANALYZE|DELIVER|MAINTAIN|REPO|LOG|LEARN|ERROR|CHECK|REPORT|SKILLSET|TOOLING|MONITOR) ;;
|
QUESTION|PLAN|ANALYZE|DELIVER|MAINTAIN|REPO|LOG|LEARN|ERROR|CHECK|REPORT|SKILLSET|TOOLING|MONITOR|CONTENTS) ;;
|
||||||
*) echo "unknown wiki type: $type" >&2; exit 2 ;;
|
*) echo "unknown wiki type: $type" >&2; exit 2 ;;
|
||||||
esac
|
esac
|
||||||
eval "v=\${JSC_WIKI_REPO_${type}:-}"
|
eval "v=\${JSC_WIKI_REPO_${type}:-}"
|
||||||
@@ -280,6 +281,19 @@ print(json.dumps({"title":title,"content_base64":base64.b64encode(content).decod
|
|||||||
fi
|
fi
|
||||||
rm -f "$tmp"
|
rm -f "$tmp"
|
||||||
echo "OK $page" ;;
|
echo "OK $page" ;;
|
||||||
|
wiki-delete)
|
||||||
|
# 刪除 wiki 頁。刪除是不可回復的寫入,所以走 wiki-put 同一道人工確認。
|
||||||
|
# 404 要回 4 而不是併進通用失敗碼:搬移流程靠「舊頁還在不在」決定要不要重跑,
|
||||||
|
# 把找不到讀成一般失敗,會讓呼叫端一直重試一個已經刪掉的頁。
|
||||||
|
or="${1:?owner/repo required}"; page="${2:?page required}"
|
||||||
|
confirm_write "刪除 wiki 頁" "$page"
|
||||||
|
if ! req DELETE "/repos/$or/wiki/page/$page" >/dev/null 2>&1; then
|
||||||
|
case "$(req_code)" in
|
||||||
|
404) echo "wiki page not found: $page" >&2; exit 4 ;;
|
||||||
|
*) api_fail "刪除 wiki 頁 $or/$page 失敗" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
echo "OK $page" ;;
|
||||||
pr-create)
|
pr-create)
|
||||||
or="${1:?owner/repo required}"; head="${2:?head required}"; base="${3:?base required}"
|
or="${1:?owner/repo required}"; head="${2:?head required}"; base="${3:?base required}"
|
||||||
title="${4:?title required}"; body_file="${5:?body file required}"
|
title="${4:?title required}"; body_file="${5:?body file required}"
|
||||||
|
|||||||
+11
-9
@@ -1,12 +1,14 @@
|
|||||||
#!/usr/bin/env sh
|
#!/usr/bin/env sh
|
||||||
# hash-id — 產生 8 碼大寫 SHA-1 hash。
|
# hash-id — 產生完整 40 碼大寫 SHA-1 hash。
|
||||||
# 用法:
|
# 用法:
|
||||||
# hash-id <text>
|
# hash-id <text>
|
||||||
# printf '%s' <text> | hash-id
|
# printf '%s' <text> | hash-id
|
||||||
# 規則:
|
# 規則:
|
||||||
# 先取 SHA-1 前 8 碼大寫。
|
# 取完整 SHA-1 四十碼,a-f 轉大寫,不截短、不加前綴。
|
||||||
# 若首碼為 0-9、A、B、C,改成 H 加上原 SHA-1 前 7 碼。
|
# 輸入為空即 exit 2。空字串本身也有合法的 SHA-1,靜靜回傳它就會生出
|
||||||
|
# 一個看起來正常的頁名,把內容寫到沒有人讀的那一頁。
|
||||||
# 結束碼: 0=成功 1=這台機器沒有 sha1sum 也沒有 shasum,算不出雜湊
|
# 結束碼: 0=成功 1=這台機器沒有 sha1sum 也沒有 shasum,算不出雜湊
|
||||||
|
# 2=用法錯誤:沒有給文字,或給的是空字串
|
||||||
set -eu
|
set -eu
|
||||||
|
|
||||||
input=''
|
input=''
|
||||||
@@ -18,6 +20,11 @@ else
|
|||||||
input=$(cat)
|
input=$(cat)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ -z "$input" ]; then
|
||||||
|
echo 'usage: hash-id <text>' >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
if command -v sha1sum >/dev/null 2>&1; then
|
if command -v sha1sum >/dev/null 2>&1; then
|
||||||
raw=$(printf '%s' "$input" | sha1sum | awk '{print $1}')
|
raw=$(printf '%s' "$input" | sha1sum | awk '{print $1}')
|
||||||
elif command -v shasum >/dev/null 2>&1; then
|
elif command -v shasum >/dev/null 2>&1; then
|
||||||
@@ -27,9 +34,4 @@ else
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
hash=$(printf '%s' "$raw" | cut -c1-8 | tr a-f A-F)
|
printf '%s\n' "$raw" | tr a-f A-F
|
||||||
case "$hash" in
|
|
||||||
[0-9ABC]*) hash="H$(printf '%s' "$hash" | cut -c1-7)" ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
printf '%s\n' "$hash"
|
|
||||||
|
|||||||
Executable
+435
@@ -0,0 +1,435 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
# migrate-wiki.sh — 把既有 wiki 頁搬到新規則。
|
||||||
|
#
|
||||||
|
# 一次改兩件事:
|
||||||
|
# 目錄頁 {TYPE}_CONTENTS 換存取庫,頁名不變。
|
||||||
|
# 內容頁 {TYPE}_{舊 HASH} 換頁名,存取庫不變。
|
||||||
|
#
|
||||||
|
# 為什麼只做正推:SHA-1 不可逆,新頁名算不回舊頁名。所以拿候選鍵跑舊演算法,
|
||||||
|
# 對得上現有頁名才算配對成功。配不上就列成孤兒,交給人處理,絕不猜。
|
||||||
|
#
|
||||||
|
# 用法:
|
||||||
|
# migrate-wiki.sh [--apply] [--key <候選鍵>]...
|
||||||
|
# 不帶 --apply 時只印對照表與孤兒清單,不寫任何東西。
|
||||||
|
# --key 可重複,補充自動蒐集不到的候選鍵。
|
||||||
|
#
|
||||||
|
# 候選鍵來源:
|
||||||
|
# 目錄頁表格的存取庫、主機、帳號、工具、期間欄,以及這幾欄依序串成的組合鍵。
|
||||||
|
# 內容頁的 H1 標題(CHECK 頁的 H1 直接就是 {主機}/{帳號})。
|
||||||
|
# --key 補充的候選。
|
||||||
|
#
|
||||||
|
# --apply 的順序:先把每一頁寫到新位置,再改連結,最後才刪舊頁。
|
||||||
|
# 連結改寫不能省:[[...]] 只在同一個 wiki 內解析,頁名或存取庫一變,
|
||||||
|
# 連結就指向一個不存在的頁,而畫面上看不出異常。
|
||||||
|
# 先全部寫到新位置,wiki-url 才查得到每一個目的地的絕對網址。
|
||||||
|
# 刪除排在確認新頁讀得回來之後:先刪再寫,中間出錯就兩邊都沒有。
|
||||||
|
# 每一頁寫到新位置之前一定先讀目的地,只有結束碼 4 才准寫:目的地那一頁可能是
|
||||||
|
# 前一次搬到一半留下的,也可能是 wiki-contents.sh 剛建好的活頁,直接寫就是覆蓋。
|
||||||
|
#
|
||||||
|
# 連結改寫的範圍限制:只改寫被搬的那些頁自己的內容。沒被搬的頁——已經合規的頁、
|
||||||
|
# 目的地存取庫裡原有的頁——裡面指向被搬頁的 [[...]] 這支不動。要改寫全部,
|
||||||
|
# 得把每個存取庫的每一頁都讀回來重寫,代價是整批頁的讀寫都變成寫入風險。
|
||||||
|
# 所以改採列清單:報告的「引用被搬頁、但自己沒被搬」一節列出每一個引用方與它指到的頁名,
|
||||||
|
# 交給人改。清單不空時結束碼是 3。
|
||||||
|
#
|
||||||
|
# 逐列處理的迴圈一律從 fd 3 讀清單,不佔用 stdin。wiki-put 與 wiki-delete 的
|
||||||
|
# 人工確認要從終端讀一行,stdin 被清單檔佔走的話,每一次寫入都會被判成
|
||||||
|
# 「沒有互動終端」而拒絕。
|
||||||
|
# 結束碼: 0=全部搬完 1=有頁搬移失敗 2=用法錯誤,含 CONTENTS 存取庫未設定
|
||||||
|
# 3=有需人工處理的項目:孤兒頁、沒被搬的引用方、目的地已有內容的頁
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||||
|
gitea="$dir/gitea.sh"
|
||||||
|
hash_id="$dir/hash-id"
|
||||||
|
TAB=$(printf '\t')
|
||||||
|
|
||||||
|
TYPES='QUESTION PLAN ANALYZE DELIVER MAINTAIN REPO LOG LEARN ERROR CHECK REPORT SKILLSET TOOLING MONITOR'
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
echo 'usage: migrate-wiki.sh [--apply] [--key <候選鍵>]...' >&2
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
|
||||||
|
apply=0
|
||||||
|
work=$(mktemp -d)
|
||||||
|
trap 'rm -rf "$work"' EXIT
|
||||||
|
: > "$work/keys"
|
||||||
|
|
||||||
|
while [ "$#" -gt 0 ]; do
|
||||||
|
case "$1" in
|
||||||
|
--apply) apply=1; shift ;;
|
||||||
|
--key)
|
||||||
|
[ "$#" -ge 2 ] && [ -n "$2" ] || usage
|
||||||
|
printf '%s\n' "$2" >> "$work/keys"; shift 2 ;;
|
||||||
|
*) usage ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
old_hash() { # 舊演算法:SHA-1 前 8 碼大寫;首碼落在 0-9ABC 就改成 H 加前 7 碼
|
||||||
|
if command -v sha1sum >/dev/null 2>&1; then
|
||||||
|
raw=$(printf '%s' "$1" | sha1sum | awk '{print $1}')
|
||||||
|
else
|
||||||
|
raw=$(printf '%s' "$1" | shasum -a 1 | awk '{print $1}')
|
||||||
|
fi
|
||||||
|
h=$(printf '%s' "$raw" | cut -c1-8 | tr a-f A-F)
|
||||||
|
case "$h" in [0-9ABC]*) h="H$(printf '%s' "$h" | cut -c1-7)" ;; esac
|
||||||
|
printf '%s\n' "$h"
|
||||||
|
}
|
||||||
|
|
||||||
|
rc=0
|
||||||
|
contents_repo=$(sh "$gitea" wiki-repo CONTENTS) || rc=$?
|
||||||
|
if [ "$rc" -ne 0 ]; then
|
||||||
|
echo '[jsc][gitea][ERR]:目錄頁的專用存取庫沒有設定。請先設 JSC_WIKI_REPO_CONTENTS 或 JSC_WIKI_REPO。' >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
: > "$work/map" # 舊存取庫 \t 舊頁名 \t 新存取庫 \t 新頁名
|
||||||
|
: > "$work/orphans" # 存取庫 \t 頁名 \t 原因
|
||||||
|
: > "$work/pending" # 存取庫 \t 頁名 \t 型別 \t 舊 HASH
|
||||||
|
: > "$work/allpages" # 存取庫 \t 頁名(列得出來的每一頁,含不搬的)
|
||||||
|
: > "$work/refs" # 存取庫 \t 頁名 \t 被指到的舊頁名
|
||||||
|
: > "$work/manual" # 需人工確認的項目,一行一句
|
||||||
|
: > "$work/notes"
|
||||||
|
|
||||||
|
# ---- 第一輪:列出各型別的頁,分成目錄頁、待配對的內容頁、已經合規的頁 ----
|
||||||
|
for ty in $TYPES; do
|
||||||
|
rc=0
|
||||||
|
repo=$(sh "$gitea" wiki-repo "$ty" 2>/dev/null) || rc=$?
|
||||||
|
if [ "$rc" -ne 0 ]; then
|
||||||
|
printf '%s:沒有設定存取庫,略過。\n' "$ty" >> "$work/notes"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
rc=0
|
||||||
|
pages=$(sh "$gitea" wiki-list "$repo" 2>/dev/null) || rc=$?
|
||||||
|
if [ "$rc" -ne 0 ]; then
|
||||||
|
printf '%s(%s):列不出 wiki 頁(結束碼 %s)。\n' "$ty" "$repo" "$rc" >> "$work/notes"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
printf '%s\n' "$pages" > "$work/pagelist"
|
||||||
|
while IFS= read -r pg <&3; do
|
||||||
|
[ -n "$pg" ] || continue
|
||||||
|
# 先收進全頁清單,再過型別前綴。引用方掃描要看的是沒被搬的那些頁。
|
||||||
|
printf '%s\t%s\n' "$repo" "$pg" >> "$work/allpages"
|
||||||
|
case "$pg" in "${ty}_"*) ;; *) continue ;; esac
|
||||||
|
suffix=${pg#"${ty}_"}
|
||||||
|
if [ "$suffix" = CONTENTS ]; then
|
||||||
|
if [ "$repo" = "$contents_repo" ]; then
|
||||||
|
printf '%s:已經在目錄頁存取庫。\n' "$pg" >> "$work/notes"
|
||||||
|
else
|
||||||
|
printf '%s\t%s\t%s\t%s\n' "$repo" "$pg" "$contents_repo" "$pg" >> "$work/map"
|
||||||
|
fi
|
||||||
|
elif printf '%s' "$suffix" | grep -Eq '^[0-9A-F]{40}$'; then
|
||||||
|
printf '%s:已經是 40 碼頁名。\n' "$pg" >> "$work/notes"
|
||||||
|
elif printf '%s' "$suffix" | grep -Eq '^([0-9A-F]{8}|H[0-9A-F]{7})$'; then
|
||||||
|
printf '%s\t%s\t%s\t%s\n' "$repo" "$pg" "$ty" "$suffix" >> "$work/pending"
|
||||||
|
else
|
||||||
|
printf '%s\t%s\t%s\n' "$repo" "$pg" '頁名不符任何已知樣式' >> "$work/orphans"
|
||||||
|
fi
|
||||||
|
done 3< "$work/pagelist"
|
||||||
|
done
|
||||||
|
|
||||||
|
# ---- 第二輪:蒐集候選鍵 ----
|
||||||
|
# 目錄頁表格的欄位值,以及依序串成的組合鍵
|
||||||
|
while IFS="$TAB" read -r orepo opage nrepo npage <&3; do
|
||||||
|
[ -n "$opage" ] || continue
|
||||||
|
case "$opage" in *_CONTENTS) ;; *) continue ;; esac
|
||||||
|
sh "$gitea" wiki-get "$orepo" "$opage" 2>/dev/null > "$work/one" || continue
|
||||||
|
python3 - "$work/one" <<'PY' >> "$work/keys" || true
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
|
||||||
|
WANT = ['存取庫', '主機', '工具', '帳號', '期間']
|
||||||
|
|
||||||
|
lines = open(sys.argv[1], encoding='utf-8').read().split('\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)
|
||||||
|
|
||||||
|
|
||||||
|
def plain(v):
|
||||||
|
v = re.sub(r'\[\[([^\]|]*)\|([^\]]*)\]\]', r'\1', v)
|
||||||
|
v = re.sub(r'\[\[([^\]]*)\]\]', r'\1', v)
|
||||||
|
v = re.sub(r'\[([^\]]*)\]\([^)]*\)', r'\1', v)
|
||||||
|
return v.replace('`', '').strip()
|
||||||
|
|
||||||
|
|
||||||
|
idx = {}
|
||||||
|
seen_sep = False
|
||||||
|
out = []
|
||||||
|
for line in lines:
|
||||||
|
cs = cells(line)
|
||||||
|
if cs is None:
|
||||||
|
idx, seen_sep = {}, False
|
||||||
|
continue
|
||||||
|
if is_sep(cs):
|
||||||
|
seen_sep = True
|
||||||
|
continue
|
||||||
|
if not seen_sep:
|
||||||
|
# 分隔列之前的那一列是表頭,欄位位置從它認出來。
|
||||||
|
idx = {}
|
||||||
|
for w in WANT:
|
||||||
|
for i, h in enumerate(cs):
|
||||||
|
if w in h:
|
||||||
|
idx[w] = i
|
||||||
|
break
|
||||||
|
continue
|
||||||
|
if not idx:
|
||||||
|
continue
|
||||||
|
parts = []
|
||||||
|
for w in WANT:
|
||||||
|
i = idx.get(w)
|
||||||
|
if i is None or i >= len(cs):
|
||||||
|
continue
|
||||||
|
v = plain(cs[i])
|
||||||
|
if not v:
|
||||||
|
continue
|
||||||
|
out.append(v)
|
||||||
|
parts.append(v)
|
||||||
|
if len(parts) > 1:
|
||||||
|
out.append('/'.join(parts))
|
||||||
|
|
||||||
|
for v in out:
|
||||||
|
print(v)
|
||||||
|
PY
|
||||||
|
done 3< "$work/map"
|
||||||
|
|
||||||
|
# 內容頁的 H1 標題
|
||||||
|
while IFS="$TAB" read -r repo pg ty suffix <&3; do
|
||||||
|
[ -n "$pg" ] || continue
|
||||||
|
sh "$gitea" wiki-get "$repo" "$pg" 2>/dev/null > "$work/one" || continue
|
||||||
|
sed -n 's/^# \{1,\}//p' "$work/one" | head -n 3 >> "$work/keys"
|
||||||
|
done 3< "$work/pending"
|
||||||
|
|
||||||
|
sort -u "$work/keys" > "$work/keys.uniq"
|
||||||
|
|
||||||
|
# 候選鍵的舊 HASH 對照表
|
||||||
|
: > "$work/hashes"
|
||||||
|
while IFS= read -r k <&3; do
|
||||||
|
[ -n "$k" ] || continue
|
||||||
|
printf '%s\t%s\n' "$(old_hash "$k")" "$k" >> "$work/hashes"
|
||||||
|
done 3< "$work/keys.uniq"
|
||||||
|
|
||||||
|
# ---- 第三輪:正推配對 ----
|
||||||
|
while IFS="$TAB" read -r repo pg ty suffix <&3; do
|
||||||
|
[ -n "$pg" ] || continue
|
||||||
|
key=$(awk -F'\t' -v h="$suffix" '$1==h {print $2; exit}' "$work/hashes")
|
||||||
|
if [ -z "$key" ]; then
|
||||||
|
printf '%s\t%s\t%s\n' "$repo" "$pg" '找不到能正推出這個 HASH 的候選鍵' >> "$work/orphans"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
printf '%s\t%s\t%s\t%s_%s\n' "$repo" "$pg" "$repo" "$ty" "$(sh "$hash_id" "$key")" >> "$work/map"
|
||||||
|
done 3< "$work/pending"
|
||||||
|
|
||||||
|
# ---- 第四輪:找出指向被搬頁、但自己沒被搬的引用方 ----
|
||||||
|
# 連結改寫只動被搬的那些頁自己的內容。沒被搬的頁裡那些 [[...]] 這支碰不到,
|
||||||
|
# 所以至少要把它們列出來交給人改,不能讓它們搬完就靜靜 404。
|
||||||
|
if [ -s "$work/map" ]; then
|
||||||
|
cut -f2 "$work/map" | sort -u > "$work/movednames"
|
||||||
|
cut -f1,2 "$work/map" | sort -u > "$work/movedkeys"
|
||||||
|
sort -u "$work/allpages" > "$work/allpages.uniq"
|
||||||
|
while IFS="$TAB" read -r repo pg <&3; do
|
||||||
|
[ -n "$pg" ] || continue
|
||||||
|
if grep -qxF "$repo$TAB$pg" "$work/movedkeys"; then continue; fi
|
||||||
|
sh "$gitea" wiki-get "$repo" "$pg" 2>/dev/null > "$work/one" || continue
|
||||||
|
# [[顯示文字|頁名]] 的頁名在豎線右邊,[[頁名]] 就是整段。兩種都收成頁名再比對。
|
||||||
|
targets=$(grep -oE '\[\[[^]]+\]\]' "$work/one" 2>/dev/null \
|
||||||
|
| sed -e 's/^\[\[//' -e 's/\]\]$//' -e 's/^.*|//' -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' \
|
||||||
|
| sort -u | grep -xF -f "$work/movednames" 2>/dev/null || true)
|
||||||
|
for t in $targets; do
|
||||||
|
printf '%s\t%s\t%s\n' "$repo" "$pg" "$t" >> "$work/refs"
|
||||||
|
done
|
||||||
|
done 3< "$work/allpages.uniq"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---- 報告 ----
|
||||||
|
echo '對照表(舊存取庫/舊頁名 → 新存取庫/新頁名)'
|
||||||
|
if [ -s "$work/map" ]; then
|
||||||
|
while IFS="$TAB" read -r orepo opage nrepo npage <&3; do
|
||||||
|
[ -n "$opage" ] || continue
|
||||||
|
printf ' %s/%s → %s/%s\n' "$orepo" "$opage" "$nrepo" "$npage"
|
||||||
|
done 3< "$work/map"
|
||||||
|
else
|
||||||
|
echo ' (沒有需要搬移的頁)'
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo '孤兒頁(需人工處理)'
|
||||||
|
if [ -s "$work/orphans" ]; then
|
||||||
|
while IFS="$TAB" read -r repo pg why <&3; do
|
||||||
|
[ -n "$pg" ] || continue
|
||||||
|
printf ' %s/%s:%s\n' "$repo" "$pg" "$why"
|
||||||
|
done 3< "$work/orphans"
|
||||||
|
else
|
||||||
|
echo ' (無)'
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo '引用被搬頁、但自己沒被搬(連結改寫碰不到,需人工改)'
|
||||||
|
if [ -s "$work/refs" ]; then
|
||||||
|
while IFS="$TAB" read -r repo pg target <&3; do
|
||||||
|
[ -n "$pg" ] || continue
|
||||||
|
printf ' %s/%s 指向 %s\n' "$repo" "$pg" "$target"
|
||||||
|
done 3< "$work/refs"
|
||||||
|
else
|
||||||
|
echo ' (無)'
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -s "$work/notes" ]; then
|
||||||
|
echo '備註'
|
||||||
|
sed 's/^/ /' "$work/notes"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$apply" -eq 0 ]; then
|
||||||
|
echo '(預覽模式,沒有寫入任何內容。加 --apply 才真的搬。)'
|
||||||
|
if [ -s "$work/orphans" ] || [ -s "$work/refs" ]; then exit 3; fi
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---- 搬移第一步:把每一頁寫到新位置 ----
|
||||||
|
mkdir -p "$work/body"
|
||||||
|
: > "$work/done" # 舊存取庫 \t 舊頁名 \t 新存取庫 \t 新頁名 \t 序號
|
||||||
|
: > "$work/failed"
|
||||||
|
|
||||||
|
abort() { # $1=訊息。金鑰或 API 出問題就整個停下,把已經做完的部分照樣報出來。
|
||||||
|
printf '%s\n' "$1" >> "$work/failed"
|
||||||
|
echo '搬移中止'
|
||||||
|
sed 's/^/ /' "$work/failed"
|
||||||
|
if [ -s "$work/manual" ]; then
|
||||||
|
echo '需人工確認'
|
||||||
|
sed 's/^/ /' "$work/manual"
|
||||||
|
fi
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
n=0
|
||||||
|
while IFS="$TAB" read -r orepo opage nrepo npage <&3; do
|
||||||
|
[ -n "$opage" ] || continue
|
||||||
|
n=$((n+1))
|
||||||
|
rc=0
|
||||||
|
sh "$gitea" wiki-get "$orepo" "$opage" > "$work/body/$n" 2>/dev/null || rc=$?
|
||||||
|
if [ "$rc" -ne 0 ]; then
|
||||||
|
printf '%s/%s:讀不回舊頁(結束碼 %s),這一頁不搬。\n' "$orepo" "$opage" "$rc" >> "$work/failed"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 寫之前先讀目的地。只有 4 才准建新頁——部分搬完後重跑,或目的地那一頁已經由
|
||||||
|
# wiki-contents.sh 建好,直接寫就是拿舊庫那份蓋掉一個活著的頁。
|
||||||
|
rc=0
|
||||||
|
sh "$gitea" wiki-get "$nrepo" "$npage" >/dev/null 2>&1 || rc=$?
|
||||||
|
case "$rc" in
|
||||||
|
4) ;;
|
||||||
|
0)
|
||||||
|
printf '%s/%s 已經有內容,不覆蓋。請比對它與 %s/%s 之後自行決定。\n' \
|
||||||
|
"$nrepo" "$npage" "$orepo" "$opage" >> "$work/manual"
|
||||||
|
continue ;;
|
||||||
|
7)
|
||||||
|
abort "$(printf '讀 %s/%s 遇金鑰失效或權限不足(結束碼 7)。' "$nrepo" "$npage")" ;;
|
||||||
|
*)
|
||||||
|
abort "$(printf '讀 %s/%s 失敗(結束碼 %s)。' "$nrepo" "$npage" "$rc")" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
rc=0
|
||||||
|
sh "$gitea" wiki-put "$nrepo" "$npage" "$work/body/$n" >/dev/null || rc=$?
|
||||||
|
if [ "$rc" -ne 0 ]; then
|
||||||
|
printf '%s/%s:寫不進 %s/%s(結束碼 %s)。\n' "$orepo" "$opage" "$nrepo" "$npage" "$rc" >> "$work/failed"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
printf '%s\t%s\t%s\t%s\t%s\n' "$orepo" "$opage" "$nrepo" "$npage" "$n" >> "$work/done"
|
||||||
|
done 3< "$work/map"
|
||||||
|
|
||||||
|
# ---- 搬移第二步:查每一個目的地的絕對網址,改寫 [[...]] 連結 ----
|
||||||
|
: > "$work/urls" # 舊頁名 \t 新頁名 \t 絕對網址
|
||||||
|
while IFS="$TAB" read -r orepo opage nrepo npage idx <&3; do
|
||||||
|
[ -n "$opage" ] || continue
|
||||||
|
url=$(sh "$gitea" wiki-url "$nrepo" "$npage" 2>/dev/null) || continue
|
||||||
|
printf '%s\t%s\t%s\n' "$opage" "$npage" "$url" >> "$work/urls"
|
||||||
|
done 3< "$work/done"
|
||||||
|
|
||||||
|
while IFS="$TAB" read -r orepo opage nrepo npage idx <&3; do
|
||||||
|
[ -n "$opage" ] || continue
|
||||||
|
rc=0
|
||||||
|
python3 - "$work/body/$idx" "$work/urls" "$work/body/$idx.new" <<'PY' || rc=$?
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
|
||||||
|
src, urlmap_path, out = sys.argv[1:4]
|
||||||
|
urlmap = {}
|
||||||
|
for line in open(urlmap_path, encoding='utf-8'):
|
||||||
|
parts = line.rstrip('\n').split('\t')
|
||||||
|
if len(parts) == 3:
|
||||||
|
urlmap[parts[0]] = (parts[1], parts[2])
|
||||||
|
|
||||||
|
text = open(src, encoding='utf-8').read()
|
||||||
|
|
||||||
|
|
||||||
|
def repl(m):
|
||||||
|
inner = m.group(1)
|
||||||
|
if '|' in inner:
|
||||||
|
label, target = inner.split('|', 1)
|
||||||
|
label, target = label.strip(), target.strip()
|
||||||
|
else:
|
||||||
|
target = inner.strip()
|
||||||
|
label = None
|
||||||
|
hit = urlmap.get(target)
|
||||||
|
if not hit:
|
||||||
|
return m.group(0)
|
||||||
|
newname, url = hit
|
||||||
|
# 原本沒有顯示文字的寫法,顯示的就是頁名本身;頁名換了就跟著顯示新頁名。
|
||||||
|
return '[%s](%s)' % (label if label is not None else newname, url)
|
||||||
|
|
||||||
|
|
||||||
|
new = re.sub(r'\[\[([^\]]+)\]\]', repl, text)
|
||||||
|
open(out, 'w', encoding='utf-8').write(new)
|
||||||
|
# 9 代表這一頁沒有需要改寫的連結,不必再寫一次。
|
||||||
|
raise SystemExit(0 if new != text else 9)
|
||||||
|
PY
|
||||||
|
if [ "$rc" -eq 9 ]; then continue; fi
|
||||||
|
if [ "$rc" -ne 0 ]; then
|
||||||
|
printf '%s/%s:連結改寫失敗。\n' "$nrepo" "$npage" >> "$work/failed"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
if ! sh "$gitea" wiki-put "$nrepo" "$npage" "$work/body/$idx.new" >/dev/null; then
|
||||||
|
printf '%s/%s:連結改寫後寫不回去。\n' "$nrepo" "$npage" >> "$work/failed"
|
||||||
|
fi
|
||||||
|
done 3< "$work/done"
|
||||||
|
|
||||||
|
# ---- 搬移第三步:確認新頁讀得回來,才刪舊頁 ----
|
||||||
|
: > "$work/moved"
|
||||||
|
while IFS="$TAB" read -r orepo opage nrepo npage idx <&3; do
|
||||||
|
[ -n "$opage" ] || continue
|
||||||
|
if ! sh "$gitea" wiki-get "$nrepo" "$npage" >/dev/null 2>&1; then
|
||||||
|
printf '%s/%s:新頁讀不回來,舊頁保留不刪。\n' "$nrepo" "$npage" >> "$work/failed"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
if ! sh "$gitea" wiki-delete "$orepo" "$opage" >/dev/null; then
|
||||||
|
printf '%s/%s:新頁已就位,舊頁刪不掉,請人工刪除。\n' "$orepo" "$opage" >> "$work/failed"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
printf '%s/%s → %s/%s\n' "$orepo" "$opage" "$nrepo" "$npage" >> "$work/moved"
|
||||||
|
done 3< "$work/done"
|
||||||
|
|
||||||
|
echo '已搬移'
|
||||||
|
if [ -s "$work/moved" ]; then sed 's/^/ /' "$work/moved"; else echo ' (無)'; fi
|
||||||
|
|
||||||
|
if [ -s "$work/manual" ]; then
|
||||||
|
echo '需人工確認'
|
||||||
|
sed 's/^/ /' "$work/manual"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -s "$work/failed" ]; then
|
||||||
|
echo '搬移失敗'
|
||||||
|
sed 's/^/ /' "$work/failed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [ -s "$work/orphans" ] || [ -s "$work/refs" ] || [ -s "$work/manual" ]; then exit 3; fi
|
||||||
|
exit 0
|
||||||
Executable
+44
@@ -0,0 +1,44 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
# page-name.sh — wiki 頁名樣式的唯一正本。
|
||||||
|
#
|
||||||
|
# 為什麼要有這支腳本:頁名樣式散在多支腳本與技能裡各寫一份,改一次規則就得
|
||||||
|
# 到處找。集中成一支,比對的人與比對的程式讀的是同一條式子。
|
||||||
|
#
|
||||||
|
# 用法:
|
||||||
|
# page-name.sh regex # 印出合法頁名的 ERE
|
||||||
|
# page-name.sh check <page> # 驗證單一頁名
|
||||||
|
#
|
||||||
|
# 規則:
|
||||||
|
# 合法頁名是 {前綴}_{尾段}。前綴取十四種內容型別之一。
|
||||||
|
# 尾段四選一:CONTENTS 是目錄頁;40 碼大寫十六進位是內容頁;
|
||||||
|
# 8 碼大寫十六進位,以及 H 加 7 碼大寫十六進位,留給還沒搬過來的舊頁。
|
||||||
|
#
|
||||||
|
# 為什麼舊頁收兩種樣式:舊演算法算出前 8 碼後,首碼落在 0-9ABC 就改寫成 H 加前 7 碼。
|
||||||
|
# 十六個首碼裡有十三個會命中,既有舊頁名絕大多數長成 H 開頭那一種。只收純 8 碼,
|
||||||
|
# 等於把八成的舊頁判成不合法,先驗頁名的技能就再也讀不到它們。
|
||||||
|
#
|
||||||
|
# 為什麼 CONTENTS 不能當前綴:CONTENTS 只用來解目錄頁的存取庫,它自己沒有頁。
|
||||||
|
# 放行 CONTENTS_CONTENTS 或 CONTENTS_{HASH},會建出一個規格上不存在的頁。
|
||||||
|
# 結束碼: 0=合法 1=不合法 2=用法錯誤
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
PREFIXES='QUESTION|PLAN|ANALYZE|DELIVER|MAINTAIN|REPO|LOG|LEARN|ERROR|CHECK|REPORT|SKILLSET|TOOLING|MONITOR'
|
||||||
|
RE="^($PREFIXES)_(CONTENTS|[0-9A-F]{8}|H[0-9A-F]{7}|[0-9A-F]{40})\$"
|
||||||
|
|
||||||
|
cmd="${1-}"
|
||||||
|
case "$cmd" in
|
||||||
|
regex)
|
||||||
|
[ "$#" -eq 1 ] || { echo 'usage: page-name.sh regex' >&2; exit 2; }
|
||||||
|
printf '%s\n' "$RE" ;;
|
||||||
|
check)
|
||||||
|
page="${2-}"
|
||||||
|
[ "$#" -eq 2 ] && [ -n "$page" ] || { echo 'usage: page-name.sh check <page>' >&2; exit 2; }
|
||||||
|
if printf '%s\n' "$page" | grep -Eq "$RE"; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "[jsc][gitea][ERR]:頁名不合規則:$page" >&2
|
||||||
|
exit 1 ;;
|
||||||
|
*)
|
||||||
|
echo 'usage: page-name.sh regex | page-name.sh check <page>' >&2
|
||||||
|
exit 2 ;;
|
||||||
|
esac
|
||||||
Executable
+178
@@ -0,0 +1,178 @@
|
|||||||
|
#!/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
|
||||||
Reference in New Issue
Block a user