55from pathlib import Path
66from dataclasses import dataclass
77
8+
89@dataclass
910class TParamDescriptor :
1011 name : str
1112 desc : str
1213
14+
1315@dataclass
1416class ConceptDescriptor :
1517 name : str
@@ -19,11 +21,21 @@ class ConceptDescriptor:
1921 params : list [TParamDescriptor ]
2022 definition : str
2123
24+
2225class ConceptParser :
23- def __init__ (self , xml_dir : Path , out_dir : Path , groups : dict ):
26+ def __init__ (self , xml_dir : Path , out_dir : Path , config : dict ):
2427 self .xml_dir = xml_dir
2528 self .out_dir = out_dir
26- self .groups = groups
29+
30+ # Extract the index configuration
31+ self .index_config = config .get ("index" )
32+ if not self .index_config :
33+ raise ValueError (
34+ "Configuration must include an 'index' block with title, anchor, and description."
35+ )
36+
37+ self .groups = config .get ("groups" , {})
38+
2739 self .concept_links = {} # Registry mapping refid -> file#anchor
2840 self .categorized_concepts = {key : [] for key in self .groups .keys ()}
2941
@@ -72,7 +84,7 @@ def _xml_to_md(self, elem: ET.Element) -> str:
7284 # SMART LINK MERGE:
7385 # Prevents wrapping a Markdown link in backticks, which breaks the link.
7486 # Instead, it places backticks INSIDE the brackets.
75- match = re .fullmatch (r' \[(`?)(.*?)\1\]\((.*?)\)' , inner .strip ())
87+ match = re .fullmatch (r" \[(`?)(.*?)\1\]\((.*?)\)" , inner .strip ())
7688 if match :
7789 res += f"[`{ match .group (2 )} `]({ match .group (3 )} )"
7890 elif "](" in inner :
@@ -144,10 +156,14 @@ def _parse_concept_xml(self, xml_path: Path) -> ConceptDescriptor | None:
144156
145157 detailed_desc = root .find ("detaileddescription" )
146158 if detailed_desc is not None :
147- for param_list in detailed_desc .findall ('.//parameterlist[@kind="templateparam"]' ):
159+ for param_list in detailed_desc .findall (
160+ './/parameterlist[@kind="templateparam"]'
161+ ):
148162 for item in param_list .findall ("parameteritem" ):
149163 p_name = self ._xml_to_md (item .find (".//parametername" )).strip ()
150- p_desc = self ._xml_to_md (item .find (".//parameterdescription" )).strip ()
164+ p_desc = self ._xml_to_md (
165+ item .find (".//parameterdescription" )
166+ ).strip ()
151167 params .append (TParamDescriptor (name = p_name , desc = p_desc ))
152168 param_list .clear ()
153169
@@ -165,7 +181,9 @@ def _parse_concept_xml(self, xml_path: Path) -> ConceptDescriptor | None:
165181 tpl_nodes = root .findall (".//templateparamlist/param" )
166182 tpl_strings = [self ._get_text (p ).strip () for p in tpl_nodes ]
167183 template_decl += ", " .join (tpl_strings ) + ">\n "
168- definition = f"{ template_decl } concept { name .split ('::' )[- 1 ]} = { constraint } ;"
184+ definition = (
185+ f"{ template_decl } concept { name .split ('::' )[- 1 ]} = { constraint } ;"
186+ )
169187
170188 return ConceptDescriptor (
171189 name = name ,
@@ -180,7 +198,9 @@ def process(self):
180198 """Main execution flow: builds registry, parses data, and generates markdown."""
181199 index_xml = self .xml_dir / "index.xml"
182200 if not index_xml .exists ():
183- print (f"Error: Could not find Doxygen index at { index_xml } . Run Doxygen first." )
201+ print (
202+ f"Error: Could not find Doxygen index at { index_xml } . Run Doxygen first."
203+ )
184204 return
185205
186206 tree = ET .parse (index_xml )
@@ -246,8 +266,8 @@ def _generate_group_files(self):
246266
247267 def _generate_index_file (self ):
248268 """Generates the central API index mapping to all grouped concepts."""
249- md = "# Concepts API Reference { : #concepts-api-reference }\n \n "
250- md += "This page serves as the central index for all C++20 concepts used across the library to enforce type safety and template constraints. \n \n ---\n \n "
269+ md = f "# { self . index_config [ 'title' ] } {{ : #{ self . index_config [ 'anchor' ] } } }\n \n "
270+ md += f" { self . index_config [ 'description' ] } \n \n ---\n \n "
251271
252272 for group_key , group_info in self .groups .items ():
253273 md += f"## { group_key } Concepts\n \n "
@@ -266,11 +286,14 @@ def _generate_index_file(self):
266286 index_path .write_text (md , encoding = "utf-8" )
267287 print (f"Generated { index_path } (API Index)" )
268288
289+
269290if __name__ == "__main__" :
270291 parser = argparse .ArgumentParser ()
271- parser .add_argument ("--xml" , default = "xml" , type = Path , help = "Path to Doxygen XML output" )
272- parser .add_argument ("--out" , default = "docs/cpp-gl" , type = Path , help = "Path to MkDocs output folder" )
273- parser .add_argument ("--config" , default = "groups.json" , type = Path , help = "Path to the groups JSON configuration file" )
292+ parser .add_argument ("--xml" , type = Path , help = "Path to Doxygen XML output" )
293+ parser .add_argument ("--out" , type = Path , help = "Path to MkDocs output folder" )
294+ parser .add_argument (
295+ "--config" , type = Path , help = "Path to the groups JSON configuration file"
296+ )
274297 args = parser .parse_args ()
275298
276299 # Load configuration from JSON
@@ -279,7 +302,7 @@ def _generate_index_file(self):
279302 exit (1 )
280303
281304 with open (args .config , "r" , encoding = "utf-8" ) as f :
282- groups_config = json .load (f )
305+ config = json .load (f )
283306
284- app = ConceptParser (args .xml , args .out , groups = groups_config )
307+ app = ConceptParser (args .xml , args .out , config = config )
285308 app .process ()
0 commit comments