2026-01-12 11:35:01 +08:00
|
|
|
|
#!/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)
|
|
|
|
|
|
|
2026-01-13 11:22:33 +08:00
|
|
|
|
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
|
|
|
|
|
|
|
2026-01-12 11:35:01 +08:00
|
|
|
|
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():
|
2026-01-13 11:22:33 +08:00
|
|
|
|
print("开始自动同步docs目录和mkdocs.yml导航配置...")
|
2026-01-12 11:35:01 +08:00
|
|
|
|
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)} 个文件")
|
|
|
|
|
|
|
2026-01-13 11:22:33 +08:00
|
|
|
|
# 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. 找出缺失的文件(需要添加到导航的)
|
2026-01-12 11:35:01 +08:00
|
|
|
|
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)
|
|
|
|
|
|
|
2026-01-13 11:22:33 +08:00
|
|
|
|
# 6. 汇总变更
|
|
|
|
|
|
has_changes = len(deleted_files) > 0 or len(missing_files) > 0
|
|
|
|
|
|
|
|
|
|
|
|
if not has_changes:
|
|
|
|
|
|
print("\n✓ 导航配置已是最新,无需更新")
|
2026-01-12 11:35:01 +08:00
|
|
|
|
return
|
|
|
|
|
|
|
2026-01-13 11:22:33 +08:00
|
|
|
|
# 显示变更摘要
|
|
|
|
|
|
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}")
|
2026-01-12 11:35:01 +08:00
|
|
|
|
|
2026-01-13 11:22:33 +08:00
|
|
|
|
# 7. 确认是否继续
|
|
|
|
|
|
print("\n6. 是否继续更新mkdocs.yml?")
|
2026-01-12 11:35:01 +08:00
|
|
|
|
response = input(" 输入 'y' 继续,其他键取消: ")
|
|
|
|
|
|
|
|
|
|
|
|
if response.lower() != 'y':
|
|
|
|
|
|
print("操作已取消")
|
|
|
|
|
|
return
|
|
|
|
|
|
|
2026-01-13 11:22:33 +08:00
|
|
|
|
# 8. 添加文件到导航
|
|
|
|
|
|
print("\n7. 正在更新mkdocs.yml...")
|
|
|
|
|
|
if missing_files:
|
|
|
|
|
|
updated_nav = add_files_to_nav(updated_nav, missing_files)
|
2026-01-12 11:35:01 +08:00
|
|
|
|
|
2026-01-13 11:22:33 +08:00
|
|
|
|
# 9. 保存配置
|
2026-01-12 11:35:01 +08:00
|
|
|
|
save_mkdocs_nav(updated_nav)
|
|
|
|
|
|
|
|
|
|
|
|
print("\n" + "=" * 60)
|
|
|
|
|
|
print("完成!mkdocs.yml已更新")
|
2026-01-13 11:22:33 +08:00
|
|
|
|
if deleted_files:
|
|
|
|
|
|
print(f"删除了 {len(deleted_files)} 个已不存在的文件")
|
|
|
|
|
|
if missing_files:
|
|
|
|
|
|
print(f"添加了 {len(missing_files)} 个新文件到导航中")
|
2026-01-12 11:35:01 +08:00
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
|
|
main()
|