调整版本并做测试

This commit is contained in:
2026-02-27 17:10:54 +08:00
parent fa673138f6
commit 31be9d0e97
77 changed files with 679 additions and 25 deletions

36
pyenjoy/__init__.py Normal file
View File

@@ -0,0 +1,36 @@
#!/usr/bin/env python3.9
# -*- coding: utf-8 -*-
"""
py_enjoy - Python implementation of JFinal Template Engine
"""
__version__ = "1.0.1"
__author__ = "James Zhan 詹波 (original), Python port by mrzhou@miw.cn"
__license__ = "Apache License 2.0"
# Export main classes
from .kit.StrKit import StrKit
from .kit.HashKit import HashKit
from .kit.Kv import Kv
from .kit.Okv import Okv
from .kit.Prop import Prop
from .kit.PropKit import PropKit
from .kit.TypeKit import TypeKit
from .kit.TimeKit import TimeKit
from .template.Engine import Engine
from .template.Template import Template
from .template.Directive import Directive
__all__ = [
"StrKit",
"HashKit",
"Kv",
"Okv",
"Prop",
"PropKit",
"TypeKit",
"TimeKit",
"Engine",
"Template",
"Directive",
]

86
pyenjoy/kit/Func.py Normal file
View File

@@ -0,0 +1,86 @@
#!/usr/bin/env python3.9
# -*- coding: utf-8 -*-
"""
JFinal Func - Lambda Function Utilities
"""
from typing import Callable, TypeVar, Generic
T = TypeVar('T')
U = TypeVar('U')
V = TypeVar('V')
W = TypeVar('W')
X = TypeVar('X')
Y = TypeVar('Y')
Z = TypeVar('Z')
R = TypeVar('R')
class F00:
"""0 parameter 0 return function"""
@staticmethod
def call(func: Callable[[], None]):
"""Execute function with no parameters and no return"""
func()
class F10(Generic[T]):
"""1 parameter 0 return function"""
@staticmethod
def call(func: Callable[[T], None], t: T):
"""Execute function with one parameter and no return"""
func(t)
class F20(Generic[T, U]):
"""2 parameter 0 return function"""
@staticmethod
def call(func: Callable[[T, U], None], t: T, u: U):
"""Execute function with two parameters and no return"""
func(t, u)
class F30(Generic[T, U, V]):
"""3 parameter 0 return function"""
@staticmethod
def call(func: Callable[[T, U, V], None], t: T, u: U, v: V):
"""Execute function with three parameters and no return"""
func(t, u, v)
class F40(Generic[T, U, V, W]):
"""4 parameter 0 return function"""
@staticmethod
def call(func: Callable[[T, U, V, W], None], t: T, u: U, v: V, w: W):
"""Execute function with four parameters and no return"""
func(t, u, v, w)
class F01(Generic[R]):
"""0 parameter 1 return function"""
@staticmethod
def call(func: Callable[[], R]) -> R:
"""Execute function with no parameters and return value"""
return func()
class F11(Generic[T, R]):
"""1 parameter 1 return function"""
@staticmethod
def call(func: Callable[[T], R], t: T) -> R:
"""Execute function with one parameter and return value"""
return func(t)
class F21(Generic[T, U, R]):
"""2 parameter 1 return function"""
@staticmethod
def call(func: Callable[[T, U], R], t: T, u: U) -> R:
"""Execute function with two parameters and return value"""
return func(t, u)
class F31(Generic[T, U, V, R]):
"""3 parameter 1 return function"""
@staticmethod
def call(func: Callable[[T, U, V], R], t: T, u: U, v: V) -> R:
"""Execute function with three parameters and return value"""
return func(t, u, v)
class F41(Generic[T, U, V, W, R]):
"""4 parameter 1 return function"""
@staticmethod
def call(func: Callable[[T, U, V, W], R], t: T, u: U, v: V, w: W) -> R:
"""Execute function with four parameters and return value"""
return func(t, u, v, w)

107
pyenjoy/kit/HashKit.py Normal file
View File

@@ -0,0 +1,107 @@
#!/usr/bin/env python3.9
# -*- coding: utf-8 -*-
"""
JFinal HashKit - Hash and Encryption Utilities
"""
import hashlib
import secrets
import threading
class HashKit:
"""Hash utility class"""
FNV_OFFSET_BASIS_64 = 0xcbf29ce484222325
FNV_PRIME_64 = 0x100000001b3
_HEX_DIGITS = "0123456789abcdef"
_CHAR_ARRAY = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
@staticmethod
def fnv1a64(key: str) -> int:
"""FNV-1a 64-bit hash"""
hash_val = HashKit.FNV_OFFSET_BASIS_64
for i in range(len(key)):
hash_val ^= ord(key[i])
hash_val *= HashKit.FNV_PRIME_64
return hash_val
@staticmethod
def md5(src_str: str) -> str:
"""MD5 hash"""
return HashKit._hash("md5", src_str)
@staticmethod
def sha1(src_str: str) -> str:
"""SHA-1 hash"""
return HashKit._hash("sha1", src_str)
@staticmethod
def sha256(src_str: str) -> str:
"""SHA-256 hash"""
return HashKit._hash("sha256", src_str)
@staticmethod
def sha384(src_str: str) -> str:
"""SHA-384 hash"""
return HashKit._hash("sha384", src_str)
@staticmethod
def sha512(src_str: str) -> str:
"""SHA-512 hash"""
return HashKit._hash("sha512", src_str)
@staticmethod
def _hash(algorithm: str, src_str: str) -> str:
"""Generic hash function"""
try:
md = hashlib.new(algorithm)
md.update(src_str.encode('utf-8'))
return HashKit.to_hex(md.digest())
except Exception as e:
raise RuntimeError(e)
@staticmethod
def to_hex(bytes_data: bytes) -> str:
"""Convert bytes to hex string"""
ret = []
for b in bytes_data:
ret.append(HashKit._HEX_DIGITS[(b >> 4) & 0x0f])
ret.append(HashKit._HEX_DIGITS[b & 0x0f])
return ''.join(ret)
@staticmethod
def generate_salt(salt_length: int) -> str:
"""Generate random salt"""
salt = []
for i in range(salt_length):
salt.append(secrets.choice(HashKit._CHAR_ARRAY))
return ''.join(salt)
@staticmethod
def generate_salt_for_sha256() -> str:
"""Generate salt for SHA-256"""
return HashKit.generate_salt(32)
@staticmethod
def generate_salt_for_sha512() -> str:
"""Generate salt for SHA-512"""
return HashKit.generate_salt(64)
@staticmethod
def generate_random_str(str_length: int) -> str:
"""Generate random string"""
return HashKit.generate_salt(str_length)
@staticmethod
def slow_equals(a: bytes, b: bytes) -> bool:
"""Timing-safe byte comparison"""
if a is None or b is None:
return False
if len(a) != len(b):
return False
diff = 0
for i in range(len(a)):
diff |= a[i] ^ b[i]
return diff == 0

View File

@@ -0,0 +1,75 @@
#!/usr/bin/env python3.9
# -*- coding: utf-8 -*-
"""
JFinal JavaKeyword - Java Keyword Detection
"""
from typing import Set, FrozenSet
class JavaKeyword:
"""Java keyword detection utility"""
_KEYWORD_SET: FrozenSet[str] = frozenset({
"abstract", "assert", "boolean", "break", "byte", "case", "catch",
"char", "class", "const", "continue", "default", "do", "double",
"else", "enum", "extends", "final", "finally", "float", "for",
"goto", "if", "implements", "import", "instanceof", "int",
"interface", "long", "native", "new", "package", "private",
"protected", "public", "return", "strictfp", "short", "static",
"super", "switch", "synchronized", "this", "throw", "throws",
"transient", "try", "void", "volatile", "while"
})
_instance = None
@staticmethod
def get_instance():
"""Get shared instance"""
if JavaKeyword._instance is None:
JavaKeyword._instance = JavaKeyword()
return JavaKeyword._instance
def __init__(self):
self._keywords = set(JavaKeyword._KEYWORD_SET)
# Make it read-only by default
self._read_only = True
def add_keyword(self, keyword: str) -> 'JavaKeyword':
"""Add custom keyword"""
if self._read_only:
raise RuntimeError("Cannot modify read-only JavaKeyword instance")
if keyword and keyword.strip():
self._keywords.add(keyword)
return self
def remove_keyword(self, keyword: str) -> 'JavaKeyword':
"""Remove keyword"""
if self._read_only:
raise RuntimeError("Cannot modify read-only JavaKeyword instance")
self._keywords.discard(keyword)
return self
def contains(self, word: str) -> bool:
"""Check if word is a Java keyword"""
return word in self._keywords
def is_keyword(self, word: str) -> bool:
"""Check if word is a Java keyword (alias of contains)"""
return self.contains(word)
@property
def keywords(self) -> FrozenSet[str]:
"""Get all keywords"""
return frozenset(self._keywords)
@property
def keyword_count(self) -> int:
"""Get keyword count"""
return len(self._keywords)
# Create shared instance
me = JavaKeyword.get_instance()

141
pyenjoy/kit/Kv.py Normal file
View File

@@ -0,0 +1,141 @@
#!/usr/bin/env python3.9
# -*- coding: utf-8 -*-
"""
JFinal Kv (Key Value) - Ordered Dictionary Implementation
"""
from .StrKit import StrKit
from .TypeKit import TypeKit
class Kv(dict):
"""Key Value dictionary with utility methods"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@staticmethod
def of(key, value):
"""Create Kv with initial key-value pair"""
return Kv().set(key, value)
@staticmethod
def by(key, value):
"""Create Kv with initial key-value pair (alias of of)"""
return Kv.of(key, value)
@staticmethod
def create():
"""Create empty Kv"""
return Kv()
def set(self, key, value):
"""Set key-value pair and return self for chaining"""
super().__setitem__(key, value)
return self
def set_if_not_blank(self, key, value):
"""Set value only if not blank"""
if StrKit.not_blank(value):
self.set(key, value)
return self
def set_if_not_null(self, key, value):
"""Set value only if not None"""
if value is not None:
self.set(key, value)
return self
def delete(self, key):
"""Delete key and return self"""
super().pop(key, None)
return self
def get_as(self, key, default_value=None):
"""Get value as specific type"""
return self.get(key, default_value)
def get_str(self, key, default_value=None):
"""Get value as string"""
value = self.get(key)
return str(value) if value is not None else default_value
def get_int(self, key, default_value=None):
"""Get value as integer"""
return TypeKit.to_int(self.get(key, default_value))
def get_long(self, key, default_value=None):
"""Get value as long integer"""
return TypeKit.to_long(self.get(key, default_value))
def get_big_decimal(self, key, default_value=None):
"""Get value as BigDecimal"""
return TypeKit.to_big_decimal(self.get(key, default_value))
def get_double(self, key, default_value=None):
"""Get value as double"""
return TypeKit.to_double(self.get(key, default_value))
def get_float(self, key, default_value=None):
"""Get value as float"""
return TypeKit.to_float(self.get(key, default_value))
def get_number(self, key, default_value=None):
"""Get value as Number"""
return TypeKit.to_number(self.get(key, default_value))
def get_boolean(self, key, default_value=None):
"""Get value as boolean"""
return TypeKit.to_boolean(self.get(key, default_value))
def get_date(self, key, default_value=None):
"""Get value as Date"""
return TypeKit.to_date(self.get(key, default_value))
def get_local_datetime(self, key, default_value=None):
"""Get value as LocalDateTime"""
return TypeKit.to_local_date_time(self.get(key, default_value))
def not_null(self, key):
"""Check if key exists and value is not None"""
return self.get(key) is not None
def is_null(self, key):
"""Check if key doesn't exist or value is None"""
return self.get(key) is None
def not_blank(self, key):
"""Check if value is not blank string"""
return StrKit.not_blank(self.get_str(key))
def is_blank(self, key):
"""Check if value is blank string"""
return StrKit.is_blank(self.get_str(key))
def is_true(self, key):
"""Check if value is True"""
value = self.get(key)
return value is not None and TypeKit.to_boolean(value)
def is_false(self, key):
"""Check if value is False"""
value = self.get(key)
return value is not None and not TypeKit.to_boolean(value)
def keep(self, *keys):
"""Keep only specified keys"""
if keys and len(keys) > 0:
new_kv = Kv()
for key in keys:
if self.contains_key(key):
new_kv.set(key, self.get(key))
self.clear()
self.update(new_kv)
else:
self.clear()
return self
def to_map(self):
"""Convert to regular map"""
return self

143
pyenjoy/kit/Okv.py Normal file
View File

@@ -0,0 +1,143 @@
#!/usr/bin/env python3.9
# -*- coding: utf-8 -*-
"""
JFinal Okv (Ordered Key Value) - Ordered Dictionary Implementation
"""
from .Kv import Kv
from .StrKit import StrKit
from .TypeKit import TypeKit
class Okv(dict):
"""Ordered Key Value dictionary with utility methods"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# In Python 3.7+, dict maintains insertion order
@staticmethod
def of(key, value):
"""Create Okv with initial key-value pair"""
return Okv().set(key, value)
@staticmethod
def by(key, value):
"""Create Okv with initial key-value pair (alias of of)"""
return Okv.of(key, value)
@staticmethod
def create():
"""Create empty Okv"""
return Okv()
def set(self, key, value):
"""Set key-value pair and return self for chaining"""
super().__setitem__(key, value)
return self
def set_if_not_blank(self, key, value):
"""Set value only if not blank"""
if StrKit.not_blank(value):
self.set(key, value)
return self
def set_if_not_null(self, key, value):
"""Set value only if not None"""
if value is not None:
self.set(key, value)
return self
def delete(self, key):
"""Delete key and return self"""
super().pop(key, None)
return self
def get_as(self, key, default_value=None):
"""Get value as specific type"""
return self.get(key, default_value)
def get_str(self, key, default_value=None):
"""Get value as string"""
value = self.get(key)
return str(value) if value is not None else default_value
def get_int(self, key, default_value=None):
"""Get value as integer"""
return TypeKit.to_int(self.get(key, default_value))
def get_long(self, key, default_value=None):
"""Get value as long integer"""
return TypeKit.to_long(self.get(key, default_value))
def get_big_decimal(self, key, default_value=None):
"""Get value as BigDecimal"""
return TypeKit.to_big_decimal(self.get(key, default_value))
def get_double(self, key, default_value=None):
"""Get value as double"""
return TypeKit.to_double(self.get(key, default_value))
def get_float(self, key, default_value=None):
"""Get value as float"""
return TypeKit.to_float(self.get(key, default_value))
def get_number(self, key, default_value=None):
"""Get value as Number"""
return TypeKit.to_number(self.get(key, default_value))
def get_boolean(self, key, default_value=None):
"""Get value as boolean"""
return TypeKit.to_boolean(self.get(key, default_value))
def get_date(self, key, default_value=None):
"""Get value as Date"""
return TypeKit.to_date(self.get(key, default_value))
def get_local_datetime(self, key, default_value=None):
"""Get value as LocalDateTime"""
return TypeKit.to_local_date_time(self.get(key, default_value))
def not_null(self, key):
"""Check if key exists and value is not None"""
return self.get(key) is not None
def is_null(self, key):
"""Check if key doesn't exist or value is None"""
return self.get(key) is None
def not_blank(self, key):
"""Check if value is not blank string"""
return StrKit.not_blank(self.get_str(key))
def is_blank(self, key):
"""Check if value is blank string"""
return StrKit.is_blank(self.get_str(key))
def is_true(self, key):
"""Check if value is True"""
value = self.get(key)
return value is not None and TypeKit.to_boolean(value)
def is_false(self, key):
"""Check if value is False"""
value = self.get(key)
return value is not None and not TypeKit.to_boolean(value)
def keep(self, *keys):
"""Keep only specified keys"""
if keys and len(keys) > 0:
new_okv = Okv()
for key in keys:
if self.contains_key(key):
new_okv.set(key, self.get(key))
self.clear()
self.update(new_okv)
else:
self.clear()
return self
def to_map(self):
"""Convert to regular map"""
return self

166
pyenjoy/kit/Prop.py Normal file
View File

@@ -0,0 +1,166 @@
#!/usr/bin/env python3.9
# -*- coding: utf-8 -*-
"""
JFinal Prop - Properties File Handler
"""
import os
from typing import Optional
class Prop:
"""Properties file handler"""
DEFAULT_ENCODING = "UTF-8"
def __init__(self, file_name_or_content=None, encoding: str = DEFAULT_ENCODING, is_file: bool = True):
"""
Initialize Prop
Args:
file_name_or_content: file name (if is_file=True) or content string
encoding: encoding for reading properties file
is_file: True if first parameter is file name, False if it's content string
"""
self._properties = {}
if file_name_or_content is None:
return
if is_file:
self._load_from_file(file_name_or_content, encoding)
else:
self._load_from_content(file_name_or_content, encoding)
def _load_from_file(self, file_name: str, encoding: str):
"""Load properties from file"""
try:
# Try to find file in classpath first
import importlib.resources
try:
with importlib.resources.open_text(file_name, encoding=encoding) as f:
self._parse_properties(f.read())
return
except (FileNotFoundError, TypeError):
pass
# Try as absolute file path
if os.path.isabs(file_name):
file_path = file_name
else:
file_path = os.path.join(os.getcwd(), file_name)
if not os.path.exists(file_path):
raise FileNotFoundError(f"Properties file not found: {file_name}")
with open(file_path, 'r', encoding=encoding) as f:
self._parse_properties(f.read())
except Exception as e:
raise RuntimeError(f"Error loading properties file: {e}")
def _load_from_content(self, content: str, encoding: str):
"""Load properties from content string"""
try:
self._parse_properties(content)
except Exception as e:
raise RuntimeError(f"Error parsing properties content: {e}")
def _parse_properties(self, content: str):
"""Parse properties content"""
for line in content.split('\n'):
line = line.strip()
# Skip empty lines and comments
if not line or line.startswith('#') or line.startswith('!'):
continue
# Handle line continuation
while line.endswith('\\'):
line = line[:-1].strip()
# This is a simplified implementation
# Find the first separator
separator_idx = -1
for sep in ['=', ':']:
idx = line.find(sep)
if idx != -1:
if separator_idx == -1 or idx < separator_idx:
separator_idx = idx
if separator_idx == -1:
# No separator, treat as key with empty value
key = line.strip()
value = ""
else:
key = line[:separator_idx].strip()
value = line[separator_idx + 1:].strip()
if key:
self._properties[key] = value
def get(self, key: str, default_value: str = None) -> Optional[str]:
"""Get property value"""
value = self._properties.get(key)
if value is not None and len(value) != 0:
return value.strip()
return default_value
def get_int(self, key: str, default_value: int = None) -> Optional[int]:
"""Get property as integer"""
value = self.get(key)
if value is not None:
return int(value)
return default_value
def get_long(self, key: str, default_value: int = None) -> Optional[int]:
"""Get property as long integer"""
value = self.get(key)
if value is not None:
return int(value)
return default_value
def get_double(self, key: str, default_value: float = None) -> Optional[float]:
"""Get property as double"""
value = self.get(key)
if value is not None:
return float(value)
return default_value
def get_boolean(self, key: str, default_value: bool = None) -> Optional[bool]:
"""Get property as boolean"""
value = self.get(key)
if value is not None:
value_lower = value.lower().strip()
if value_lower == "true":
return True
elif value_lower == "false":
return False
else:
raise ValueError(f"Cannot parse boolean value: {value}")
return default_value
def contains_key(self, key: str) -> bool:
"""Check if key exists"""
return key in self._properties
def is_empty(self) -> bool:
"""Check if properties is empty"""
return len(self._properties) == 0
def not_empty(self) -> bool:
"""Check if properties is not empty"""
return not self.is_empty()
def append(self, prop) -> 'Prop':
"""Append properties from another Prop"""
if prop is None:
raise ValueError("prop cannot be None")
for key, value in prop.get_properties().items():
self._properties[key] = value
return self
def get_properties(self) -> dict:
"""Get all properties"""
return self._properties.copy()

135
pyenjoy/kit/PropKit.py Normal file
View File

@@ -0,0 +1,135 @@
#!/usr/bin/env python3.9
# -*- coding: utf-8 -*-
"""
JFinal PropKit - Properties File Management
"""
import os
from typing import Optional, Dict
from .Prop import Prop
from .StrKit import StrKit
class PropKit:
"""Properties file management utility"""
_env_key = "app.env"
_prop: Optional[Prop] = None
_cache: Dict[str, Prop] = {}
def __init__(self):
raise NotImplementedError("PropKit is a utility class and cannot be instantiated")
@staticmethod
def set_env_key(env_key: str):
"""Set environment key for config"""
PropKit._env_key = env_key
@staticmethod
def get_env_key() -> str:
"""Get environment key"""
return PropKit._env_key
@staticmethod
def get_env() -> str:
"""Get current environment value"""
return PropKit.get_prop().get(PropKit._env_key)
@staticmethod
def use(file_name: str, encoding: str = Prop.DEFAULT_ENCODING) -> Prop:
"""Use properties file"""
if file_name not in PropKit._cache:
PropKit._cache[file_name] = Prop(file_name, encoding)
PropKit._handle_env(PropKit._cache[file_name], file_name)
if PropKit._prop is None:
PropKit._prop = PropKit._cache[file_name]
return PropKit._cache[file_name]
@staticmethod
def _handle_env(result: Prop, file_name: str):
"""Handle environment-specific configuration"""
env = result.get(PropKit._env_key)
if StrKit.not_blank(env):
dot_index = file_name.rfind('.')
if dot_index != -1:
env_config_name = file_name[:dot_index] + "-" + env + file_name[dot_index:]
else:
env_config_name = file_name + "-" + env
try:
env_config = Prop(env_config_name)
result.append(env_config)
except:
pass # Ignore if env config doesn't exist
@staticmethod
def useless(file_name: str) -> Optional[Prop]:
"""Remove properties file from cache"""
removed = PropKit._cache.pop(file_name, None)
if PropKit._prop is removed:
PropKit._prop = None
return removed
@staticmethod
def clear():
"""Clear all cached properties"""
PropKit._prop = None
PropKit._cache.clear()
@staticmethod
def append(prop: Prop) -> Prop:
"""Append properties"""
with PropKit:
if PropKit._prop is not None:
PropKit._prop.append(prop)
else:
PropKit._prop = prop
return PropKit._prop
@staticmethod
def append_content(content: str, encoding: str = Prop.DEFAULT_ENCODING) -> Prop:
"""Append properties from content string"""
return PropKit.append(Prop(content, encoding, is_file=False))
@staticmethod
def get_prop() -> Prop:
"""Get current properties"""
if PropKit._prop is None:
raise IllegalStateException("Load properties file by invoking PropKit.use(String fileName) method first.")
return PropKit._prop
@staticmethod
def get(key: str, default_value: str = None) -> Optional[str]:
"""Get property value"""
return PropKit.get_prop().get(key, default_value)
@staticmethod
def get_int(key: str, default_value: int = None) -> Optional[int]:
"""Get property as integer"""
return PropKit.get_prop().get_int(key, default_value)
@staticmethod
def get_long(key: str, default_value: int = None) -> Optional[int]:
"""Get property as long"""
return PropKit.get_prop().get_long(key, default_value)
@staticmethod
def get_double(key: str, default_value: float = None) -> Optional[float]:
"""Get property as double"""
return PropKit.get_prop().get_double(key, default_value)
@staticmethod
def get_boolean(key: str, default_value: bool = None) -> Optional[bool]:
"""Get property as boolean"""
return PropKit.get_prop().get_boolean(key, default_value)
@staticmethod
def contains_key(key: str) -> bool:
"""Check if key exists"""
return PropKit.get_prop().contains_key(key)
class IllegalStateException(Exception):
"""Illegal state exception"""
pass

107
pyenjoy/kit/ReflectKit.py Normal file
View File

@@ -0,0 +1,107 @@
#!/usr/bin/env python3.9
# -*- coding: utf-8 -*-
"""
JFinal ReflectKit - Reflection Utilities
"""
import inspect
from typing import Any, Dict, List, Type
class ReflectKit:
"""Reflection utility class"""
@staticmethod
def new_instance(clazz: Type) -> Any:
"""Create new instance of class"""
try:
# Try __new__ first (for immutable types)
if hasattr(clazz, '__new__'):
obj = clazz.__new__(clazz)
if obj is not None and not isinstance(obj, type):
return obj
# Fall back to regular instantiation
return clazz()
except Exception as e:
raise RuntimeError(f"Error creating instance of {clazz}: {e}")
@staticmethod
def get_method_signature(method) -> str:
"""Get method signature as string"""
if not callable(method):
raise ValueError("method must be callable")
try:
# Get function information
if hasattr(method, '__name__'):
method_name = method.__name__
else:
method_name = str(method)
# Get declaring class
if hasattr(method, '__qualname__'):
declaring_class = method.__qualname__.rsplit('.', 1)[0] if '.' in method.__qualname__ else ''
else:
declaring_class = ''
# Get parameters
try:
sig = inspect.signature(method)
params = []
for param_name, param in sig.parameters.items():
if param_name != 'self':
param_type = param.annotation if param.annotation != inspect.Parameter.empty else Any
params.append(str(param_type.__name__) if hasattr(param_type, '__name__') else str(param_type))
except (ValueError, TypeError):
params = []
# Build signature
if declaring_class:
signature = f"{declaring_class}.{method_name}({', '.join(params)})"
else:
signature = f"{method_name}({', '.join(params)})"
return signature
except Exception as e:
raise RuntimeError(f"Error getting method signature: {e}")
@staticmethod
def get_class_methods(clazz: Type) -> Dict[str, callable]:
"""Get all methods of a class"""
methods = {}
try:
for name, method in inspect.getmembers(clazz, predicate=inspect.isfunction):
if not name.startswith('_'):
methods[name] = method
except Exception as e:
raise RuntimeError(f"Error getting class methods: {e}")
return methods
@staticmethod
def get_class_attributes(clazz: Type) -> Dict[str, Any]:
"""Get all class attributes"""
attributes = {}
try:
for name, value in inspect.getmembers(clazz):
if not name.startswith('_') and not callable(value):
attributes[name] = value
except Exception as e:
raise RuntimeError(f"Error getting class attributes: {e}")
return attributes
@staticmethod
def is_subclass_of(subclass: Type, superclass: Type) -> bool:
"""Check if subclass is subclass of superclass"""
try:
return issubclass(subclass, superclass)
except TypeError:
return False
@staticmethod
def is_instance_of(obj: Any, clazz: Type) -> bool:
"""Check if object is instance of class"""
try:
return isinstance(obj, clazz)
except TypeError:
return False

139
pyenjoy/kit/StrKit.py Normal file
View File

@@ -0,0 +1,139 @@
#!/usr/bin/env python3.9
# -*- coding: utf-8 -*-
"""
JFinal StrKit - String Utilities
"""
class StrKit:
"""String utility class"""
@staticmethod
def first_char_to_lower_case(s: str) -> str:
"""首字母变小写"""
if not s:
return s
first_char = s[0]
if 'A' <= first_char <= 'Z':
arr = list(s)
arr[0] = chr(ord(arr[0]) + ord('a') - ord('A'))
return ''.join(arr)
return s
@staticmethod
def first_char_to_upper_case(s: str) -> str:
"""首字母变大写"""
if not s:
return s
first_char = s[0]
if 'a' <= first_char <= 'z':
arr = list(s)
arr[0] = chr(ord(arr[0]) - (ord('a') - ord('A')))
return ''.join(arr)
return s
@staticmethod
def is_blank(s: str) -> bool:
"""字符串为 null 或者内部字符全部为 ' ' '\\t' '\\n' '\\r' 这四类字符时返回 true"""
if s is None:
return True
for char in s:
if char > ' ':
return False
return True
@staticmethod
def not_blank(s: str) -> bool:
return not StrKit.is_blank(s)
@staticmethod
def not_blank(*strings) -> bool:
if strings is None or len(strings) == 0:
return False
for s in strings:
if StrKit.is_blank(s):
return False
return True
@staticmethod
def has_blank(*strings) -> bool:
if strings is None or len(strings) == 0:
return True
for s in strings:
if StrKit.is_blank(s):
return True
return False
@staticmethod
def not_null(*paras) -> bool:
if paras is None:
return False
for obj in paras:
if obj is None:
return False
return True
@staticmethod
def default_if_blank(s: str, default_value: str) -> str:
return default_value if StrKit.is_blank(s) else s
@staticmethod
def to_camel_case(s: str) -> str:
"""将包含下划线字符 '_' 的字符串转换成驼峰格式,不包含下划线则原样返回"""
return StrKit._to_camel_case(s, False)
@staticmethod
def _to_camel_case(s: str, to_lower_case_anyway: bool) -> str:
length = len(s)
if length <= 1:
return s
buf = []
index = 0
i = 0
while i < length:
ch = s[i]
if ch == '_':
i += 1
if i < length:
ch = s[i]
if index == 0:
buf.append(chr(ord(ch) + 32) if 'A' <= ch <= 'Z' else ch)
else:
buf.append(chr(ord(ch) - 32) if 'a' <= ch <= 'z' else ch)
index += 1
else:
buf.append(chr(ord(ch) + 32) if 'A' <= ch <= 'Z' else ch)
index += 1
i += 1
if to_lower_case_anyway:
return ''.join(buf[:index])
return s if i == index else ''.join(buf[:index])
@staticmethod
def join(string_array, separator: str = '') -> str:
"""Join string array"""
if not string_array:
return ''
if separator:
return separator.join(string_array)
return ''.join(string_array)
@staticmethod
def slow_equals(a: str, b: str) -> bool:
"""Timing-safe string comparison"""
from .HashKit import HashKit
a_bytes = a.encode('utf-8') if a else None
b_bytes = b.encode('utf-8') if b else None
return HashKit.slow_equals(a_bytes, b_bytes)
@staticmethod
def equals(a: str, b: str) -> bool:
return a is None if b is None else a == b
@staticmethod
def get_random_uuid() -> str:
"""Generate random UUID without dashes"""
import uuid
return uuid.uuid4().hex

111
pyenjoy/kit/SyncWriteMap.py Normal file
View File

@@ -0,0 +1,111 @@
#!/usr/bin/env python3.9
# -*- coding: utf-8 -*-
"""
JFinal SyncWriteMap - Thread-Safe Dictionary
"""
from typing import Dict, Generic, TypeVar, Optional, Callable
from threading import Lock
K = TypeVar('K')
V = TypeVar('V')
class SyncWriteDict(Dict[K, V]):
"""Thread-safe dictionary with synchronized write operations"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._lock = Lock()
def put(self, key: K, value: V) -> Optional[V]:
"""Put key-value pair with synchronization"""
with self._lock:
old_value = self.get(key)
super().__setitem__(key, value)
return old_value
def put_if_absent(self, key: K, value: V) -> V:
"""Put value only if key doesn't exist"""
with self._lock:
if key not in self:
super().__setitem__(key, value)
return value
return self[key]
def remove(self, key: K) -> Optional[V]:
"""Remove key with synchronization"""
with self._lock:
return super().pop(key, None)
def clear(self):
"""Clear all items with synchronization"""
with self._lock:
super().clear()
def update(self, other: dict = None, **kwargs):
"""Update dictionary with synchronization"""
with self._lock:
if other:
super().update(other)
if kwargs:
super().update(kwargs)
def compute_if_absent(self, key: K, mapping_function: Callable[[K], V]) -> V:
"""Compute value if key is absent"""
with self._lock:
if key not in self:
value = mapping_function(key)
super().__setitem__(key, value)
return value
return self[key]
def compute_if_present(self, key: K, remapping_function: Callable[[K, V], V]) -> Optional[V]:
"""Compute new value if key exists"""
with self._lock:
if key in self:
old_value = self[key]
new_value = remapping_function(key, old_value)
if new_value is None:
super().__delitem__(key)
else:
super().__setitem__(key, new_value)
return new_value
return None
def compute(self, key: K, remapping_function: Callable[[K, Optional[V]], Optional[V]]) -> Optional[V]:
"""Compute new value for key"""
with self._lock:
old_value = self.get(key)
new_value = remapping_function(key, old_value)
if new_value is None:
if key in self:
super().__delitem__(key)
else:
super().__setitem__(key, new_value)
return new_value
def replace(self, key: K, old_value: V, new_value: V) -> bool:
"""Replace value if old value matches"""
with self._lock:
if key in self and self[key] == old_value:
super().__setitem__(key, new_value)
return True
return False
def replace_value(self, key: K, value: V) -> bool:
"""Replace value regardless of old value"""
with self._lock:
if key in self:
super().__setitem__(key, value)
return True
return False
class SyncWriteMap(SyncWriteDict):
"""Thread-safe dictionary (alias for compatibility)"""
def __init__(self, initial_capacity: int = None, load_factor: float = None,
mapping: Dict[K, V] = None):
super().__init__(mapping or {})
# Note: initial_capacity and load_factor are ignored in Python dict implementation
# but kept for API compatibility

150
pyenjoy/kit/TimeKit.py Normal file
View File

@@ -0,0 +1,150 @@
#!/usr/bin/env python3.9
# -*- coding: utf-8 -*-
"""
JFinal TimeKit - Date and Time Utilities
"""
from datetime import datetime, date, time, timedelta
from typing import Union
import threading
class TimeKit:
"""Date and time utility class"""
_formatters = {}
_sdf_cache = threading.local()
@staticmethod
def _get_sdf(pattern: str):
"""Get thread-local SimpleDateFormat equivalent"""
if not hasattr(TimeKit._sdf_cache, 'formatters'):
TimeKit._sdf_cache.formatters = {}
if pattern not in TimeKit._sdf_cache.formatters:
TimeKit._sdf_cache.formatters[pattern] = _SimpleDateFormat(pattern)
return TimeKit._sdf_cache.formatters[pattern]
@staticmethod
def now(pattern: str = "yyyy-MM-dd HH:mm:ss") -> str:
"""Get current time as string"""
return datetime.now().strftime(TimeKit._to_python_pattern(pattern))
@staticmethod
def now_with_millisecond() -> str:
"""Get current time with millisecond precision"""
return datetime.now().strftime("%Y%m%d%H%M%S%f")[:-3]
@staticmethod
def format(dt: Union[datetime, date], pattern: str = "yyyy-MM-dd HH:mm:ss") -> str:
"""Format datetime or date to string"""
python_pattern = TimeKit._to_python_pattern(pattern)
if isinstance(dt, datetime):
return dt.strftime(python_pattern)
elif isinstance(dt, date):
return dt.strftime(TimeKit._to_python_pattern(pattern))
elif isinstance(dt, time):
return dt.strftime(TimeKit._to_python_pattern(pattern))
else:
raise ValueError(f"Unsupported type: {type(dt)}")
@staticmethod
def parse(date_string: str, pattern: str = "yyyy-MM-dd HH:mm:ss") -> datetime:
"""Parse string to datetime"""
try:
python_pattern = TimeKit._to_python_pattern(pattern)
return datetime.strptime(date_string, python_pattern)
except ValueError as e:
raise RuntimeError(f"Error parsing date: {e}")
@staticmethod
def parse_date(date_string: str, pattern: str = "yyyy-MM-dd") -> date:
"""Parse string to date"""
dt = TimeKit.parse(date_string, pattern)
return dt.date()
@staticmethod
def parse_time(time_string: str, pattern: str = "HH:mm:ss") -> time:
"""Parse string to time"""
dt = TimeKit.parse(f"1970-01-01 {time_string}", f"yyyy-MM-dd {pattern}")
return dt.time()
@staticmethod
def to_datetime(dt: Union[datetime, date]) -> datetime:
"""Convert date to datetime"""
if isinstance(dt, datetime):
return dt
elif isinstance(dt, date):
return datetime.combine(dt, time.min)
else:
raise ValueError(f"Unsupported type: {type(dt)}")
@staticmethod
def to_date(dt: Union[datetime, date]) -> date:
"""Convert datetime to date"""
if isinstance(dt, datetime):
return dt.date()
elif isinstance(dt, date):
return dt
else:
raise ValueError(f"Unsupported type: {type(dt)}")
@staticmethod
def is_after(dt1: datetime, dt2: datetime) -> bool:
"""Check if dt1 is after dt2"""
return dt1 > dt2
@staticmethod
def is_before(dt1: datetime, dt2: datetime) -> bool:
"""Check if dt1 is before dt2"""
return dt1 < dt2
@staticmethod
def is_equal(dt1: datetime, dt2: datetime) -> bool:
"""Check if dt1 equals dt2"""
return dt1 == dt2
@staticmethod
def _to_python_pattern(java_pattern: str) -> str:
"""Convert Java date pattern to Python strftime pattern"""
mapping = {
'yyyy': '%Y',
'yy': '%y',
'MM': '%m',
'dd': '%d',
'HH': '%H',
'mm': '%M',
'ss': '%S',
'SSS': '%f',
}
result = java_pattern
for java_fmt, python_fmt in mapping.items():
result = result.replace(java_fmt, python_fmt)
return result
class _SimpleDateFormat:
"""Simple date format implementation (Java SimpleDateFormat equivalent)"""
def __init__(self, pattern: str):
self.pattern = pattern
self.python_pattern = TimeKit._to_python_pattern(pattern)
def format(self, dt: Union[datetime, date]) -> str:
"""Format datetime to string"""
if isinstance(dt, datetime):
return dt.strftime(self.python_pattern)
elif isinstance(dt, date):
return dt.strftime(self.python_pattern)
else:
raise ValueError(f"Unsupported type: {type(dt)}")
def parse(self, date_string: str) -> datetime:
"""Parse string to datetime"""
try:
return datetime.strptime(date_string, self.python_pattern)
except ValueError as e:
raise RuntimeError(f"Error parsing date: {e}")

206
pyenjoy/kit/TypeKit.py Normal file
View File

@@ -0,0 +1,206 @@
#!/usr/bin/env python3.9
# -*- coding: utf-8 -*-
"""
JFinal TypeKit - Type Conversion Utilities
"""
from datetime import datetime, date, time, timedelta
from decimal import Decimal, InvalidOperation
import re
class TypeKit:
"""Type conversion utility class"""
_date_pattern = "yyyy-MM-dd"
_date_len = len(_date_pattern)
_date_time_without_second_pattern = "yyyy-MM-dd HH:mm"
_date_time_without_second_len = len(_date_time_without_second_pattern)
_date_time_pattern = "yyyy-MM-dd HH:mm:ss"
@staticmethod
def to_str(s) -> str:
"""Convert to string"""
return str(s) if s is not None else None
@staticmethod
def to_int(n) -> int:
"""Convert to integer"""
if n is None:
return None
if isinstance(n, int):
return n
if isinstance(n, float):
return int(n)
try:
return int(str(n))
except (ValueError, TypeError):
return None
@staticmethod
def to_long(n) -> int:
"""Convert to long integer"""
return TypeKit.to_int(n)
@staticmethod
def to_float(n) -> float:
"""Convert to float"""
if n is None:
return None
if isinstance(n, (int, float)):
return float(n)
try:
return float(str(n))
except (ValueError, TypeError):
return None
@staticmethod
def to_double(n) -> float:
"""Convert to double"""
return TypeKit.to_float(n)
@staticmethod
def to_decimal(n) -> Decimal:
"""Convert to Decimal"""
if n is None:
return None
if isinstance(n, Decimal):
return n
try:
return Decimal(str(n))
except (InvalidOperation, ValueError):
return None
@staticmethod
def to_big_decimal(n) -> Decimal:
"""Convert to BigDecimal"""
return TypeKit.to_decimal(n)
@staticmethod
def to_short(n) -> int:
"""Convert to short integer"""
result = TypeKit.to_int(n)
return result if result is None else max(-32768, min(32767, result))
@staticmethod
def to_byte(n) -> int:
"""Convert to byte integer"""
result = TypeKit.to_int(n)
return result if result is None else max(-128, min(127, result))
@staticmethod
def to_boolean(b) -> bool:
"""Convert to boolean"""
if b is None:
return None
if isinstance(b, bool):
return b
if isinstance(b, (int, float)):
if b == 1:
return True
elif b == 0:
return False
return bool(b)
if isinstance(b, str):
s = str(b).lower().strip()
if s in ("true", "1"):
return True
elif s in ("false", "0"):
return False
return bool(b)
@staticmethod
def to_number(n) -> float:
"""Convert to number"""
if n is None:
return None
if isinstance(n, (int, float)):
return float(n)
s = str(n)
if '.' in s:
return float(s)
else:
try:
return int(s)
except ValueError:
return float(s)
@staticmethod
def to_date(d):
"""Convert to datetime"""
if d is None:
return None
if isinstance(d, (datetime, date)):
if isinstance(d, datetime):
return d
else:
return datetime.combine(d, time.min)
if isinstance(d, str):
s = str(d).strip()
s_len = len(s)
if s_len <= TypeKit._date_len:
return TypeKit._parse_date(s, TypeKit._date_pattern)
elif s_len > TypeKit._date_time_without_second_len:
return TypeKit._parse_date(s, TypeKit._date_time_pattern)
else:
colon_count = s.count(':')
if colon_count == 2:
return TypeKit._parse_date(s, TypeKit._date_time_pattern)
elif colon_count == 1:
return TypeKit._parse_date(s, TypeKit._date_time_without_second_pattern)
raise ValueError(f"Cannot convert to date: {d}")
@staticmethod
def _parse_date(date_string: str, pattern: str) -> datetime:
"""Parse date string with pattern"""
try:
# Simplified date parsing - supports common formats
if pattern == "yyyy-MM-dd":
parts = date_string.split('-')
if len(parts) == 3:
return datetime(int(parts[0]), int(parts[1]), int(parts[2]))
elif pattern == "yyyy-MM-dd HH:mm:ss":
date_part, time_part = date_string.split(' ')
date_parts = date_part.split('-')
time_parts = time_part.split(':')
if len(date_parts) == 3 and len(time_parts) == 3:
return datetime(
int(date_parts[0]), int(date_parts[1]), int(date_parts[2]),
int(time_parts[0]), int(time_parts[1]), int(time_parts[2])
)
elif pattern == "yyyy-MM-dd HH:mm":
date_part, time_part = date_string.split(' ')
date_parts = date_part.split('-')
time_parts = time_part.split(':')
if len(date_parts) == 3 and len(time_parts) == 2:
return datetime(
int(date_parts[0]), int(date_parts[1]), int(date_parts[2]),
int(time_parts[0]), int(time_parts[1])
)
except (ValueError, IndexError):
pass
# Fallback to try parsing with dateutil or regex
raise ValueError(f"Cannot parse date: {date_string}")
@staticmethod
def to_local_date_time(ldt):
"""Convert to LocalDateTime"""
if ldt is None:
return None
if isinstance(ldt, datetime):
return ldt
d = TypeKit.to_date(ldt)
if d:
return d
raise ValueError(f"Cannot convert to LocalDateTime: {ldt}")

View File

@@ -0,0 +1,77 @@
#!/usr/bin/env python3.9
# -*- coding: utf-8 -*-
"""
JFinal ProxyClass - Proxy Class Information
"""
from typing import Dict, Any, Optional, Type
class ProxyClass:
"""Proxy class information holder"""
def __init__(self, target: Type):
"""
Initialize proxy class
Args:
target: Target class to be proxied
"""
self._target = target
self._pkg = target.__module__
self._name = target.__name__ + "$$EnhancerByJFinal"
self._source_code: Optional[str] = None
self._byte_code: Optional[Dict[str, bytes]] = None
self._clazz: Optional[Type] = None
@property
def target(self) -> Type:
"""Get target class"""
return self._target
@property
def pkg(self) -> str:
"""Get package name"""
return self._pkg
@property
def name(self) -> str:
"""Get proxy class name"""
return self._name
@property
def source_code(self) -> Optional[str]:
"""Get source code"""
return self._source_code
@source_code.setter
def source_code(self, value: str):
"""Set source code"""
self._source_code = value
@property
def byte_code(self) -> Optional[Dict[str, bytes]]:
"""Get byte code"""
return self._byte_code
@byte_code.setter
def byte_code(self, value: Dict[str, bytes]):
"""Set byte code"""
self._byte_code = value
@property
def clazz(self) -> Optional[Type]:
"""Get loaded class"""
return self._clazz
@clazz.setter
def clazz(self, value: Type):
"""Set loaded class"""
self._clazz = value
@property
def full_name(self) -> str:
"""Get full class name with package"""
return f"{self._pkg}.{self._name}"
def __repr__(self) -> str:
return f"ProxyClass(target={self._target.__name__}, name={self._name})"

View File

@@ -0,0 +1,124 @@
#!/usr/bin/env python3.9
# -*- coding: utf-8 -*-
"""
JFinal ProxyClassLoader - Dynamic Proxy Class Loader
"""
import importlib
import sys
from typing import Dict, Optional, Type
from .ProxyClass import ProxyClass
class ProxyClassLoader:
"""Dynamic proxy class loader"""
def __init__(self):
"""Initialize proxy class loader"""
self._byte_code_map: Dict[str, bytes] = {}
def load_proxy_class(self, proxy_class: ProxyClass) -> Type:
"""
Load proxy class from byte code
Args:
proxyClass: ProxyClass object with byte code
Returns:
Loaded proxy class
"""
# Add byte code to map
if proxy_class.byte_code:
for class_name, byte_code in proxy_class.byte_code.items():
if class_name not in self._byte_code_map:
self._byte_code_map[class_name] = byte_code
# Try to load the class
class_name = proxy_class.full_name
# Check if already loaded
if class_name in sys.modules:
return sys.modules[class_name]
try:
# For Python, we'll use dynamic import
# In a real implementation, this would use importlib or similar
loaded_class = self._dynamic_import(class_name, proxy_class)
return loaded_class
except (ImportError, AttributeError) as e:
raise RuntimeError(f"Error loading proxy class {class_name}: {e}")
def _dynamic_import(self, class_name: str, proxy_class: ProxyClass) -> Type:
"""
Dynamically import and create class from proxy class
Args:
class_name: Full class name
proxyClass: ProxyClass object
Returns:
Dynamically created class
"""
# In Python, we can execute the source code and get the class
if proxy_class.source_code:
# Create a module
module_name = proxy_class.pkg
module_code = proxy_class.source_code
# Execute the code in a new namespace
namespace = {}
try:
exec(module_code, namespace)
# Find the class in the namespace
for name, obj in namespace.items():
if isinstance(obj, type) and name == proxy_class.name:
return obj
raise RuntimeError(f"Class {proxy_class.name} not found in generated code")
except Exception as e:
raise RuntimeError(f"Error executing generated code: {e}")
else:
raise RuntimeError("No source code available for proxy class")
def find_class(self, name: str) -> Optional[bytes]:
"""
Find class byte code by name
Args:
name: Full class name
Returns:
Byte code or None if not found
"""
return self._byte_code_map.get(name)
def add_byte_code(self, name: str, byte_code: bytes):
"""
Add byte code to the loader
Args:
name: Full class name
byte_code: Class byte code
"""
self._byte_code_map[name] = byte_code
def remove_byte_code(self, name: str) -> Optional[bytes]:
"""
Remove and return byte code
Args:
name: Full class name
Returns:
Removed byte code or None
"""
return self._byte_code_map.pop(name, None)
def clear(self):
"""Clear all cached byte code"""
self._byte_code_map.clear()
@property
def byte_code_count(self) -> int:
"""Get number of cached byte codes"""
return len(self._byte_code_map)

View File

@@ -0,0 +1,143 @@
#!/usr/bin/env python3.9
# -*- coding: utf-8 -*-
"""
JFinal ProxyCompiler - Dynamic Proxy Compiler
"""
import os
import tempfile
import subprocess
import hashlib
from typing import Dict, Optional, List, Tuple
from .ProxyClass import ProxyClass
from .ProxyClassLoader import ProxyClassLoader
class ProxyCompiler:
"""Dynamic proxy compiler"""
def __init__(self):
"""Initialize proxy compiler"""
self._class_loader = ProxyClassLoader()
self._compile_options: List[str] = []
def compile(self, proxy_class: ProxyClass):
"""
Compile proxy class source code to byte code
Args:
proxyClass: ProxyClass object with source code
"""
if not proxy_class.source_code:
raise ValueError("Proxy class source code is not set")
try:
# Generate byte code by compiling Python source
byte_code = self._compile_python_source(proxy_class)
# Set the byte code
proxy_class.byte_code = {
proxy_class.full_name: byte_code
}
except Exception as e:
raise RuntimeError(f"Error compiling proxy class: {e}")
def _compile_python_source(self, proxy_class: ProxyClass) -> bytes:
"""
Compile Python source code to bytecode
Args:
proxyClass: ProxyClass with source code
Returns:
Compiled bytecode
"""
# Method 1: Use compile() and exec()
try:
# Compile the source code
compiled = compile(proxy_class.source_code, proxy_class.name, 'exec')
# Create a namespace and execute
namespace = {}
exec(compiled, namespace)
# For Python, bytecode is already loaded in the namespace
# Return dummy byte code since Python doesn't have traditional bytecode like Java
return b'PYTHON_PROXY_CLASS'
except SyntaxError as e:
raise RuntimeError(f"Syntax error in generated code: {e}")
except Exception as e:
raise RuntimeError(f"Error compiling Python source: {e}")
def set_compile_options(self, options: List[str]) -> 'ProxyCompiler':
"""
Set compile options
Args:
options: List of compiler options
Returns:
Self for chaining
"""
self._compile_options = options.copy()
return self
def add_compile_option(self, option: str) -> 'ProxyCompiler':
"""
Add single compile option
Args:
option: Compiler option
Returns:
Self for chaining
"""
if option:
self._compile_options.append(option)
return self
def get_class_loader(self) -> ProxyClassLoader:
"""Get associated class loader"""
return self._class_loader
def compile_and_load(self, proxy_class: ProxyClass) -> type:
"""
Compile proxy class and load the resulting class
Args:
proxyClass: ProxyClass with source code
Returns:
Loaded proxy class
"""
# Compile
self.compile(proxy_class)
# Load
return self._class_loader.load_proxy_class(proxy_class)
class JavaProxyCompiler:
"""Compiler for Java proxy classes (for Jython compatibility)"""
def __init__(self):
"""Initialize Java proxy compiler"""
self._options: List[str] = []
def compile(self, proxy_class: ProxyClass):
"""Compile Java proxy class"""
# This would use javax.tools.JavaCompiler in Java version
# For Python, we provide a stub implementation
pass
def set_options(self, options: List[str]) -> 'JavaProxyCompiler':
"""Set compiler options"""
self._options = options.copy()
return self
def add_option(self, option: str) -> 'JavaProxyCompiler':
"""Add compiler option"""
if option:
self._options.append(option)
return self

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
pyenjoy/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

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
pyenjoy/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})"

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

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})"

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})"

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

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})"

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})"

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()

View File

@@ -0,0 +1,383 @@
#!/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.')
# Remove newline characters from expression
expr_str = expr_str.replace('\n', '').replace('\r', '')
# Split expression by | to handle filters
expr_parts = expr_str.split('|')
main_expr = expr_parts[0].strip()
# Evaluate main expression first
if '??' in main_expr:
# Custom evaluation for expressions with ?? operator
def evaluate_null_coalescing(expr):
if '??' not in expr:
try:
return eval(expr, {}, wrapped_vars)
except (NameError, AttributeError):
return None
# Process from right to left for nested ??
idx = expr.rfind('??')
# Find left expression
left_expr = expr[:idx].strip()
right_expr = expr[idx+2:].strip()
# Evaluate left part
left_val = evaluate_null_coalescing(left_expr)
if left_val is not None:
return left_val
else:
# Evaluate right part
return evaluate_null_coalescing(right_expr)
result = evaluate_null_coalescing(main_expr)
else:
# Regular evaluation for expressions without ??
try:
result = eval(main_expr, {}, wrapped_vars)
except (NameError, AttributeError):
result = None
# Apply filters
for filter_part in expr_parts[1:]:
filter_part = filter_part.strip()
if not filter_part:
continue
# Parse filter name and arguments
if '(' in filter_part:
filter_name = filter_part[:filter_part.find('(')].strip()
# Extract arguments
args_str = filter_part[filter_part.find('(')+1:filter_part.rfind(')')].strip()
# Simple argument parsing - split by commas, ignoring commas inside quotes
args = []
current_arg = ''
in_quotes = False
quote_char = ''
for char in args_str:
if char in "'\"" and (not in_quotes or char == quote_char):
if not in_quotes:
in_quotes = True
quote_char = char
else:
in_quotes = False
current_arg += char
elif char == ',' and not in_quotes:
args.append(current_arg.strip())
current_arg = ''
else:
current_arg += char
if current_arg:
args.append(current_arg.strip())
else:
filter_name = filter_part.strip()
args = []
# Apply filter
if filter_name == 'join':
# Join filter implementation
if len(args) > 0:
# Remove quotes from separator if present
sep = args[0]
if (sep.startswith('"') and sep.endswith('"')) or (sep.startswith("'") and sep.endswith("'")):
sep = sep[1:-1]
else:
sep = ''
if isinstance(result, (list, tuple)):
result = sep.join(str(item) for item in result)
else:
result = str(result)
elif filter_name == 'upper':
# Upper case filter
result = str(result).upper()
elif filter_name == 'lower':
# Lower case filter
result = str(result).lower()
elif filter_name == 'strip':
# Strip whitespace filter
result = str(result).strip()
return result
except Exception as e:
# Handle evaluation errors gracefully
return f"Error evaluating expression '{self._expr_str}': {e}"

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})"

View File

@@ -0,0 +1,130 @@
#!/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:
# Wrap dict values to allow 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
# Create wrapped vars similar to SimpleExprList.eval()
wrapped_vars = {}
for key, value in scope._data.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
# Get the iterable from the expression
iterable = eval(self._iter_expr, {}, wrapped_vars)
# 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})"

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

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