Files
mkdocs/add_docs_to_mkdocs.py
2026-01-13 11:22:33 +08:00

291 lines
9.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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()