diff options
| author | eltrac <hi@eltr.ac> | 2026-03-08 11:23:06 +0800 |
|---|---|---|
| committer | eltrac <hi@eltr.ac> | 2026-03-08 11:23:06 +0800 |
| commit | facd5a84f298292c4eb5676c5bea6b4677782427 (patch) | |
| tree | ff2562b8a69fd19a792e366de3d7dd387d2cd428 | |
Initial commit
| -rw-r--r-- | hugo-convert-yaml-frontmatter-to-toml/README.md | 12 | ||||
| -rw-r--r-- | hugo-convert-yaml-frontmatter-to-toml/script.py | 170 |
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() |
