from pathlib import Path from typing import Union, Callable from pprint import pprint import mistune from qtoml.encoder import dumps from qtoml.decoder import loads from lib_not_dr import loggers from lib_not_dr.types.options import Options ast_markdown = mistune.create_markdown(renderer="ast") ast_type = list[dict[str, Union[str, dict]]] def get_all_iter( ast: ast_type, type: str, func: Callable[[ast_type], bool] = None ) -> tuple[int, dict]: for i, node in enumerate(ast): if node["type"] == type: if func is not None: if not func(node): continue yield i, node yield -1, {} def get_text(ast: ast_type) -> str: """ 返回第一个找到的字符串 """ if ast['type'] == 'text': return ast['raw'] elif ast['type'] in ('link', 'block_text', 'list_item', 'list'): return get_text(ast['children'][0]) print('unkown type', ast['type'], ast) class TagParser(Options): module_root: Path tags: dict[str, list[str]] = {} tag_map: dict[str, list[str]] = {} logger: loggers.logger.Logger = None # noqa def load_module(self, **kwargs): for readme in self.module_root.rglob("readme.md"): self.logger.debug(readme.absolute(), tag="load file") self.get_module_data(readme) tag_toml = dumps(self.tags) tag_path = self.module_root / ".." / "build" / "tags.toml" tag_path.parent.mkdir(parents=True, exist_ok=True) tag_path.touch(exist_ok=True) with open(tag_path, "w", encoding="utf-8") as file: file.write(tag_toml) def get_module_data(self, module_path: Path): with open(module_path, "r", encoding="utf-8") as f: file = f.read() ast = ast_markdown(file) if len(ast) == 0: return if ast[0] != {"type": "thematic_break"}: # 排除开头不是注释块的 return self.logger.info(f"开始解析 {ast[1]}") config_code = ast[1].get("raw", "") config_dict = loads(config_code) self.logger.trace(config_dict) if not (tag_list := config_dict.get("tags")): self.logger.warn("未找到 tags", tag=str(module_path)) return for tag in tag_list: if tag not in self.tags: self.tags[tag] = [module_path.__str__()] else: self.tags[tag].append(module_path.__str__()) def init(self, **kwargs) -> bool: self.logger = loggers.get_logger() self.logger.global_level = 0 self.load_tags() return False def load_tags(self): tag_list_path = Path("./tags/readme.md") if not tag_list_path.exists(): self.logger.error("未找到 tags/readme.md") return False with open(tag_list_path, "r", encoding="utf-8") as f: file = f.read() tag_ast: ast_type = ast_markdown(file) start_tag = -1 # 找到二级标题 start_tag = get_all_iter( tag_ast, "heading", lambda node: node["attrs"]["level"] == 2 ) start_tag, node = next(start_tag) if start_tag == -1: self.logger.error("未找到二级标题") return False # 获取 tag tag_ast: ast_type = tag_ast[start_tag + 1 :] start_tag = get_all_iter(tag_ast, "list") start_tag, node = next(start_tag) if start_tag == -1: self.logger.error("未找到 tag 列表") return False tag_ast: ast_type = [ item["children"] for item in tag_ast[start_tag]["children"] if item.get("type") == "list_item" ] # 顺手过滤一下 for tag in tag_ast: # self.logger.debug([(item['type'], item['children']) for item in tag]) this_tag = get_text(tag[0]) if len(tag) == 1: # 只有一个 tag, 无别名 self.tag_map[this_tag] = [this_tag] self.logger.debug(f'添加 tag {this_tag}') # len > 1 elif len(tag) >= 1: # 寻找 "别名" tag_names = tag[1]['children'] tag_list_start = get_all_iter(tag_names, 'list_item', lambda ast: get_text(ast) == '别名') tag_list_start, node = next(tag_list_start) if tag_list_start == -1: continue sub_names = [this_tag] for sub_name in node['children'][1]['children']: sub_names.append(get_text(sub_name)) self.logger.debug(f"添加 tag {this_tag} 和别名 {sub_names}") self.tag_map[this_tag] = sub_names # 获取 tag 列表 self.logger.info(f'可用 tag: {self.tag_map}') with open('./tags/tag.toml', 'w', encoding='utf-8') as file: file.write(dumps(self.tag_map)) if __name__ == "__main__": parser = TagParser(module_root=Path("modules")) parser.load_module() parser.logger.info(parser.tags)