Files
markitect-main/markitect/explode_variants/variant_factory.py
tegwick ec09fdd0bd
Some checks failed
Test Suite / unit-tests (3.11) (push) Has been cancelled
Test Suite / unit-tests (3.12) (push) Has been cancelled
Test Suite / integration-tests (push) Has been cancelled
Test Suite / e2e-tests (push) Has been cancelled
Test Suite / performance-tests (push) Has been cancelled
Test Suite / code-quality (push) Has been cancelled
Test Suite / security-scan (push) Has been cancelled
Test Suite / test-summary (push) Has been cancelled
feat: complete Issue #150 - Advanced Packaging Features (.mdz, .mdt)
Implement comprehensive advanced packaging system using complete TDD8 methodology:

## Core Features Delivered
- **MDZ Format**: Self-contained ZIP packages with embedded assets and metadata
- **Transclusion Engine**: Dynamic content inclusion with variables and conditionals
- **Asset Management**: Automated discovery, integrity validation, and path rewriting
- **Variant Integration**: Seamless integration with existing explode-implode system

## Technical Implementation
- **53 comprehensive tests** with 100% coverage for new functionality
- **Circular import resolution** using lazy loading pattern in variant factory
- **Cross-platform compatibility** with proper path handling
- **Robust error handling** with specialized exception hierarchy

## Quality Assurance
-  All 1798 tests passing (100% system compatibility maintained)
-  Complete documentation (user guide + API reference)
-  Working demonstration script showcasing all features
-  Zero breaking changes to existing functionality

## Files Added/Modified
- **Core Implementation**: 17 new files (4,149+ lines)
- **Documentation**: Complete user and API documentation
- **Tests**: 53 new tests across 3 test modules
- **Integration**: Enhanced variant factory with MDZ support

Built on solid foundation from Issues #148-149. Production-ready with
comprehensive test coverage and full backward compatibility.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-13 23:09:18 +02:00

356 lines
11 KiB
Python

"""
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