0904
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
gen_changelog.py — 生成 PocketBase 风格的 changelogs JSON(无需 PocketBase 服务)
|
||||
|
||||
用途:
|
||||
你在本地维护一个 Markdown 源文件(按 `## <时间戳>` 分段写 changelog),
|
||||
本脚本把它导出成前端 change-logs.html 所 fetch 的 tamabox.changelogs.json。
|
||||
输出格式完全兼容 PocketBase list API 的返回结构,但无需部署任何后端服务,
|
||||
因此不会在你的 1C1G 服务器上多占哪怕 1 MB 常驻内存。
|
||||
|
||||
输出位置:static/tamabox.changelogs.json —— 该文件被 static 包以 //go:embed 打入
|
||||
二进制,由 flamego.Static 在 /static 路径同源提供,前端直接 fetch 同源路径,
|
||||
不再依赖任何外部域名(去掉 api-direct.337845818.best 等外链,降低可用性/安全风险)。
|
||||
|
||||
源文件格式(dev/changelog-source.md 示例):
|
||||
## 2024-03-04 21:45:55
|
||||
新增:用户私信功能上线。
|
||||
修复:移动端问题列表间距过窄。
|
||||
|
||||
## 2024-02-29 00:00:55
|
||||
优化:首页加载速度提升约 30%。
|
||||
|
||||
注意:
|
||||
- 时间戳支持 `YYYY-MM-DD HH:MM:SS` / `YYYY-MM-DD` / 带毫秒的 `...fffZ` / ISO 格式,
|
||||
脚本会统一归一化为 PocketBase 风格的 `YYYY-MM-DD HH:MM:SS.fffZ`(UTC)。
|
||||
- 正文里的换行默认会被转成 `<br>`,因为前端用 x-html 渲染;传 --no-br 可关闭。
|
||||
- 导出时按时间倒序排列,最新一条排在最前(前端读 items[0] 即取最新)。
|
||||
|
||||
用法:
|
||||
python dev/gen_changelog.py
|
||||
python dev/gen_changelog.py 源文件.md 输出.json
|
||||
python dev/gen_changelog.py 源文件.md 输出.json --no-br
|
||||
"""
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from datetime import datetime, timezone
|
||||
|
||||
COLLECTION_ID = "tamabox"
|
||||
COLLECTION_NAME = "tamabox-changelogs"
|
||||
DEFAULT_SRC = "dev/changelog-source.md"
|
||||
DEFAULT_OUT = "static/tamabox.changelogs.json"
|
||||
|
||||
TS_LINE = re.compile(r"^##\s+(.+?)\s*$")
|
||||
FULL_TS = re.compile(r"^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}Z$")
|
||||
|
||||
|
||||
def normalize_ts(raw: str) -> str:
|
||||
"""把用户输入的时间归一化为 PocketBase 风格时间戳:YYYY-MM-DD HH:MM:SS.fffZ(UTC)。"""
|
||||
raw = raw.strip()
|
||||
if FULL_TS.match(raw):
|
||||
return raw
|
||||
for fmt in ("%Y-%m-%d %H:%M:%S.%fZ", "%Y-%m-%d %H:%M:%S",
|
||||
"%Y-%m-%d %H:%M", "%Y-%m-%d"):
|
||||
try:
|
||||
dt = datetime.strptime(raw, fmt)
|
||||
return dt.strftime("%Y-%m-%d %H:%M:%S") + ".000Z"
|
||||
except ValueError:
|
||||
continue
|
||||
try:
|
||||
dt = datetime.fromisoformat(raw.replace("Z", "+00:00"))
|
||||
return dt.astimezone(timezone.utc).strftime("%Y-%m-%d %H:%M:%S") + ".000Z"
|
||||
except ValueError:
|
||||
sys.exit(f"[错误] 无法解析时间戳: {raw!r}")
|
||||
|
||||
|
||||
def parse_source(text: str):
|
||||
"""按 `## <时间戳>` 分段,返回 [(ts, content), ...],顺序与源文件一致。"""
|
||||
entries, cur_ts, buf = [], None, []
|
||||
for line in text.splitlines():
|
||||
m = TS_LINE.match(line)
|
||||
if m:
|
||||
if cur_ts is not None:
|
||||
entries.append((cur_ts, "\n".join(buf).strip()))
|
||||
cur_ts, buf = m.group(1).strip(), []
|
||||
elif cur_ts is not None:
|
||||
buf.append(line)
|
||||
if cur_ts is not None:
|
||||
entries.append((cur_ts, "\n".join(buf).strip()))
|
||||
if not entries:
|
||||
sys.exit("[错误] 源文件未找到任何 '## <时间戳>' 分段")
|
||||
return entries
|
||||
|
||||
|
||||
def build(entries, br=True):
|
||||
"""把条目导出为 PocketBase list API 风格 JSON(最新在前)。"""
|
||||
parsed = [(normalize_ts(ts), c) for ts, c in entries]
|
||||
parsed.sort(key=lambda x: x[0], reverse=True) # 倒序:最新 published_at 在前
|
||||
items = []
|
||||
for ts, content in parsed:
|
||||
items.append({
|
||||
"collectionId": COLLECTION_ID,
|
||||
"collectionName": COLLECTION_NAME,
|
||||
"content": content.replace("\n", "<br>\n") if br else content,
|
||||
"created": ts,
|
||||
"id": "none",
|
||||
"published_at": ts,
|
||||
"updated": ts,
|
||||
})
|
||||
return {
|
||||
"page": 1,
|
||||
"perPage": len(items) or 1,
|
||||
"totalItems": len(items),
|
||||
"totalPages": 1,
|
||||
"items": items,
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
args = sys.argv[1:]
|
||||
br = "--no-br" not in args
|
||||
paths = [a for a in args if not a.startswith("--")]
|
||||
src = paths[0] if len(paths) > 0 else DEFAULT_SRC
|
||||
out = paths[1] if len(paths) > 1 else DEFAULT_OUT
|
||||
try:
|
||||
with open(src, encoding="utf-8") as f:
|
||||
text = f.read()
|
||||
except FileNotFoundError:
|
||||
sys.exit(f"[错误] 找不到源文件: {src}")
|
||||
data = build(parse_source(text), br=br)
|
||||
with open(out, "w", encoding="utf-8") as f:
|
||||
json.dump(data, f, ensure_ascii=False, indent=2)
|
||||
latest = data["items"][0]["published_at"] if data["items"] else "N/A"
|
||||
print(f"[完成] 已生成 {out}({data['totalItems']} 条记录,最新发布于 {latest})")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user