diff --git a/tools/2_apply_docs_json.py b/tools/2_apply_docs_json.py deleted file mode 100644 index 48b0c00e..00000000 --- a/tools/2_apply_docs_json.py +++ /dev/null @@ -1,777 +0,0 @@ -import json -import os -import re -from collections import defaultdict - -# --- 配置 --- -refresh = True # 如果为 True,将清空指定版本的 tabs -DOCS_JSON_PATH = "docs.json" - -# --- 简体中文配置(docs_config) --- -plugin-dev-zh = { - "DOCS_DIR": "plugin-dev-zh", # 插件开发文档目录 - "LANGUAGE_CODE": "简体中文", # 注意:虽然变量名是 LANGUAGE_CODE,但会部署为 docs.json 中的 'version' 值。 - "FILE_EXTENSION": ".mdx", - "TARGET_TAB_NAME": "插件开发", # 新增:目标 Tab 名称 - "FILENAME_PATTERN": re.compile(r"^(\d{4})-(.*?)\.zh\.mdx$"), # 新增:文件名匹配模式 - "PWX_TO_GROUP_MAP": { - # --- PWX 到 Group 名称的映射 (统一到 "插件开发" Tab) --- - # (P, W, X) -> (tab_name, group_name, nested_group_name) - # Tab: 插件开发 - # Group: 概念与入门 - ("0", "1", "1"): ("插件开发", "概念与入门", "概览"), - ("0", "1", "3"): ("插件开发", "概念与入门", None), - # Group: 开发实践 - ("0", "2", "1"): ("插件开发", "开发实践", "快速开始"), - ("0", "2", "2"): ("插件开发", "开发实践", "开发 Dify 插件"), - # Group: 贡献与发布 - ("0", "3", "1"): ("插件开发", "贡献与发布", "行为准则与规范"), - ("0", "3", "2"): ("插件开发", "贡献与发布", "发布与上架"), - ("0", "3", "3"): ("插件开发", "贡献与发布", "常见问题解答"), - # Group: 实践案例与示例 - ("0", "4", "3"): ("插件开发", "实践案例与示例", "开发示例"), - # Group: 高级开发 - ("9", "2", "2"): ("插件开发", "高级开发", "Extension 与 Agent"), - ("9", "2", "3"): ("插件开发", "高级开发", "Extension 与 Agent"), - ("9", "4", "3"): ("插件开发", "高级开发", "Extension 与 Agent"), - ("9", "2", "4"): ("插件开发", "高级开发", "反向调用"), - # Group: Reference & Specifications - ("0", "4", "1"): ("插件开发", "Reference & Specifications", "核心规范与功能"), - }, - "DESIRED_GROUP_ORDER": [ - "概念与入门", - "开发实践", - "贡献与发布", - "实践案例与示例", - "高级开发", - "Reference & Specifications", # 确保这个在最后 - ], -} - -# --- English Configuration --- -plugin-dev-en = { - "DOCS_DIR": "plugin-dev-en", # Plugin development documentation directory - # Note: Although the variable name is LANGUAGE_CODE, it will be deployed as the 'version' value in docs.json. - "LANGUAGE_CODE": "English", - "FILE_EXTENSION": ".mdx", - "TARGET_TAB_NAME": "Plugin Development", - "FILENAME_PATTERN": re.compile(r"^(\d{4})-(.*?)\.en\.mdx$"), - "PWX_TO_GROUP_MAP": { - # --- PWX to Group Name Mapping (Unified under the "Plugin Development" Tab) --- - # (P, W, X) -> (tab_name, group_name, nested_group_name) - # Tab: Plugin Development - # Group: Concepts & Getting Started - ("0", "1", "1"): ( - "Plugin Development", - "Concepts & Getting Started", - "Overview", - ), - ("0", "1", "3"): ("Plugin Development", "Concepts & Getting Started", None), - # Group: Development Practices - ("0", "2", "1"): ("Plugin Development", "Development Practices", "Quick Start"), - ("0", "2", "2"): ( - "Plugin Development", - "Development Practices", - "Developing Dify Plugins", - ), - # Group: Contribution & Publishing - ("0", "3", "1"): ( - "Plugin Development", - "Contribution & Publishing", - "Code of Conduct & Standards", - ), - ("0", "3", "2"): ( - "Plugin Development", - "Contribution & Publishing", - "Publishing & Listing", - ), - ("0", "3", "3"): ("Plugin Development", "Contribution & Publishing", "FAQ"), - # Group: Examples & Use Cases - ("0", "4", "3"): ( - "Plugin Development", - "Examples & Use Cases", - "Development Examples", - ), - # Group: Advanced Development - ("9", "2", "2"): ( - "Plugin Development", - "Advanced Development", - "Extension & Agent", - ), - ("9", "2", "3"): ( - "Plugin Development", - "Advanced Development", - "Extension & Agent", - ), - ("9", "4", "3"): ( - "Plugin Development", - "Advanced Development", - "Extension & Agent", - ), - ("9", "2", "4"): ( - "Plugin Development", - "Advanced Development", - "Reverse Calling", - ), - # Group: Reference & Specifications - ("0", "4", "1"): ( - "Plugin Development", - "Reference & Specifications", - "Core Specifications & Features", - ), - }, - "DESIRED_GROUP_ORDER": [ - "Concepts & Getting Started", - "Development Practices", - "Contribution & Publishing", - "Examples & Use Cases", - "Advanced Development", - "Reference & Specifications", # Ensure this is last - ], -} - -# --- 日本語設定 (Japanese Configuration) --- -PLUGIN_DEV_JA = { - "DOCS_DIR": "plugin_dev_ja", # プラグイン開発ドキュメントディレクトリ - "LANGUAGE_CODE": "日本語", # 注意:変数名は LANGUAGE_CODE ですが、docs.json の 'version' 値としてデプロイされます。 - "FILE_EXTENSION": ".ja.mdx", - "TARGET_TAB_NAME": "プラグイン開発", # 対象タブ名 - "FILENAME_PATTERN": re.compile( - r"^(\d{4})-(.*?)\.ja\.mdx$" - ), # ファイル名照合パターン - "PWX_TO_GROUP_MAP": { - # --- PWX からグループ名へのマッピング(「プラグイン開発」タブに統一)--- - # (P, W, X) -> (tab_name, group_name, nested_group_name) - # Tab: プラグイン開発 - # Group: 概念と概要 - ("0", "1", "1"): ("プラグイン開発", "概念と概要", "概要"), - ("0", "1", "3"): ("プラグイン開発", "概念と概要", None), - # Group: 開発実践 - ("0", "2", "1"): ("プラグイン開発", "開発実践", "クイックスタート"), - ("0", "2", "2"): ("プラグイン開発", "開発実践", "Difyプラグインの開発"), - # Group: 貢献と公開 - ("0", "3", "1"): ("プラグイン開発", "貢献と公開", "行動規範と基準"), - ("0", "3", "2"): ("プラグイン開発", "貢献と公開", "公開と掲載"), - ("0", "3", "3"): ("プラグイン開発", "貢献と公開", "よくある質問 (FAQ)"), - # Group: 実践例とユースケース - ("0", "4", "3"): ("プラグイン開発", "実践例とユースケース", "開発例"), - # Group: 高度な開発 - ("9", "2", "2"): ("プラグイン開発", "高度な開発", "Extension と Agent"), - ("9", "2", "3"): ("プラグイン開発", "高度な開発", "Extension と Agent"), - ("9", "4", "3"): ("プラグイン開発", "高度な開発", "Extension と Agent"), - ("9", "2", "4"): ( - "プラグイン開発", - "高度な開発", - "リバースコール", - ), # Reverse Calling - # Group: リファレンスと仕様 - ("0", "4", "1"): ("プラグイン開発", "リファレンスと仕様", "コア仕様と機能"), - }, - "DESIRED_GROUP_ORDER": [ - "概念と概要", - "開発実践", - "貢献と公開", - "実践例とユースケース", - "高度な開発", - "リファレンスと仕様", # これが最後になるように確認 - ], -} - - -# --- 辅助函数 --- - - -def clear_tabs_if_refresh(navigation_data, version_code, target_tab_name, do_refresh): - """如果 do_refresh 为 True,则查找指定版本和目标 Tab,并清空其 groups 列表""" - if not do_refresh: - return False # 未执行清空 - - if not navigation_data or "versions" not in navigation_data: - print("警告: 'navigation.versions' 未找到,无法清空 tabs。") - return False - - version_found = False - tab_found_and_cleared = False - for version_nav in navigation_data.get("versions", []): - if version_nav.get("version") == version_code: - version_found = True - target_tab = None - if "tabs" in version_nav and isinstance(version_nav["tabs"], list): - for tab in version_nav["tabs"]: - if isinstance(tab, dict) and tab.get("tab") == target_tab_name: - target_tab = tab - break - - if target_tab: - if "groups" in target_tab: - target_tab["groups"] = [] - print( - f"信息: 已清空版本 '{version_code}' 下 Tab '{target_tab_name}' 的 groups (因为 refresh=True)。" - ) - tab_found_and_cleared = True - else: - # 如果 'groups' 不存在,也视为一种“清空”状态,或者可以创建一个空的 - target_tab["groups"] = [] - print( - f"信息: 版本 '{version_code}' 下 Tab '{target_tab_name}' 没有 'groups' 键,已确保其为空列表 (因为 refresh=True)。" - ) - tab_found_and_cleared = True - else: - print( - f"警告: 在版本 '{version_code}' 中未找到目标 Tab '{target_tab_name}',无法清空其 groups。" - ) - break # 找到版本后即可退出循环 - - if not version_found: - print(f"警告: 未找到版本 '{version_code}',无法清空任何 Tab。") - return False - - return tab_found_and_cleared - - -def get_page_path( - filename, docs_config -): # docs_config 参数保留,但 FILE_EXTENSION 不再用于此处的后缀移除 - """从 mdx 文件名获取 mintlify 页面路径 (固定去掉末尾 .mdx 后缀)""" - docs_dir = docs_config["DOCS_DIR"] - # 固定移除末尾的 .mdx,以保留 .zh 或 .en 等语言标识 - if filename.endswith(".mdx"): - base_filename = filename[: -len(".mdx")] - else: - # 如果不以 .mdx 结尾,则引发错误,因为这是预期格式 - raise ValueError(f"错误: 文件名 '{filename}' 不以 '.mdx' 结尾,无法处理。") - - return os.path.join(docs_dir, base_filename) - - -def extract_existing_pages(navigation_data, version_code, target_tab_name): - """递归提取指定版本和目标 Tab 下所有已存在的页面路径""" - existing_pages = set() - target_version_nav = None - target_tab_nav = None # 新增:用于存储找到的目标 Tab 对象 - - if not navigation_data or "versions" not in navigation_data: - print("警告: 'navigation.versions' 未找到") - return existing_pages, None, None # 返回三个值 - - # 查找目标版本 - for version_nav in navigation_data.get("versions", []): - if version_nav.get("version") == version_code: - target_version_nav = version_nav - break - - if not target_version_nav: - print(f"警告: 版本 '{version_code}' 在 docs.json 中未找到") - return existing_pages, None, None # 返回三个值 - - # 在目标版本中查找目标 Tab - if "tabs" in target_version_nav and isinstance(target_version_nav["tabs"], list): - for tab in target_version_nav["tabs"]: - if isinstance(tab, dict) and tab.get("tab") == target_tab_name: - target_tab_nav = tab # 存储找到的 Tab 对象 - # 仅从目标 Tab 中提取页面 - for group in tab.get("groups", []): - if isinstance(group, dict): - _recursive_extract(group, existing_pages) - break # 找到目标 Tab 后即可退出循环 - else: # 'tabs' might not exist or not be a list - target_version_nav["tabs"] = [] - - if not target_tab_nav: - print( - f"警告: 在版本 '{version_code}' 中未找到 Tab '{target_tab_name}',无法提取现有页面。" - ) - # 即使 Tab 不存在,也返回版本导航对象,以便后续可能创建 Tab - return existing_pages, target_version_nav, None - - # 返回提取到的页面、版本导航对象和目标 Tab 对象 - return existing_pages, target_version_nav, target_tab_nav - - -def _recursive_extract(group_item, pages_set): - """递归辅助函数""" - # Ensure group_item is a dictionary before proceeding - if not isinstance(group_item, dict): - return - - if "pages" in group_item and isinstance(group_item["pages"], list): - for page in group_item["pages"]: - if isinstance(page, str): - pages_set.add(page) - elif isinstance(page, dict) and "group" in page: - # Recurse into nested groups - _recursive_extract(page, pages_set) - - -def remove_obsolete_pages(target_tab_data, pages_to_remove): - """递归地从目标 Tab 的 groups 结构中移除失效页面路径。 - 注意:此函数直接修改传入的 target_tab_data 字典。 - """ - if not isinstance(target_tab_data, dict) or "groups" not in target_tab_data: - # 如果输入不是预期的 Tab 结构,则直接返回 - return - - groups = target_tab_data.get("groups", []) - if not isinstance(groups, list): - # 如果 groups 不是列表,也无法处理 - return - - # 使用索引迭代以安全地移除项 - i = 0 - while i < len(groups): - group_item = groups[i] - if isinstance(group_item, dict): - # 递归处理 group 内部的 pages - _remove_obsolete_from_group(group_item, pages_to_remove) - # 如果处理后 group 的 pages 为空(且没有嵌套 group),可以选择移除该 group - # 当前逻辑:保留空 group 结构 - if not group_item.get("pages"): - print( - f"信息: Group '{group_item.get('group')}' 清理后为空,已保留结构。" - ) - i += 1 - else: - # 如果 groups 列表中包含非字典项(不符合预期),则跳过 - i += 1 - - -def _remove_obsolete_from_group(group_dict, pages_to_remove): - """辅助函数,递归处理单个 group 或 nested group 内的 pages""" - if not isinstance(group_dict, dict) or "pages" not in group_dict: - return - - pages = group_dict.get("pages", []) - if not isinstance(pages, list): - return - - new_pages = [] - for page_item in pages: - if isinstance(page_item, str): - if page_item not in pages_to_remove: - new_pages.append(page_item) - else: - print(f" - {page_item} (从 Group '{group_dict.get('group')}' 移除)") - elif isinstance(page_item, dict) and "group" in page_item: - # 递归处理嵌套的 group - _remove_obsolete_from_group(page_item, pages_to_remove) - # 保留嵌套 group 结构,即使它变空 - if page_item or page_item.get("pages"): # 检查字典是否为空或 pages 是否存在 - new_pages.append(page_item) - else: - print( - f"信息: 嵌套 Group '{page_item.get('group')}' 清理后为空,已保留结构。" - ) - new_pages.append(page_item) # 仍然添加空的嵌套组结构 - else: - # 保留无法识别的项 - new_pages.append(page_item) - group_dict["pages"] = new_pages - - -def find_or_create_target_group( - target_version_nav, tab_name, group_name, nested_group_name -): - # 注意:target_version_nav 是特定版本对象,例如 {"version": "简体中文", "tabs": [...]} - target_tab = None - # Ensure 'tabs' exists and is a list - if "tabs" not in target_version_nav or not isinstance( - target_version_nav["tabs"], list - ): - target_version_nav["tabs"] = [] - - for tab in target_version_nav["tabs"]: - if isinstance(tab, dict) and tab.get("tab") == tab_name: - target_tab = tab - break - if target_tab is None: - target_tab = {"tab": tab_name, "groups": []} - target_version_nav["tabs"].append(target_tab) - - target_group = None - # Ensure 'groups' exists and is a list - if "groups" not in target_tab or not isinstance(target_tab["groups"], list): - target_tab["groups"] = [] - - for group in target_tab["groups"]: - if isinstance(group, dict) and group.get("group") == group_name: - target_group = group - break - if target_group is None: - target_group = {"group": group_name, "pages": []} - target_tab["groups"].append(target_group) - - # Ensure 'pages' exists in the target_group and is a list - if "pages" not in target_group or not isinstance(target_group["pages"], list): - target_group["pages"] = [] - - # Default container is the top-level group's pages list - target_pages_container = target_group["pages"] - - if nested_group_name: - target_nested_group = None - # Find existing nested group - for item in target_group["pages"]: - if isinstance(item, dict) and item.get("group") == nested_group_name: - target_nested_group = item - # Ensure pages list exists in nested group - target_pages_container = target_nested_group.setdefault("pages", []) - # Ensure it's actually a list after setdefault - if not isinstance(target_pages_container, list): - target_nested_group["pages"] = [] - target_pages_container = target_nested_group["pages"] - break - # If not found, create it - if target_nested_group is None: - target_nested_group = {"group": nested_group_name, "pages": []} - # Check if target_group['pages'] is already the container we want to add to - # This logic assumes nested groups are *always* dicts within the parent's 'pages' list - target_group["pages"].append(target_nested_group) - target_pages_container = target_nested_group["pages"] - - # Final check before returning - if not isinstance(target_pages_container, list): - # 这表示内部逻辑错误,应该引发异常 - raise RuntimeError( - f"内部错误: 无法为 Tab='{tab_name}', Group='{group_name}', Nested='{nested_group_name}' 获取有效的 pages 列表。" - ) - - return target_pages_container - - -# --- 主逻辑 --- - - -def get_group_sort_key(group_dict, docs_config): - """为排序提供 key,根据 DESIRED_GROUP_ORDER 返回索引,未知组放在最后""" - group_name = group_dict.get("group", "") - desired_order = docs_config["DESIRED_GROUP_ORDER"] - try: - return desired_order.index(group_name) - except ValueError: - return len(desired_order) # 将未在列表中的组排在最后 - - -def main( - docs_config, navigation_data -): # navigation_data: 传入内存中的 navigation 字典供直接修改 - """处理单个文档配置,并直接修改传入的 navigation_data""" - print( - f"\n--- 开始处理版本: {docs_config['LANGUAGE_CODE']} / Tab: {docs_config['TARGET_TAB_NAME']} ---" - ) - - # 从 docs_config 获取配置值 - language_code = docs_config["LANGUAGE_CODE"] - docs_dir = docs_config["DOCS_DIR"] - file_extension = docs_config["FILE_EXTENSION"] - pwx_to_group_map = docs_config["PWX_TO_GROUP_MAP"] - filename_pattern = docs_config["FILENAME_PATTERN"] # 使用配置中的 pattern - target_tab_name = docs_config["TARGET_TAB_NAME"] # 使用配置中的 tab name - - # 1. 清理或准备版本导航 (不再加载 JSON,直接使用传入的 navigation_data) - navigation = navigation_data # 使用传入的 navigation 对象进行操作 - - # 使用 language_code 和 target_tab_name 清理目标 Tab - was_refreshed = clear_tabs_if_refresh( - navigation, language_code, target_tab_name, refresh - ) - if was_refreshed: - print(f"继续执行 Tab '{target_tab_name}' 的后续页面提取和添加操作...") - - # 2. 提取目标 Tab 的现有页面或创建版本/Tab 导航 - existing_pages, target_version_nav, target_tab_nav = extract_existing_pages( - navigation, language_code, target_tab_name - ) - - if target_version_nav is None: - print(f"信息:在导航数据中未找到版本 '{language_code}',将创建。") - if "versions" not in navigation: # 确保 versions 列表存在 - navigation["versions"] = [] - target_version_nav = {"version": language_code, "tabs": []} - navigation["versions"].append(target_version_nav) - existing_pages = set() - target_tab_nav = None # 版本是新建的,Tab 肯定不存在 - - # 如果目标 Tab 不存在,需要创建它 - if target_tab_nav is None: - print( - f"信息: 在版本 '{language_code}' 中未找到 Tab '{target_tab_name}',将创建。" - ) - target_tab_nav = {"tab": target_tab_name, "groups": []} - # 确保 target_version_nav['tabs'] 是列表 - if "tabs" not in target_version_nav or not isinstance( - target_version_nav["tabs"], list - ): - target_version_nav["tabs"] = [] - target_version_nav["tabs"].append(target_tab_nav) - existing_pages = set() # 新 Tab 没有现有页面 - - print( - f"找到 {len(existing_pages)} 个已存在的页面 (版本: '{language_code}', Tab: '{target_tab_name}')。" - ) - - # 3. 扫描文件系统 (这部分不变,扫描目录下的所有匹配文件) - filesystem_pages = set() - valid_files = [] - if not os.path.isdir(docs_dir): - # 如果目录不存在,则无法继续处理此配置,引发错误 - raise FileNotFoundError( - f"错误: 配置 '{language_code}' 的文档目录 '{docs_dir}' 不存在。" - ) - else: - for filename in os.listdir(docs_dir): - # 使用配置中的 filename_pattern - if filename.endswith(file_extension) and filename_pattern.match(filename): - try: # 添加 try-except 块以捕获 get_page_path 可能引发的 ValueError - page_path = get_page_path(filename, docs_config) - filesystem_pages.add(page_path) - valid_files.append(filename) - except ValueError as e: - # 从 get_page_path 捕获到错误,打印并继续处理其他文件,或重新引发以停止 - print(f"错误处理文件 '{filename}': {e}。将跳过此文件。") - # 如果希望停止整个过程,取消注释下一行: - # raise e - print(f"在 '{docs_dir}' 找到 {len(filesystem_pages)} 个有效的文档文件。") - - # 4. 计算差异 (相对于目标 Tab 的 existing_pages) - new_files_paths = filesystem_pages - existing_pages - removed_files_paths = existing_pages - filesystem_pages - - print(f"新增文件数 (相对于 Tab '{target_tab_name}'): {len(new_files_paths)}") - print(f"移除文件数 (相对于 Tab '{target_tab_name}'): {len(removed_files_paths)}") - - # 5. 移除失效页面 (仅从目标 Tab 移除) - if removed_files_paths and target_tab_nav: # 确保目标 Tab 存在 - print(f"正在从 Tab '{target_tab_name}' 移除失效页面...") - remove_obsolete_pages( - target_tab_nav, removed_files_paths - ) # 直接传入目标 Tab 对象 - print(f"已处理从 Tab '{target_tab_name}' 移除: {removed_files_paths}") - elif removed_files_paths: - print( - f"警告: 存在失效页面 {removed_files_paths},但未找到目标 Tab '{target_tab_name}' 进行移除。" - ) - - # 6. 添加新页面 (逻辑不变,但 find_or_create_target_group 会确保添加到正确的 Tab 和 Group) - if new_files_paths: - print(f"正在向 Tab '{target_tab_name}' 添加新页面...") - new_files_sorted = sorted( - [f for f in valid_files if get_page_path(f, docs_config) in new_files_paths] - ) - - groups_to_add = defaultdict(list) - for filename in new_files_sorted: - match = filename_pattern.match(filename) # 使用配置中的 pattern - if match: - pwxy = match.group(1) - if len(pwxy) >= 3: - p, w, x = pwxy[0], pwxy[1], pwxy[2] - try: # 包裹 get_page_path 调用 - page_path = get_page_path(filename, docs_config) - except ValueError as e: - print( - f"错误处理文件 '{filename}' (添加阶段): {e}。将跳过此文件。" - ) - continue # 跳过这个文件 - - group_key = (p, w, x) - if group_key in pwx_to_group_map: - map_result = pwx_to_group_map[group_key] - current_tab_name_from_map = map_result[0] - # 强制使用配置的目标 Tab 名称 - if current_tab_name_from_map != target_tab_name: - print( - f"警告: 文件 '{filename}' 根据 PWX 映射到 Tab '{current_tab_name_from_map}',但当前配置强制处理 Tab '{target_tab_name}'。将添加到 '{target_tab_name}'。" - ) - # 始终使用配置中定义的 target_tab_name - tab_name_to_use = target_tab_name - - if len(map_result) == 3: - _, group_name, nested_group_name = map_result - else: # 兼容旧格式或只有两项的情况 - if len(map_result) >= 2: - _, group_name = map_result[:2] # 取前两项 - else: - # 处理 map_result 项数不足的情况 - print( - f"错误: PWX_TO_GROUP_MAP 中键 '{group_key}' 的值 '{map_result}' 格式不正确,至少需要两项。跳过文件 '{filename}'。" - ) - continue - nested_group_name = None # 假设没有嵌套组 - - groups_to_add[ - (tab_name_to_use, group_name, nested_group_name) - ].append(page_path) - else: - print( - f"警告: 文件 '{filename}' 的 PWX 前缀 ('{p}', '{w}', '{x}') 在 PWX_TO_GROUP_MAP 中没有找到映射,将跳过添加。" - ) - else: - # 数字前缀不足3位是文件名格式错误,应引发异常 - raise ValueError( - f"错误: 文件 '{filename}' 的数字前缀 '{pwxy}' 不足3位,无法解析 PWX。" - ) - - for ( - tab_name, - group_name, - nested_group_name, - ), pages_to_append in groups_to_add.items(): - # 确保只添加到目标 Tab 下 (此检查现在是多余的,因为上面强制使用了 target_tab_name) - # if tab_name == target_tab_name: - print( - f" 添加到 Tab='{tab_name}', Group='{group_name}', Nested='{nested_group_name or '[无]'}' : {len(pages_to_append)} 个页面" - ) - # find_or_create_target_group 现在需要 target_version_nav 来定位或创建 Tab - target_pages_list = find_or_create_target_group( - # tab_name 此时应等于 target_tab_name - target_version_nav, - tab_name, - group_name, - nested_group_name, - ) - - if isinstance(target_pages_list, list): - for new_page in pages_to_append: - if new_page not in target_pages_list: - target_pages_list.append(new_page) - print(f" + {new_page}") - else: - # find_or_create_target_group 内部出错时会抛出 RuntimeError - # 这里可以加日志,但理论上不应到达 - print( - f"错误: 未能为 Tab='{tab_name}', Group='{group_name}', Nested='{nested_group_name}' 获取有效的 pages 列表进行添加。" - ) - # else: # 这个 else 分支现在不会被触发 - # print(f"信息: 跳过向非目标 Tab '{tab_name}' 添加页面 (目标 Tab: '{target_tab_name}')。") - - # <-- 排序 Group (仅排序目标 Tab 内的 Group) --> - print(f"正在排序 Tab '{target_tab_name}' 内的 Group...") - if target_tab_nav and "groups" in target_tab_nav: # 确保目标 Tab 和 groups 存在 - groups_list = [g for g in target_tab_nav["groups"] if isinstance(g, dict)] - groups_list.sort(key=lambda g: get_group_sort_key(g, docs_config)) - target_tab_nav["groups"] = groups_list - print(f" 已对 Tab '{target_tab_name}' 中的 Group 进行排序。") - elif target_tab_nav: - print(f" Tab '{target_tab_name}' 中没有 'groups' 或为空,无需排序。") - else: - print(f" 未找到 Tab '{target_tab_name}',无法排序 Group。") - - # 不再返回 docs_data,因为直接修改了传入的 navigation_data - print( - f"--- 完成处理版本: {docs_config['LANGUAGE_CODE']} / Tab: {docs_config['TARGET_TAB_NAME']} ---" - ) - - -def load_docs_data(path): - """加载 JSON 文件,处理文件不存在和格式错误的情况""" - try: - with open(path, "r", encoding="utf-8") as f: - return json.load(f) - except FileNotFoundError: - print(f"信息: {path} 未找到,将创建新的结构。") - return {"navigation": {"versions": []}} # 返回初始结构 - except json.JSONDecodeError as e: - # 引发更具体的错误,而不是返回 None - raise json.JSONDecodeError( - f"错误: {path} 格式错误。无法继续。- {e.msg}", e.doc, e.pos - ) - - -def save_docs_data(path, data): - """保存 JSON 数据到文件""" - try: - with open(path, "w", encoding="utf-8") as f: - json.dump(data, f, ensure_ascii=False, indent=4) - print(f"\n成功更新 {path},包含所有已处理的版本。") - # 不再需要返回 True/False,因为异常会处理失败情况 - except IOError as e: - # 引发 IO 错误 - raise IOError(f"错误: 无法写入 {path} - {e}") - except Exception as e: - # 引发其他未知错误 - raise Exception(f"写入 {path} 时发生未知错误: {e}") - - -def process_configurations(configs, docs_path): - """加载数据,处理所有有效配置,然后保存数据""" - # 1. 加载初始数据 - try: - current_docs_data = load_docs_data(docs_path) - except json.JSONDecodeError as e: - print(e) # 打印加载错误信息 - return # 加载失败则退出 - # current_docs_data 不会是 None,因为 load_docs_data 要么返回数据要么引发异常 - - # 2. 确保基本结构存在 - navigation_data = current_docs_data.setdefault( - "navigation", {} - ) # 获取 navigation 字典 - navigation_data.setdefault("versions", []) - - # 3. 筛选有效配置 - valid_configs = [] - for config in configs: - required_keys = [ - "DOCS_DIR", - "LANGUAGE_CODE", - "FILE_EXTENSION", - "PWX_TO_GROUP_MAP", - "DESIRED_GROUP_ORDER", - "TARGET_TAB_NAME", - "FILENAME_PATTERN", - ] - if all(k in config for k in required_keys): - # 可选:检查 PWX_TO_GROUP_MAP 和 DESIRED_GROUP_ORDER 是否为空 - # 并且检查 FILENAME_PATTERN 是否是编译后的正则表达式对象 - if ( - config.get("PWX_TO_GROUP_MAP") - and config.get("DESIRED_GROUP_ORDER") - and isinstance(config.get("FILENAME_PATTERN"), re.Pattern) - ): - valid_configs.append(config) - else: - reason = [] - if not config.get("PWX_TO_GROUP_MAP"): - reason.append("PWX_TO_GROUP_MAP 为空或不存在") - if not config.get("DESIRED_GROUP_ORDER"): - reason.append("DESIRED_GROUP_ORDER 为空或不存在") - if not isinstance(config.get("FILENAME_PATTERN"), re.Pattern): - reason.append("FILENAME_PATTERN 不是有效的正则表达式对象") - print( - f"警告: 配置 {config.get('LANGUAGE_CODE', '未知')} 无效 ({'; '.join(reason)}),跳过处理。" - ) - else: - missing_keys = [k for k in required_keys if k not in config] - print( - f"警告: 配置 {config.get('LANGUAGE_CODE', '未知')} 不完整 (缺少: {', '.join(missing_keys)}),跳过处理。" - ) - - # 4. 处理有效配置 - if not valid_configs: - print("没有有效的配置可供处理。") - else: - try: # 包裹所有配置的处理过程 - for config in valid_configs: - # 将 navigation_data 传递给 main 函数进行修改 - # main 函数会直接修改这个 navigation_data 字典 - main(config, navigation_data) - - # 5. 所有配置处理完毕后,统一写回文件 - save_docs_data(docs_path, current_docs_data) - except (FileNotFoundError, ValueError, RuntimeError, IOError, Exception) as e: - # 捕获 main 或 save_docs_data 中可能引发的已知错误 - print(f"\n处理过程中发生错误: {e}") - print("操作已终止,文件可能未完全更新。") - # 根据需要,可以在这里决定是否尝试保存部分结果或直接退出 - - -if __name__ == "__main__": - # 定义要处理的配置列表 - CONFIGS_TO_PROCESS = [ - plugin-dev-zh, - plugin-dev-en, - PLUGIN_DEV_JA, - ] - - # 调用主处理函数 - process_configurations(CONFIGS_TO_PROCESS, DOCS_JSON_PATH)