@@ -184,6 +184,7 @@ def _split_generic_args(s: str) -> list[str]:
184184# XML doc comment parsing
185185# ---------------------------------------------------------------------------
186186
187+
187188def _strip_xml_tags (text : str ) -> str :
188189 """Convert XML doc content to plain text."""
189190 if not text :
@@ -218,9 +219,7 @@ def _parse_xml_doc(lines: list[str]) -> dict:
218219 root = ElementTree .fromstring (wrapped )
219220 except ElementTree .ParseError :
220221 # Fallback: extract summary with regex
221- summary_match = re .search (
222- r"<summary>(.*?)</summary>" , xml_text , re .DOTALL
223- )
222+ summary_match = re .search (r"<summary>(.*?)</summary>" , xml_text , re .DOTALL )
224223 return {
225224 "summary" : _strip_xml_tags (summary_match .group (1 )) if summary_match else ""
226225 }
@@ -293,6 +292,7 @@ def _inner_xml(el: ElementTree.Element) -> str:
293292# Data model
294293# ---------------------------------------------------------------------------
295294
295+
296296@dataclass
297297class DocParam :
298298 name : str
@@ -304,6 +304,7 @@ class DocParam:
304304@dataclass
305305class DocMember :
306306 """A documented method, property, or constant."""
307+
307308 kind : str # "method", "property", "constant"
308309 name : str # Sharpy name (snake_case)
309310 cs_name : str # Original C# name
@@ -321,6 +322,7 @@ class DocMember:
321322@dataclass
322323class DocType :
323324 """A documented module type (e.g., ArgumentParser in argparse)."""
325+
324326 name : str
325327 cs_name : str
326328 summary : str = ""
@@ -330,6 +332,7 @@ class DocType:
330332@dataclass
331333class DocModule :
332334 """A documented module or core type."""
335+
333336 name : str # Sharpy module name
334337 kind : str # "module", "type", "builtins"
335338 summary : str = ""
@@ -345,16 +348,20 @@ class DocModule:
345348
346349# Skip patterns
347350_SKIP_NAMES = {
348- "GetEnumerator" , "GetReverseEnumerator" , "CompareTo" ,
349- "GetHashCode" , "Equals" , "ToString" , "Dispose" ,
350- "TryGetValue" , "ContainsKey" ,
351+ "GetEnumerator" ,
352+ "GetReverseEnumerator" ,
353+ "CompareTo" ,
354+ "GetHashCode" ,
355+ "Equals" ,
356+ "ToString" ,
357+ "Dispose" ,
358+ "TryGetValue" ,
359+ "ContainsKey" ,
351360}
352361
353362
354363def _is_operator (name : str ) -> bool :
355- return name .startswith ("operator" ) and (
356- len (name ) <= 8 or not name [8 ].isalnum ()
357- )
364+ return name .startswith ("operator" ) and (len (name ) <= 8 or not name [8 ].isalnum ())
358365
359366
360367def _is_skippable (name : str , doc_lines : list [str ]) -> bool :
@@ -429,11 +436,13 @@ def _parse_params(param_str: str, is_extension: bool = False) -> list[DocParam]:
429436 tokens = part .rsplit (None , 1 )
430437 if len (tokens ) == 2 :
431438 ptype , pname = tokens
432- params .append (DocParam (
433- name = pascal_to_snake (pname ),
434- type = map_type (ptype .strip ()),
435- default = default ,
436- ))
439+ params .append (
440+ DocParam (
441+ name = pascal_to_snake (pname ),
442+ type = map_type (ptype .strip ()),
443+ default = default ,
444+ )
445+ )
437446 elif len (tokens ) == 1 :
438447 params .append (DocParam (name = tokens [0 ], type = "" ))
439448
@@ -557,20 +566,25 @@ def parse_cs_file(
557566 doc_lines = _collect_doc_lines (lines , i )
558567 if not _is_skippable (cname , doc_lines ):
559568 doc = _parse_xml_doc (doc_lines )
560- members .append (DocMember (
561- kind = "constant" ,
562- name = pascal_to_snake (cname ),
563- cs_name = cname ,
564- signature = "" ,
565- summary = doc .get ("summary" , "" ),
566- return_type = map_type (ctype .strip ()),
567- is_static = True ,
568- ))
569+ members .append (
570+ DocMember (
571+ kind = "constant" ,
572+ name = pascal_to_snake (cname ),
573+ cs_name = cname ,
574+ signature = "" ,
575+ summary = doc .get ("summary" , "" ),
576+ return_type = map_type (ctype .strip ()),
577+ is_static = True ,
578+ )
579+ )
569580 i = end_i + 1
570581 continue
571582
572583 # Skip class/struct/interface/enum declarations
573- if re .match (r"public\s+(?:(?:static|sealed|abstract|partial|readonly)\s+)*(?:class|struct|interface|enum)\s" , joined ):
584+ if re .match (
585+ r"public\s+(?:(?:static|sealed|abstract|partial|readonly)\s+)*(?:class|struct|interface|enum)\s" ,
586+ joined ,
587+ ):
574588 i = end_i + 1
575589 continue
576590
@@ -617,20 +631,22 @@ def parse_cs_file(
617631 if mapped_ret and mapped_ret != "None" :
618632 sig += f" -> { mapped_ret } "
619633
620- members .append (DocMember (
621- kind = "method" ,
622- name = sharpy_name ,
623- cs_name = mname ,
624- signature = sig ,
625- summary = doc .get ("summary" , "" ),
626- params = params ,
627- returns = doc .get ("returns" , "" ),
628- return_type = mapped_ret ,
629- example = doc .get ("example" , "" ),
630- remarks = doc .get ("remarks" , "" ),
631- exceptions = doc .get ("exceptions" , []),
632- is_static = is_static ,
633- ))
634+ members .append (
635+ DocMember (
636+ kind = "method" ,
637+ name = sharpy_name ,
638+ cs_name = mname ,
639+ signature = sig ,
640+ summary = doc .get ("summary" , "" ),
641+ params = params ,
642+ returns = doc .get ("returns" , "" ),
643+ return_type = mapped_ret ,
644+ example = doc .get ("example" , "" ),
645+ remarks = doc .get ("remarks" , "" ),
646+ exceptions = doc .get ("exceptions" , []),
647+ is_static = is_static ,
648+ )
649+ )
634650 i = end_i + 1
635651 continue
636652
@@ -642,15 +658,17 @@ def parse_cs_file(
642658 doc_lines = _collect_doc_lines (lines , i )
643659 if not _is_skippable (pname , doc_lines ):
644660 doc = _parse_xml_doc (doc_lines )
645- members .append (DocMember (
646- kind = "property" ,
647- name = pascal_to_snake (pname ),
648- cs_name = pname ,
649- signature = "" ,
650- summary = doc .get ("summary" , "" ),
651- return_type = map_type (ptype .strip ()),
652- is_static = "static" in modifiers ,
653- ))
661+ members .append (
662+ DocMember (
663+ kind = "property" ,
664+ name = pascal_to_snake (pname ),
665+ cs_name = pname ,
666+ signature = "" ,
667+ summary = doc .get ("summary" , "" ),
668+ return_type = map_type (ptype .strip ()),
669+ is_static = "static" in modifiers ,
670+ )
671+ )
654672
655673 i = end_i + 1
656674
@@ -682,6 +700,7 @@ def _get_class_summary(filepath: Path) -> str:
682700# Source discovery
683701# ---------------------------------------------------------------------------
684702
703+
685704def discover_modules (core_dir : Path ) -> list [DocModule ]:
686705 """Discover all stdlib modules from Sharpy.Core source."""
687706 modules : list [DocModule ] = []
@@ -724,15 +743,18 @@ def discover_modules(core_dir: Path) -> list[DocModule]:
724743
725744 # Check if this file contains SharpyModuleType-annotated classes
726745 file_text = cs_file .read_text (encoding = "utf-8" )
727- type_annotations = list (re .finditer (
728- r'\[SharpyModuleType\("([^"]+)"\)\]' , file_text ,
729- ))
746+ type_annotations = list (
747+ re .finditer (
748+ r'\[SharpyModuleType\("([^"]+)"\)\]' ,
749+ file_text ,
750+ )
751+ )
730752 if type_annotations :
731753 # Find all annotated class names and their line positions
732754 file_lines = file_text .split ("\n " )
733755 annotated_classes : list [tuple [str , int , int ]] = []
734756 for ta in type_annotations :
735- after = file_text [ta .end ():]
757+ after = file_text [ta .end () :]
736758 cm = re .search (
737759 r"public\s+(?:sealed\s+|abstract\s+|static\s+|partial\s+)*"
738760 r"class\s+(\w+)" ,
@@ -747,34 +769,41 @@ def discover_modules(core_dir: Path) -> list[DocModule]:
747769 # Compute end lines (start of next class or EOF)
748770 for ci in range (len (annotated_classes )):
749771 name , start , _ = annotated_classes [ci ]
750- end = (annotated_classes [ci + 1 ][1 ] - 1
751- if ci + 1 < len (annotated_classes )
752- else len (file_lines ) - 1 )
772+ end = (
773+ annotated_classes [ci + 1 ][1 ] - 1
774+ if ci + 1 < len (annotated_classes )
775+ else len (file_lines ) - 1
776+ )
753777 annotated_classes [ci ] = (name , start , end )
754778
755779 # Parse each class range separately
756780 for class_name , start , end in annotated_classes :
757781 type_summary = _get_class_summary (cs_file )
758782 type_members = parse_cs_file (
759- cs_file , line_range = (start , end ),
783+ cs_file ,
784+ line_range = (start , end ),
785+ )
786+ all_types .append (
787+ DocType (
788+ name = class_name ,
789+ cs_name = cs_file .stem ,
790+ summary = type_summary ,
791+ members = type_members ,
792+ )
760793 )
761- all_types .append (DocType (
762- name = class_name ,
763- cs_name = cs_file .stem ,
764- summary = type_summary ,
765- members = type_members ,
766- ))
767794 else :
768795 members = parse_cs_file (cs_file )
769796 all_members .extend (members )
770797
771- modules .append (DocModule (
772- name = mod_name ,
773- kind = "module" ,
774- summary = summary ,
775- members = all_members ,
776- types = all_types ,
777- ))
798+ modules .append (
799+ DocModule (
800+ name = mod_name ,
801+ kind = "module" ,
802+ summary = summary ,
803+ members = all_members ,
804+ types = all_types ,
805+ )
806+ )
778807
779808 return modules
780809
@@ -809,12 +838,14 @@ def discover_core_types(core_dir: Path) -> list[DocModule]:
809838 members = parse_cs_file (cs_file , is_extension = is_extension )
810839 all_members .extend (members )
811840
812- types .append (DocModule (
813- name = type_name ,
814- kind = "type" ,
815- summary = summary ,
816- members = all_members ,
817- ))
841+ types .append (
842+ DocModule (
843+ name = type_name ,
844+ kind = "type" ,
845+ summary = summary ,
846+ members = all_members ,
847+ )
848+ )
818849
819850 return types
820851
@@ -854,6 +885,7 @@ def discover_builtins(core_dir: Path) -> DocModule:
854885# Markdown rendering
855886# ---------------------------------------------------------------------------
856887
888+
857889def _one_line (text : str ) -> str :
858890 """Collapse multi-line text into a single line for table cells."""
859891 return " " .join (text .split ()).strip ()
@@ -930,10 +962,16 @@ def _render_constants_table(constants: list[DocMember]) -> str:
930962 if not constants :
931963 return ""
932964
933- lines = ["## Constants" , "" , "| Name | Type | Description |" ,
934- "|------|------|-------------|" ]
965+ lines = [
966+ "## Constants" ,
967+ "" ,
968+ "| Name | Type | Description |" ,
969+ "|------|------|-------------|" ,
970+ ]
935971 for c in constants :
936- lines .append (f"| `{ c .name } ` | `{ c .return_type } ` | { _escape_table_cell (c .summary )} |" )
972+ lines .append (
973+ f"| `{ c .name } ` | `{ c .return_type } ` | { _escape_table_cell (c .summary )} |"
974+ )
937975 lines .append ("" )
938976 return "\n " .join (lines )
939977
@@ -971,13 +1009,17 @@ def render_module_page(module: DocModule) -> str:
9711009 lines .append ("| Name | Type | Description |" )
9721010 lines .append ("|------|------|-------------|" )
9731011 for p in unique_props :
974- lines .append (f"| `{ p .name } ` | `{ p .return_type } ` | { _escape_table_cell (p .summary )} |" )
1012+ lines .append (
1013+ f"| `{ p .name } ` | `{ p .return_type } ` | { _escape_table_cell (p .summary )} |"
1014+ )
9751015 lines .append ("" )
9761016
9771017 # Methods/Functions
9781018 methods = [m for m in module .members if m .kind == "method" ]
9791019 if methods :
980- section_title = "Functions" if module .kind in ("module" , "builtins" ) else "Methods"
1020+ section_title = (
1021+ "Functions" if module .kind in ("module" , "builtins" ) else "Methods"
1022+ )
9811023 lines .append (f"## { section_title } " )
9821024 lines .append ("" )
9831025
@@ -1020,7 +1062,7 @@ def render_index_page(
10201062 "" ,
10211063 "## Built-in Functions" ,
10221064 "" ,
1023- f"[Built-in functions](builtins.md) available without any import: "
1065+ f"[Built-in functions](builtins.md) available without any import: " ,
10241066 ]
10251067
10261068 builtin_names = sorted (set (m .name for m in builtins .members if m .kind == "method" ))
@@ -1058,6 +1100,7 @@ def render_index_page(
10581100# Main
10591101# ---------------------------------------------------------------------------
10601102
1103+
10611104def generate (
10621105 source_dir : Path ,
10631106 output_dir : Path ,
0 commit comments