Files
markitect-main/markitect/packaging/transclusion/engine.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

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