调整版本并做测试
This commit is contained in:
@@ -1,3 +1,3 @@
|
|||||||
include README.md
|
include README.md
|
||||||
include LICENSE
|
include LICENSE
|
||||||
recursive-include py_enjoy *
|
recursive-include pyenjoy *
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# py_enjoy
|
# py-enjoy
|
||||||
|
|
||||||
jfinal-enjoy的 python3.9+ 实现
|
jfinal-enjoy的 python3.9+ 实现
|
||||||
|
|
||||||
|
|||||||
12
README.py
12
README.py
@@ -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):
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|
||||||
@@ -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}"
|
||||||
@@ -35,8 +35,32 @@ class For(Stat):
|
|||||||
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
89
render_example.py
Normal 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!")
|
||||||
10
setup.py
10
setup.py
@@ -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
23
templates/test.html
Normal 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
14
test.py
@@ -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
98
test_file_template.py
Normal 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)
|
||||||
104
test_file_template_newline.py
Normal file
104
test_file_template_newline.py
Normal 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
66
test_filters.py
Normal 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
64
test_null_coalescing.py
Normal 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
86
test_user_scenario.py
Normal 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)
|
||||||
Reference in New Issue
Block a user