-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapp.py
More file actions
121 lines (96 loc) · 4.27 KB
/
Copy pathapp.py
File metadata and controls
121 lines (96 loc) · 4.27 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# app.py
import os
import re
from flask import Flask, render_template_string, abort, request, session
from werkzeug.utils import safe_join
import traceback
# --- 初始化和配置 ---
app = Flask(__name__)
app.secret_key = 'a-very-secret-and-complex-key-for-pyecho'
PYECHO_DIR = 'pyecho_pages'
DEFAULT_PAGE = 'index.pyecho'
# --- Pyecho 解析器 ---
def parse_pyecho_to_python(content):
parts = re.split(r'(<\?py.*?\?>)', content, flags=re.DOTALL)
py_script_lines = []
for part in parts:
if not part:
continue
if part.startswith('<?py') and part.endswith('?>'):
code = part[4:-2].strip()
py_script_lines.append(code)
else:
escaped_html = f"echo(r'''{part}''')"
py_script_lines.append(escaped_html)
return "\n".join(py_script_lines)
# --- 核心路由 ---
@app.route('/', defaults={'page': ''}, methods=['GET', 'POST'])
@app.route('/<path:page>', methods=['GET', 'POST'])
def pyecho_executor(page):
file_name = DEFAULT_PAGE if not page else f"{page}.pyecho"
try:
file_path = safe_join(os.path.abspath(PYECHO_DIR), file_name)
except Exception:
abort(404)
if not os.path.exists(file_path):
abort(404)
output_buffer = []
template_context = {}
def echo(*args):
if len(args) == 1:
output_buffer.append(str(args[0]))
elif len(args) == 2 and isinstance(args[0], str):
template_context[args[0]] = args[1]
else:
raise TypeError(f"echo() takes 1 or 2 arguments, but {len(args)} were given.")
exec_globals = {
'echo': echo,
'request': request,
'session': session,
'include': None
}
# 定义 include 函数以支持文件包含
def include(include_file_name):
# 安全地拼接路径
include_path = safe_join(os.path.abspath(PYECHO_DIR), include_file_name)
if not os.path.exists(include_path):
# 如果文件不存在,直接抛出异常,这样错误页面就会显示出来
raise FileNotFoundError(f"Pyecho include error: File '{include_file_name}' not found at path '{include_path}'.")
with open(include_path, 'r', encoding='utf-8') as f:
include_content = f.read()
# 解析被包含的文件
include_script = parse_pyecho_to_python(include_content)
# 在当前的执行上下文中执行该脚本。
# 异常会向上传递,并被下面的主 try...except 块捕获。
exec(include_script, exec_globals)
exec_globals['include'] = include
py_script = "" # 预定义,以防解析失败
try:
with open(file_path, 'r', encoding='utf-8') as f:
pyecho_content = f.read()
py_script = parse_pyecho_to_python(pyecho_content)
exec(py_script, exec_globals)
except Exception as e:
# 使用 traceback 来获取发生错误的具体位置
error_trace = traceback.format_exc()
error_message = f"""
<h1>Pyecho Execution Error</h1>
<p>An error occurred while executing <strong>{file_name}</strong>.</p>
<h2>Error Details:</h2>
<pre style="background-color: #f8d7da; color: #721c24; padding: 1em; border-radius: 5px; white-space: pre-wrap; word-wrap: break-word;">{error_trace}</pre>
<h2>Generated Python Script (for debugging):</h2>
<pre style="background-color: #eee; border: 1px solid #ccc; padding: 1em; border-radius: 5px; white-space: pre-wrap; word-wrap: break-word;">{py_script if py_script else 'Parsing failed before script generation.'}</pre>
"""
return error_message, 500
template_string = "".join(output_buffer)
return render_template_string(template_string, **template_context)
# --- 运行应用 ---
if __name__ == '__main__':
if not os.path.exists(PYECHO_DIR):
os.makedirs(PYECHO_DIR)
print(f"Created directory: {PYECHO_DIR}")
if not os.path.exists('blog_posts.json'):
with open('blog_posts.json', 'w') as f:
f.write('[]')
print("Created empty blog_posts.json")
app.run(debug=True, port=5000)