"""Navigation structure for doc-forge documentation. Navigation provides a hierarchical structure for organizing documentation modules. It is derived automatically from the project structure rather than being manually authored, ensuring consistency between the documentation model and the navigation. The navigation is used by renderers to generate table of contents, sidebars, and other navigation elements. """ from __future__ import annotations from typing import Dict, List, Optional class NavEntry: """Single navigation entry linking to a module. A NavEntry represents one item in the documentation navigation, typically corresponding to a module. It contains a display title and the module path it links to. Attributes: title: The display title for this navigation entry module: The import path of the module this entry links to """ def __init__(self, title: str, module: str) -> None: """Initialize a NavEntry. Args: title: The display title for this entry module: The import path of the linked module Raises: ValueError: If title or module is empty """ if not title: raise ValueError("NavEntry title cannot be empty") if not module: raise ValueError("NavEntry module cannot be empty") self.title: str = title self.module: str = module def __repr__(self) -> str: """Return a string representation of the NavEntry. Returns: String representation showing title and module """ return f"NavEntry(title='{self.title}', module='{self.module}')" class Navigation: """Navigation structure derived from project modules. Navigation provides an organized hierarchy for browsing documentation. It is automatically generated from the project's module structure, ensuring that the navigation always reflects the actual available documentation. The navigation can be customized by: - Changing the order of entries - Grouping related modules - Providing custom titles Attributes: entries: List of navigation entries in order """ def __init__(self) -> None: """Initialize an empty Navigation.""" self.entries: List[NavEntry] = [] def add_entry(self, entry: NavEntry) -> None: """Add a navigation entry. Args: entry: The navigation entry to add """ self.entries.append(entry) def add_entry_by_module(self, module: str, title: Optional[str] = None) -> None: """Add a navigation entry for a module. This is a convenience method that creates a NavEntry from a module path. If no title is provided, the module name is used as the title. Args: module: The import path of the module title: Optional custom title (defaults to module name) """ if title is None: # Use the last part of the module path as the title title = module.split('.')[-1].replace('_', ' ').title() entry = NavEntry(title, module) self.add_entry(entry) def get_entry(self, title: str) -> Optional[NavEntry]: """Get a navigation entry by title. Args: title: The title of the entry to find Returns: The NavEntry if found, None otherwise """ for entry in self.entries: if entry.title == title: return entry return None def get_entry_by_module(self, module: str) -> Optional[NavEntry]: """Get a navigation entry by module path. Args: module: The module path to search for Returns: The NavEntry if found, None otherwise """ for entry in self.entries: if entry.module == module: return entry return None def remove_entry(self, title: str) -> bool: """Remove a navigation entry by title. Args: title: The title of the entry to remove Returns: True if entry was removed, False if not found """ for i, entry in enumerate(self.entries): if entry.title == title: del self.entries[i] return True return False def remove_entry_by_module(self, module: str) -> bool: """Remove a navigation entry by module path. Args: module: The module path of the entry to remove Returns: True if entry was removed, False if not found """ for i, entry in enumerate(self.entries): if entry.module == module: del self.entries[i] return True return False def reorder_entries(self, titles: List[str]) -> None: """Reorder entries based on provided title order. Entries not mentioned in the titles list will maintain their relative order and be placed after the specified entries. Args: titles: List of titles in the desired order """ # Create a mapping of title to entry for quick lookup entry_map = {entry.title: entry for entry in self.entries} # Build new ordered list ordered_entries = [] remaining_entries = list(self.entries) # Add entries in specified order for title in titles: if title in entry_map: ordered_entries.append(entry_map[title]) # Remove from remaining entries remaining_entries = [e for e in remaining_entries if e.title != title] # Add remaining entries in their original order ordered_entries.extend(remaining_entries) self.entries = ordered_entries def is_empty(self) -> bool: """Check if navigation has no entries. Returns: True if navigation has no entries, False otherwise """ return len(self.entries) == 0 def get_module_list(self) -> List[str]: """Get list of all module paths in navigation. Returns: List of module paths in navigation order """ return [entry.module for entry in self.entries] def __repr__(self) -> str: """Return a string representation of the Navigation. Returns: String representation showing entry count """ return f"Navigation(entries={len(self.entries)})"