""" Factory for creating and managing explode-implode variants. This module provides a centralized factory for instantiating variants, auto-detecting appropriate variants, and managing variant registration. """ from pathlib import Path from typing import Dict, List, Optional, Type, Any from .base_variant import BaseVariant from .enums import ExplodeVariant, DetectionConfidence from .flat_variant import FlatVariant from .hierarchical_variant import HierarchicalVariant from .semantic_variant import SemanticVariant from .variant_detector import VariantDetector, DetectionResult # Packaging variants are imported lazily to avoid circular imports _MDZ_AVAILABLE = None # Lazy evaluation _MDZ_IMPORT_ERROR = None _MdzVariant = None # Cached import def _check_mdz_availability(): """Check if MDZ variant is available, with lazy import.""" global _MDZ_AVAILABLE, _MDZ_IMPORT_ERROR, _MdzVariant if _MDZ_AVAILABLE is not None: return _MDZ_AVAILABLE try: from ..packaging.mdz_variant import MdzVariant _MdzVariant = MdzVariant _MDZ_AVAILABLE = True return True except ImportError as e: _MDZ_AVAILABLE = False _MDZ_IMPORT_ERROR = str(e) return False except Exception as e: _MDZ_AVAILABLE = False _MDZ_IMPORT_ERROR = f"Unexpected error: {e}" return False class VariantFactory: """ Factory for creating and managing explode-implode variants. Provides a centralized interface for: - Creating variant instances - Auto-detecting variants from directory structures - Registering new variant types - Getting variant information and capabilities """ def __init__(self): """Initialize the variant factory.""" self._variants: Dict[ExplodeVariant, Type[BaseVariant]] = {} self._detector = VariantDetector() self._register_builtin_variants() def _register_builtin_variants(self) -> None: """Register all built-in variants.""" self.register_variant(ExplodeVariant.FLAT, FlatVariant) self.register_variant(ExplodeVariant.HIERARCHICAL, HierarchicalVariant) self.register_variant(ExplodeVariant.SEMANTIC, SemanticVariant) # Register packaging variants if available (lazy loading) if _check_mdz_availability(): self.register_variant(ExplodeVariant.MDZ, _MdzVariant) def register_variant(self, variant_type: ExplodeVariant, variant_class: Type[BaseVariant]) -> None: """ Register a variant class with the factory. Args: variant_type: The variant enum type variant_class: The variant implementation class Raises: ValueError: If variant_class is not a subclass of BaseVariant """ if not issubclass(variant_class, BaseVariant): raise ValueError(f"Variant class {variant_class} must inherit from BaseVariant") self._variants[variant_type] = variant_class def create_variant(self, variant_type: ExplodeVariant) -> BaseVariant: """ Create an instance of the specified variant. Args: variant_type: The type of variant to create Returns: Instance of the specified variant Raises: ValueError: If variant_type is not registered """ if variant_type not in self._variants: raise ValueError(f"Unknown variant type: {variant_type}") variant_class = self._variants[variant_type] return variant_class() def detect_variant(self, directory: Path) -> DetectionResult: """ Auto-detect the variant used for a directory structure. Args: directory: Directory to analyze Returns: Detection result with variant, confidence, and evidence """ return self._detector.detect_variant(directory) def create_variant_for_directory(self, directory: Path) -> BaseVariant: """ Create the appropriate variant instance for a directory structure. Args: directory: Directory to analyze Returns: Variant instance best suited for the directory Raises: ValueError: If no suitable variant can be determined """ detection_result = self.detect_variant(directory) if detection_result.variant is None: # Fallback to flat variant return self.create_variant(ExplodeVariant.FLAT) return self.create_variant(detection_result.variant) def get_variant_info(self, variant_type: ExplodeVariant) -> Dict[str, Any]: """ Get information about a variant type. Args: variant_type: The variant type to get info for Returns: Dictionary with variant information Raises: ValueError: If variant_type is not registered """ if variant_type not in self._variants: raise ValueError(f"Unknown variant type: {variant_type}") variant_instance = self.create_variant(variant_type) detection_patterns = variant_instance.get_detection_patterns() return { 'type': variant_type, 'name': variant_instance.name, 'description': variant_instance.description, 'detection_patterns': detection_patterns, 'class_name': self._variants[variant_type].__name__ } def list_available_variants(self) -> List[Dict[str, Any]]: """ Get information about all registered variants. Returns: List of variant information dictionaries """ variants_info = [] for variant_type in self._variants: try: info = self.get_variant_info(variant_type) variants_info.append(info) except Exception as e: # Skip variants that fail to load continue # Sort by variant order (flat, hierarchical, semantic) order_map = { ExplodeVariant.FLAT: 1, ExplodeVariant.HIERARCHICAL: 2, ExplodeVariant.SEMANTIC: 3 } variants_info.sort(key=lambda x: order_map.get(x['type'], 999)) return variants_info def get_best_variant_for_content(self, content: str) -> ExplodeVariant: """ Analyze content and suggest the best variant for explosion. Args: content: Markdown content to analyze Returns: Recommended variant type """ # Simple content analysis to suggest variants lines = content.split('\n') heading_count = sum(1 for line in lines if line.strip().startswith('#')) h1_count = sum(1 for line in lines if line.strip().startswith('# ')) h2_count = sum(1 for line in lines if line.strip().startswith('## ')) # Check for numbered headings (hierarchical indicator) numbered_headings = sum(1 for line in lines if re.match(r'^#+\s*\d+[\.\)]\s+', line.strip())) # Check for semantic keywords content_lower = content.lower() semantic_keywords = [ 'chapter', 'section', 'introduction', 'conclusion', 'appendix', 'reference', 'tutorial', 'guide' ] semantic_score = sum(1 for keyword in semantic_keywords if keyword in content_lower) # Decision logic if numbered_headings > heading_count * 0.3: return ExplodeVariant.HIERARCHICAL elif semantic_score > 3 and h1_count > 2: return ExplodeVariant.SEMANTIC else: return ExplodeVariant.FLAT def validate_variant_for_directory(self, variant_type: ExplodeVariant, directory: Path) -> bool: """ Validate if a variant can handle a specific directory structure. Args: variant_type: The variant type to validate directory: Directory to check Returns: True if the variant can handle the directory """ try: variant_instance = self.create_variant(variant_type) return variant_instance.can_handle_directory(directory) except Exception: return False def get_compatible_variants(self, directory: Path) -> List[ExplodeVariant]: """ Get all variants that can handle a directory structure. Args: directory: Directory to check Returns: List of compatible variant types """ compatible = [] for variant_type in self._variants: if self.validate_variant_for_directory(variant_type, directory): compatible.append(variant_type) return compatible def is_exploded_directory(self, directory: Path) -> bool: """ Check if a directory appears to be an exploded markdown structure. Args: directory: Directory to check Returns: True if directory appears to be exploded markdown content """ return self._detector.is_exploded_directory(directory) def get_variant_statistics(self) -> Dict[str, Any]: """ Get statistics about registered variants. Returns: Dictionary with variant statistics """ return { 'total_variants': len(self._variants), 'variant_types': list(self._variants.keys()), 'builtin_variants': [ ExplodeVariant.FLAT, ExplodeVariant.HIERARCHICAL, ExplodeVariant.SEMANTIC ], 'custom_variants': [ vt for vt in self._variants.keys() if vt not in [ExplodeVariant.FLAT, ExplodeVariant.HIERARCHICAL, ExplodeVariant.SEMANTIC] ] } # Global factory instance _factory_instance: Optional[VariantFactory] = None def get_variant_factory() -> VariantFactory: """ Get the global variant factory instance. Returns: The global VariantFactory instance """ global _factory_instance if _factory_instance is None: _factory_instance = VariantFactory() return _factory_instance def create_variant(variant_type: ExplodeVariant) -> BaseVariant: """ Convenience function to create a variant instance. Args: variant_type: The type of variant to create Returns: Instance of the specified variant """ return get_variant_factory().create_variant(variant_type) def detect_variant(directory: Path) -> DetectionResult: """ Convenience function to detect variant from directory. Args: directory: Directory to analyze Returns: Detection result """ return get_variant_factory().detect_variant(directory) def auto_create_variant(directory: Path) -> BaseVariant: """ Convenience function to auto-create variant for directory. Args: directory: Directory to analyze Returns: Appropriate variant instance """ return get_variant_factory().create_variant_for_directory(directory) # Import required for content analysis import re