124 lines
3.6 KiB
Python
124 lines
3.6 KiB
Python
#!/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
|