Files
markitect-main/markitect/template/engine.py
tegwick bcbe78d04f feat: Complete Issue #65 Template Engine Foundation + Fix CLI Regression
## Issue #65 - Template Engine Foundation (COMPLETED)
- Implement complete TDD8 methodology with 30 comprehensive tests (100% passing)
- Add template variable parser with Unicode and dot notation support
- Add template rendering engine with strict/lenient modes
- Add business document generation (invoices, reports)
- Add CLI integration with `markitect template-render` command
- Add performance optimization (1000+ variables in <0.1s)

## Critical CLI Regression Fix
- Fix broken `markitect --help` due to import path issues in markitect/issues/base.py
- Add proper path resolution for domain module accessibility
- Add 12 comprehensive CLI integration tests to prevent future regressions
- Restore full CLI functionality with 35+ working commands

## Template Engine Architecture
- markitect/template/parser.py - Variable parsing with comprehensive validation
- markitect/template/engine.py - Template rendering with business logic
- markitect/template/__init__.py - Structured package exports
- Comprehensive exception hierarchy for robust error handling

## Test Coverage Excellence
- 30 Issue #65 tests: parser (9), substitution (14), integration (7)
- 12 CLI integration tests for regression prevention
- Business scenario validation with real invoice/report generation
- Performance benchmarking and error handling validation

## CLI Professional Enhancement
- Add template-render command with comprehensive options
- Fix import path issues preventing CLI access
- Add validation, data checking, output options
- Support JSON/YAML data formats with auto-detection

## Business Impact
- Transform MarkiTect from document analysis to business automation platform
- Enable professional invoice and report generation
- Provide robust CLI interface for document workflows
- Establish foundation for Epic #64 advanced template features

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-02 15:33:32 +02:00

147 lines
4.8 KiB
Python

"""
Template engine for rendering templates with data.
This module provides the core template rendering functionality,
building on the parser module for variable extraction and substitution.
"""
import re
from typing import Dict, Any, Optional, Union
from .parser import TemplateParser, TemplateParsingError
class TemplateRenderError(TemplateParsingError):
"""Exception raised during template rendering."""
pass
class VariableNotFoundError(TemplateRenderError):
"""Raised when required variable is missing from data."""
pass
class TemplateEngine:
"""Template rendering engine for dynamic document generation."""
def __init__(self):
"""Initialize the template engine."""
self.parser = TemplateParser()
def render(self, template_text: str, data: Dict[str, Any], strict: bool = True) -> str:
"""
Render a template with the provided data.
Args:
template_text: The template content to render
data: Dictionary containing data for variable substitution
strict: If True, raise error for missing variables. If False, preserve placeholders.
Returns:
Rendered template with variables substituted
Raises:
TemplateRenderError: When variables are missing in strict mode
TypeError: When data is not a dictionary
"""
if not isinstance(data, dict):
raise TypeError("Data must be a dictionary")
if not template_text:
return template_text
# Use the parser's regex pattern to find and replace variables
def replace_variable(match):
variable_name = match.group(1)
try:
value = self._get_nested_value(data, variable_name)
return str(value) if value is not None else "None"
except (KeyError, TypeError, AttributeError) as e:
if strict:
raise VariableNotFoundError(f"Variable '{variable_name}' not found in data", context=str(e))
else:
# Return the original placeholder in lenient mode
return match.group(0)
# Perform the substitution
result = self.parser.VARIABLE_PATTERN.sub(replace_variable, template_text)
return result
def _get_nested_value(self, data: Dict[str, Any], key: str) -> Any:
"""
Get nested value using dot notation.
Args:
data: Dictionary containing the data
key: Key with dot notation (e.g., "nested.category")
Returns:
Value at the specified key path
Raises:
KeyError: When the key path is not found
"""
keys = key.split('.')
current = data
path_so_far = []
for k in keys:
path_so_far.append(k)
if isinstance(current, dict) and k in current:
current = current[k]
else:
available_keys = list(current.keys()) if isinstance(current, dict) else "not a dictionary"
raise KeyError(f"Key '{k}' not found in path '{key}'. Available keys at '{'.'.join(path_so_far[:-1])}': {available_keys}")
return current
def validate_template(self, template_text: str) -> list:
"""
Validate template syntax and return any errors.
Args:
template_text: The template content to validate
Returns:
List of validation errors (empty if template is valid)
"""
return self.parser.validate_variable_syntax(template_text)
def get_required_variables(self, template_text: str) -> list:
"""
Get list of variables required by the template.
Args:
template_text: The template content to analyze
Returns:
List of variable names required by the template
"""
return self.parser.extract_variables(template_text)
def check_data_completeness(self, template_text: str, data: Dict[str, Any]) -> Dict[str, list]:
"""
Check if provided data contains all required variables.
Args:
template_text: The template content to check
data: Data dictionary to validate
Returns:
Dictionary with 'missing' and 'available' variable lists
"""
required_vars = self.get_required_variables(template_text)
missing_vars = []
available_vars = []
for var in required_vars:
try:
self._get_nested_value(data, var)
available_vars.append(var)
except (KeyError, TypeError, AttributeError):
missing_vars.append(var)
return {
'missing': missing_vars,
'available': available_vars,
'completeness': len(available_vars) / len(required_vars) if required_vars else 1.0
}