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
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>
209 lines
7.0 KiB
Python
209 lines
7.0 KiB
Python
"""
|
|
Transclusion engine implementation.
|
|
|
|
Provides the core engine for processing transclusion directives,
|
|
managing context, and producing final rendered content.
|
|
"""
|
|
|
|
from pathlib import Path
|
|
from typing import Dict, Any, Optional, List
|
|
|
|
from .context import TransclusionContext
|
|
from .directives import DirectiveParser, Directive
|
|
from ..errors import TransclusionError
|
|
|
|
|
|
class TransclusionEngine:
|
|
"""
|
|
Core engine for processing transclusion directives.
|
|
|
|
Handles file inclusion, variable substitution, conditional content,
|
|
and maintains processing context with circular reference detection.
|
|
"""
|
|
|
|
def __init__(self, base_path: Optional[Path] = None,
|
|
variables: Optional[Dict[str, Any]] = None,
|
|
max_depth: int = 10):
|
|
"""
|
|
Initialize the transclusion engine.
|
|
|
|
Args:
|
|
base_path: Base path for relative file resolution
|
|
variables: Initial variables for substitution
|
|
max_depth: Maximum inclusion depth
|
|
"""
|
|
self.base_path = base_path or Path.cwd()
|
|
self.initial_variables = variables or {}
|
|
self.max_depth = max_depth
|
|
|
|
def process_content(self, content: str,
|
|
context: Optional[TransclusionContext] = None) -> str:
|
|
"""
|
|
Process transclusion directives in content.
|
|
|
|
Args:
|
|
content: Content containing transclusion directives
|
|
context: Processing context (created if None)
|
|
|
|
Returns:
|
|
Processed content with directives resolved
|
|
"""
|
|
if context is None:
|
|
context = TransclusionContext(
|
|
base_path=self.base_path,
|
|
variables=self.initial_variables.copy(),
|
|
max_depth=self.max_depth
|
|
)
|
|
|
|
# Parse all directives
|
|
directives = DirectiveParser.parse_directives(content)
|
|
|
|
# Process directives in reverse order to maintain positions
|
|
processed_content = content
|
|
for directive in reversed(directives):
|
|
try:
|
|
replacement = self._process_directive(directive, context)
|
|
processed_content = (
|
|
processed_content[:directive.start_pos] +
|
|
replacement +
|
|
processed_content[directive.end_pos:]
|
|
)
|
|
except Exception as e:
|
|
# Replace with error message in development
|
|
error_msg = f"[TRANSCLUSION ERROR: {str(e)}]"
|
|
processed_content = (
|
|
processed_content[:directive.start_pos] +
|
|
error_msg +
|
|
processed_content[directive.end_pos:]
|
|
)
|
|
|
|
return processed_content
|
|
|
|
def process_file(self, file_path: Path,
|
|
context: Optional[TransclusionContext] = None) -> str:
|
|
"""
|
|
Process a file with transclusion directives.
|
|
|
|
Args:
|
|
file_path: Path to file to process
|
|
context: Processing context (created if None)
|
|
|
|
Returns:
|
|
Processed file content
|
|
"""
|
|
if context is None:
|
|
context = TransclusionContext(
|
|
base_path=file_path.parent,
|
|
variables=self.initial_variables.copy(),
|
|
max_depth=self.max_depth
|
|
)
|
|
|
|
try:
|
|
# Enter file processing
|
|
context.enter_file(file_path)
|
|
|
|
# Read file content
|
|
if not file_path.exists():
|
|
raise TransclusionError(f"File not found: {file_path}")
|
|
|
|
content = file_path.read_text(encoding='utf-8')
|
|
|
|
# Process transclusion directives
|
|
processed_content = self.process_content(content, context)
|
|
|
|
# Exit file processing
|
|
context.exit_file(file_path)
|
|
|
|
return processed_content
|
|
|
|
except Exception as e:
|
|
# Exit file processing on error
|
|
context.exit_file(file_path)
|
|
raise TransclusionError(f"Error processing file {file_path}: {e}")
|
|
|
|
def _process_directive(self, directive: Directive,
|
|
context: TransclusionContext) -> str:
|
|
"""
|
|
Process a single directive.
|
|
|
|
Args:
|
|
directive: Directive to process
|
|
context: Processing context
|
|
|
|
Returns:
|
|
Replacement content for the directive
|
|
"""
|
|
if directive.type == 'include':
|
|
return self._process_include_directive(directive, context)
|
|
elif directive.type == 'variable':
|
|
return self._process_variable_directive(directive, context)
|
|
elif directive.type == 'conditional':
|
|
return self._process_conditional_directive(directive, context)
|
|
else:
|
|
raise TransclusionError(f"Unknown directive type: {directive.type}")
|
|
|
|
def _process_include_directive(self, directive: Directive,
|
|
context: TransclusionContext) -> str:
|
|
"""
|
|
Process a file include directive.
|
|
|
|
Args:
|
|
directive: Include directive
|
|
context: Processing context
|
|
|
|
Returns:
|
|
Content of included file
|
|
"""
|
|
file_path_str = directive.args['file']
|
|
file_path = context.resolve_path(file_path_str)
|
|
|
|
# Create child context for the included file
|
|
child_context = context.create_child_context(file_path.parent)
|
|
|
|
# Add any directive arguments as variables
|
|
for key, value in directive.args.items():
|
|
if key != 'file':
|
|
child_context.set_variable(key, value)
|
|
|
|
# Process the included file
|
|
return self.process_file(file_path, child_context)
|
|
|
|
def _process_variable_directive(self, directive: Directive,
|
|
context: TransclusionContext) -> str:
|
|
"""
|
|
Process a variable substitution directive.
|
|
|
|
Args:
|
|
directive: Variable directive
|
|
context: Processing context
|
|
|
|
Returns:
|
|
Variable value as string
|
|
"""
|
|
var_name = directive.args['name']
|
|
value = context.get_variable(var_name, f"{{{{UNDEFINED: {var_name}}}}}")
|
|
return str(value)
|
|
|
|
def _process_conditional_directive(self, directive: Directive,
|
|
context: TransclusionContext) -> str:
|
|
"""
|
|
Process a conditional content directive.
|
|
|
|
Args:
|
|
directive: Conditional directive
|
|
context: Processing context
|
|
|
|
Returns:
|
|
Conditional content if condition is true, empty string otherwise
|
|
"""
|
|
condition = directive.args['condition']
|
|
|
|
# Simple condition evaluation (just variable existence for now)
|
|
if condition in context.variables:
|
|
var_value = context.get_variable(condition)
|
|
# Evaluate truthy/falsy
|
|
if var_value and str(var_value).lower() not in ('false', '0', ''):
|
|
# Process the content block recursively
|
|
return self.process_content(directive.content or '', context)
|
|
|
|
return '' |