# -*- coding: utf-8 -*-"""Code generation utilities for atlas_doc_parser.This module provides utilities to auto-generate the api.py file by scanningthe marks/ and nodes/ directories for public API classes."""importdataclassesimportimportlibimportinspectfrompathlibimportPathfromjinja2importTemplatefrom.pathsimportpath_enum
[docs]@dataclasses.dataclassclassPublicAPI:""" Metadata for a public API class. :param module_name: The module name (e.g., "mark_link", "node_heading") :param class_name: The class name (e.g., "MarkLink", "NodeHeading") :param is_attrs: Whether this is an Attrs class (e.g., "MarkLinkAttrs") :param is_type_alias: Whether this is a type alias (e.g., ``T_NODE_...``) """module_name:strclass_name:stris_attrs:bool=Falseis_type_alias:bool=False@propertydefrelative_import_path(self)->str:""" Get the relative import path for this API. For marks: .marks.mark_xxx For nodes: .nodes.node_xxx """ifself.module_name.startswith("mark_"):returnf".marks.{self.module_name}"elifself.module_name.startswith("node_"):returnf".nodes.{self.module_name}"else:raiseValueError(f"Unknown module type: {self.module_name}")
[docs]defscan_module_for_public_apis(module_path:Path,base_classes:tuple,)->list[PublicAPI]:""" Scan a module for public API classes. :param module_path: Path to the Python module file :param base_classes: Tuple of base classes to check inheritance against :return: List of PublicAPI objects for classes that inherit from base_classes """module_name=module_path.stem# e.g., "mark_link"# Determine the full module path for importif"marks"inmodule_path.parts:full_module_name=f"atlas_doc_parser.marks.{module_name}"elif"nodes"inmodule_path.parts:full_module_name=f"atlas_doc_parser.nodes.{module_name}"else:return[]# Import the moduletry:module=importlib.import_module(full_module_name)exceptImportErrorase:print(f"Warning: Could not import {full_module_name}: {e}")return[]public_apis=[]# Get all public members of the moduleforname,objininspect.getmembers(module):# Skip private/protected membersifname.startswith("_"):continue# Check if it's a class defined in this moduleifinspect.isclass(obj)andobj.__module__==full_module_name:# Check if it inherits from any of the base classesifissubclass(obj,base_classes):is_attrs=name.endswith("Attrs")public_apis.append(PublicAPI(module_name=module_name,class_name=name,is_attrs=is_attrs,))# Check for type aliases (T_NODE_*, T_MARK_*)# Type aliases are typically typing constructsifname.startswith("T_NODE_")orname.startswith("T_MARK_"):public_apis.append(PublicAPI(module_name=module_name,class_name=name,is_type_alias=True,))returnpublic_apis
[docs]defscan_all_modules()->dict[str,list[PublicAPI]]:""" Scan all mark and node modules for public APIs. :return: Dictionary with keys 'marks' and 'nodes', each containing a list of PublicAPI """# Import base classesfrom.mark_or_nodeimportBasebase_classes=(Base,)result={"marks":[],"nodes":[],}# Scan marks directorymarks_dir=path_enum.dir_package/"marks"formodule_pathinsorted(marks_dir.glob("mark_*.py")):apis=scan_module_for_public_apis(module_path,base_classes)result["marks"].extend(apis)# Scan nodes directorynodes_dir=path_enum.dir_package/"nodes"formodule_pathinsorted(nodes_dir.glob("node_*.py")):apis=scan_module_for_public_apis(module_path,base_classes)result["nodes"].extend(apis)returnresult
[docs]defgenerate_api_py()->str:""" Generate the content for api.py using Jinja2 template. :return: The generated Python source code """# Load the templatetemplate_path=path_enum.dir_package/"templates"/"api.py.jinja"withopen(template_path,"r")asf:template_content=f.read()template=Template(template_content)# Scan for all public APIsall_apis=scan_all_modules()# Group APIs by module for cleaner outputmarks_by_module:dict[str,list[PublicAPI]]={}forapiinall_apis["marks"]:ifapi.module_namenotinmarks_by_module:marks_by_module[api.module_name]=[]marks_by_module[api.module_name].append(api)nodes_by_module:dict[str,list[PublicAPI]]={}forapiinall_apis["nodes"]:ifapi.module_namenotinnodes_by_module:nodes_by_module[api.module_name]=[]nodes_by_module[api.module_name].append(api)# Render the templatecontent=template.render(marks_by_module=marks_by_module,nodes_by_module=nodes_by_module,)returncontent
[docs]defmain():""" Main entry point for code generation. Generates the api.py file by scanning marks/ and nodes/ directories. """content=generate_api_py()# Write the generated content to api.pyapi_py_path=path_enum.dir_package/"api.py"withopen(api_py_path,"w")asf:f.write(content)print(f"Generated {api_py_path}")