@@ -31,6 +31,8 @@ def render_item(self, full_name: str) -> t.Iterable[str]:
3131 if type_ in ("package" , "module" ):
3232 yield "---"
3333 yield "layout: overview"
34+ slug = self ._generate_slug (full_name )
35+ yield f"slug: { slug } "
3436 yield "---"
3537 yield ""
3638
@@ -137,35 +139,25 @@ def render_package(self, item: ItemData) -> t.Iterable[str]:
137139 yield ""
138140 for child in children_by_type ["package" ]:
139141 name = child ["full_name" ].split ("." )[- 1 ]
140- # Create simple relative link using just the child name
141- link_path = name
142+ # Create slug-based link using full dotted name
143+ slug = self . _generate_slug ( child [ "full_name" ])
142144 doc_summary = child .get ('doc' , '' ).split ('\n ' )[0 ][:80 ] if child .get ('doc' ) else ""
143145 if len (child .get ('doc' , '' )) > 80 :
144146 doc_summary += "..."
145- yield f"- **[`{ name } `]({ link_path } )** - { doc_summary } " if doc_summary else f"- **[`{ name } `]({ link_path } )**"
147+ yield f"- **[`{ name } `]({ slug } )** - { doc_summary } " if doc_summary else f"- **[`{ name } `]({ slug } )**"
146148 yield ""
147149
148150 if has_submodules :
149151 yield "## Submodules"
150152 yield ""
151153 for child in children_by_type ["module" ]:
152154 name = child ["full_name" ].split ("." )[- 1 ]
153- # Replace underscores with dashes in display text for better readability
154- display_name = name .replace ('_' , '-' )
155-
156- # Create contextual link based on current page
157- current_parts = item ["full_name" ].split ("." )
158- if len (current_parts ) == 1 :
159- # On root page - use simple name
160- link_path = display_name
161- else :
162- # On subpackage page - use full filename (convert dots and underscores to dashes)
163- link_path = child ["full_name" ].replace ('.' , '-' ).replace ('_' , '-' )
164-
155+ # Create slug-based link using full dotted name
156+ slug = self ._generate_slug (child ["full_name" ])
165157 doc_summary = child .get ('doc' , '' ).split ('\n ' )[0 ][:80 ] if child .get ('doc' ) else ""
166158 if len (child .get ('doc' , '' )) > 80 :
167159 doc_summary += "..."
168- yield f"- **[`{ display_name } `]({ link_path } )** - { doc_summary } " if doc_summary else f"- **[`{ display_name } `]({ link_path } )**"
160+ yield f"- **[`{ name } `]({ slug } )** - { doc_summary } " if doc_summary else f"- **[`{ name } `]({ slug } )**"
169161 yield ""
170162
171163 # Show Module Contents summary if we have actual content (not just submodules)
@@ -711,4 +703,82 @@ def replace_tag(match):
711703 escaped_tag = tag .replace ('{' , '\\ {' ).replace ('}' , '\\ }' )
712704 escaped_content = escaped_content .replace (placeholder , f'`{ escaped_tag } `' )
713705
714- return escaped_content
706+ return escaped_content
707+
708+ def _generate_slug (self , full_name : str ) -> str :
709+ """Generate slug from full dotted name: nemo_curator.utils.grouping → nemo-curator-utils-grouping"""
710+ return full_name .replace ('.' , '-' ).replace ('_' , '-' )
711+
712+ def generate_navigation_yaml (self ) -> str :
713+ """Generate navigation YAML for Fern docs.yml following sphinx autodoc2 toctree logic."""
714+ import yaml
715+
716+ # Find root packages (no dots in name)
717+ root_packages = []
718+ for item in self ._db .get_by_type ("package" ):
719+ full_name = item ["full_name" ]
720+ if "." not in full_name : # Root packages only
721+ root_packages .append (item )
722+
723+ if not root_packages :
724+ return ""
725+
726+ # Build navigation structure recursively
727+ nav_contents = []
728+ for root_pkg in sorted (root_packages , key = lambda x : x ["full_name" ]):
729+ nav_item = self ._build_nav_item_recursive (root_pkg )
730+ if nav_item :
731+ nav_contents .append (nav_item )
732+
733+ # Create the final navigation structure
734+ navigation = {
735+ "navigation" : [
736+ {
737+ "section" : "API Reference" ,
738+ "skip-slug" : True ,
739+ "contents" : nav_contents
740+ }
741+ ]
742+ }
743+
744+ return yaml .dump (navigation , default_flow_style = False , sort_keys = False , allow_unicode = True )
745+
746+ def _build_nav_item_recursive (self , item : ItemData ) -> dict [str , t .Any ] | None :
747+ """Build navigation item recursively following sphinx autodoc2 toctree logic."""
748+ full_name = item ["full_name" ]
749+ slug = self ._generate_slug (full_name )
750+
751+ # Get children (same logic as sphinx toctrees)
752+ subpackages = list (self .get_children (item , {"package" }))
753+ submodules = list (self .get_children (item , {"module" }))
754+
755+ if subpackages or submodules :
756+ # This has children - make it a section with skip-slug
757+ section_item = {
758+ "section" : full_name .split ("." )[- 1 ], # Use short name for section
759+ "skip-slug" : True ,
760+ "path" : f"{ slug } .md" ,
761+ "contents" : []
762+ }
763+
764+ # Add subpackages recursively (maxdepth: 3 like sphinx)
765+ for child in sorted (subpackages , key = lambda x : x ["full_name" ]):
766+ child_nav = self ._build_nav_item_recursive (child )
767+ if child_nav :
768+ section_item ["contents" ].append (child_nav )
769+
770+ # Add submodules as pages (maxdepth: 1 like sphinx)
771+ for child in sorted (submodules , key = lambda x : x ["full_name" ]):
772+ child_slug = self ._generate_slug (child ["full_name" ])
773+ section_item ["contents" ].append ({
774+ "page" : child ["full_name" ].split ("." )[- 1 ], # Use short name
775+ "path" : f"{ child_slug } .md"
776+ })
777+
778+ return section_item
779+ else :
780+ # Leaf item - just a page
781+ return {
782+ "page" : full_name .split ("." )[- 1 ], # Use short name
783+ "path" : f"{ slug } .md"
784+ }
0 commit comments