@@ -230,7 +230,13 @@ def sanitize_description(text: str, max_length: int = 150) -> str:
230230SKIP_MODULES = {
231231 "__pycache__" , "_config" , "_lazy" , "_logging" , "_warning_patch" ,
232232 "_resolver_helpers" , "audit" , "lite" , "profiling" , "utils" , "__init__" ,
233- "_dev" , "test" , "tests" , "__main__"
233+ "_dev" , "test" , "tests" , "__main__" , "setup" , ".ipynb_checkpoints"
234+ }
235+ SKIP_METHODS = {
236+ "to_dict" , "from_dict" , "to_json" , "from_json" , "to_yaml" , "from_yaml" ,
237+ "copy" , "dict" , "json" , "__init__" , "__call__" , "__str__" , "__repr__" ,
238+ "model_dump" , "model_validate" , "model_json_schema" , "__post_init__" ,
239+ "__enter__" , "__exit__" , "__iter__" , "__len__" , "__getitem__" , "__setitem__"
234240}
235241
236242ICON_MAP = {
@@ -565,8 +571,10 @@ def _parse_docstring(self, docstring: str) -> Dict[str, Any]:
565571 if not docstring :
566572 return result
567573
568- # More robust splitting that handles optional trailing colon and optional newline
569- sections = re .split (r'\n\s*(Args|Parameters|Returns|Raises|Example|Examples|Usage):?\s*(?:\n|$)' , docstring , flags = re .IGNORECASE )
574+ # More robust splitting: only match sections at the start of original lines
575+ # This prevents picking up 'Examples:' inside an 'Args' description
576+ section_pattern = r'\n\s*(Args|Parameters|Returns|Raises|Example|Examples|Usage):?\s*\n'
577+ sections = re .split (section_pattern , '\n ' + docstring )
570578 result ["description" ] = sections [0 ].strip ()
571579
572580 for i in range (1 , len (sections ), 2 ):
@@ -601,7 +609,10 @@ def _parse_docstring(self, docstring: str) -> Dict[str, Any]:
601609 result ["raises" ].append ((match .group (1 ), match .group (2 )))
602610
603611 elif section_name in ("example" , "examples" , "usage" ):
604- result ["examples" ].append (section_content .strip ())
612+ content = section_content .strip ()
613+ # Strip existing triple backticks if they wrap the entire example
614+ content = re .sub (r'^```[a-z]*\n?(.*?)\n?```$' , r'\1' , content , flags = re .DOTALL )
615+ result ["examples" ].append (content .strip ())
605616
606617 return result
607618
@@ -734,12 +745,12 @@ def _parse_functions(self, content: str) -> List[FunctionInfo]:
734745class MDXGenerator :
735746 """Generate MDX documentation files."""
736747
737- def __init__ (self , output_dir : Path , package_name : str , config : dict , layout : LayoutType = LayoutType .GRANULAR ):
748+ def __init__ (self , output_dir : Path , package_name : str , config : Dict [ str , Any ], layout : LayoutType = LayoutType .LEGACY ):
738749 self .output_dir = output_dir
739750 self .package_name = package_name
740- self .generated_files : List [str ] = []
741751 self .config = config
742752 self .layout = layout
753+ self .generated_files = set () # Use set for faster lookups and cleanup
743754
744755 def generate_module_doc (self , info : ModuleInfo , dry_run : bool = False ) -> List [Path ]:
745756 """Generate MDX documentation for a module (potentially multiple files)."""
@@ -784,8 +795,22 @@ def _generate_granular(self, info: ModuleInfo, dry_run: bool = False) -> List[Pa
784795 self ._track_file (class_file )
785796 generated .append (class_file )
786797
787- # 3. Class Methods (as separate function pages)
798+ # 2. Class Methods (as separate function pages)
799+ for m in cls .class_methods :
800+ if m .name in SKIP_METHODS :
801+ continue
802+ method_file = self .output_dir / "functions" / f"{ cls .name } -{ m .name } .mdx"
803+ method_content = self ._render_function_page (m , info , cls_name = cls .name )
804+ if not dry_run :
805+ method_file .parent .mkdir (parents = True , exist_ok = True )
806+ method_file .write_text (method_content )
807+ self ._track_file (method_file )
808+ generated .append (method_file )
809+
810+ # 3. Instance Methods (as separate function pages)
788811 for m in cls .methods :
812+ if m .name in SKIP_METHODS :
813+ continue
789814 method_file = self .output_dir / "functions" / f"{ cls .name } -{ m .name } .mdx"
790815 method_content = self ._render_function_page (m , info , cls_name = cls .name )
791816 if not dry_run :
@@ -812,7 +837,7 @@ def _track_file(self, path: Path):
812837 docs_root = Path ("/Users/praison/PraisonAIDocs" )
813838 rel_path = str (path .relative_to (docs_root ))
814839 nav_path = rel_path .replace ('.mdx' , '' ).replace ('\\ ' , '/' )
815- self .generated_files .append (nav_path )
840+ self .generated_files .add (nav_path )
816841
817842 def _render_module (self , info : ModuleInfo ) -> str :
818843 safe_docstring = escape_mdx (info .docstring ) if info .docstring else ""
@@ -906,14 +931,16 @@ def _render_module_granular(self, info: ModuleInfo) -> str:
906931 lines .append ("</CardGroup>\n " )
907932
908933 if info .functions :
909- lines .append ("## Functions\n " )
910- lines .append ("<CardGroup cols={2}>" )
911- for func in info .functions :
912- lines .append (f' <Card title="{ func .name } ()" icon="function" href="../functions/{ func .name } ">' )
913- func_desc = sanitize_description (func .docstring ) or "Function definition."
914- lines .append (f" { func_desc } " )
915- lines .append (" </Card>" )
916- lines .append ("</CardGroup>\n " )
934+ visible_functions = [f for f in info .functions if f .name not in SKIP_METHODS ]
935+ if visible_functions :
936+ lines .append ("## Functions\n " )
937+ lines .append ("<CardGroup cols={2}>" )
938+ for func in visible_functions :
939+ lines .append (f' <Card title="{ func .name } ()" icon="function" href="../functions/{ func .name } ">' )
940+ func_desc = sanitize_description (func .docstring ) or "Function definition."
941+ lines .append (f" { func_desc } " )
942+ lines .append (" </Card>" )
943+ lines .append ("</CardGroup>\n " )
917944
918945 if info .constants :
919946 lines .extend ([
@@ -980,19 +1007,31 @@ def _render_class_page(self, cls: ClassInfo, module_info: ModuleInfo) -> str:
9801007 ])
9811008
9821009 if cls .methods or cls .class_methods :
983- lines .append ("## Methods\n " )
984- lines .append ("<CardGroup cols={2}>" )
985- for m in cls .class_methods :
986- lines .append (f' <Card title="{ m .name } ()" icon="function" href="../functions/{ cls .name } -{ m .name } ">' )
987- m_desc = sanitize_description (m .docstring ) or "Class method."
988- lines .append (f" { m_desc } " )
989- lines .append (" </Card>" )
990- for m in cls .methods :
991- lines .append (f' <Card title="{ m .name } ()" icon="function" href="../functions/{ cls .name } -{ m .name } ">' )
992- m_desc = sanitize_description (m .docstring ) or "Instance method."
993- lines .append (f" { m_desc } " )
994- lines .append (" </Card>" )
995- lines .append ("</CardGroup>\n " )
1010+ visible_class_methods = [m for m in cls .class_methods if m .name not in SKIP_METHODS ]
1011+ visible_methods = [m for m in cls .methods if m .name not in SKIP_METHODS ]
1012+
1013+ if visible_class_methods or visible_methods :
1014+ lines .append ("## Methods\n " )
1015+ lines .append ("<CardGroup cols={2}>" )
1016+ for m in visible_class_methods :
1017+ lines .append (f' <Card title="{ m .name } ()" icon="function" href="../functions/{ cls .name } -{ m .name } ">' )
1018+ m_desc = sanitize_description (m .docstring ) or "Class method."
1019+ lines .append (f" { m_desc } " )
1020+ lines .append (" </Card>" )
1021+ for m in visible_methods :
1022+ lines .append (f' <Card title="{ m .name } ()" icon="function" href="../functions/{ cls .name } -{ m .name } ">' )
1023+ m_desc = sanitize_description (m .docstring ) or "Instance method."
1024+ lines .append (f" { m_desc } " )
1025+ lines .append (" </Card>" )
1026+ lines .append ("</CardGroup>\n " )
1027+
1028+ # List skipped methods in an accordion without links for completeness
1029+ skipped_methods = [m for m in (cls .class_methods + cls .methods ) if m .name in SKIP_METHODS ]
1030+ if skipped_methods :
1031+ lines .append ('<Accordion title="Internal & Generic Methods">' )
1032+ for m in skipped_methods :
1033+ lines .append (f"- **{ m .name } **: { sanitize_description (m .docstring ) or 'Generic utility method.' } " )
1034+ lines .append ("</Accordion>\n " )
9961035
9971036 if cls .examples :
9981037 lines .append ("## Usage\n " )
@@ -1096,10 +1135,15 @@ def _render_function_page(self, func: FunctionInfo, module_info: ModuleInfo, cls
10961135 if len (func .examples ) > 1 :
10971136 lines .append ("<CodeGroup>" )
10981137 for i , ex in enumerate (func .examples ):
1099- lines .append (f"```python Example { i + 1 } \n { ex } \n ```" )
1138+ # Dedent example content for cleaner display
1139+ import textwrap
1140+ dedented_ex = textwrap .dedent (ex )
1141+ lines .append (f"```python Example { i + 1 } \n { dedented_ex } \n ```" )
11001142 lines .append ("</CodeGroup>\n " )
11011143 else :
1102- lines .append (f"```python\n { func .examples [0 ]} \n ```\n " )
1144+ import textwrap
1145+ dedented_ex = textwrap .dedent (func .examples [0 ])
1146+ lines .append (f"```python\n { dedented_ex } \n ```\n " )
11031147
11041148 return "\n " .join (lines )
11051149
@@ -1151,12 +1195,14 @@ def _render_class(self, cls: ClassInfo, package: str) -> List[str]:
11511195 lines .append ("</Accordion>\n " )
11521196
11531197 if cls .methods :
1154- lines .append ('<Accordion title="Methods">' )
1155- for m in cls .methods :
1156- lines .append (f"- **{ 'async ' if m .is_async else '' } { m .name } **(`{ escape_for_table (m .signature , is_type = True )} `) → `{ escape_for_table (m .return_type , is_type = True )} `" )
1157- if m .docstring :
1158- lines .append (f" { escape_mdx (m .docstring .split ('\\ n' )[0 ][:80 ])} " )
1159- lines .append ("</Accordion>\n " )
1198+ visible_methods = [m for m in cls .methods if m .name not in SKIP_METHODS ]
1199+ if visible_methods :
1200+ lines .append ('<Accordion title="Methods">' )
1201+ for m in visible_methods :
1202+ lines .append (f"- **{ 'async ' if m .is_async else '' } { m .name } **(`{ escape_for_table (m .signature , is_type = True )} `) → `{ escape_for_table (m .return_type , is_type = True )} `" )
1203+ if m .docstring :
1204+ lines .append (f" { escape_mdx (m .docstring .split ('\\ n' )[0 ][:80 ])} " )
1205+ lines .append ("</Accordion>\n " )
11601206
11611207 return lines
11621208
@@ -1277,9 +1323,27 @@ def generate_package(self, package_name: str, dry_run: bool = False):
12771323 print (f" Generated: { r .name } " )
12781324
12791325 if generator .generated_files and not dry_run :
1326+ print (f"\n Cleaning up orphaned MDX files..." )
1327+ self .cleanup_orphaned_files (config ["output" ], generator .generated_files )
1328+
12801329 print (f"\n Updating docs.json navigation..." )
1281- self .update_docs_json (package_name , generator .generated_files )
1282- print (f"Updated docs.json with { len (generator .generated_files )} pages" )
1330+ self .update_docs_json (package_name , sorted (list (generator .generated_files )))
1331+
1332+ def cleanup_orphaned_files (self , output_dir : Path , current_files : set ):
1333+ """Delete MDX files that are no longer part of the generated set."""
1334+ docs_root = Path ("/Users/praison/PraisonAIDocs" )
1335+ for sub_dir in ["functions" , "classes" , "modules" ]:
1336+ target_dir = output_dir / sub_dir
1337+ if not target_dir .exists ():
1338+ continue
1339+
1340+ for mdx_file in target_dir .glob ("*.mdx" ):
1341+ rel_path = str (mdx_file .relative_to (docs_root ))
1342+ nav_path = rel_path .replace ('.mdx' , '' ).replace ('\\ ' , '/' )
1343+ if nav_path not in current_files :
1344+ print (f" Deleting orphaned file: { mdx_file .name } " )
1345+ mdx_file .unlink ()
1346+ print (f"Updated package with { len (current_files )} pages" )
12831347
12841348
12851349 def update_docs_json (self , package_name : str , generated_pages : List [str ]):
0 commit comments