"""Project representation in the doc-forge documentation model. Project is the root container for all documentation in a doc-forge project. It represents the entire codebase being documented and serves as the entry point for all documentation operations. A Project contains modules, navigation, and metadata about the codebase being documented. """ from __future__ import annotations from typing import Dict, List, Optional from .module import Module from .nav import Navigation class Project: """Root container for all documentation in a project. Project is the top-level object in the doc-forge documentation model. It represents the entire codebase being documented and serves as the central hub for all documentation operations. Each Project contains: - Basic metadata (name, version) - All documented modules - Navigation structure for browsing The Project is the single source of truth that all renderers and exporters work with, ensuring consistency across all output formats. Attributes: name: The name of the project version: Optional version string modules: Dictionary of all modules in the project nav: Navigation structure for the project """ def __init__(self, name: str, version: Optional[str] = None) -> None: """Initialize a Project. Args: name: The name of the project version: Optional version string Raises: ValueError: If name is empty """ if not name: raise ValueError("Project name cannot be empty") self.name: str = name self.version: Optional[str] = version self.modules: Dict[str, Module] = {} self.nav: Navigation = Navigation() def add_module(self, module: Module) -> None: """Add a module to the project. When a module is added, it's also automatically added to the navigation structure if not already present. Args: module: The module to add Raises: ValueError: If module path conflicts with existing module """ if module.path in self.modules: raise ValueError(f"Module '{module.path}' already exists in project") self.modules[module.path] = module # Add to navigation if not already present if not self.nav.get_entry_by_module(module.path): self.nav.add_entry_by_module(module.path) def get_module(self, path: str) -> Optional[Module]: """Get a module by its import path. Args: path: The import path of the module to retrieve Returns: The Module if found, None otherwise """ return self.modules.get(path) def get_all_modules(self) -> List[Module]: """Get all modules in the project. Returns: List of all Module objects in the project """ return list(self.modules.values()) def get_public_modules(self) -> List[Module]: """Get all modules with public (non-empty) content. Returns: List of modules that have at least one public object """ return [ module for module in self.modules.values() if module.get_public_objects() ] def remove_module(self, path: str) -> bool: """Remove a module from the project. Args: path: The import path of the module to remove Returns: True if module was removed, False if not found """ if path in self.modules: del self.modules[path] # Also remove from navigation self.nav.remove_entry_by_module(path) return True return False def has_module(self, path: str) -> bool: """Check if a module exists in the project. Args: path: The import path to check Returns: True if module exists, False otherwise """ return path in self.modules def get_module_count(self) -> int: """Get the total number of modules in the project. Returns: Number of modules in the project """ return len(self.modules) def is_empty(self) -> bool: """Check if the project has no modules. Returns: True if project has no modules, False otherwise """ return len(self.modules) == 0 def get_total_object_count(self) -> int: """Get the total count of all documentation objects. Returns: Total number of DocObjects across all modules """ return sum(len(module.members) for module in self.modules.values()) def get_modules_by_pattern(self, pattern: str) -> List[Module]: """Get modules matching a pattern. Args: pattern: Pattern to match against module paths (supports wildcards) Returns: List of modules whose paths match the pattern """ import fnmatch return [ module for module in self.modules.values() if fnmatch.fnmatch(module.path, pattern) ] def rebuild_navigation(self) -> None: """Rebuild navigation from current modules. This clears the existing navigation and rebuilds it from the current set of modules, ensuring navigation is in sync with the actual project structure. """ self.nav = Navigation() for module_path in sorted(self.modules.keys()): self.nav.add_entry_by_module(module_path) def __repr__(self) -> str: """Return a string representation of the Project. Returns: String representation showing name and module count """ return f"Project(name='{self.name}', modules={len(self.modules)})"