#!/usr/bin/env python3.9 # -*- coding: utf-8 -*- """ JFinal Template - Template Rendering Engine """ from typing import Dict, Optional, Union, Callable from io import StringIO from .Env import Env from .stat.ast.Stat import Stat class Template: """Template rendering engine""" def __init__(self, env: Env, ast: Stat): """ Initialize template Args: env: Template environment ast: Abstract syntax tree """ if env is None or ast is None: raise ValueError("env and ast cannot be null") self._env = env self._ast = ast def render(self, data: Dict = None, output = None): """ Render template to output Args: data: Data dictionary output: Output target (file, stream, writer, etc.) """ data = data or {} scope = self._create_scope(data) if hasattr(output, 'write'): # It's a writer-like object self._ast.exec(self._env, scope, output) elif hasattr(output, 'writebytes') or hasattr(output, 'write_bytes'): # It's a binary stream self._render_to_binary(output, scope) else: # Try to convert to appropriate output self._render_to_output(output, scope) def render_to_string(self, data: Dict = None) -> str: """ Render template to string Args: data: Data dictionary Returns: Rendered string """ output = StringIO() self.render(data, output) return output.getvalue() def render_to_string_builder(self, data: Dict = None) -> StringIO: """ Render template to StringIO Args: data: Data dictionary Returns: StringIO object """ output = StringIO() self.render(data, output) return output def _create_scope(self, data: Dict) -> 'Scope': """Create execution scope""" from .stat.Scope import Scope shared_objects = self._env.engine_config.shared_object_map or {} return Scope(data, shared_objects) def _render_to_binary(self, output, scope): """Render to binary output""" # Simplified binary output handling result = self._ast.exec(self._env, scope, StringIO()) if isinstance(result, str): output.write(result.encode('utf-8')) def _render_to_output(self, output, scope): """Render to various output types""" result = self._ast.exec(self._env, scope, StringIO()) if isinstance(result, str): if hasattr(output, '__fspath__'): # It's a file path with open(output, 'w', encoding='utf-8') as f: f.write(result) elif isinstance(output, str): # It's a file path string with open(output, 'w', encoding='utf-8') as f: f.write(result) else: # Try to convert to string output_str = str(result) if hasattr(output, 'write'): output.write(output_str) def is_modified(self) -> bool: """Check if template has been modified (for dev mode)""" return self._env.is_source_list_modified() def get_env(self) -> Env: """Get template environment""" return self._env def get_ast(self) -> Stat: """Get abstract syntax tree""" return self._ast # Import Scope to avoid circular import from .stat.Scope import Scope