初始提交,未完全测试

This commit is contained in:
2026-02-27 14:37:10 +08:00
parent 76c0f469be
commit e270f02073
68 changed files with 5886 additions and 0 deletions

46
template/Directive.py Normal file
View File

@@ -0,0 +1,46 @@
#!/usr/bin/env python3.9
# -*- coding: utf-8 -*-
"""
JFinal Directive - Base Directive Class
"""
from typing import Optional
from .expr.ast import ExprList
from .stat.ast.Stat import Stat
class Directive(Stat):
"""Base class for custom directives"""
def __init__(self):
"""Initialize directive"""
super().__init__()
self._expr_list: Optional[ExprList] = None
self._stat: Optional[Stat] = None
@property
def expr_list(self) -> Optional[ExprList]:
"""Get expression list"""
return self._expr_list
@expr_list.setter
def expr_list(self, value: ExprList):
"""Set expression list"""
self._expr_list = value
@property
def stat(self) -> Optional[Stat]:
"""Get nested stat"""
return self._stat
@stat.setter
def stat(self, value: Stat):
"""Set nested stat"""
self._stat = value
def set_expr_list(self, expr_list: ExprList):
"""Set expression list (for parser)"""
self._expr_list = expr_list
def set_stat(self, stat: Stat):
"""Set nested stat (for parser)"""
self._stat = stat

401
template/Engine.py Normal file
View File

@@ -0,0 +1,401 @@
#!/usr/bin/env python3.9
# -*- coding: utf-8 -*-
"""
JFinal Engine - Template Engine Main Class
"""
from typing import Dict, Optional, Set, Type, Any, Callable
from .EngineConfig import EngineConfig
from .Env import Env
from .Template import Template
from .stat.Parser import Parser
from .stat.ast.Stat import Stat
from .source.ISourceFactory import ISourceFactory
from .source.ClassPathSourceFactory import ClassPathSourceFactory
from .source.StringSource import StringSource
from .io.WriterBuffer import WriterBuffer
from .io.EncoderFactory import EncoderFactory
from .io.JdkEncoderFactory import JdkEncoderFactory
from .stat.Compressor import Compressor
from .expr.ast.FieldKit import FieldKit
from .expr.ast.FieldKeyBuilder import FieldKeyBuilder
from .expr.ast.MethodKit import MethodKit
from .Directive import Directive
from .stat.OutputDirectiveFactory import OutputDirectiveFactory
from ..kit.SyncWriteMap import SyncWriteMap
from ..kit.StrKit import StrKit
from ..kit.HashKit import HashKit
class Engine:
"""Template Engine - Main class for JFinal Template Engine"""
MAIN_ENGINE_NAME = "main"
_MAIN_ENGINE: 'Engine' = None
_engine_map: Dict[str, 'Engine'] = {}
def __init__(self, engine_name: str = None):
"""
Initialize engine
Args:
engine_name: Engine name (optional)
"""
self._name = engine_name or "NO_NAME"
self._dev_mode = True
self._cache_string_template = False
self._config = EngineConfig()
self._source_factory = self._config.source_factory
self._template_cache = SyncWriteMap(2048, 0.5)
@classmethod
def use(cls, engine_name: str = None) -> 'Engine':
"""
Get engine instance
Args:
engine_name: Engine name (optional, uses main engine if not specified)
Returns:
Engine instance
"""
if engine_name is None:
if cls._MAIN_ENGINE is None:
cls._MAIN_ENGINE = cls(cls.MAIN_ENGINE_NAME)
cls._engine_map[cls.MAIN_ENGINE_NAME] = cls._MAIN_ENGINE
return cls._MAIN_ENGINE
return cls._engine_map.get(engine_name)
@classmethod
def create(cls, engine_name: str, configurator: Callable[['Engine'], None] = None) -> 'Engine':
"""
Create new engine with name
Args:
engine_name: Engine name
configurator: Configuration callback
Returns:
New engine instance
"""
if StrKit.is_blank(engine_name):
raise ValueError("Engine name cannot be blank")
engine_name = engine_name.strip()
if engine_name in cls._engine_map:
raise ValueError(f"Engine already exists: {engine_name}")
new_engine = cls(engine_name)
cls._engine_map[engine_name] = new_engine
if configurator:
configurator(new_engine)
return new_engine
@classmethod
def create_if_absent(cls, engine_name: str, configurator: Callable[['Engine'], None] = None) -> 'Engine':
"""
Create engine if absent
Args:
engine_name: Engine name
configurator: Configuration callback
Returns:
Engine instance
"""
ret = cls._engine_map.get(engine_name)
if ret is None:
# Double-check locking
if engine_name not in cls._engine_map:
ret = cls.create(engine_name)
if configurator:
configurator(ret)
return ret
@classmethod
def remove(cls, engine_name: str) -> Optional['Engine']:
"""
Remove engine by name
Args:
engine_name: Engine name
Returns:
Removed engine or None
"""
removed = cls._engine_map.pop(engine_name, None)
if removed and cls.MAIN_ENGINE_NAME == removed.name:
cls._MAIN_ENGINE = None
return removed
@classmethod
def set_main_engine(cls, engine: 'Engine'):
"""
Set main engine
Args:
engine: Engine to set as main
"""
if engine is None:
raise ValueError("Engine cannot be null")
engine._name = cls.MAIN_ENGINE_NAME
cls._engine_map[cls.MAIN_ENGINE_NAME] = engine
cls._MAIN_ENGINE = engine
def get_template(self, file_name: str) -> Template:
"""
Get template by file name
Args:
file_name: Template file name
Returns:
Template instance
"""
template = self._template_cache.get(file_name)
if template is None:
template = self._build_template_by_source_factory(file_name)
self._template_cache[file_name] = template
elif self._dev_mode and template.is_modified():
template = self._build_template_by_source_factory(file_name)
self._template_cache[file_name] = template
return template
def _build_template_by_source_factory(self, file_name: str) -> Template:
"""Build template using source factory"""
source = self._source_factory.get_source(
self._config.base_template_path,
file_name,
self._config.encoding
)
return self._build_template(source, file_name)
def get_template_by_string(self, content: str, cache: bool = False) -> Template:
"""
Get template by string content
Args:
content: Template content
cache: Whether to cache the template
Returns:
Template instance
"""
if not cache:
source = StringSource(content, False)
return self._build_template(source, None)
cache_key = HashKit.md5(content)
template = self._template_cache.get(cache_key)
if template is None:
source = StringSource(content, cache_key)
template = self._build_template(source, cache_key)
self._template_cache[cache_key] = template
elif self._dev_mode and template.is_modified():
source = StringSource(content, cache_key)
template = self._build_template(source, cache_key)
self._template_cache[cache_key] = template
return template
def _build_template(self, source, cache_key: str = None) -> Template:
"""Build template from source"""
env = Env(self._config)
if self._dev_mode:
env.add_source(source)
parser = Parser(env, source.get_content(), cache_key)
stat = parser.parse()
return Template(env, stat)
def add_shared_function(self, file_name: str) -> 'Engine':
"""Add shared function from file"""
self._config.add_shared_function(file_name)
return self
def add_shared_function_by_string(self, content: str) -> 'Engine':
"""Add shared function from string"""
self._config.add_shared_function_by_content(content)
return self
def add_shared_object(self, name: str, obj: Any) -> 'Engine':
"""Add shared object"""
self._config.add_shared_object(name, obj)
return self
def remove_shared_object(self, name: str) -> 'Engine':
"""Remove shared object"""
self._config.remove_shared_object(name)
return self
def add_directive(self, directive_name: str, directive_class: Type[Directive],
keep_line_blank: bool = False) -> 'Engine':
"""Add custom directive"""
self._config.add_directive(directive_name, directive_class, keep_line_blank)
return self
def remove_directive(self, directive_name: str) -> 'Engine':
"""Remove directive"""
self._config.remove_directive(directive_name)
return self
def add_shared_method(self, obj: Any) -> 'Engine':
"""Add shared method from object"""
self._config.add_shared_method(obj)
return self
def add_shared_method_from_class(self, clazz: Type) -> 'Engine':
"""Add shared method from class"""
self._config.add_shared_method_from_class(clazz)
return self
def add_shared_static_method(self, clazz: Type) -> 'Engine':
"""Add shared static method from class"""
self._config.add_shared_static_method(clazz)
return self
def remove_shared_method(self, method_name: str) -> 'Engine':
"""Remove shared method by name"""
self._config.remove_shared_method(method_name)
return self
def remove_template_cache(self, cache_key: str):
"""Remove template cache by key"""
self._template_cache.pop(cache_key, None)
def remove_all_template_cache(self):
"""Remove all template cache"""
self._template_cache.clear()
@property
def dev_mode(self) -> bool:
"""Get dev mode setting"""
return self._dev_mode
@dev_mode.setter
def dev_mode(self, value: bool):
"""Set dev mode"""
self._dev_mode = value
self._config.dev_mode = value
if self._dev_mode:
self.remove_all_template_cache()
@property
def cache_string_template(self) -> bool:
"""Get cache string template setting"""
return self._cache_string_template
@cache_string_template.setter
def cache_string_template(self, value: bool):
"""Set cache string template"""
self._cache_string_template = value
@property
def name(self) -> str:
"""Get engine name"""
return self._name
@property
def config(self) -> EngineConfig:
"""Get engine configuration"""
return self._config
@property
def source_factory(self) -> ISourceFactory:
"""Get source factory"""
return self._source_factory
@source_factory.setter
def source_factory(self, value: ISourceFactory):
"""Set source factory"""
self._config.source_factory = value
self._source_factory = value
def set_to_class_path_source_factory(self) -> 'Engine':
"""Set to class path source factory"""
self.source_factory = ClassPathSourceFactory()
return self
@property
def base_template_path(self) -> Optional[str]:
"""Get base template path"""
return self._config.base_template_path
@base_template_path.setter
def base_template_path(self, value: str):
"""Set base template path"""
self._config.base_template_path = value
@property
def encoding(self) -> str:
"""Get encoding"""
return self._config.encoding
@encoding.setter
def encoding(self, value: str):
"""Set encoding"""
self._config.encoding = value
@property
def date_pattern(self) -> str:
"""Get date pattern"""
return self._config.date_pattern
@date_pattern.setter
def date_pattern(self, value: str):
"""Set date pattern"""
self._config.date_pattern = value
def set_compressor(self, compressor: Compressor) -> 'Engine':
"""Set compressor"""
self._config._compressor = compressor
return self
def set_compressor_on(self, separator: str = '\n') -> 'Engine':
"""Set compressor on"""
self._config._compressor = Compressor(separator)
return self
def set_encoder_factory(self, encoder_factory: EncoderFactory) -> 'Engine':
"""Set encoder factory"""
self._config.writer_buffer.set_encoder_factory(encoder_factory)
self._config.writer_buffer.set_encoding(self._config.encoding)
return self
def set_to_jdk_encoder_factory(self) -> 'Engine':
"""Set to JDK encoder factory"""
self.set_encoder_factory(JdkEncoderFactory())
return self
def set_buffer_size(self, buffer_size: int) -> 'Engine':
"""Set buffer size"""
self._config.writer_buffer.set_buffer_size(buffer_size)
return self
def set_reentrant_buffer_size(self, reentrant_buffer_size: int) -> 'Engine':
"""Set reentrant buffer size"""
self._config.writer_buffer.set_reentrant_buffer_size(reentrant_buffer_size)
return self
@property
def template_cache_size(self) -> int:
"""Get template cache size"""
return len(self._template_cache)
def __repr__(self) -> str:
return f"Template Engine: {self._name}"
# Initialize main engine
Engine._MAIN_ENGINE = Engine(Engine.MAIN_ENGINE_NAME)
Engine._engine_map[Engine.MAIN_ENGINE_NAME] = Engine._MAIN_ENGINE

266
template/EngineConfig.py Normal file
View File

@@ -0,0 +1,266 @@
#!/usr/bin/env python3.9
# -*- coding: utf-8 -*-
"""
JFinal EngineConfig - Template Engine Configuration
"""
from typing import Dict, Optional, List, Set, Type, Any
from decimal import Decimal
from .Directive import Directive
from .stat.Parser import Parser
from .stat.ast.Define import Define
from .stat.OutputDirectiveFactory import OutputDirectiveFactory
from .io.WriterBuffer import WriterBuffer
from .source.ISourceFactory import ISourceFactory
from .source.FileSourceFactory import FileSourceFactory
from .expr.ast.SharedMethodKit import SharedMethodKit
from .ext.directive.RenderDirective import RenderDirective
from .ext.directive.DateDirective import DateDirective
from .ext.directive.EscapeDirective import EscapeDirective
from .ext.directive.RandomDirective import RandomDirective
from .ext.directive.NumberDirective import NumberDirective
from .ext.directive.CallDirective import CallDirective
from .ext.directive.StringDirective import StringDirective
from .ext.sharedmethod.SharedMethodLib import SharedMethodLib
class EngineConfig:
"""Template engine configuration"""
DEFAULT_ENCODING = "UTF-8"
def __init__(self):
"""Initialize engine configuration"""
self._writer_buffer = WriterBuffer()
self._compressor = None
self._shared_function_map: Dict[str, Define] = {}
self._shared_function_source_list: List[object] = []
self._shared_object_map: Optional[Dict[str, Any]] = None
self._output_directive_factory = OutputDirectiveFactory()
self._source_factory = FileSourceFactory()
self._directive_map: Dict[str, Type[Directive]] = {}
self._shared_method_kit = SharedMethodKit()
self._keep_line_blank_directives: Set[str] = set()
self._dev_mode = False
self._reload_modified_shared_function_in_dev_mode = True
self._base_template_path: Optional[str] = None
self._encoding = EngineConfig.DEFAULT_ENCODING
self._date_pattern = "yyyy-MM-dd HH:mm"
self._rounding_mode = "HALF_UP" # Default rounding mode
self._support_static_method_expression = False
self._support_static_field_expression = False
# Initialize built-in directives
self._init_builtin_directives()
def _init_builtin_directives(self):
"""Initialize built-in directives"""
self._keep_line_blank_directives.add("output")
self._keep_line_blank_directives.add("include")
self.add_directive("render", RenderDirective, True)
self.add_directive("date", DateDirective, True)
self.add_directive("escape", EscapeDirective, True)
self.add_directive("random", RandomDirective, True)
self.add_directive("number", NumberDirective, True)
self.add_directive("call", CallDirective, False)
self.add_directive("string", StringDirective, False)
# Add built-in shared methods
self.add_shared_method(SharedMethodLib())
def add_shared_function(self, file_name: str):
"""Add shared function from file"""
file_name = file_name.replace("\\", "/")
source = self._source_factory.get_source(self._base_template_path, file_name, self._encoding)
self._do_add_shared_function(source, file_name)
def _do_add_shared_function(self, source, file_name: str):
"""Internal method to add shared function"""
# Create a minimal env for parsing
env = Env(self)
parser = Parser(env, source.get_content(), file_name)
parser.parse()
# Add to shared function map
func_map = env.get_function_map()
for func_name, func in func_map.items():
if func_name in self._shared_function_map:
raise ValueError(f"Template function already exists: {func_name}")
self._shared_function_map[func_name] = func
# Add source for dev mode
if self._dev_mode:
self._shared_function_source_list.append(source)
env.add_source(source)
def add_shared_function_by_content(self, content: str):
"""Add shared function from content string"""
from .source.StringSource import StringSource
string_source = StringSource(content, False)
self._do_add_shared_function(string_source, None)
def add_shared_object(self, name: str, obj: Any):
"""Add shared object"""
if self._shared_object_map is None:
self._shared_object_map = {}
if name in self._shared_object_map:
raise ValueError(f"Shared object already exists: {name}")
self._shared_object_map[name] = obj
def get_shared_object(self, name: str) -> Optional[Any]:
"""Get shared object"""
if self._shared_object_map:
return self._shared_object_map.get(name)
return None
def remove_shared_object(self, name: str):
"""Remove shared object"""
if self._shared_object_map:
self._shared_object_map.pop(name, None)
def add_directive(self, directive_name: str, directive_class: Type[Directive], keep_line_blank: bool = False):
"""Add custom directive"""
if not directive_name or not directive_name.strip():
raise ValueError("directive name cannot be blank")
if directive_class is None:
raise ValueError("directiveClass cannot be null")
if directive_name in self._directive_map:
raise ValueError(f"directive already exists: {directive_name}")
self._directive_map[directive_name] = directive_class
if keep_line_blank:
self._keep_line_blank_directives.add(directive_name)
def get_directive(self, directive_name: str) -> Optional[Type[Directive]]:
"""Get directive class"""
return self._directive_map.get(directive_name)
def remove_directive(self, directive_name: str):
"""Remove directive"""
self._directive_map.pop(directive_name, None)
self._keep_line_blank_directives.discard(directive_name)
def add_shared_method(self, obj: Any):
"""Add shared method from object"""
self._shared_method_kit.add_shared_method(obj)
def add_shared_method_from_class(self, clazz: Type):
"""Add shared method from class"""
self._shared_method_kit.add_shared_method(clazz)
def add_shared_static_method(self, clazz: Type):
"""Add shared static method from class"""
self._shared_method_kit.add_shared_static_method(clazz)
def remove_shared_method(self, method_name: str):
"""Remove shared method by name"""
self._shared_method_kit.remove_shared_method(method_name)
def get_shared_method_kit(self) -> SharedMethodKit:
"""Get shared method kit"""
return self._shared_method_kit
# Property accessors
@property
def dev_mode(self) -> bool:
"""Get dev mode setting"""
return self._dev_mode
@dev_mode.setter
def dev_mode(self, value: bool):
"""Set dev mode"""
self._dev_mode = value
@property
def base_template_path(self) -> Optional[str]:
"""Get base template path"""
return self._base_template_path
@base_template_path.setter
def base_template_path(self, value: str):
"""Set base template path"""
if value is None:
self._base_template_path = None
return
value = value.strip().replace("\\", "/")
if not value:
raise ValueError("baseTemplatePath cannot be blank")
if len(value) > 1 and value.endswith("/"):
value = value[:-1]
self._base_template_path = value
@property
def encoding(self) -> str:
"""Get encoding"""
return self._encoding
@encoding.setter
def encoding(self, value: str):
"""Set encoding"""
if not value:
raise ValueError("encoding cannot be blank")
self._encoding = value
self._writer_buffer.set_encoding(value)
@property
def date_pattern(self) -> str:
"""Get date pattern"""
return self._date_pattern
@date_pattern.setter
def date_pattern(self, value: str):
"""Set date pattern"""
if not value:
raise ValueError("datePattern cannot be blank")
self._date_pattern = value
@property
def rounding_mode(self) -> str:
"""Get rounding mode"""
return self._rounding_mode
@rounding_mode.setter
def rounding_mode(self, value: str):
"""Set rounding mode"""
self._rounding_mode = value
@property
def writer_buffer(self) -> WriterBuffer:
"""Get writer buffer"""
return self._writer_buffer
@property
def source_factory(self) -> ISourceFactory:
"""Get source factory"""
return self._source_factory
@source_factory.setter
def source_factory(self, value: ISourceFactory):
"""Set source factory"""
if value is None:
raise ValueError("sourceFactory cannot be null")
self._source_factory = value
@property
def shared_function_map(self) -> Dict[str, Define]:
"""Get shared function map"""
return self._shared_function_map.copy()
@property
def shared_object_map(self) -> Optional[Dict[str, Any]]:
"""Get shared object map"""
return self._shared_object_map.copy() if self._shared_object_map else None
# Import needed classes to avoid circular imports
from .Env import Env

92
template/Env.py Normal file
View File

@@ -0,0 +1,92 @@
#!/usr/bin/env python3.9
# -*- coding: utf-8 -*-
"""
JFinal Env - Template Environment
"""
from typing import Dict, Optional, List
class Env:
"""Template environment for storing template functions and managing sources"""
def __init__(self, engine_config):
"""
Initialize environment
Args:
engine_config: Engine configuration
"""
self._engine_config = engine_config
self._function_map: Dict[str, object] = {}
self._source_list: Optional[List[object]] = None
@property
def engine_config(self):
"""Get engine configuration"""
return self._engine_config
@property
def is_dev_mode(self) -> bool:
"""Check if in development mode"""
return self._engine_config.is_dev_mode()
def add_function(self, function_name: str, function: object):
"""
Add template function
Args:
function_name: Function name
function: Function object
"""
if function_name in self._function_map:
raise ValueError(f"Template function '{function_name}' already defined")
self._function_map[function_name] = function
def get_function(self, function_name: str) -> Optional[object]:
"""
Get template function
Args:
function_name: Function name
Returns:
Function object or None
"""
func = self._function_map.get(function_name)
if func is not None:
return func
# Try to get from shared functions
return self._engine_config.get_shared_function(function_name)
def get_function_map(self) -> Dict[str, object]:
"""Get all functions"""
return self._function_map.copy()
def add_source(self, source):
"""
Add source for dev mode tracking
Args:
source: Source object
"""
if self._source_list is None:
self._source_list = []
self._source_list.append(source)
def is_source_list_modified(self) -> bool:
"""Check if any source has been modified (for dev mode)"""
if self._source_list:
for source in self._source_list:
if hasattr(source, 'is_modified') and source.is_modified():
return True
return False
def clear_functions(self):
"""Clear all functions"""
self._function_map.clear()
def __repr__(self) -> str:
return f"Env(function_count={len(self._function_map)}, dev_mode={self.is_dev_mode})"

123
template/Template.py Normal file
View File

@@ -0,0 +1,123 @@
#!/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

View File

@@ -0,0 +1,44 @@
#!/usr/bin/env python3.9
# -*- coding: utf-8 -*-
"""
JFinal TemplateException - Template Exception
"""
class TemplateException(Exception):
"""Template runtime exception"""
def __init__(self, message: str, location = None, cause: Exception = None):
"""
Initialize template exception
Args:
message: Error message
location: Error location (optional)
cause: Original exception (optional)
"""
full_message = message
if location is not None:
full_message += str(location)
super().__init__(full_message)
self._location = location
self._cause = cause
@property
def location(self):
"""Get error location"""
return self._location
@property
def cause(self) -> Exception:
"""Get original cause"""
return self._cause
def __str__(self) -> str:
result = super().__str__()
if self._cause and self._cause != self:
result += f"\nCaused by: {self._cause}"
return result
def __repr__(self) -> str:
return f"TemplateException(message={super().__repr__()}, location={self._location})"

View File

@@ -0,0 +1 @@
# Python package initialization

172
template/expr/ast/Arith.py Normal file
View File

@@ -0,0 +1,172 @@
#!/usr/bin/env python3.9
# -*- coding: utf-8 -*-
"""
JFinal Arith - Arithmetic Expression
"""
from .Expr import Expr
from decimal import Decimal, ROUND_HALF_UP
class Arith(Expr):
"""Arithmetic expression"""
ADD = "+"
SUB = "-"
MUL = "*"
DIV = "/"
MOD = "%"
_rounding_mode = ROUND_HALF_UP
def __init__(self, left: Expr, operator: str, right: Expr):
"""
Initialize arithmetic expression
Args:
left: Left expression
operator: Arithmetic operator
right: Right expression
"""
self._left = left
self._operator = operator
self._right = right
def eval(self, scope):
"""Evaluate arithmetic expression"""
left_val = self._left.eval(scope)
right_val = self._right.eval(scope)
# Handle None values
if left_val is None or right_val is None:
return None
# Convert to appropriate numeric types
left_num = self._to_number(left_val)
right_num = self._to_number(right_val)
if left_num is None or right_num is None:
return None
# Perform operation
if self._operator == Arith.ADD:
return left_num + right_num
elif self._operator == Arith.SUB:
return left_num - right_num
elif self._operator == Arith.MUL:
return left_num * right_num
elif self._operator == Arith.DIV:
if right_num == 0:
raise ZeroDivisionError("Division by zero")
return self._divide(left_num, right_num)
elif self._operator == Arith.MOD:
return left_num % right_num
else:
raise ValueError(f"Unknown operator: {self._operator}")
def _to_number(self, value):
"""Convert value to number"""
if isinstance(value, (int, float)):
return value
if isinstance(value, Decimal):
return value
if isinstance(value, str):
try:
if '.' in value:
return float(value)
else:
return int(value)
except ValueError:
return None
return None
def _divide(self, left, right):
"""Handle division with proper rounding"""
if isinstance(left, Decimal) or isinstance(right, Decimal):
result = Decimal(str(left)) / Decimal(str(right))
return result.quantize(Decimal('0.00000001'), rounding=self._rounding_mode)
else:
return left / right
@classmethod
def set_big_decimal_divide_rounding_mode(cls, rounding_mode):
"""Set rounding mode for decimal division"""
cls._rounding_mode = rounding_mode
def __repr__(self) -> str:
return f"Arith({self._left} {self._operator} {self._right})"
class Unary(Expr):
"""Unary expression (positive, negative, increment, decrement)"""
POSITIVE = "+"
NEGATIVE = "-"
NOT = "!"
def __init__(self, operator: str, expr: Expr):
"""
Initialize unary expression
Args:
operator: Unary operator
expr: Expression to operate on
"""
self._operator = operator
self._expr = expr
def eval(self, scope):
"""Evaluate unary expression"""
val = self._expr.eval(scope)
if self._operator == Unary.POSITIVE:
return +val if isinstance(val, (int, float)) else val
elif self._operator == Unary.NEGATIVE:
return -val if isinstance(val, (int, float)) else val
elif self._operator == Unary.NOT:
return not val
else:
raise ValueError(f"Unknown operator: {self._operator}")
def __repr__(self) -> str:
return f"Unary({self._operator}{self._expr})"
class IncDec(Expr):
"""Increment/Decrement expression"""
INC = "++"
DEC = "--"
def __init__(self, expr: Expr, operator: str, is_prefix: bool = True):
"""
Initialize increment/decrement
Args:
expr: Expression to increment/decrement
operator: INC or DEC
is_prefix: Whether this is prefix (++x) or postfix (x++)
"""
self._expr = expr
self._operator = operator
self._is_prefix = is_prefix
def eval(self, scope):
"""Evaluate increment/decrement"""
# This is a simplified implementation
# In a real implementation, this would modify the variable
current_val = self._expr.eval(scope)
if isinstance(current_val, int):
if self._operator == IncDec.INC:
return current_val + 1
else:
return current_val - 1
return current_val
def __repr__(self) -> str:
op_str = self._operator
if self._is_prefix:
return f"IncDec({op_str}{self._expr})"
else:
return f"IncDec({self._expr}{op_str})"

View File

@@ -0,0 +1,138 @@
#!/usr/bin/env python3.9
# -*- coding: utf-8 -*-
"""
JFinal Compare - Comparison Expression
"""
from .Expr import Expr
class Compare(Expr):
"""Comparison expression"""
EQ = "=="
NE = "!="
GT = ">"
GTE = ">="
LT = "<"
LTE = "<="
def __init__(self, left: Expr, operator: str, right: Expr):
"""
Initialize comparison
Args:
left: Left expression
operator: Comparison operator
right: Right expression
"""
self._left = left
self._operator = operator
self._right = right
def eval(self, scope):
"""Evaluate comparison"""
left_val = self._left.eval(scope)
right_val = self._right.eval(scope)
# Handle None comparisons
if left_val is None and right_val is None:
return self._operator == Compare.EQ
if left_val is None or right_val is None:
return self._operator == Compare.NE
# Perform comparison based on operator
if self._operator == Compare.EQ:
return left_val == right_val
elif self._operator == Compare.NE:
return left_val != right_val
elif self._operator == Compare.GT:
return left_val > right_val
elif self._operator == Compare.GTE:
return left_val >= right_val
elif self._operator == Compare.LT:
return left_val < right_val
elif self._operator == Compare.LTE:
return left_val <= right_val
else:
raise ValueError(f"Unknown operator: {self._operator}")
@property
def operator(self) -> str:
"""Get operator"""
return self._operator
def __repr__(self) -> str:
return f"Compare({self._left} {self._operator} {self._right})"
class Logic(Expr):
"""Logical expression (and, or, not)"""
AND = "&&"
OR = "||"
NOT = "!"
def __init__(self, operator: str, *expressions: Expr):
"""
Initialize logical expression
Args:
operator: Logical operator
expressions: Expressions to operate on
"""
self._operator = operator
self._expressions = expressions
def eval(self, scope):
"""Evaluate logical expression"""
if self._operator == Logic.NOT:
# Unary NOT
if not self._expressions:
return True
return not self._expressions[0].eval(scope)
else:
# Binary AND/OR
results = [expr.eval(scope) for expr in self._expressions]
if self._operator == Logic.AND:
return all(results)
elif self._operator == Logic.OR:
return any(results)
else:
raise ValueError(f"Unknown operator: {self._operator}")
def __repr__(self) -> str:
expr_str = f" {self._operator} ".join(str(expr) for expr in self._expressions)
if self._operator == Logic.NOT:
return f"NOT({expr_str})"
return f"({expr_str})"
class Ternary(Expr):
"""Ternary conditional expression (condition ? true_val : false_val)"""
def __init__(self, condition: Expr, true_expr: Expr, false_expr: Expr):
"""
Initialize ternary expression
Args:
condition: Condition expression
true_expr: Expression when condition is true
false_expr: Expression when condition is false
"""
self._condition = condition
self._true_expr = true_expr
self._false_expr = false_expr
def eval(self, scope):
"""Evaluate ternary expression"""
cond_val = self._condition.eval(scope)
# Convert to boolean (handle None, 0, empty strings, etc.)
if cond_val:
return self._true_expr.eval(scope)
else:
return self._false_expr.eval(scope)
def __repr__(self) -> str:
return f"Ternary({self._condition} ? {self._true_expr} : {self._false_expr})"

151
template/expr/ast/Const.py Normal file
View File

@@ -0,0 +1,151 @@
#!/usr/bin/env python3.9
# -*- coding: utf-8 -*-
"""
JFinal Const - Constant Expression
"""
from .Expr import Expr
class Const(Expr):
"""Constant value expression"""
def __init__(self, value):
"""
Initialize constant
Args:
value: Constant value
"""
self._value = value
def eval(self, scope):
"""Evaluate and return constant value"""
return self._value
def __repr__(self) -> str:
return f"Const({self._value})"
class NullExpr(Expr):
"""Null expression"""
def eval(self, scope):
"""Evaluate and return None"""
return None
def __repr__(self) -> str:
return "NullExpr()"
class Id(Expr):
"""Identifier expression (variable reference)"""
def __init__(self, name: str):
"""
Initialize identifier
Args:
name: Variable name
"""
self._name = name
def eval(self, scope):
"""Evaluate and return variable value"""
return scope.get(self._name)
@property
def name(self) -> str:
"""Get identifier name"""
return self._name
def __repr__(self) -> str:
return f"Id({self._name})"
class Map(Expr):
"""Map/dictionary expression"""
def __init__(self):
"""Initialize empty map"""
self._entries = []
def add_entry(self, key_expr: Expr, value_expr: Expr):
"""
Add map entry
Args:
key_expr: Key expression
value_expr: Value expression
"""
self._entries.append((key_expr, value_expr))
def eval(self, scope):
"""Evaluate and return map"""
result = {}
for key_expr, value_expr in self._entries:
key = key_expr.eval(scope)
value = value_expr.eval(scope)
if key is not None:
result[key] = value
return result
def __repr__(self) -> str:
return f"Map({len(self._entries)} entries)"
class Array(Expr):
"""Array/list expression"""
def __init__(self):
"""Initialize empty array"""
self._elements = []
def add_element(self, element_expr: Expr):
"""
Add array element
Args:
element_expr: Element expression
"""
self._elements.append(element_expr)
def eval(self, scope):
"""Evaluate and return array"""
return [expr.eval(scope) for expr in self._elements]
def __repr__(self) -> str:
return f"Array({len(self._elements)} elements)"
class RangeArray(Expr):
"""Range array expression (e.g., 1..10)"""
def __init__(self, start_expr: Expr, end_expr: Expr, inclusive: bool = True):
"""
Initialize range array
Args:
start_expr: Start expression
end_expr: End expression
inclusive: Whether end is inclusive
"""
self._start_expr = start_expr
self._end_expr = end_expr
self._inclusive = inclusive
def eval(self, scope):
"""Evaluate and return range array"""
start = self._start_expr.eval(scope)
end = self._end_expr.eval(scope)
if isinstance(start, int) and isinstance(end, int):
if self._inclusive:
return list(range(start, end + 1))
else:
return list(range(start, end))
# For non-integer ranges, return empty list
return []
def __repr__(self) -> str:
return f"RangeArray({self._start_expr}, {self._end_expr}, inclusive={self._inclusive})"

100
template/expr/ast/Expr.py Normal file
View File

@@ -0,0 +1,100 @@
#!/usr/bin/env python3.9
# -*- coding: utf-8 -*-
"""
JFinal Expr - Expression Evaluation Base Class
"""
from typing import Any
from ...stat.Scope import Scope
from ...Env import Env
class Expr:
"""Base class for expression AST nodes"""
def eval(self, scope: Scope) -> Any:
"""
Evaluate expression
Args:
scope: Execution scope
Returns:
Expression result
"""
raise NotImplementedError("Expr.eval() must be implemented by subclasses")
def eval_expr_list(self, scope: Scope) -> list:
"""
Evaluate as expression list
Args:
scope: Execution scope
Returns:
List of expression results
"""
return [self.eval(scope)]
def __repr__(self) -> str:
return f"Expr({self.__class__.__name__})"
class ExprList(Expr):
"""Expression list containing multiple expressions"""
def __init__(self, expressions: list = None):
"""
Initialize expression list
Args:
expressions: List of expressions
"""
self._expressions = expressions or []
def add_expr(self, expr: Expr):
"""
Add expression to list
Args:
expr: Expression to add
"""
if expr:
self._expressions.append(expr)
def eval(self, scope: Scope) -> Any:
"""
Evaluate all expressions and return last result
Args:
scope: Execution scope
Returns:
Last expression result
"""
result = None
for expr in self._expressions:
result = expr.eval(scope)
return result
def eval_expr_list(self, scope: Scope) -> list:
"""
Evaluate all expressions and return list of results
Args:
scope: Execution scope
Returns:
List of results
"""
return [expr.eval(scope) for expr in self._expressions]
def get_expressions(self) -> list:
"""Get all expressions"""
return self._expressions.copy()
def __len__(self) -> int:
"""Get number of expressions"""
return len(self._expressions)
def __repr__(self) -> str:
return f"ExprList({len(self._expressions)} expressions)"

View File

@@ -0,0 +1,27 @@
#!/usr/bin/env python3.9
# -*- coding: utf-8 -*-
"""
JFinal FieldKeyBuilder - Field Key Builder
"""
class FieldKeyBuilder:
"""Field key builder for field access"""
def __init__(self):
"""Initialize field key builder"""
pass
def build_field_key(self, field_name: str) -> str:
"""
Build field key
Args:
field_name: Field name
Returns:
Field key
"""
return field_name
def __repr__(self) -> str:
return "FieldKeyBuilder()"

View File

@@ -0,0 +1,64 @@
#!/usr/bin/env python3.9
# -*- coding: utf-8 -*-
"""
JFinal FieldKit - Field Access Utilities
"""
class FieldKit:
"""Field access utilities"""
@staticmethod
def get_field(obj, field_name: str):
"""
Get field value from object
Args:
obj: Object to get field from
field_name: Field name
Returns:
Field value or None
"""
if obj is None:
return None
# Try attribute access
if hasattr(obj, field_name):
return getattr(obj, field_name)
# Try dictionary access
if isinstance(obj, dict) and field_name in obj:
return obj[field_name]
return None
@staticmethod
def set_field(obj, field_name: str, value):
"""
Set field value on object
Args:
obj: Object to set field on
field_name: Field name
value: Value to set
Returns:
True if successful, False otherwise
"""
if obj is None:
return False
# Try attribute access
if hasattr(obj, field_name):
setattr(obj, field_name, value)
return True
# Try dictionary access
if isinstance(obj, dict):
obj[field_name] = value
return True
return False
def __repr__(self) -> str:
return "FieldKit()"

View File

@@ -0,0 +1,56 @@
#!/usr/bin/env python3.9
# -*- coding: utf-8 -*-
"""
JFinal MethodKit - Method Access Utilities
"""
class MethodKit:
"""Method access utilities"""
@staticmethod
def get_method(obj, method_name: str, *args):
"""
Get method from object
Args:
obj: Object to get method from
method_name: Method name
args: Method arguments
Returns:
Method or None
"""
if obj is None:
return None
# Try attribute access
if hasattr(obj, method_name):
method = getattr(obj, method_name)
if callable(method):
return method
return None
@staticmethod
def invoke_method(obj, method_name: str, *args):
"""
Invoke method on object
Args:
obj: Object to invoke method on
method_name: Method name
args: Method arguments
Returns:
Method result or None
"""
method = MethodKit.get_method(obj, method_name)
if method:
try:
return method(*args)
except:
return None
return None
def __repr__(self) -> str:
return "MethodKit()"

View File

@@ -0,0 +1,121 @@
#!/usr/bin/env python3.9
# -*- coding: utf-8 -*-
"""
JFinal SharedMethodKit - Shared Method Kit
"""
from typing import Dict, List, Any, Optional
class SharedMethodKit:
"""Shared method kit for template functions"""
def __init__(self):
"""Initialize shared method kit"""
self._shared_methods: Dict[str, List[Any]] = {}
def add_shared_method(self, obj: Any):
"""
Add shared methods from object
Args:
obj: Object with methods to add
"""
if obj is None:
return
# Get all methods from object
import inspect
for name, method in inspect.getmembers(obj, predicate=inspect.ismethod):
if not name.startswith('_'):
if name not in self._shared_methods:
self._shared_methods[name] = []
self._shared_methods[name].append(method)
def add_shared_method_from_class(self, clazz):
"""
Add shared methods from class
Args:
clazz: Class with methods to add
"""
if clazz is None:
return
# Get all methods from class
import inspect
for name, method in inspect.getmembers(clazz, predicate=inspect.isfunction):
if not name.startswith('_'):
if name not in self._shared_methods:
self._shared_methods[name] = []
self._shared_methods[name].append(method)
def add_shared_static_method(self, clazz):
"""
Add shared static methods from class
Args:
clazz: Class with static methods to add
"""
if clazz is None:
return
# Get all static methods from class
import inspect
for name, method in inspect.getmembers(clazz, predicate=inspect.isfunction):
if not name.startswith('_'):
if name not in self._shared_methods:
self._shared_methods[name] = []
self._shared_methods[name].append(method)
def remove_shared_method(self, method_name: str):
"""
Remove shared method by name
Args:
method_name: Method name to remove
"""
if method_name in self._shared_methods:
del self._shared_methods[method_name]
def get_shared_method(self, method_name: str) -> Optional[Any]:
"""
Get shared method by name
Args:
method_name: Method name to get
Returns:
Shared method or None
"""
if method_name in self._shared_methods:
methods = self._shared_methods[method_name]
if methods:
return methods[0]
return None
def get_shared_methods(self, method_name: str) -> List[Any]:
"""
Get all shared methods with given name
Args:
method_name: Method name
Returns:
List of shared methods
"""
return self._shared_methods.get(method_name, [])
def has_shared_method(self, method_name: str) -> bool:
"""
Check if shared method exists
Args:
method_name: Method name
Returns:
True if method exists, False otherwise
"""
return method_name in self._shared_methods and self._shared_methods[method_name]
def __repr__(self) -> str:
return f"SharedMethodKit({len(self._shared_methods)} methods)"

View File

@@ -0,0 +1,20 @@
# Export expression AST classes
from .Expr import Expr, ExprList
from .Const import Const, NullExpr, Id, Map, Array, RangeArray
from .Compare import Compare, Logic, Ternary
from .Arith import Arith, Unary, IncDec
from .SharedMethodKit import SharedMethodKit
from .FieldKit import FieldKit
from .FieldKeyBuilder import FieldKeyBuilder
from .MethodKit import MethodKit
__all__ = [
'Expr', 'ExprList',
'Const', 'NullExpr', 'Id', 'Map', 'Array', 'RangeArray',
'Compare', 'Logic', 'Ternary',
'Arith', 'Unary', 'IncDec',
'SharedMethodKit',
'FieldKit',
'FieldKeyBuilder',
'MethodKit'
]

1
template/ext/__init__.py Normal file
View File

@@ -0,0 +1 @@
# Python package initialization

View File

@@ -0,0 +1,36 @@
#!/usr/bin/env python3.9
# -*- coding: utf-8 -*-
"""
JFinal CallDirective - Call Directive
"""
from ...Directive import Directive
from ...Env import Env
from ...stat.Scope import Scope
class CallDirective(Directive):
"""Call directive for calling template functions"""
def exec(self, env: Env, scope: Scope, writer) -> None:
"""
Execute call directive
Args:
env: Template environment
scope: Execution scope
writer: Output writer
"""
if self.expr_list:
# Get function name
function_name = self.expr_list.eval(scope)
if function_name:
# Get function from env
func = env.get_function(str(function_name))
if func:
# Call function
func.exec(env, scope, writer)
def __repr__(self) -> str:
return "CallDirective()"

View File

@@ -0,0 +1,39 @@
#!/usr/bin/env python3.9
# -*- coding: utf-8 -*-
"""
JFinal DateDirective - Date Directive
"""
from ...Directive import Directive
from ...Env import Env
from ...stat.Scope import Scope
class DateDirective(Directive):
"""Date directive for date formatting"""
def exec(self, env: Env, scope: Scope, writer) -> None:
"""
Execute date directive
Args:
env: Template environment
scope: Execution scope
writer: Output writer
"""
if self.expr_list:
# Get date value and pattern
values = self.expr_list.eval_expr_list(scope)
if values:
date_value = values[0]
pattern = values[1] if len(values) > 1 else "yyyy-MM-dd HH:mm"
# Format date
from ...kit.TimeKit import TimeKit
if date_value:
formatted_date = TimeKit.format(date_value, pattern)
if hasattr(writer, 'write'):
writer.write(formatted_date)
def __repr__(self) -> str:
return "DateDirective()"

View File

@@ -0,0 +1,34 @@
#!/usr/bin/env python3.9
# -*- coding: utf-8 -*-
"""
JFinal EscapeDirective - Escape Directive
"""
from ...Directive import Directive
from ...Env import Env
from ...stat.Scope import Scope
class EscapeDirective(Directive):
"""Escape directive for HTML escaping"""
def exec(self, env: Env, scope: Scope, writer) -> None:
"""
Execute escape directive
Args:
env: Template environment
scope: Execution scope
writer: Output writer
"""
if self.expr_list:
# Get value to escape
value = self.expr_list.eval(scope)
if value:
# Escape HTML special characters
escaped = str(value).replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;').replace('"', '&quot;').replace("'", '&#39;')
if hasattr(writer, 'write'):
writer.write(escaped)
def __repr__(self) -> str:
return "EscapeDirective()"

View File

@@ -0,0 +1,34 @@
#!/usr/bin/env python3.9
# -*- coding: utf-8 -*-
"""
JFinal NumberDirective - Number Directive
"""
from ...Directive import Directive
from ...Env import Env
from ...stat.Scope import Scope
class NumberDirective(Directive):
"""Number directive for number formatting"""
def exec(self, env: Env, scope: Scope, writer) -> None:
"""
Execute number directive
Args:
env: Template environment
scope: Execution scope
writer: Output writer
"""
if self.expr_list:
# Get number value
number_value = self.expr_list.eval(scope)
if number_value is not None:
# Format number
formatted = str(number_value)
if hasattr(writer, 'write'):
writer.write(formatted)
def __repr__(self) -> str:
return "NumberDirective()"

View File

@@ -0,0 +1,48 @@
#!/usr/bin/env python3.9
# -*- coding: utf-8 -*-
"""
JFinal RandomDirective - Random Directive
"""
import random
from ...Directive import Directive
from ...Env import Env
from ...stat.Scope import Scope
class RandomDirective(Directive):
"""Random directive for generating random numbers"""
def exec(self, env: Env, scope: Scope, writer) -> None:
"""
Execute random directive
Args:
env: Template environment
scope: Execution scope
writer: Output writer
"""
if self.expr_list:
# Get parameters
values = self.expr_list.eval_expr_list(scope)
if len(values) == 0:
# No parameters, generate random float between 0 and 1
random_value = random.random()
elif len(values) == 1:
# One parameter, generate random int between 0 and max
max_value = int(values[0])
random_value = random.randint(0, max_value - 1)
elif len(values) == 2:
# Two parameters, generate random int between min and max
min_value = int(values[0])
max_value = int(values[1])
random_value = random.randint(min_value, max_value)
else:
# Too many parameters
random_value = random.random()
if hasattr(writer, 'write'):
writer.write(str(random_value))
def __repr__(self) -> str:
return "RandomDirective()"

View File

@@ -0,0 +1,38 @@
#!/usr/bin/env python3.9
# -*- coding: utf-8 -*-
"""
JFinal RenderDirective - Render Directive
"""
from ...Directive import Directive
from ...Env import Env
from ...stat.Scope import Scope
class RenderDirective(Directive):
"""Render directive for template rendering"""
def exec(self, env: Env, scope: Scope, writer) -> None:
"""
Execute render directive
Args:
env: Template environment
scope: Execution scope
writer: Output writer
"""
# Simplified implementation
if self.expr_list:
# Get template name
template_name = self.expr_list.eval(scope)
if template_name:
# Get engine from env
from ...Engine import Engine
engine = Engine.use()
# Render template
template = engine.get_template(str(template_name))
template.render(scope.get_data(), writer)
def __repr__(self) -> str:
return "RenderDirective()"

View File

@@ -0,0 +1,33 @@
#!/usr/bin/env python3.9
# -*- coding: utf-8 -*-
"""
JFinal StringDirective - String Directive
"""
from ...Directive import Directive
from ...Env import Env
from ...stat.Scope import Scope
class StringDirective(Directive):
"""String directive for string operations"""
def exec(self, env: Env, scope: Scope, writer) -> None:
"""
Execute string directive
Args:
env: Template environment
scope: Execution scope
writer: Output writer
"""
if self.expr_list:
# Get string value
string_value = self.expr_list.eval(scope)
if string_value is not None:
# Output string
if hasattr(writer, 'write'):
writer.write(str(string_value))
def __repr__(self) -> str:
return "StringDirective()"

View File

@@ -0,0 +1 @@
# Python package initialization

View File

@@ -0,0 +1,27 @@
#!/usr/bin/env python3.9
# -*- coding: utf-8 -*-
"""
JFinal SharedMethodLib - Shared Method Library
"""
class SharedMethodLib:
"""Shared method library for template functions"""
def __init__(self):
"""Initialize shared method library"""
pass
def add_shared_method(self, method_name: str, method):
"""Add shared method"""
pass
def get_shared_method(self, method_name: str):
"""Get shared method by name"""
return None
def has_shared_method(self, method_name: str) -> bool:
"""Check if shared method exists"""
return False
def __repr__(self) -> str:
return "SharedMethodLib()"

View File

@@ -0,0 +1 @@
# Python package initialization

View File

@@ -0,0 +1,28 @@
#!/usr/bin/env python3.9
# -*- coding: utf-8 -*-
"""
JFinal EncoderFactory - Encoder Factory
"""
class EncoderFactory:
"""Encoder factory for template output"""
encoding = "UTF-8"
def get_encoder(self):
"""Get encoder"""
from .JdkEncoder import JdkEncoder
return JdkEncoder()
@classmethod
def set_encoding(cls, encoding: str):
"""Set encoding"""
cls.encoding = encoding
@classmethod
def get_encoding(cls) -> str:
"""Get encoding"""
return cls.encoding
def __repr__(self) -> str:
return f"EncoderFactory(encoding={self.encoding})"

47
template/io/JdkEncoder.py Normal file
View File

@@ -0,0 +1,47 @@
#!/usr/bin/env python3.9
# -*- coding: utf-8 -*-
"""
JFinal JdkEncoder - JDK Encoder
"""
class JdkEncoder:
"""JDK encoder for template output"""
def encode(self, str_val: str) -> str:
"""
Encode string
Args:
str_val: String to encode
Returns:
Encoded string
"""
return str_val
def encode_half(self, str_val: str) -> str:
"""
Encode string (half encoding)
Args:
str_val: String to encode
Returns:
Encoded string
"""
return str_val
def encode_all(self, str_val: str) -> str:
"""
Encode string (full encoding)
Args:
str_val: String to encode
Returns:
Encoded string
"""
return str_val
def __repr__(self) -> str:
return "JdkEncoder()"

View File

@@ -0,0 +1,22 @@
#!/usr/bin/env python3.9
# -*- coding: utf-8 -*-
"""
JFinal JdkEncoderFactory - JDK Encoder Factory
"""
from .EncoderFactory import EncoderFactory
class JdkEncoderFactory(EncoderFactory):
"""JDK encoder factory"""
def __init__(self):
"""Initialize JDK encoder factory"""
super().__init__()
def get_encoder(self):
"""Get JDK encoder"""
from .JdkEncoder import JdkEncoder
return JdkEncoder()
def __repr__(self) -> str:
return "JdkEncoderFactory()"

View File

@@ -0,0 +1,53 @@
#!/usr/bin/env python3.9
# -*- coding: utf-8 -*-
"""
JFinal WriterBuffer - Writer Buffer
"""
class WriterBuffer:
"""Writer buffer for template output"""
def __init__(self):
"""Initialize writer buffer"""
self._encoding = "UTF-8"
self._buffer_size = 8192
self._reentrant_buffer_size = 1024
self._encoder_factory = None
def set_encoding(self, encoding: str):
"""Set encoding"""
self._encoding = encoding
if self._encoder_factory:
# Set encoding on encoder factory if available
pass
def set_buffer_size(self, buffer_size: int):
"""Set buffer size"""
self._buffer_size = buffer_size
def set_reentrant_buffer_size(self, reentrant_buffer_size: int):
"""Set reentrant buffer size"""
self._reentrant_buffer_size = reentrant_buffer_size
def set_encoder_factory(self, encoder_factory):
"""Set encoder factory"""
self._encoder_factory = encoder_factory
def get_encoding(self) -> str:
"""Get encoding"""
return self._encoding
def get_buffer_size(self) -> int:
"""Get buffer size"""
return self._buffer_size
def get_reentrant_buffer_size(self) -> int:
"""Get reentrant buffer size"""
return self._reentrant_buffer_size
def get_encoder_factory(self):
"""Get encoder factory"""
return self._encoder_factory
def __repr__(self) -> str:
return f"WriterBuffer(encoding={self._encoding}, buffer_size={self._buffer_size})"

1
template/io/__init__.py Normal file
View File

@@ -0,0 +1 @@
# Python package initialization

View File

@@ -0,0 +1,109 @@
#!/usr/bin/env python3.9
# -*- coding: utf-8 -*-
"""
JFinal ClassPathSource - Class Path Source
"""
import importlib.resources
import os
class ClassPathSource:
"""Class path source for template loading"""
def __init__(self, base_template_path: str, fileName: str, encoding: str):
"""
Initialize class path source
Args:
base_template_path: Base template path
fileName: File name
encoding: Encoding
"""
self._fileName = fileName
self._encoding = encoding
self._baseTemplatePath = base_template_path
# Build resource path
if base_template_path:
self._resourcePath = os.path.join(base_template_path, fileName).replace('\\', '/')
else:
self._resourcePath = fileName.replace('\\', '/')
def get_content(self) -> str:
"""
Get class path resource content
Returns:
Resource content as string
"""
try:
# Try to load from classpath
import importlib
import sys
# Try different approaches to load resource
try:
# Try direct resource loading
with open(self._resourcePath, 'r', encoding=self._encoding) as f:
return f.read()
except:
# Try as package resource
if '.' in self._resourcePath:
parts = self._resourcePath.split('/')
if len(parts) > 1:
package_name = '.'.join(parts[:-1])
resource_name = parts[-1]
try:
with importlib.resources.open_text(package_name, resource_name, encoding=self._encoding) as f:
return f.read()
except:
pass
# Fall back to file system
return self._try_file_system()
except Exception as e:
raise RuntimeError(f"Error reading classpath resource {self._resourcePath}: {e}")
def _try_file_system(self) -> str:
"""
Try to load from file system as fallback
Returns:
File content as string
"""
# Try current directory
try:
current_dir = os.getcwd()
file_path = os.path.join(current_dir, self._resourcePath)
if os.path.exists(file_path):
with open(file_path, 'r', encoding=self._encoding) as f:
return f.read()
except:
pass
# Try absolute path
if os.path.isabs(self._resourcePath):
try:
with open(self._resourcePath, 'r', encoding=self._encoding) as f:
return f.read()
except:
pass
raise RuntimeError(f"Resource not found: {self._resourcePath}")
def is_modified(self) -> bool:
"""
Check if resource has been modified
Returns:
False for classpath resources (not supported)
"""
return False
def get_file_name(self) -> str:
"""Get file name"""
return self._fileName
def __repr__(self) -> str:
return f"ClassPathSource({self._fileName})"

View File

@@ -0,0 +1,28 @@
#!/usr/bin/env python3.9
# -*- coding: utf-8 -*-
"""
JFinal ClassPathSourceFactory - Class Path Source Factory
"""
from .ISourceFactory import ISourceFactory
class ClassPathSourceFactory(ISourceFactory):
"""Class path source factory for template loading"""
def get_source(self, base_template_path: str, fileName: str, encoding: str):
"""
Get class path source
Args:
base_template_path: Base template path
fileName: File name
encoding: Encoding
Returns:
Class path source object
"""
from .ClassPathSource import ClassPathSource
return ClassPathSource(base_template_path, fileName, encoding)
def __repr__(self) -> str:
return "ClassPathSourceFactory()"

View File

@@ -0,0 +1,76 @@
#!/usr/bin/env python3.9
# -*- coding: utf-8 -*-
"""
JFinal FileSource - File Source
"""
import os
import time
class FileSource:
"""File source for template loading"""
def __init__(self, base_template_path: str, fileName: str, encoding: str):
"""
Initialize file source
Args:
base_template_path: Base template path
fileName: File name
encoding: Encoding
"""
self._fileName = fileName
self._encoding = encoding
self._lastModified = 0
# Build file path
if base_template_path:
self._filePath = os.path.join(base_template_path, fileName)
else:
self._filePath = fileName
# Normalize path
self._filePath = os.path.normpath(self._filePath)
# Get last modified time
self._update_last_modified()
def get_content(self) -> str:
"""
Get file content
Returns:
File content as string
"""
try:
with open(self._filePath, 'r', encoding=self._encoding) as f:
return f.read()
except Exception as e:
raise RuntimeError(f"Error reading file {self._filePath}: {e}")
def is_modified(self) -> bool:
"""
Check if file has been modified
Returns:
True if modified, False otherwise
"""
current_modified = os.path.getmtime(self._filePath)
if current_modified > self._lastModified:
self._lastModified = current_modified
return True
return False
def _update_last_modified(self):
"""Update last modified time"""
try:
self._lastModified = os.path.getmtime(self._filePath)
except:
self._lastModified = 0
def get_file_name(self) -> str:
"""Get file name"""
return self._fileName
def __repr__(self) -> str:
return f"FileSource({self._fileName})"

View File

@@ -0,0 +1,29 @@
#!/usr/bin/env python3.9
# -*- coding: utf-8 -*-
"""
JFinal FileSourceFactory - File Source Factory
"""
import os
from .ISourceFactory import ISourceFactory
class FileSourceFactory(ISourceFactory):
"""File source factory for template loading"""
def get_source(self, base_template_path: str, fileName: str, encoding: str):
"""
Get file source
Args:
base_template_path: Base template path
fileName: File name
encoding: Encoding
Returns:
File source object
"""
from .FileSource import FileSource
return FileSource(base_template_path, fileName, encoding)
def __repr__(self) -> str:
return "FileSourceFactory()"

View File

@@ -0,0 +1,22 @@
#!/usr/bin/env python3.9
# -*- coding: utf-8 -*-
"""
JFinal ISourceFactory - Source Factory Interface
"""
class ISourceFactory:
"""Source factory interface for template loading"""
def get_source(self, base_template_path: str, fileName: str, encoding: str):
"""
Get source by file name
Args:
base_template_path: Base template path
fileName: File name
encoding: Encoding
Returns:
Source object
"""
raise NotImplementedError("ISourceFactory.get_source() must be implemented by subclasses")

View File

@@ -0,0 +1,50 @@
#!/usr/bin/env python3.9
# -*- coding: utf-8 -*-
"""
JFinal StringSource - String Source
"""
class StringSource:
"""String source for template loading"""
def __init__(self, content: str, cache_key: str = None):
"""
Initialize string source
Args:
content: Template content
cache_key: Cache key (optional)
"""
self._content = content
self._cache_key = cache_key
def get_content(self) -> str:
"""
Get string content
Returns:
Content as string
"""
return self._content
def is_modified(self) -> bool:
"""
Check if content has been modified
Returns:
Always False for string sources
"""
return False
def get_cache_key(self) -> str:
"""
Get cache key
Returns:
Cache key
"""
return self._cache_key
def __repr__(self) -> str:
content_preview = self._content[:50] + "..." if len(self._content) > 50 else self._content
return f"StringSource('{content_preview}')"

View File

@@ -0,0 +1 @@
# Python package initialization

View File

@@ -0,0 +1,45 @@
#!/usr/bin/env python3.9
# -*- coding: utf-8 -*-
"""
JFinal Compressor - Template Compressor
"""
class Compressor:
"""Template compressor for minimizing output"""
def __init__(self, separator: str = '\n'):
"""
Initialize compressor
Args:
separator: Separator string
"""
self._separator = separator
def compress(self, content: str) -> str:
"""
Compress template content
Args:
content: Content to compress
Returns:
Compressed content
"""
# Simplified compression
if not content:
return content
# Remove extra whitespace
lines = content.split(self._separator)
compressed_lines = []
for line in lines:
compressed_line = line.strip()
if compressed_line:
compressed_lines.append(compressed_line)
return self._separator.join(compressed_lines)
def __repr__(self) -> str:
return f"Compressor(separator={repr(self._separator)})"

View File

@@ -0,0 +1,35 @@
#!/usr/bin/env python3.9
# -*- coding: utf-8 -*-
"""
JFinal OutputDirectiveFactory - Output Directive Factory
"""
from typing import Optional
from .ast.Stat import Stat
from .Scope import Scope
from ..Env import Env
class OutputDirectiveFactory:
"""Factory for creating output directives"""
def __init__(self):
"""Initialize factory"""
pass
def get_output_directive(self, expr_list, location) -> 'Output':
"""
Get output directive
Args:
expr_list: Expression list
location: Location information
Returns:
Output directive
"""
from .ast.Output import Output
return Output(expr_list)
# Create singleton instance
me = OutputDirectiveFactory()

283
template/stat/Parser.py Normal file
View File

@@ -0,0 +1,283 @@
#!/usr/bin/env python3.9
# -*- coding: utf-8 -*-
"""
JFinal Parser - Template Parser
"""
from typing import Optional
from .ast.Stat import Stat, StatList
from ..Env import Env
class Parser:
"""Template parser for parsing template files"""
def __init__(self, env: Env, content: str, name: Optional[str] = None):
"""
Initialize parser
Args:
env: Template environment
content: Template content
name: Template name (optional)
"""
self._env = env
self._content = content
self._name = name
def parse(self) -> Stat:
"""
Parse template content
Returns:
Root statement
"""
from .ast.Text import Text
from .ast.Output import Output
from .ast.For import For
stat_list = StatList()
content = self._content
length = len(content)
pos = 0
while pos < length:
# Find the next # character
next_hash = content.find("#", pos)
if next_hash == -1:
# No more # characters, add the remaining text
if pos < length:
stat_list.add_stat(Text(content[pos:]))
break
# Add text before the # character
if next_hash > pos:
stat_list.add_stat(Text(content[pos:next_hash]))
# Check what follows the #
if next_hash + 1 < length:
next_char = content[next_hash + 1]
# Handle #( expression
if next_char == "(":
# Find the end of the expression
end = self._find_matching_parenthesis(content, next_hash + 2)
if end != -1:
# Extract the expression content
expr_content = content[next_hash + 2:end]
# Create a simple expression list for evaluation
expr_list = SimpleExprList(expr_content)
# Add output stat
stat_list.add_stat(Output(expr_list))
# Move to next position
pos = end + 1
continue
# Check for #for directive
elif next_char == "f" and next_hash + 4 <= length and content[next_hash:next_hash + 4] == "#for":
# Handle #for directive with optional parentheses and different syntax
for_start = next_hash + 4
# Skip whitespace
while for_start < length and content[for_start].isspace():
for_start += 1
# Check if #for is followed by (
if for_start < length and content[for_start] == "(":
# Find the end of the parentheses
for_paren_end = self._find_matching_parenthesis(content, for_start + 1)
if for_paren_end != -1:
# Parse for content inside parentheses
for_content = content[for_start + 1:for_paren_end].strip()
for_end = for_paren_end + 1
else:
# No matching parenthesis, treat as text
stat_list.add_stat(Text(content[next_hash:next_hash + 4]))
pos = next_hash + 4
continue
else:
# Find the end of the #for line
for_end = content.find("\n", next_hash)
if for_end == -1:
for_end = length
# Parse for content
for_content = content[for_start:for_end].strip()
# Handle both "item in items" and "x : listAaa" syntax
if " in " in for_content:
# Python style: for item in items
parts = for_content.split(" in ")
var_name = parts[0].strip()
iter_expr = parts[1].strip()
elif " : " in for_content:
# Java style: for x : listAaa
parts = for_content.split(" : ")
var_name = parts[0].strip()
iter_expr = parts[1].strip()
else:
# Invalid for syntax, treat as text
stat_list.add_stat(Text(content[next_hash:for_end]))
pos = for_end
continue
# Find the matching #end directive
end_pos = self._find_matching_end(content, for_end)
if end_pos != -1:
# Extract the loop body content
body_content = content[for_end:end_pos]
# Parse the loop body
body_parser = Parser(self._env, body_content, self._name)
body_stat = body_parser.parse()
# Create For directive
for_directive = For(var_name, iter_expr, body_stat)
# Add for directive to stat list
stat_list.add_stat(for_directive)
# Move to position after #end directive
pos = end_pos + 4 # Skip "#end"
continue
# If we get here, it's just a # character, add as text
stat_list.add_stat(Text("#"))
pos = next_hash + 1
return stat_list
def _find_matching_end(self, content: str, start: int) -> int:
"""
Find matching #end directive
Args:
content: Content string
start: Start position
Returns:
Position of matching #end directive or -1 if not found
"""
pos = start
length = len(content)
depth = 1 # Start with 1 for the current #for
while pos < length:
# Find the next # character
next_hash = content.find("#", pos)
if next_hash == -1:
break
# Check for #for (increase depth)
if next_hash + 5 <= length and content[next_hash:next_hash + 5] == "#for ":
depth += 1
pos = next_hash + 5
# Check for #end (decrease depth)
elif next_hash + 4 <= length and content[next_hash:next_hash + 4] == "#end":
depth -= 1
if depth == 0:
return next_hash
pos = next_hash + 4
# Otherwise, move past this #
else:
pos = next_hash + 1
return -1
def _find_matching_parenthesis(self, content: str, start: int) -> int:
"""
Find matching parenthesis
Args:
content: Content string
start: Start position
Returns:
Position of matching parenthesis or -1 if not found
"""
count = 1
pos = start
length = len(content)
while pos < length and count > 0:
char = content[pos]
if char == '(':
count += 1
elif char == ')':
count -= 1
if count == 0:
return pos
pos += 1
return -1
class SimpleExprList:
"""
Simple expression list implementation for evaluating template expressions
"""
def __init__(self, expr_str: str):
"""
Initialize simple expression list
Args:
expr_str: Expression string
"""
self._expr_str = expr_str
def eval(self, scope: 'Scope') -> any:
"""
Evaluate expression in the given scope
Args:
scope: Execution scope
Returns:
Evaluation result
"""
try:
# Create a local context with scope variables
local_vars = scope._data.copy()
# Create a special dict wrapper that allows attribute access
def wrap_dict(d):
"""Wrap a dict to allow attribute access"""
if isinstance(d, dict):
# Create a wrapper that allows both dot access and bracket access
class DictWrapper:
def __init__(self, data):
self.__dict__ = data
def __getitem__(self, key):
return data[key]
return DictWrapper(d)
return d
# Wrap all dict values in the local vars
wrapped_vars = {}
for key, value in local_vars.items():
if isinstance(value, dict):
wrapped_vars[key] = wrap_dict(value)
elif isinstance(value, list):
# Wrap dictionaries in lists
wrapped_vars[key] = [wrap_dict(item) for item in value]
else:
wrapped_vars[key] = value
# Handle special case for 'for' variable which is a keyword in Python
expr_str = self._expr_str
if 'for' in wrapped_vars:
# Create a wrapper to avoid keyword conflict
wrapped_vars['_for'] = wrapped_vars['for']
# Replace 'for.' with '_for.' in the expression
expr_str = expr_str.replace('for.', '_for.')
# Evaluate the expression
return eval(expr_str, {}, wrapped_vars)
except Exception as e:
# Handle evaluation errors gracefully
return f"Error evaluating expression '{self._expr_str}': {e}"

133
template/stat/Scope.py Normal file
View File

@@ -0,0 +1,133 @@
#!/usr/bin/env python3.9
# -*- coding: utf-8 -*-
"""
JFinal Scope - Template Execution Scope
"""
from typing import Dict, Any, Optional
class Scope:
"""Template execution scope"""
def __init__(self, data: Dict = None, shared_objects: Dict = None):
"""
Initialize scope
Args:
data: Data dictionary
shared_objects: Shared objects dictionary
"""
self._parent = None
self._data = data or {}
self._shared_objects = shared_objects or {}
self._local_vars: Dict[str, Any] = {}
def set_parent(self, parent: 'Scope'):
"""Set parent scope"""
self._parent = parent
def get(self, key: str) -> Any:
"""
Get value by key, searching through scopes
Args:
key: Variable key
Returns:
Value or None
"""
# Check local vars first
if key in self._local_vars:
return self._local_vars[key]
# Check data
if key in self._data:
return self._data[key]
# Check shared objects
if key in self._shared_objects:
return self._shared_objects[key]
# Check parent scope
if self._parent:
return self._parent.get(key)
return None
def set(self, key: str, value: Any):
"""
Set value in local scope
Args:
key: Variable key
value: Value to set
"""
self._local_vars[key] = value
def set_local(self, key: str, value: Any):
"""Set local variable"""
self._local_vars[key] = value
def remove(self, key: str) -> Any:
"""
Remove and return value
Args:
key: Variable key
Returns:
Removed value or None
"""
# Try local vars
if key in self._local_vars:
return self._local_vars.pop(key)
# Try data
if key in self._data:
return self._data.pop(key, None)
return None
def contains(self, key: str) -> bool:
"""Check if key exists in any scope"""
return self.get(key) is not None
def keys(self) -> set:
"""Get all keys in scope"""
keys = set()
keys.update(self._local_vars.keys())
keys.update(self._data.keys())
keys.update(self._shared_objects.keys())
if self._parent:
keys.update(self._parent.keys())
return keys
def get_local_vars(self) -> Dict[str, Any]:
"""Get local variables"""
return self._local_vars.copy()
def get_data(self) -> Dict[str, Any]:
"""Get data dictionary"""
return self._data.copy()
def get_shared_objects(self) -> Dict[str, Any]:
"""Get shared objects"""
return self._shared_objects.copy()
def clear_local_vars(self):
"""Clear local variables"""
self._local_vars.clear()
def new_scope(self) -> 'Scope':
"""
Create a new scope with this scope as parent
Returns:
New scope instance
"""
new_scope = Scope(self._data.copy(), self._shared_objects.copy())
new_scope.set_parent(self)
return new_scope
def __repr__(self) -> str:
return f"Scope(local_vars={len(self._local_vars)}, data={len(self._data)}, shared={len(self._shared_objects)})"

View File

@@ -0,0 +1,48 @@
#!/usr/bin/env python3.9
# -*- coding: utf-8 -*-
"""
JFinal Define - Template Function Definition
"""
from .Stat import Stat
from ..Scope import Scope
from ...Env import Env
class Define(Stat):
"""Template function definition"""
def __init__(self, function_name: str):
"""
Initialize define statement
Args:
function_name: Function name
"""
self._function_name = function_name
def exec(self, env: Env, scope: Scope, writer) -> None:
"""
Execute define statement
Args:
env: Template environment
scope: Execution scope
writer: Output writer
"""
# Register the function with the environment
env.add_function(self._function_name, self)
def get_function_name(self) -> str:
"""Get function name"""
return self._function_name
def set_env_for_dev_mode(self, env: Env):
"""Set environment for dev mode"""
pass
def is_source_modified_for_dev_mode(self) -> bool:
"""Check if source is modified (for dev mode)"""
return False
def __repr__(self) -> str:
return f"Define({self._function_name})"

106
template/stat/ast/For.py Normal file
View File

@@ -0,0 +1,106 @@
#!/usr/bin/env python3.9
# -*- coding: utf-8 -*-
"""
JFinal For - For Directive
"""
from typing import Optional
from .Stat import Stat, StatList
from ..Scope import Scope
from ...Env import Env
class For(Stat):
"""For loop directive"""
def __init__(self, var_name: str, iter_expr: str, body: Stat):
"""
Initialize for directive
Args:
var_name: Loop variable name
iter_expr: Expression to iterate over
body: Loop body
"""
self._var_name = var_name
self._iter_expr = iter_expr
self._body = body
def exec(self, env: Env, scope: Scope, writer) -> None:
"""
Execute for loop
Args:
env: Template environment
scope: Execution scope
writer: Output writer
"""
try:
# Get the iterable from the expression
iterable = eval(self._iter_expr, {}, scope._data.copy())
# Convert iterable to list for easier manipulation
items = list(iterable)
size = len(items)
# Create a ForLoopInfo class to hold loop information
class ForLoopInfo:
def __init__(self, size, index, outer=None):
self._size = size
self._index = index
self._outer = outer
@property
def size(self):
return self._size
@property
def index(self):
return self._index
@property
def count(self):
return self._index + 1
@property
def first(self):
return self._index == 0
@property
def last(self):
return self._index == self._size - 1
@property
def odd(self):
return self._index % 2 == 0
@property
def even(self):
return self._index % 2 == 1
@property
def outer(self):
return self._outer
# Get outer for info if exists
outer_for = scope._data.get("for")
# Iterate over the items
for index, item in enumerate(items):
# Create for loop info
for_loop = ForLoopInfo(size, index, outer_for)
# Create a new scope for this iteration
loop_scope = Scope(scope._data.copy(), scope._shared_objects.copy())
loop_scope.set_parent(scope)
loop_scope._data[self._var_name] = item
loop_scope._data["for"] = for_loop
# Execute the loop body
if self._body:
self._body.exec(env, loop_scope, writer)
except Exception as e:
# Handle errors gracefully
writer.write(f"Error in for loop: {e}")
def __repr__(self) -> str:
return f"For(var={self._var_name}, expr={self._iter_expr})"

View File

@@ -0,0 +1,40 @@
#!/usr/bin/env python3.9
# -*- coding: utf-8 -*-
"""
JFinal Output - Output Directive
"""
from .Stat import Stat
from ..Scope import Scope
from ...Env import Env
from ...expr.ast import ExprList
class Output(Stat):
"""Output directive for template expressions"""
def __init__(self, expr_list: ExprList):
"""
Initialize output directive
Args:
expr_list: Expression list to evaluate
"""
self._expr_list = expr_list
def exec(self, env: Env, scope: Scope, writer) -> None:
"""
Execute output directive
Args:
env: Template environment
scope: Execution scope
writer: Output writer
"""
if self._expr_list:
result = self._expr_list.eval(scope)
if result is not None:
if hasattr(writer, 'write'):
writer.write(str(result))
def __repr__(self) -> str:
return f"Output({self._expr_list})"

79
template/stat/ast/Stat.py Normal file
View File

@@ -0,0 +1,79 @@
#!/usr/bin/env python3.9
# -*- coding: utf-8 -*-
"""
JFinal Stat - Abstract Syntax Tree Base Class
"""
from typing import Any
from ..Scope import Scope
from ...Env import Env
class Stat:
"""Base class for statement AST nodes"""
def exec(self, env: Env, scope: Scope, writer) -> Any:
"""
Execute the statement
Args:
env: Template environment
scope: Execution scope
writer: Output writer
Returns:
Execution result
"""
raise NotImplementedError("Stat.exec() must be implemented by subclasses")
def get_stat_list(self) -> 'StatList':
"""Get as StatList if applicable"""
return None
def __repr__(self) -> str:
return f"Stat({self.__class__.__name__})"
class StatList(Stat):
"""Statement list containing multiple statements"""
def __init__(self):
"""Initialize statement list"""
self._stats: list = []
def add_stat(self, stat: Stat):
"""
Add statement to list
Args:
stat: Statement to add
"""
if stat:
self._stats.append(stat)
def exec(self, env: Env, scope: Scope, writer) -> Any:
"""
Execute all statements in list
Args:
env: Template environment
scope: Execution scope
writer: Output writer
Returns:
Last statement result or None
"""
result = None
for stat in self._stats:
result = stat.exec(env, scope, writer)
return result
def get_stats(self) -> list:
"""Get all statements"""
return self._stats.copy()
def __len__(self) -> int:
"""Get number of statements"""
return len(self._stats)
def __repr__(self) -> str:
return f"StatList({len(self._stats)} statements)"

81
template/stat/ast/Text.py Normal file
View File

@@ -0,0 +1,81 @@
#!/usr/bin/env python3.9
# -*- coding: utf-8 -*-
"""
JFinal Text - Text Statement
"""
from .Stat import Stat
from ..Scope import Scope
from ...Env import Env
class Text(Stat):
"""Text statement for template content"""
def __init__(self, content: str):
"""
Initialize text statement
Args:
content: Text content
"""
self._content = content
def exec(self, env: Env, scope: Scope, writer) -> None:
"""
Execute text statement
Args:
env: Template environment
scope: Execution scope
writer: Output writer
"""
if hasattr(writer, 'write'):
# Parse and evaluate template expressions
parsed_content = self._parse_expressions(env, scope)
writer.write(parsed_content)
def _parse_expressions(self, env: Env, scope: Scope) -> str:
"""
Parse and evaluate template expressions in content
Args:
env: Template environment
scope: Execution scope
Returns:
Parsed and evaluated content
"""
import re
def replace_expression(match):
"""Replace expression with evaluated value"""
expr_str = match.group(1).strip()
# Try to evaluate expression
try:
# First try variable access
if expr_str.isidentifier():
value = scope.get(expr_str)
return str(value) if value is not None else ''
# Try arithmetic expression
try:
# Simple arithmetic evaluation
# This is a simplified implementation
result = eval(expr_str, {})
return str(result)
except:
pass
# Fallback to original expression
return match.group(0)
except Exception as e:
return f"{match.group(0)} [Error: {e}]"
# Replace all #(...) expressions
pattern = r'#\((.*?)\)'
return re.sub(pattern, replace_expression, self._content)
def __repr__(self) -> str:
return f"Text('{self._content[:50]}...')"

View File

@@ -0,0 +1,12 @@
# Export statement AST classes
from .Stat import Stat, StatList
from .Text import Text
from .Define import Define
from .Output import Output
__all__ = [
'Stat', 'StatList',
'Text',
'Define',
'Output'
]