110 lines
3.6 KiB
Python
110 lines
3.6 KiB
Python
#!/usr/bin/env python3
|
||||
|
|
# -*- coding: utf-8 -*-
|
|||
|
|
"""
|
|||
|
|
将所有包含 'status: hidden' 的文章标记为草稿 (draft: true),并清理冲突的设置。
|
|||
|
|
|
|||
|
|
原 PowerShell 版本:scripts/add_draft_to_hidden.ps1
|
|||
|
|
改写原因:Gitea 自建 runner 镜像不含 pwsh,改用 Python 3(GitHub/Gitea 均自带)。
|
|||
|
|
|
|||
|
|
对每篇 content/posts 下的 .md:
|
|||
|
|
1. 若 frontmatter 含 status: hidden
|
|||
|
|
a. draft: false -> draft: true
|
|||
|
|
b. 注释掉冲突的 status: public
|
|||
|
|
c. 若无 draft: true 则在 status: hidden 行后补一行
|
|||
|
|
2. 安全检查:新内容非空、且长度不低于原内容的 90%,否则跳过保存
|
|||
|
|
3. 以 UTF-8(无 BOM)写回
|
|||
|
|
"""
|
|||
|
|
import os
|
|||
|
|
import re
|
|||
|
|
import sys
|
|||
|
|
|
|||
|
|
POSTS_PATH = "content/posts"
|
|||
|
|
|
|||
|
|
# 与原脚本保持一致的正则(多行模式)
|
|||
|
|
RE_STATUS_HIDDEN = re.compile(r'^status:\s*("hidden"|hidden)', re.M)
|
|||
|
|
RE_DRAFT_FALSE = re.compile(r'^draft:\s*false', re.M)
|
|||
|
|
RE_STATUS_PUBLIC = re.compile(r'^status:\s*("public"|public)', re.M)
|
|||
|
|
RE_DRAFT_TRUE = re.compile(r'^draft:\s*true', re.M)
|
|||
|
|
RE_STATUS_HIDDEN_LINE = re.compile(r'^(status:\s*("hidden"|hidden).*$)', re.M)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def main() -> int:
|
|||
|
|
if not os.path.isdir(POSTS_PATH):
|
|||
|
|
print(f"Path not found: {POSTS_PATH}", file=sys.stderr)
|
|||
|
|
return 1
|
|||
|
|
|
|||
|
|
md_files = []
|
|||
|
|
for root, _dirs, files in os.walk(POSTS_PATH):
|
|||
|
|
for name in files:
|
|||
|
|
if name.endswith(".md"):
|
|||
|
|
md_files.append(os.path.join(root, name))
|
|||
|
|
md_files.sort()
|
|||
|
|
|
|||
|
|
count = 0
|
|||
|
|
print("Starting scan for hidden posts cleanup...")
|
|||
|
|
|
|||
|
|
for path in md_files:
|
|||
|
|
with open(path, "r", encoding="utf-8") as f:
|
|||
|
|
content = f.read()
|
|||
|
|
|
|||
|
|
if not content:
|
|||
|
|
print(f" Skipping empty file: {path}")
|
|||
|
|
continue
|
|||
|
|
|
|||
|
|
# 仅处理含 status: hidden 的文件(与原脚本一致,仅匹配 frontmatter 行首)
|
|||
|
|
if not RE_STATUS_HIDDEN.search(content):
|
|||
|
|
continue
|
|||
|
|
|
|||
|
|
modified = False
|
|||
|
|
new_content = content
|
|||
|
|
|
|||
|
|
# 1. draft: false -> draft: true
|
|||
|
|
if RE_DRAFT_FALSE.search(new_content):
|
|||
|
|
new_content = RE_DRAFT_FALSE.sub("draft: true", new_content)
|
|||
|
|
modified = True
|
|||
|
|
print(f" Fixed 'draft: false' in {path}")
|
|||
|
|
|
|||
|
|
# 2. 注释掉 status: public
|
|||
|
|
if RE_STATUS_PUBLIC.search(new_content):
|
|||
|
|
new_content = RE_STATUS_PUBLIC.sub(
|
|||
|
|
r"# \g<0> # Auto-hidden by script", new_content
|
|||
|
|
)
|
|||
|
|
modified = True
|
|||
|
|
print(f" Commented out 'status: public' in {path}")
|
|||
|
|
|
|||
|
|
# 3. 确保存在 draft: true
|
|||
|
|
if not RE_DRAFT_TRUE.search(new_content):
|
|||
|
|
new_content = RE_STATUS_HIDDEN_LINE.sub(
|
|||
|
|
lambda m: f"{m.group(1)}\ndraft: true", new_content
|
|||
|
|
)
|
|||
|
|
modified = True
|
|||
|
|
print(f" Added 'draft: true' to {path}")
|
|||
|
|
|
|||
|
|
if modified:
|
|||
|
|
# 安全检查 1:新内容非空
|
|||
|
|
if not new_content:
|
|||
|
|
print(
|
|||
|
|
f"CRITICAL: New content is empty for {path}. Skipping save.",
|
|||
|
|
file=sys.stderr,
|
|||
|
|
)
|
|||
|
|
continue
|
|||
|
|
|
|||
|
|
# 安全检查 2:长度不应异常缩小(>10%)
|
|||
|
|
if len(new_content) < len(content) * 0.9:
|
|||
|
|
print(
|
|||
|
|
f"CRITICAL: Suspicious size drop for {path}. Skipping save.",
|
|||
|
|
file=sys.stderr,
|
|||
|
|
)
|
|||
|
|
continue
|
|||
|
|
|
|||
|
|
with open(path, "w", encoding="utf-8", newline="") as f:
|
|||
|
|
f.write(new_content)
|
|||
|
|
print(f" -> Saved changes to {path}")
|
|||
|
|
count += 1
|
|||
|
|
|
|||
|
|
print(f"Completed. Updated {count} files.")
|
|||
|
|
return 0
|
|||
|
|
|
|||
|
|
|
|||
|
|
if __name__ == "__main__":
|
|||
|
|
sys.exit(main())
|