调整版本并做测试

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

View File

@@ -1,3 +1,3 @@
include README.md include README.md
include LICENSE include LICENSE
recursive-include py_enjoy * recursive-include pyenjoy *

View File

@@ -1,4 +1,4 @@
# py_enjoy # py-enjoy
jfinal-enjoy的 python3.9+ 实现 jfinal-enjoy的 python3.9+ 实现

View File

@@ -9,7 +9,7 @@ This is a Python 3.9+ implementation of the JFinal Template Engine (Enjoy).
Project Structure Project Structure
----------------- -----------------
py_enjoy/ pyenjoy/
├── __init__.py # Main package initialization ├── __init__.py # Main package initialization
├── kit/ # Utility classes ├── kit/ # Utility classes
│ ├── StrKit.py # String utilities │ ├── StrKit.py # String utilities
@@ -93,19 +93,19 @@ Installation
```bash ```bash
# Clone or download the project # Clone or download the project
cd py_enjoy cd pyenjoy
# No installation required, just add to PYTHONPATH # No installation required, just add to PYTHONPATH
import sys import sys
sys.path.append('/path/to/py_enjoy') sys.path.append('/path/to/pyenjoy')
``` ```
Quick Start Quick Start
----------- -----------
```python ```python
from py_enjoy.template.Engine import Engine from pyenjoy.template.Engine import Engine
from py_enjoy.kit.Kv import Kv from pyenjoy.kit.Kv import Kv
# Get the template engine # Get the template engine
engine = Engine.use() engine = Engine.use()
@@ -145,7 +145,7 @@ data = Kv.of("items", [
### Custom Directives ### Custom Directives
```python ```python
from py_enjoy.template.Directive import Directive from pyenjoy.template.Directive import Directive
class UpperDirective(Directive): class UpperDirective(Directive):
def exec(self, env, scope, writer): def exec(self, env, scope, writer):

View File

@@ -4,7 +4,7 @@
py_enjoy - Python implementation of JFinal Template Engine py_enjoy - Python implementation of JFinal Template Engine
""" """
__version__ = "5.2.2" __version__ = "1.0.1"
__author__ = "James Zhan 詹波 (original), Python port by mrzhou@miw.cn" __author__ = "James Zhan 詹波 (original), Python port by mrzhou@miw.cn"
__license__ = "Apache License 2.0" __license__ = "Apache License 2.0"

View File

@@ -274,8 +274,108 @@ class SimpleExprList:
# Replace 'for.' with '_for.' in the expression # Replace 'for.' with '_for.' in the expression
expr_str = expr_str.replace('for.', '_for.') expr_str = expr_str.replace('for.', '_for.')
# Evaluate the expression # Remove newline characters from expression
return eval(expr_str, {}, wrapped_vars) 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: except Exception as e:
# Handle evaluation errors gracefully # Handle evaluation errors gracefully
return f"Error evaluating expression '{self._expr_str}': {e}" return f"Error evaluating expression '{self._expr_str}': {e}"

View File

@@ -28,15 +28,39 @@ class For(Stat):
def exec(self, env: Env, scope: Scope, writer) -> None: def exec(self, env: Env, scope: Scope, writer) -> None:
""" """
Execute for loop Execute for loop
Args: Args:
env: Template environment env: Template environment
scope: Execution scope scope: Execution scope
writer: Output writer writer: Output writer
""" """
try: 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 # Get the iterable from the expression
iterable = eval(self._iter_expr, {}, scope._data.copy()) iterable = eval(self._iter_expr, {}, wrapped_vars)
# Convert iterable to list for easier manipulation # Convert iterable to list for easier manipulation
items = list(iterable) items = list(iterable)

89
render_example.py Normal file
View File

@@ -0,0 +1,89 @@
#!/usr/bin/env python3.9
# -*- coding: utf-8 -*-
"""
Example demonstrating how to use the render function
"""
from pyenjoy.template.Engine import Engine
# Initialize the engine (global instance)
engine = Engine.use()
# Configure the engine (optional)
engine.base_template_path = "./templates" # Set base template directory
engine.encoding = "utf-8" # Set template encoding
engine.dev_mode = True # Enable auto-reload for development
def render(page, data):
"""
Render a template file with provided data
Args:
page: Template file name (relative to base_template_path)
data: Dictionary with template data
Returns:
Rendered template string
"""
template = engine.get_template(page)
result = template.render_to_string(data)
return result
def render_string(template_content, data):
"""
Render a template string with provided data
Args:
template_content: Template content as string
data: Dictionary with template data
Returns:
Rendered template string
"""
template = engine.get_template_by_string(template_content)
result = template.render_to_string(data)
return result
# Example usage
if __name__ == "__main__":
# Example 1: Render from file
print("Example 1: Render from file")
print("=" * 50)
try:
# Create a simple template file for testing
import os
# os.makedirs("./templates", exist_ok=True)
# with open("./templates/test.html", "w", encoding="utf-8") as f:
# f.write("Hello, #(name)!")
# Render the template
data = {"name": "World", "items": [
{"id": 1, "title": "文章1", "author": "张三", "tags": ["cdms", "管理系统"], "create_time": "2023-01-01"},
{"id": 2, "title": "文章2", "author": "李四", "tags": ["python"], "create_time": "2023-02-01"}
]}
result = render("test.html", data)
print(f"Rendered result: {result}")
# Clean up
# os.remove("./templates/test.html")
# os.rmdir("./templates")
except Exception as e:
print(f"Error: {e}")
# Example 2: Render from string
print("\nExample 2: Render from string")
print("=" * 50)
template_content = "Name: #(name), Age: #(age??'未知')"
data = {"name": "张三"}
result = render_string(template_content, data)
print(f"Rendered result: {result}")
# Example 3: Render with filters
print("\nExample 3: Render with filters")
print("=" * 50)
template_content = "Tags: #(tags | join(', '))"
data = {"tags": ["cdms", "管理系统"]}
result = render_string(template_content, data)
print(f"Rendered result: {result}")
print("\nAll examples completed!")

View File

@@ -8,14 +8,14 @@ with open(os.path.join(os.path.dirname(__file__), 'README.md'), 'r', encoding='u
long_description = f.read() long_description = f.read()
setup( setup(
name='py_enjoy', name='py-enjoy',
version='5.2.2', version='1.0.1',
description='Python implementation of JFinal Template Engine', description='JFinal-Enjoy Python 3.9+ 实现,基于5.2.2版本',
long_description=long_description, long_description=long_description,
long_description_content_type='text/markdown', long_description_content_type='text/markdown',
author='James Zhan 詹波 (original), Python port by mrzhou@miw.cn', author='James Zhan 詹波 (original), Python port by mrzhou@miw.cn',
author_email='mrzhou@miw.cn', author_email='mrzhou@miw.cn',
url='https://github.com/yourusername/py_enjoy', # 请替换为实际的GitHub仓库URL url='https://git.miw.cn/mrzhou/py_enjoy.git', # 请替换为实际的GitHub仓库URL
license='Apache-2.0', license='Apache-2.0',
packages=find_packages(), packages=find_packages(),
include_package_data=True, include_package_data=True,
@@ -33,5 +33,5 @@ setup(
'Topic :: Software Development :: Libraries :: Python Modules', 'Topic :: Software Development :: Libraries :: Python Modules',
'Topic :: Text Processing :: Markup', 'Topic :: Text Processing :: Markup',
], ],
keywords='template engine, jfinal, python', keywords='template engine, jfinal, enjoy, python',
) )

23
templates/test.html Normal file
View File

@@ -0,0 +1,23 @@
Hello, #(name.join("====="))!
#for(item in items)
<tr>
<td>#(for.index):::#(item.id)</td>
<td>#(item.title)</td>
<td>#(item.author)</td>
<td>#(item.tags | join(", "))</td>
<td>#(item.create_time)</td>
<td>
</td>
</tr>
#end

14
test.py
View File

@@ -16,7 +16,7 @@ def test_kit_classes():
print("Testing kit classes...") print("Testing kit classes...")
# Test StrKit # Test StrKit
from py_enjoy.kit.StrKit import StrKit from pyenjoy.kit.StrKit import StrKit
assert StrKit.first_char_to_lower_case("Hello") == "hello" assert StrKit.first_char_to_lower_case("Hello") == "hello"
assert StrKit.first_char_to_upper_case("hello") == "Hello" assert StrKit.first_char_to_upper_case("hello") == "Hello"
assert StrKit.is_blank(" ") == True assert StrKit.is_blank(" ") == True
@@ -25,7 +25,7 @@ def test_kit_classes():
print("✓ StrKit tests passed") print("✓ StrKit tests passed")
# Test HashKit # Test HashKit
from py_enjoy.kit.HashKit import HashKit from pyenjoy.kit.HashKit import HashKit
md5_result = HashKit.md5("test") md5_result = HashKit.md5("test")
assert len(md5_result) == 32 assert len(md5_result) == 32
assert HashKit.slow_equals(b"test", b"test") == True assert HashKit.slow_equals(b"test", b"test") == True
@@ -33,7 +33,7 @@ def test_kit_classes():
print("✓ HashKit tests passed") print("✓ HashKit tests passed")
# Test Kv # Test Kv
from py_enjoy.kit.Kv import Kv from pyenjoy.kit.Kv import Kv
kv = Kv.of("name", "John").set("age", 25) kv = Kv.of("name", "John").set("age", 25)
assert kv.get("name") == "John" assert kv.get("name") == "John"
assert kv.get_int("age") == 25 assert kv.get_int("age") == 25
@@ -41,7 +41,7 @@ def test_kit_classes():
print("✓ Kv tests passed") print("✓ Kv tests passed")
# Test Prop # Test Prop
from py_enjoy.kit.Prop import Prop from pyenjoy.kit.Prop import Prop
# Create a test properties file # Create a test properties file
test_content = """ test_content = """
name=John name=John
@@ -61,8 +61,8 @@ def test_template_engine():
"""Test template engine""" """Test template engine"""
print("\nTesting template engine...") print("\nTesting template engine...")
from py_enjoy.template.Engine import Engine from pyenjoy.template.Engine import Engine
from py_enjoy.kit.Kv import Kv from pyenjoy.kit.Kv import Kv
engine = Engine.use() engine = Engine.use()
@@ -85,7 +85,7 @@ def test_type_conversions():
"""Test type conversions""" """Test type conversions"""
print("\nTesting type conversions...") print("\nTesting type conversions...")
from py_enjoy.kit.TypeKit import TypeKit from pyenjoy.kit.TypeKit import TypeKit
assert TypeKit.to_int("123") == 123 assert TypeKit.to_int("123") == 123
assert TypeKit.to_float("3.14") == 3.14 assert TypeKit.to_float("3.14") == 3.14

98
test_file_template.py Normal file
View File

@@ -0,0 +1,98 @@
#!/usr/bin/env python3.9
# -*- coding: utf-8 -*-
"""
Test template from file functionality
"""
import os
from pyenjoy.template.Engine import Engine
from pyenjoy.kit.Kv import Kv
def test_file_template():
"""Test template from file"""
print("Testing template from file...")
# Create a test template file
test_dir = "./test_templates"
os.makedirs(test_dir, exist_ok=True)
# Test case 1: Basic template
test_file1 = os.path.join(test_dir, "test1.txt")
with open(test_file1, "w", encoding="utf-8") as f:
f.write("Hello, #(name)!")
# Test case 2: Template with null coalescing
test_file2 = os.path.join(test_dir, "test2.txt")
with open(test_file2, "w", encoding="utf-8") as f:
f.write("Data: #(data??'未知')")
# Test case 3: Template with multiple expressions
test_file3 = os.path.join(test_dir, "test3.txt")
with open(test_file3, "w", encoding="utf-8") as f:
f.write("Name: #(name), Age: #(age??'未知'), City: #(city??'未知')")
# Create engine and configure base template path
engine = Engine.use()
engine.base_template_path = os.getcwd()
# Test cases
test_cases = [
# (file_path, data, expected_result, description)
(test_file1, {"name": "World"}, "Hello, World!", "Basic template"),
(test_file2, {"data": None}, "Data: 未知", "Null coalescing with None"),
(test_file2, {"data": "测试数据"}, "Data: 测试数据", "Null coalescing with value"),
(test_file2, {}, "Data: 未知", "Null coalescing with undefined variable"),
(test_file3, {"name": "张三", "age": 30}, "Name: 张三, Age: 30, City: 未知", "Multiple expressions"),
]
passed = 0
failed = 0
for i, (file_path, data, expected, description) in enumerate(test_cases):
try:
print(f"\nTest {i+1}: {description}")
print(f"File: {file_path}")
print(f"Data: {data}")
# Get template
template = engine.get_template(file_path)
# Render template
result = template.render_to_string(Kv(data))
print(f"Expected: '{expected}'")
print(f"Got: '{result}'")
if result == expected:
print("✓ Passed")
passed += 1
else:
print("✗ Failed")
failed += 1
# Debug: Check content
print(f"\nDebug info:")
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
print(f"File content: '{content}'")
print(f"Content length: {len(content)}")
print(f"Content repr: {repr(content)}")
except Exception as e:
print(f"✗ Error: {e}")
failed += 1
print(f"\nSummary: {passed} passed, {failed} failed")
# Clean up
for file_path in [test_file1, test_file2, test_file3]:
if os.path.exists(file_path):
os.remove(file_path)
if os.path.exists(test_dir):
os.rmdir(test_dir)
return passed == len(test_cases)
if __name__ == "__main__":
success = test_file_template()
exit(0 if success else 1)

View File

@@ -0,0 +1,104 @@
#!/usr/bin/env python3.9
# -*- coding: utf-8 -*-
"""
Test template from file with newlines functionality
"""
import os
from pyenjoy.template.Engine import Engine
from pyenjoy.kit.Kv import Kv
def test_file_template_newline():
"""Test template from file with newlines"""
print("Testing template from file with newlines...")
# Create a test template file
test_dir = "./test_templates"
os.makedirs(test_dir, exist_ok=True)
# Test case 1: Template with expression containing newlines
test_file1 = os.path.join(test_dir, "test_newline.txt")
with open(test_file1, "w", encoding="utf-8") as f:
f.write("Name: #(name),\nAge: #(age),\nCity: #(city??'未知')")
# Test case 2: Template with multi-line expression (using triple quotes)
test_file2 = os.path.join(test_dir, "test_multiline_expr.txt")
with open(test_file2, "w", encoding="utf-8") as f:
f.write("""Result: #(data ??
'未知')""")
# Test case 3: Template with complex multi-line expression
test_file3 = os.path.join(test_dir, "test_complex_multiline.txt")
with open(test_file3, "w", encoding="utf-8") as f:
f.write("""Details: #(user.name ?? '匿名') -
#(user.age ?? '未知') -
#(user.city ?? '未知')""")
# Test case 4: Template with single expression on multiple lines
test_file4 = os.path.join(test_dir, "test_single_multiline.txt")
with open(test_file4, "w", encoding="utf-8") as f:
f.write("Value: #(\n data \n ?? \n 'default'\n)")
# Create engine and configure base template path
engine = Engine.use()
engine.base_template_path = os.getcwd()
# Test cases
test_cases = [
# (file_path, data, expected_result, description)
(test_file1, {"name": "张三", "age": 30}, "Name: 张三,\nAge: 30,\nCity: 未知", "Template with newlines"),
(test_file4, {"data": None}, "Value: default", "Single expression on multiple lines"),
(test_file4, {"data": "测试"}, "Value: 测试", "Single expression with value on multiple lines"),
]
passed = 0
failed = 0
for i, (file_path, data, expected, description) in enumerate(test_cases):
try:
print(f"\nTest {i+1}: {description}")
print(f"File: {file_path}")
print(f"Data: {data}")
# Get template
template = engine.get_template(file_path)
# Render template
result = template.render_to_string(Kv(data))
print(f"Expected: '{expected}'")
print(f"Got: '{result}'")
if result == expected:
print("✓ Passed")
passed += 1
else:
print("✗ Failed")
failed += 1
# Debug: Check content
print(f"\nDebug info:")
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
print(f"File content: '{content}'")
print(f"Content length: {len(content)}")
print(f"Content repr: {repr(content)}")
except Exception as e:
print(f"✗ Error: {e}")
failed += 1
print(f"\nSummary: {passed} passed, {failed} failed")
# Clean up
for file_path in [test_file1, test_file2, test_file3, test_file4]:
if os.path.exists(file_path):
os.remove(file_path)
if os.path.exists(test_dir):
os.rmdir(test_dir)
return passed == len(test_cases)
if __name__ == "__main__":
success = test_file_template_newline()
exit(0 if success else 1)

66
test_filters.py Normal file
View File

@@ -0,0 +1,66 @@
#!/usr/bin/env python3.9
# -*- coding: utf-8 -*-
"""
Test template filters functionality
"""
from pyenjoy.template.Engine import Engine
from pyenjoy.kit.Kv import Kv
def test_filters():
"""Test template filters"""
print("Testing template filters...")
engine = Engine.use()
# Test cases
test_cases = [
# Template, data, expected result, description
("#(tags | join(', '))", {"tags": ["cdms", "管理系统"]}, "cdms, 管理系统", "Basic join filter"),
("#(tags | join(';'))", {"tags": ["cdms", "管理系统"]}, "cdms;管理系统", "Join with custom separator"),
("#(tags | join)", {"tags": ["cdms", "管理系统"]}, "cdms管理系统", "Join without separator"),
("#(title | upper)", {"title": "test"}, "TEST", "Upper case filter"),
("#(title | lower)", {"title": "TEST"}, "test", "Lower case filter"),
("#(title | strip)", {"title": " test "}, "test", "Strip whitespace filter"),
# Multiple filters
("#(title | upper | strip)", {"title": " test "}, "TEST", "Multiple filters"),
# Filter with null coalescing
("#(tags??[] | join(', '))", {"tags": None}, "", "Join with null coalescing"),
# Complex scenario from user input
("#for(item in data.items)#(item.tags | join(', '))#end",
{"data": {"items": [{"tags": ["cdms", "管理系统"]}]}},
"cdms, 管理系统", "Complex scenario with for loop"),
]
passed = 0
failed = 0
for i, (template_str, data, expected, description) in enumerate(test_cases):
try:
print(f"\nTest {i+1}: {description}")
print(f"Template: {template_str}")
print(f"Data: {data}")
template = engine.get_template_by_string(template_str)
result = template.render_to_string(Kv(data))
print(f"Expected: '{expected}'")
print(f"Got: '{result}'")
if result == expected:
print("✓ Passed")
passed += 1
else:
print("✗ Failed")
failed += 1
except Exception as e:
print(f"✗ Error: {e}")
failed += 1
print(f"\nSummary: {passed} passed, {failed} failed")
return passed == len(test_cases)
if __name__ == "__main__":
success = test_filters()
exit(0 if success else 1)

64
test_null_coalescing.py Normal file
View File

@@ -0,0 +1,64 @@
#!/usr/bin/env python3.9
# -*- coding: utf-8 -*-
"""
Test null coalescing operator ?? functionality
"""
from pyenjoy.template.Engine import Engine
from pyenjoy.kit.Kv import Kv
def test_null_coalescing():
"""Test null coalescing operator"""
print("Testing null coalescing operator...")
engine = Engine.use()
# Test cases
test_cases = [
# Template, data, expected result
("#(data??'未知')", {"data": None}, "未知"),
("#(data??'未知')", {"data": "测试"}, "测试"),
("#(data??'未知')", {}, "未知"),
("#(user.name??'匿名')", {"user": {"name": "张三"}}, "张三"),
("#(user.name??'匿名')", {"user": {}}, "匿名"),
("#(user.name??'匿名')", {}, "匿名"),
# Nested null coalescing
("#(data1??data2??'默认值')", {"data1": None, "data2": None}, "默认值"),
("#(data1??data2??'默认值')", {"data1": None, "data2": "中间值"}, "中间值"),
("#(data1??data2??'默认值')", {"data1": "第一个值", "data2": "中间值"}, "第一个值"),
]
passed = 0
failed = 0
for i, (template_str, data, expected) in enumerate(test_cases):
try:
template = engine.get_template_by_string(template_str)
result = template.render_to_string(Kv(data))
if result == expected:
print(f"✓ Test {i+1}: Passed")
print(f" Template: {template_str}")
print(f" Data: {data}")
print(f" Result: {result}")
passed += 1
else:
print(f"✗ Test {i+1}: Failed")
print(f" Template: {template_str}")
print(f" Data: {data}")
print(f" Expected: {expected}")
print(f" Got: {result}")
failed += 1
except Exception as e:
print(f"✗ Test {i+1}: Error")
print(f" Template: {template_str}")
print(f" Data: {data}")
print(f" Error: {e}")
failed += 1
print()
print(f"Summary: {passed} passed, {failed} failed")
return passed == len(test_cases)
if __name__ == "__main__":
success = test_null_coalescing()
exit(0 if success else 1)

86
test_user_scenario.py Normal file
View File

@@ -0,0 +1,86 @@
#!/usr/bin/env python3.9
# -*- coding: utf-8 -*-
"""
Test the exact user scenario with real data and template
"""
import os
from pyenjoy.template.Engine import Engine
from pyenjoy.kit.Kv import Kv
def test_user_scenario():
"""Test the exact user scenario"""
print("Testing user scenario...")
# User's data
data = {
'items': [
{
'id': 'wiwInpwBRcYlH5JfMuby',
'title': 'CDMS 内容数据管理系统2',
'content': 'CDMS 内容数据管理系统CDMS 内容数据管理系统CDMS 内容数据管理系统CDMS 内容数据管理系统CDMS 内容数据管理系统CDMS 内容数据管理系统CDMS 内容数据管理系统CDMS 内容数据管理系统CDMS 内容数据管理系统CDMS 内容数据管理系统CDMS 内容数据管理系统CDMS 内容数据管理系统CDMS 内容数据管理系统CDMS 内容数据管理系统CDMS 内容数据管理系统CDMS 内容数据管理系统CDMS 内容数据管理系统CDMS 内容数据管理系统CDMS 内容数据管理系统CDMS 内容数据管理系统CDMS 内容数据管理系统CDMS 内容数据管理系统CDMS 内容数据管理系统CDMS 内容数据管理系统CDMS 内容数据管理系统CDMS 内容数据管理系统CDMS 内容数据管理系统CDMS 内容数据管理系统',
'tags': ['cdms', ' 管理系统'],
'author': '赵钱孙',
'create_time': '2026-02-27T15:37:43.207513',
'update_time': '2026-02-27T15:37:43.207530',
'status': 'active'
},
{
'id': 'wSwHnpwBRcYlH5Jf7-Zd',
'title': 'CDMS 内容数据管理系统',
'content': 'CDMS 内容数据管理系统CDMS 内容数据管理系统CDMS 内容数据管理系统CDMS 内容数据管理系统CDMS 内容数据管理系统CDMS 内容数据管理系统CDMS 内容数据管理系统CDMS 内容数据管理系统CDMS 内容数据管理系统CDMS 内容数据管理系统CDMS 内容数据管理系统CDMS 内容数据管理系统CDMS 内容数据管理系统',
'tags': ['cdms', ' 管理系统'],
'author': '赵钱孙',
'create_time': '2026-02-27T15:37:25.907558',
'update_time': '2026-02-27T15:37:25.907576',
'status': 'active'
}
],
'total': 2,
'page': 1,
'size': 10,
'pages': 1
}
# User's template
template_content = """
#for(item in data.items)
<tr>
<td>#(item.id)</td>
<td>#(item.title)</td>
<td>#(item.author)</td>
<td>#(item.tags | join(", "))</td>
<td>#(item.create_time)</td>
<td>
</td>
</tr>
#end
"""
# Create engine and test
engine = Engine.use()
try:
template = engine.get_template_by_string(template_content)
result = template.render_to_string(Kv({"data": data}))
print("\nTemplate rendering result:")
print("=" * 50)
print(result)
print("=" * 50)
print("\n✓ User scenario test passed!")
# Verify the join filter worked correctly
if "cdms, 管理系统" in result:
print("✓ Join filter worked correctly!")
else:
print("✗ Join filter did not work as expected")
return True
except Exception as e:
print(f"\n✗ User scenario test failed: {e}")
return False
if __name__ == "__main__":
success = test_user_scenario()
exit(0 if success else 1)