#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ 自动将docs目录下的文件添加到mkdocs.yml导航配置中 """ import os import sys import re import yaml from pathlib import Path def load_mkdocs_nav(mkdocs_path='mkdocs.yml'): """加载mkdocs.yml中的导航配置""" try: with open(mkdocs_path, 'r', encoding='utf-8') as f: content = f.read() # 使用正则表达式提取nav部分 nav_match = re.search(r'nav:\s*\n(.*?)(?=\n\w+:|$)', content, re.DOTALL) if not nav_match: print("错误: 找不到nav配置") sys.exit(1) nav_content = nav_match.group(1) # 解析YAML格式的nav内容 # 为nav内容添加适当的缩进 nav_yaml = "nav:\n" + nav_content config = yaml.safe_load(nav_yaml) return config.get('nav', []) except FileNotFoundError: print(f"错误: 找不到文件 {mkdocs_path}") sys.exit(1) except Exception as e: print(f"错误: 解析文件失败: {e}") sys.exit(1) def save_mkdocs_nav(nav, mkdocs_path='mkdocs.yml'): """保存导航配置到mkdocs.yml""" try: with open(mkdocs_path, 'r', encoding='utf-8') as f: content = f.read() # 生成新的nav内容 nav_yaml = yaml.dump({'nav': nav}, allow_unicode=True, default_flow_style=False, sort_keys=False) # 移除第一行的"nav:" nav_lines = nav_yaml.split('\n') new_nav_content = '\n'.join(nav_lines[1:]) # 跳过第一行 # 替换原有的nav部分,使用re.escape避免特殊字符问题 nav_pattern = r'(nav:\s*\n)(.*?)(?=\n\w+:|$)' # 对new_nav_content进行转义处理 escaped_content = re.escape(new_nav_content) # 但我们需要保留实际的换行符等,所以使用更简单的方法 new_content = re.sub(nav_pattern, r'\1' + new_nav_content, content, flags=re.DOTALL) with open(mkdocs_path, 'w', encoding='utf-8') as f: f.write(new_content) print(f"✓ 已更新 {mkdocs_path}") except Exception as e: print(f"错误: 保存文件失败: {e}") sys.exit(1) def get_all_md_files(docs_dir='docs'): """获取docs目录下的所有.md文件""" md_files = [] for root, dirs, files in os.walk(docs_dir): for file in files: if file.endswith('.md'): rel_path = os.path.relpath(os.path.join(root, file), docs_dir) # 将Windows路径分隔符转换为Unix风格(/) rel_path = rel_path.replace('\\', '/') md_files.append(rel_path) return sorted(md_files) def get_nav_files(nav): """从导航配置中提取所有文件路径""" nav_files = [] def extract_files(item): if isinstance(item, dict): for key, value in item.items(): if isinstance(value, list): for subitem in value: extract_files(subitem) elif isinstance(value, str): nav_files.append(value) elif isinstance(item, str): nav_files.append(item) for item in nav: extract_files(item) return nav_files def categorize_file(file_path): """根据文件路径分类文件""" path_parts = file_path.split('/') # 如果文件在根目录 if len(path_parts) == 1: return "其他" # 根据目录名分类 category_map = { '技术文档': '技术文档', '开发指南': '开发指南', '学习笔记': '学习笔记', 'DevOps平台': 'DevOps平台', 'cursor': 'Cursor工具', 'Obsidian': 'Obsidian笔记' } first_dir = path_parts[0] # 如果目录名在映射中,使用映射的名称,否则使用目录名 return category_map.get(first_dir, first_dir) def remove_deleted_files_from_nav(nav, existing_files=None, docs_dir='docs'): """从导航配置中删除已不存在的文件""" deleted_files = [] def clean_item(item): """递归清理导航项,删除不存在的文件""" if isinstance(item, dict): # 处理字典类型(分类) cleaned_dict = {} for key, value in item.items(): if isinstance(value, list): # 递归处理列表 cleaned_list = [] for subitem in value: cleaned = clean_item(subitem) if cleaned is not None: cleaned_list.append(cleaned) # 如果列表不为空,保留分类 if cleaned_list: cleaned_dict[key] = cleaned_list elif isinstance(value, str): # 检查文件是否存在 file_path = os.path.join(docs_dir, value.replace('/', os.sep)) if os.path.exists(file_path) or value == 'index.md': cleaned_dict[key] = value else: deleted_files.append(value) else: # 其他类型,保留 cleaned_dict[key] = value # 如果字典为空,返回None表示删除 return cleaned_dict if cleaned_dict else None elif isinstance(item, str): # 检查文件是否存在 file_path = os.path.join(docs_dir, item.replace('/', os.sep)) if os.path.exists(file_path) or item == 'index.md': return item else: deleted_files.append(item) return None else: # 其他类型,保留 return item # 处理导航列表 new_nav = [] for item in nav: cleaned = clean_item(item) if cleaned is not None: new_nav.append(cleaned) return new_nav, deleted_files def add_files_to_nav(nav, missing_files): """将缺失的文件添加到导航配置中""" # 创建分类字典 categories = {} for file_path in missing_files: category = categorize_file(file_path) if category not in categories: categories[category] = [] categories[category].append(file_path) # 将文件添加到现有分类或创建新分类 for category, files in categories.items(): category_found = False # 查找是否已存在该分类 for i, item in enumerate(nav): if isinstance(item, dict) and category in item: # 添加到现有分类 existing_files = set(item[category]) for file_path in files: if file_path not in existing_files: item[category].append(file_path) category_found = True print(f"✓ 已将 {len(files)} 个文件添加到 '{category}' 分类") break # 如果分类不存在,创建新分类 if not category_found: nav.append({category: sorted(files)}) print(f"✓ 已创建新分类 '{category}' 并添加 {len(files)} 个文件") return nav def main(): print("开始自动同步docs目录和mkdocs.yml导航配置...") print("=" * 60) # 1. 加载当前导航配置 print("1. 加载mkdocs.yml导航配置...") nav = load_mkdocs_nav() # 2. 获取所有.md文件 print("2. 扫描docs目录下的所有.md文件...") all_md_files = get_all_md_files() print(f" 找到 {len(all_md_files)} 个.md文件") # 3. 获取导航中的文件 print("3. 分析当前导航配置...") nav_files = get_nav_files(nav) print(f" 导航中已有 {len(nav_files)} 个文件") # 4. 检查并删除已删除的文件 print("4. 检查已删除的文件...") updated_nav, deleted_files = remove_deleted_files_from_nav(nav, all_md_files) if deleted_files: print(f" 发现 {len(deleted_files)} 个文件已删除:") for i, file_path in enumerate(deleted_files, 1): print(f" {i:2d}. {file_path}") nav = updated_nav # 重新获取导航文件列表(已更新) nav_files = get_nav_files(nav) else: print(" ✓ 没有发现已删除的文件") # 5. 找出缺失的文件(需要添加到导航的) missing_files = [] for md_file in all_md_files: # 将路径统一为Unix风格进行比较 if md_file not in nav_files and md_file != 'index.md': missing_files.append(md_file) # 6. 汇总变更 has_changes = len(deleted_files) > 0 or len(missing_files) > 0 if not has_changes: print("\n✓ 导航配置已是最新,无需更新") return # 显示变更摘要 print("\n5. 变更摘要:") if deleted_files: print(f" 将删除 {len(deleted_files)} 个已不存在的文件") if missing_files: print(f" 将添加 {len(missing_files)} 个新文件:") for i, file_path in enumerate(missing_files, 1): print(f" {i:2d}. {file_path}") # 7. 确认是否继续 print("\n6. 是否继续更新mkdocs.yml?") response = input(" 输入 'y' 继续,其他键取消: ") if response.lower() != 'y': print("操作已取消") return # 8. 添加文件到导航 print("\n7. 正在更新mkdocs.yml...") if missing_files: updated_nav = add_files_to_nav(updated_nav, missing_files) # 9. 保存配置 save_mkdocs_nav(updated_nav) print("\n" + "=" * 60) print("完成!mkdocs.yml已更新") if deleted_files: print(f"删除了 {len(deleted_files)} 个已不存在的文件") if missing_files: print(f"添加了 {len(missing_files)} 个新文件到导航中") if __name__ == '__main__': main()