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