210 lines
6.7 KiB
Python
210 lines
6.7 KiB
Python
"""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)})" |