Implement all three explode-implode variants with full CLI integration: 🔧 Variant Implementations: - FlatVariant: Encapsulates existing flat structure behavior - HierarchicalVariant: Numbered directory structures (01_, 02_, 03_) - SemanticVariant: Content-based organization (intro, chapters, appendices) 🏭 Factory System: - VariantFactory: Centralized variant creation and management - Auto-detection algorithms with confidence scoring - Content analysis for variant recommendation 🖥️ CLI Integration: - Enhanced md-explode command with --variant parameter - Enhanced md-implode command with auto-detection - Improved error handling and user feedback 🧪 Comprehensive Testing: - 22 unit tests covering all variant functionality - Roundtrip validation ensuring perfect reversibility - Performance testing with large documents - Error handling and edge case coverage 📊 Key Features: - Three distinct organization strategies - Automatic variant detection from directory structures - Full backward compatibility with existing behavior - Extensible architecture for future variants - Manifest-based reversibility Files Added: - markitect/explode_variants/flat_variant.py - markitect/explode_variants/hierarchical_variant.py - markitect/explode_variants/semantic_variant.py - markitect/explode_variants/variant_factory.py - tests/test_issue_149_explode_implode_variants.py - tests/test_issue_149_roundtrip_validation.py - cost_notes/issue_149_cost_2025-10-12.md Files Modified: - markitect/explode_variants/__init__.py (updated exports) - markitect/plugins/builtin/markdown_commands.py (CLI integration) 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
325 lines
10 KiB
Python
325 lines
10 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
|
|
|
|
|
|
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)
|
|
|
|
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 |