#!/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()