summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoreltrac <hi@eltr.ac>2026-03-08 11:23:06 +0800
committereltrac <hi@eltr.ac>2026-03-08 11:23:06 +0800
commitfacd5a84f298292c4eb5676c5bea6b4677782427 (patch)
treeff2562b8a69fd19a792e366de3d7dd387d2cd428
Initial commit
-rw-r--r--hugo-convert-yaml-frontmatter-to-toml/README.md12
-rw-r--r--hugo-convert-yaml-frontmatter-to-toml/script.py170
2 files changed, 182 insertions, 0 deletions
diff --git a/hugo-convert-yaml-frontmatter-to-toml/README.md b/hugo-convert-yaml-frontmatter-to-toml/README.md
new file mode 100644
index 0000000..d2655df
--- /dev/null
+++ b/hugo-convert-yaml-frontmatter-to-toml/README.md
@@ -0,0 +1,12 @@
+这个 Python 脚本使用 Kimi K2.5 Instant 生成,提示词如下:
+
+```
+我需要你帮我写个 Python 脚本,把我 Hugo 网站的 `content` 目录下所有的 markdown 文件的 frontmatter 改为 TOML 格式。其中有一些是 TOML,有一些是 YAML。以 `---` 开头文件就是 YAML 做 frontmatter 的,只处理这些文件;TOML frontmatter 是 `+++` 开头的
+```
+
+## 使用方法
+
+1. 安装依赖 `pip install pyyaml tomli-w`
+2. 先预览改动 `python script.py --dry-run` 或者 `uv run script.py --dry-run`
+3. 确认后应用修改 `python script.py` 或者 `uv run script.py`
+
diff --git a/hugo-convert-yaml-frontmatter-to-toml/script.py b/hugo-convert-yaml-frontmatter-to-toml/script.py
new file mode 100644
index 0000000..80ea276
--- /dev/null
+++ b/hugo-convert-yaml-frontmatter-to-toml/script.py
@@ -0,0 +1,170 @@
+#!/usr/bin/env python3
+"""
+Hugo Frontmatter 转换脚本
+将 YAML 格式 (---) 转换为 TOML 格式 (+++)
+"""
+
+import os
+import re
+import sys
+from pathlib import Path
+
+import yaml # pip install pyyaml
+import tomli_w # pip install tomli-w
+
+
+def yaml_to_toml_frontmatter(content: str) -> str:
+ """
+ 将 YAML frontmatter 转换为 TOML frontmatter
+ """
+ # 匹配 YAML frontmatter: ---\n...\n---
+ yaml_pattern = r'^---\s*\n(.*?)\n---\s*\n'
+ match = re.match(yaml_pattern, content, re.DOTALL)
+
+ if not match:
+ return None # 不是 YAML frontmatter 格式
+
+ yaml_content = match.group(1)
+ body_content = content[match.end():]
+
+ # 解析 YAML
+ try:
+ data = yaml.safe_load(yaml_content)
+ if data is None:
+ data = {}
+ except yaml.YAMLError as e:
+ raise ValueError(f"YAML 解析错误: {e}")
+
+ # 转换为 TOML
+ # tomli_w 需要 dict,且不支持 None 值,需要处理
+ def clean_for_toml(obj):
+ if isinstance(obj, dict):
+ return {k: clean_for_toml(v) for k, v in obj.items() if v is not None}
+ elif isinstance(obj, list):
+ return [clean_for_toml(item) for item in obj if item is not None]
+ elif obj is None:
+ return ""
+ return obj
+
+ clean_data = clean_for_toml(data)
+
+ # 生成 TOML
+ try:
+ toml_bytes = tomli_w.dumps(clean_data)
+ toml_str = toml_bytes.decode('utf-8') if isinstance(toml_bytes, bytes) else toml_bytes
+ except Exception as e:
+ raise ValueError(f"TOML 生成错误: {e}")
+
+ # 组合新的 frontmatter
+ new_content = f"+++\n{toml_str}+++\n{body_content}"
+
+ return new_content
+
+
+def process_file(file_path: Path, dry_run: bool = False) -> bool:
+ """
+ 处理单个文件
+ 返回: 是否进行了转换
+ """
+ try:
+ content = file_path.read_text(encoding='utf-8')
+ except Exception as e:
+ print(f"❌ 读取失败: {file_path} - {e}")
+ return False
+
+ # 检查是否是 YAML frontmatter (--- 开头)
+ if not content.startswith('---'):
+ # 检查是否是 TOML frontmatter (+++ 开头),跳过
+ if content.startswith('+++'):
+ print(f"⏭️ 跳过 (已是 TOML): {file_path}")
+ else:
+ print(f"⏭️ 跳过 (无 frontmatter): {file_path}")
+ return False
+
+ # 转换
+ try:
+ new_content = yaml_to_toml_frontmatter(content)
+ if new_content is None:
+ print(f"⏭️ 跳过 (格式不匹配): {file_path}")
+ return False
+ except ValueError as e:
+ print(f"❌ 转换失败: {file_path} - {e}")
+ return False
+
+ # 写入文件
+ if not dry_run:
+ try:
+ # 备份原文件
+ backup_path = file_path.with_suffix(file_path.suffix + '.bak')
+ file_path.rename(backup_path)
+
+ # 写入新内容
+ file_path.write_text(new_content, encoding='utf-8')
+
+ # 删除备份(可选,注释掉这行会保留备份)
+ # backup_path.unlink()
+
+ print(f"✅ 已转换: {file_path}")
+ except Exception as e:
+ print(f"❌ 写入失败: {file_path} - {e}")
+ return False
+ else:
+ print(f"[预览] 将转换: {file_path}")
+ # 打印前 500 字符预览
+ preview = new_content[:500] + "..." if len(new_content) > 500 else new_content
+ print(preview)
+ print("-" * 50)
+
+ return True
+
+
+def main():
+ # 配置
+ content_dir = Path("content") # Hugo content 目录
+ dry_run = False # 设为 True 预览而不实际修改
+
+ # 检查命令行参数
+ if len(sys.argv) > 1:
+ if sys.argv[1] in ['--dry-run', '-n', '--preview']:
+ dry_run = True
+ print("🔍 预览模式 (不会实际修改文件)")
+ else:
+ content_dir = Path(sys.argv[1])
+
+ if not content_dir.exists():
+ print(f"❌ 目录不存在: {content_dir}")
+ sys.exit(1)
+
+ # 查找所有 markdown 文件
+ md_extensions = {'.md', '.markdown', '.mdown', '.mkd'}
+ md_files = [
+ f for f in content_dir.rglob('*')
+ if f.is_file() and f.suffix.lower() in md_extensions
+ ]
+
+ print(f"📁 扫描目录: {content_dir.absolute()}")
+ print(f"📄 找到 {len(md_files)} 个 Markdown 文件\n")
+
+ converted = 0
+ skipped = 0
+ failed = 0
+
+ for file_path in md_files:
+ result = process_file(file_path, dry_run)
+ if result:
+ converted += 1
+ elif result is False:
+ # 区分失败和跳过
+ pass
+
+ print(f"\n{'='*50}")
+ print(f"处理完成:")
+ print(f" - 转换: {converted} 个文件")
+ print(f" - 跳过: {len(md_files) - converted} 个文件")
+
+ if dry_run:
+ print(f"\n💡 这是预览模式,实际运行请去掉 --dry-run 参数")
+
+
+if __name__ == "__main__":
+ main()