pancake-framework 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pancake/__init__.py +46 -0
- pancake/builder/__init__.py +11 -0
- pancake/builder/build.py +14 -0
- pancake/builder/load_dlc.py +157 -0
- pancake/builder/load_src.py +114 -0
- pancake/builder/safe_import.py +0 -0
- pancake/cli.py +212 -0
- pancake/initialize/__init__.py +10 -0
- pancake/initialize/check_dlc.py +10 -0
- pancake/initialize/check_env.py +111 -0
- pancake/initialize/check_struct.py +16 -0
- pancake/initialize/print_ico.py +12 -0
- pancake/oven/__init__.py +15 -0
- pancake/oven/default.py +66 -0
- pancake/oven/muffin.py +42 -0
- pancake/oven/pancake.py +47 -0
- pancake/ovenware/__init__.py +71 -0
- pancake/ovenware/ai_memory.py +876 -0
- pancake/ovenware/ai_model.py +509 -0
- pancake/ovenware/base.py +54 -0
- pancake/ovenware/broker.py +281 -0
- pancake/ovenware/cui.py +228 -0
- pancake/ovenware/embed.py +51 -0
- pancake/ovenware/external_plugin.py +59 -0
- pancake/ovenware/gui.py +265 -0
- pancake/ovenware/inject.py +306 -0
- pancake/ovenware/langgraph/__init__.py +20 -0
- pancake/ovenware/langgraph/core.py +366 -0
- pancake/ovenware/lifecycle.py +190 -0
- pancake/ovenware/mybatis/__init__.py +83 -0
- pancake/ovenware/mybatis/config.py +37 -0
- pancake/ovenware/mybatis/connection.py +78 -0
- pancake/ovenware/mybatis/mapper.py +509 -0
- pancake/ovenware/mybatis/sql_parser.py +196 -0
- pancake/ovenware/mybatis/types.py +44 -0
- pancake/ovenware/mybatis/wrapper.py +418 -0
- pancake/ovenware/redis_cache.py +583 -0
- pancake/ovenware/remote.py +203 -0
- pancake/ovenware/web.py +352 -0
- pancake/resource/__init__.py +9 -0
- pancake/resource/config.py +3 -0
- pancake/resource/json.py +17 -0
- pancake/resource/logging.py +20 -0
- pancake/resource/xml_config.py +167 -0
- pancake/resource/yml.py +65 -0
- pancake/run.py +65 -0
- pancake/settings.py +106 -0
- pancake/tool/__init__.py +1 -0
- pancake/tool/progress_show.py +20 -0
- pancake_framework-0.1.0.dist-info/METADATA +280 -0
- pancake_framework-0.1.0.dist-info/RECORD +53 -0
- pancake_framework-0.1.0.dist-info/WHEEL +4 -0
- pancake_framework-0.1.0.dist-info/entry_points.txt +3 -0
pancake/__init__.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
disable_check = []
|
|
5
|
+
|
|
6
|
+
current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
7
|
+
if current_dir not in sys.path:
|
|
8
|
+
sys.path.insert(0, current_dir)
|
|
9
|
+
|
|
10
|
+
_initialized = False
|
|
11
|
+
|
|
12
|
+
def init():
|
|
13
|
+
global _initialized
|
|
14
|
+
if _initialized:
|
|
15
|
+
return
|
|
16
|
+
_initialized = True
|
|
17
|
+
|
|
18
|
+
import initialize
|
|
19
|
+
initialize.print_ico()
|
|
20
|
+
|
|
21
|
+
init_rask = {"check_environment":initialize.check_environment, # 检查环境
|
|
22
|
+
"check_struct":initialize.check_struct, # 检查项目结构完整性
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
# 检查运行环境
|
|
26
|
+
print("检查运行环境")
|
|
27
|
+
import tool
|
|
28
|
+
progress = tool.ProgressBar(len(init_rask), prefix="初始化环境")
|
|
29
|
+
for task in init_rask.keys():
|
|
30
|
+
if task in disable_check:
|
|
31
|
+
continue
|
|
32
|
+
init_rask[task]()
|
|
33
|
+
progress.update(1, f"{task} 完成")
|
|
34
|
+
|
|
35
|
+
progress.finish()
|
|
36
|
+
|
|
37
|
+
# 初始化 dotenv 和 logging
|
|
38
|
+
from resource import config # noqa: F401
|
|
39
|
+
import resource.logging as resource_logging # noqa: F401
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# 延迟导入,避免循环依赖
|
|
43
|
+
def run():
|
|
44
|
+
init()
|
|
45
|
+
from .run import run as _run
|
|
46
|
+
_run()
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
import pkgutil
|
|
3
|
+
|
|
4
|
+
# 当前包下的所有模块
|
|
5
|
+
for _, module_name, _ in pkgutil.iter_modules(__path__):
|
|
6
|
+
module = importlib.import_module(f".{module_name}", __name__)
|
|
7
|
+
|
|
8
|
+
for attr_name in dir(module):
|
|
9
|
+
if not attr_name.startswith('_'):
|
|
10
|
+
attr = getattr(module, attr_name)
|
|
11
|
+
globals()[f"{module_name}.{attr_name}"] = attr
|
pancake/builder/build.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from pancake import oven
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def build():
|
|
5
|
+
|
|
6
|
+
# 构建所有服务实例
|
|
7
|
+
for classes in oven.pancake_dough["Service"]:
|
|
8
|
+
oven.pancake_pie["Service"][str(classes)] = oven.pancake_dough["Service"][classes].build()
|
|
9
|
+
|
|
10
|
+
for build_method_name in oven.muffin_egg["BuildOrder"]:
|
|
11
|
+
oven.muffin_egg["Builder"][build_method_name[0]]()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
|
|
3
|
+
from pancake import oven
|
|
4
|
+
from pancake.initialize import check_dlc
|
|
5
|
+
|
|
6
|
+
import importlib
|
|
7
|
+
import inspect
|
|
8
|
+
import logging
|
|
9
|
+
import os
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _load_from_xml():
|
|
15
|
+
"""从 XML 配置加载插件列表"""
|
|
16
|
+
plugins = oven.pancake_xml.get("plugins", [])
|
|
17
|
+
if not plugins:
|
|
18
|
+
return None # 无 XML 配置,回退到目录扫描
|
|
19
|
+
|
|
20
|
+
main_classes = {}
|
|
21
|
+
|
|
22
|
+
for plugin_info in plugins:
|
|
23
|
+
name = plugin_info["name"]
|
|
24
|
+
source = plugin_info["source"]
|
|
25
|
+
enabled = plugin_info.get("enabled", True)
|
|
26
|
+
init_order = plugin_info.get("init_order", 0)
|
|
27
|
+
build_order = plugin_info.get("build_order", 0)
|
|
28
|
+
|
|
29
|
+
# 导入插件模块(无论是否启用都需要导入,以注册装饰器)
|
|
30
|
+
try:
|
|
31
|
+
plugin = importlib.import_module(source)
|
|
32
|
+
except ImportError as e:
|
|
33
|
+
if enabled:
|
|
34
|
+
logger.error(f"Failed to import plugin {name} ({source}): {e}")
|
|
35
|
+
sys.exit(1)
|
|
36
|
+
else:
|
|
37
|
+
logger.debug(f"Disabled plugin {name} not available: {e}")
|
|
38
|
+
continue
|
|
39
|
+
|
|
40
|
+
# 收集模块中的公开成员
|
|
41
|
+
all_items = {}
|
|
42
|
+
for attr_name, member in inspect.getmembers(plugin):
|
|
43
|
+
if (
|
|
44
|
+
(not attr_name.startswith("_"))
|
|
45
|
+
and (inspect.isclass(member) or inspect.isfunction(member))
|
|
46
|
+
and (member.__module__ == plugin.__name__ or member.__module__.startswith(plugin.__name__ + "."))
|
|
47
|
+
):
|
|
48
|
+
all_items[attr_name] = member
|
|
49
|
+
|
|
50
|
+
# 注册装饰器(无论是否启用都注册)
|
|
51
|
+
for item_name, obj in all_items.items():
|
|
52
|
+
if item_name.startswith("_"):
|
|
53
|
+
continue
|
|
54
|
+
if inspect.isclass(obj) and hasattr(obj, 'build') and hasattr(obj, 'init_order'):
|
|
55
|
+
continue # Main 类下面单独处理
|
|
56
|
+
oven.muffin_flour[item_name] = obj
|
|
57
|
+
|
|
58
|
+
# 注册 Main 类(仅启用时)
|
|
59
|
+
if enabled:
|
|
60
|
+
for item_name, obj in all_items.items():
|
|
61
|
+
if item_name.startswith("_"):
|
|
62
|
+
continue
|
|
63
|
+
if inspect.isclass(obj) and hasattr(obj, 'build') and hasattr(obj, 'init_order'):
|
|
64
|
+
effective_init = init_order
|
|
65
|
+
effective_build = build_order
|
|
66
|
+
|
|
67
|
+
oven.muffin_egg["BuildOrder"].append([name, effective_build])
|
|
68
|
+
oven.muffin_egg["InitOrder"].append([name, effective_init])
|
|
69
|
+
main_classes[name] = obj
|
|
70
|
+
break
|
|
71
|
+
else:
|
|
72
|
+
logger.info(f"Plugin {name} loaded (no Main class)")
|
|
73
|
+
else:
|
|
74
|
+
logger.info(f"Plugin {name} disabled (decorators still loaded)")
|
|
75
|
+
|
|
76
|
+
return main_classes
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _load_from_directory():
|
|
80
|
+
"""从 ovenware 目录扫描加载插件(回退模式)"""
|
|
81
|
+
dlc_dir = os.path.join(os.path.dirname(__file__), "../", "ovenware")
|
|
82
|
+
entries = os.listdir(dlc_dir)
|
|
83
|
+
# 单文件插件
|
|
84
|
+
plugin_files = [f[:-3] for f in entries if f.endswith(".py") and f not in ["__init__.py"]]
|
|
85
|
+
# 子包插件(目录含 __init__.py)
|
|
86
|
+
plugin_dirs = [d for d in entries
|
|
87
|
+
if os.path.isdir(os.path.join(dlc_dir, d))
|
|
88
|
+
and os.path.exists(os.path.join(dlc_dir, d, "__init__.py"))
|
|
89
|
+
and not d.startswith("_")]
|
|
90
|
+
plugin_names = plugin_files + plugin_dirs
|
|
91
|
+
|
|
92
|
+
main_classes = {}
|
|
93
|
+
|
|
94
|
+
for plugin_name in plugin_names:
|
|
95
|
+
if plugin_name in oven.pancake_yaml.get("framework.disable_dlc", []):
|
|
96
|
+
continue
|
|
97
|
+
plugin = importlib.import_module(f"ovenware.{plugin_name}")
|
|
98
|
+
all_items = {}
|
|
99
|
+
for name, member in inspect.getmembers(plugin):
|
|
100
|
+
if (
|
|
101
|
+
(not name.startswith("_"))
|
|
102
|
+
and (inspect.isclass(member) or inspect.isfunction(member))
|
|
103
|
+
and (member.__module__ == plugin.__name__ or member.__module__.startswith(plugin.__name__ + "."))
|
|
104
|
+
):
|
|
105
|
+
all_items[name] = member
|
|
106
|
+
|
|
107
|
+
# 加载 all_items 中的所有 修饰器和 main 类
|
|
108
|
+
for decorator in all_items.keys():
|
|
109
|
+
if not decorator.startswith("_"):
|
|
110
|
+
obj = all_items[decorator]
|
|
111
|
+
if inspect.isclass(obj) and hasattr(obj, 'build') and hasattr(obj, 'init_order'):
|
|
112
|
+
oven.muffin_egg["BuildOrder"].append([plugin_name, all_items[decorator].build_order])
|
|
113
|
+
oven.muffin_egg["InitOrder"].append([plugin_name, all_items[decorator].init_order])
|
|
114
|
+
main_classes[plugin_name] = all_items[decorator]
|
|
115
|
+
continue
|
|
116
|
+
oven.muffin_flour[decorator] = all_items[decorator]
|
|
117
|
+
|
|
118
|
+
return main_classes
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def run():
|
|
122
|
+
"""加载插件:优先 XML 配置,回退到目录扫描"""
|
|
123
|
+
has_xml = bool(oven.pancake_xml.get("plugins"))
|
|
124
|
+
|
|
125
|
+
if has_xml:
|
|
126
|
+
logger.info("Loading plugins from XML config")
|
|
127
|
+
main_classes = _load_from_xml()
|
|
128
|
+
else:
|
|
129
|
+
logger.info("No XML config, scanning ovenware directory")
|
|
130
|
+
main_classes = _load_from_directory()
|
|
131
|
+
|
|
132
|
+
oven.muffin_egg["BuildOrder"].sort(key=lambda x: x[1], reverse=True)
|
|
133
|
+
oven.muffin_egg["InitOrder"].sort(key=lambda x: x[1])
|
|
134
|
+
|
|
135
|
+
for plugin_name in oven.muffin_egg["InitOrder"]:
|
|
136
|
+
try:
|
|
137
|
+
check_dlc(main_classes[plugin_name[0]].check)
|
|
138
|
+
except AttributeError:
|
|
139
|
+
pass
|
|
140
|
+
except Exception as e:
|
|
141
|
+
logger.error(f"Plugin check failed for {plugin_name[0]}: {e}")
|
|
142
|
+
import sys
|
|
143
|
+
sys.exit(1)
|
|
144
|
+
|
|
145
|
+
new_main = main_classes[plugin_name[0]]()
|
|
146
|
+
|
|
147
|
+
oven.muffin_egg["Builder"][plugin_name[0]] = new_main.build
|
|
148
|
+
|
|
149
|
+
# 尝试获取 loop_method,verify_method
|
|
150
|
+
try:
|
|
151
|
+
oven.muffin_egg["LoopMethod"][plugin_name[0]] = new_main.loop_method
|
|
152
|
+
except AttributeError:
|
|
153
|
+
pass
|
|
154
|
+
try:
|
|
155
|
+
oven.muffin_egg["VerifyMethod"][plugin_name[0]] = new_main.verify
|
|
156
|
+
except AttributeError:
|
|
157
|
+
pass
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import ast
|
|
2
|
+
import builtins
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
from pancake import oven
|
|
6
|
+
|
|
7
|
+
# 所有 src 文件共享的全局命名空间
|
|
8
|
+
_shared_globals = {
|
|
9
|
+
"__builtins__": builtins,
|
|
10
|
+
"__name__": "__not_main__",
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def scan_py_files(folder="."):
|
|
15
|
+
files = []
|
|
16
|
+
for root, _, filenames in os.walk(folder):
|
|
17
|
+
for f in filenames:
|
|
18
|
+
if f.endswith(".py") and f != os.path.basename(__file__):
|
|
19
|
+
files.append(os.path.abspath(os.path.join(root, f)))
|
|
20
|
+
return files
|
|
21
|
+
|
|
22
|
+
def parse_file(filepath):
|
|
23
|
+
try:
|
|
24
|
+
with open(filepath, "r", encoding="utf-8") as f:
|
|
25
|
+
tree = ast.parse(f.read())
|
|
26
|
+
except (OSError, IOError, SyntaxError):
|
|
27
|
+
return []
|
|
28
|
+
|
|
29
|
+
dirname = os.path.dirname(filepath)
|
|
30
|
+
if dirname not in sys.path:
|
|
31
|
+
sys.path.insert(0, dirname)
|
|
32
|
+
|
|
33
|
+
results = []
|
|
34
|
+
for node in ast.walk(tree):
|
|
35
|
+
# 扫描 类 + 函数(含 async def)
|
|
36
|
+
if not isinstance(node, (ast.ClassDef, ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
37
|
+
continue
|
|
38
|
+
|
|
39
|
+
obj_type = "class" if isinstance(node, ast.ClassDef) else "function"
|
|
40
|
+
obj_name = node.name
|
|
41
|
+
|
|
42
|
+
# 遍历所有装饰器
|
|
43
|
+
for dec in node.decorator_list:
|
|
44
|
+
dec_name = None
|
|
45
|
+
if isinstance(dec, ast.Name):
|
|
46
|
+
dec_name = dec.id
|
|
47
|
+
elif isinstance(dec, ast.Call) and hasattr(dec.func, 'id'):
|
|
48
|
+
dec_name = dec.func.id
|
|
49
|
+
|
|
50
|
+
# 匹配 muffin_flour 中的装饰器
|
|
51
|
+
if dec_name in oven.muffin_flour.keys():
|
|
52
|
+
results.append((dec_name, obj_type, obj_name, filepath))
|
|
53
|
+
return results
|
|
54
|
+
|
|
55
|
+
def safe_register(filepath):
|
|
56
|
+
"""在共享命名空间中执行文件,所有定义对其他文件可见"""
|
|
57
|
+
try:
|
|
58
|
+
with open(filepath, "r", encoding="utf-8") as f:
|
|
59
|
+
source = f.read()
|
|
60
|
+
except (OSError, IOError):
|
|
61
|
+
return
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
tree = ast.parse(source)
|
|
65
|
+
except SyntaxError:
|
|
66
|
+
return
|
|
67
|
+
|
|
68
|
+
# 收集所有顶级定义
|
|
69
|
+
definitions = []
|
|
70
|
+
for node in tree.body:
|
|
71
|
+
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef, ast.Import, ast.ImportFrom)):
|
|
72
|
+
definitions.append(node)
|
|
73
|
+
|
|
74
|
+
if not definitions:
|
|
75
|
+
return
|
|
76
|
+
|
|
77
|
+
new_tree = ast.Module(body=definitions, type_ignores=[])
|
|
78
|
+
ast.fix_missing_locations(new_tree)
|
|
79
|
+
|
|
80
|
+
try:
|
|
81
|
+
code = compile(new_tree, filepath, 'exec')
|
|
82
|
+
exec(code, _shared_globals)
|
|
83
|
+
except Exception:
|
|
84
|
+
import traceback
|
|
85
|
+
traceback.print_exc()
|
|
86
|
+
return
|
|
87
|
+
|
|
88
|
+
# 将文件中定义的名称注入 builtins,实现跨文件零 import
|
|
89
|
+
for node in definitions:
|
|
90
|
+
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
|
|
91
|
+
if node.name in _shared_globals:
|
|
92
|
+
builtins.__dict__[node.name] = _shared_globals[node.name]
|
|
93
|
+
|
|
94
|
+
def run():
|
|
95
|
+
from pancake.settings import get_path
|
|
96
|
+
src_dir = get_path("src_dir")
|
|
97
|
+
files = scan_py_files(src_dir)
|
|
98
|
+
|
|
99
|
+
# 预解析所有文件,按装饰器的 _load_priority 排序(值越小越先加载,默认 50)
|
|
100
|
+
file_items = []
|
|
101
|
+
for f in files:
|
|
102
|
+
items = parse_file(f)
|
|
103
|
+
if items:
|
|
104
|
+
min_priority = 50
|
|
105
|
+
for dec_name, _, _, _ in items:
|
|
106
|
+
dec_obj = oven.muffin_flour.get(dec_name)
|
|
107
|
+
if dec_obj and hasattr(dec_obj, '_load_priority'):
|
|
108
|
+
min_priority = min(min_priority, dec_obj._load_priority)
|
|
109
|
+
file_items.append((min_priority, f, items))
|
|
110
|
+
|
|
111
|
+
file_items.sort(key=lambda x: x[0])
|
|
112
|
+
|
|
113
|
+
for _, path, _ in file_items:
|
|
114
|
+
safe_register(path)
|
|
File without changes
|
pancake/cli.py
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Pancake CLI - 命令行工具
|
|
3
|
+
支持 create / check / run / build 等子命令
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
import shutil
|
|
10
|
+
import subprocess
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def cmd_create(args):
|
|
14
|
+
"""创建新项目"""
|
|
15
|
+
name = args.name
|
|
16
|
+
project_dir = os.path.join(os.getcwd(), name)
|
|
17
|
+
|
|
18
|
+
if os.path.exists(project_dir):
|
|
19
|
+
print(f"错误: 目录 '{name}' 已存在")
|
|
20
|
+
sys.exit(1)
|
|
21
|
+
|
|
22
|
+
print(f"创建项目: {name}")
|
|
23
|
+
|
|
24
|
+
# 创建目录结构
|
|
25
|
+
dirs = [
|
|
26
|
+
os.path.join(project_dir, "src", "resource", "yaml"),
|
|
27
|
+
os.path.join(project_dir, "src", "resource", "json"),
|
|
28
|
+
os.path.join(project_dir, "src", "mapper"),
|
|
29
|
+
os.path.join(project_dir, "src", "controller"),
|
|
30
|
+
]
|
|
31
|
+
for d in dirs:
|
|
32
|
+
os.makedirs(d)
|
|
33
|
+
|
|
34
|
+
# 创建 main.py
|
|
35
|
+
with open(os.path.join(project_dir, "main.py"), "w", encoding="utf-8") as f:
|
|
36
|
+
f.write('import pancake\n\npancake.run()\n')
|
|
37
|
+
|
|
38
|
+
# 创建 pancake.xml
|
|
39
|
+
with open(os.path.join(project_dir, "pancake.xml"), "w", encoding="utf-8") as f:
|
|
40
|
+
f.write('''<?xml version="1.0" encoding="UTF-8"?>
|
|
41
|
+
<pancake>
|
|
42
|
+
<global>
|
|
43
|
+
<service.title>''' + name + '''</service.title>
|
|
44
|
+
<service.version>1.0.0</service.version>
|
|
45
|
+
<service.host>127.0.0.1</service.host>
|
|
46
|
+
<service.port>8080</service.port>
|
|
47
|
+
</global>
|
|
48
|
+
<plugins>
|
|
49
|
+
<plugin name="embed" init-order="0"/>
|
|
50
|
+
<plugin name="mybatis" init-order="1"/>
|
|
51
|
+
<plugin name="web" init-order="2"/>
|
|
52
|
+
</plugins>
|
|
53
|
+
</pancake>
|
|
54
|
+
''')
|
|
55
|
+
|
|
56
|
+
# 创建 service.yaml
|
|
57
|
+
with open(os.path.join(project_dir, "src", "resource", "yaml", "service.yaml"), "w", encoding="utf-8") as f:
|
|
58
|
+
f.write('''service:
|
|
59
|
+
title: ''' + name + '''
|
|
60
|
+
version: 1.0.0
|
|
61
|
+
host: 127.0.0.1
|
|
62
|
+
port: 8080
|
|
63
|
+
''')
|
|
64
|
+
|
|
65
|
+
# 创建 pyproject.toml
|
|
66
|
+
with open(os.path.join(project_dir, "pyproject.toml"), "w", encoding="utf-8") as f:
|
|
67
|
+
f.write('''[tool.poetry]
|
|
68
|
+
name = "''' + name + '''"
|
|
69
|
+
version = "0.1.0"
|
|
70
|
+
description = "A Pancake framework project"
|
|
71
|
+
authors = ["Your Name <you@example.com>"]
|
|
72
|
+
|
|
73
|
+
[tool.poetry.dependencies]
|
|
74
|
+
python = "^3.13"
|
|
75
|
+
pancake = "*"
|
|
76
|
+
|
|
77
|
+
[build-system]
|
|
78
|
+
requires = ["poetry-core"]
|
|
79
|
+
build-backend = "poetry.core.masonry.api"
|
|
80
|
+
''')
|
|
81
|
+
|
|
82
|
+
print(f"项目 '{name}' 创建成功!")
|
|
83
|
+
print(f" cd {name}")
|
|
84
|
+
print(f" pip install pancake")
|
|
85
|
+
print(f" python main.py")
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def cmd_check(args):
|
|
89
|
+
"""检查项目结构和环境"""
|
|
90
|
+
print("检查项目结构...")
|
|
91
|
+
|
|
92
|
+
errors = []
|
|
93
|
+
warnings = []
|
|
94
|
+
|
|
95
|
+
# 检查 main.py
|
|
96
|
+
if not os.path.exists("main.py"):
|
|
97
|
+
errors.append("缺少 main.py")
|
|
98
|
+
else:
|
|
99
|
+
with open("main.py", "r", encoding="utf-8") as f:
|
|
100
|
+
content = f.read()
|
|
101
|
+
if "import pancake" not in content:
|
|
102
|
+
warnings.append("main.py 中未找到 'import pancake'")
|
|
103
|
+
|
|
104
|
+
# 检查 pancake.xml
|
|
105
|
+
if not os.path.exists("pancake.xml"):
|
|
106
|
+
warnings.append("缺少 pancake.xml(可选,用于插件配置)")
|
|
107
|
+
|
|
108
|
+
# 检查 src 目录
|
|
109
|
+
if not os.path.isdir("src"):
|
|
110
|
+
errors.append("缺少 src 目录")
|
|
111
|
+
else:
|
|
112
|
+
yaml_dir = os.path.join("src", "resource", "yaml")
|
|
113
|
+
if not os.path.isdir(yaml_dir):
|
|
114
|
+
warnings.append(f"缺少 {yaml_dir} 目录")
|
|
115
|
+
else:
|
|
116
|
+
yaml_files = [f for f in os.listdir(yaml_dir) if f.endswith(('.yaml', '.yml'))]
|
|
117
|
+
if not yaml_files:
|
|
118
|
+
warnings.append(f"{yaml_dir} 中没有 YAML 配置文件")
|
|
119
|
+
|
|
120
|
+
# 检查 pancake 是否安装
|
|
121
|
+
try:
|
|
122
|
+
import pancake # noqa: F401
|
|
123
|
+
print(" [OK] pancake 已安装")
|
|
124
|
+
except ImportError:
|
|
125
|
+
errors.append("pancake 未安装,请运行: pip install pancake")
|
|
126
|
+
|
|
127
|
+
# 输出结果
|
|
128
|
+
if errors:
|
|
129
|
+
print("\n错误:")
|
|
130
|
+
for e in errors:
|
|
131
|
+
print(f" [ERROR] {e}")
|
|
132
|
+
|
|
133
|
+
if warnings:
|
|
134
|
+
print("\n警告:")
|
|
135
|
+
for w in warnings:
|
|
136
|
+
print(f" [WARN] {w}")
|
|
137
|
+
|
|
138
|
+
if not errors and not warnings:
|
|
139
|
+
print(" 项目结构正常!")
|
|
140
|
+
|
|
141
|
+
return len(errors) == 0
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def cmd_run(args):
|
|
145
|
+
"""运行项目"""
|
|
146
|
+
if not os.path.exists("main.py"):
|
|
147
|
+
print("错误: 当前目录没有 main.py,请在项目根目录运行")
|
|
148
|
+
sys.exit(1)
|
|
149
|
+
|
|
150
|
+
print("启动 Pancake 项目...")
|
|
151
|
+
import pancake
|
|
152
|
+
pancake.run()
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def cmd_build(args):
|
|
156
|
+
"""打包项目为 wheel"""
|
|
157
|
+
if not os.path.exists("pyproject.toml"):
|
|
158
|
+
print("错误: 当前目录没有 pyproject.toml")
|
|
159
|
+
sys.exit(1)
|
|
160
|
+
|
|
161
|
+
print("打包项目...")
|
|
162
|
+
result = subprocess.run(
|
|
163
|
+
[sys.executable, "-m", "poetry", "build"],
|
|
164
|
+
capture_output=True, text=True
|
|
165
|
+
)
|
|
166
|
+
if result.returncode == 0:
|
|
167
|
+
print("打包成功!")
|
|
168
|
+
print(result.stdout)
|
|
169
|
+
else:
|
|
170
|
+
print("打包失败:")
|
|
171
|
+
print(result.stderr)
|
|
172
|
+
sys.exit(1)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def main():
|
|
176
|
+
parser = argparse.ArgumentParser(
|
|
177
|
+
prog="pancake",
|
|
178
|
+
description="Pancake Framework - 命令行工具",
|
|
179
|
+
)
|
|
180
|
+
subparsers = parser.add_subparsers(dest="command", help="可用命令")
|
|
181
|
+
|
|
182
|
+
# create
|
|
183
|
+
create_parser = subparsers.add_parser("create", help="创建新项目")
|
|
184
|
+
create_parser.add_argument("name", help="项目名称")
|
|
185
|
+
|
|
186
|
+
# check
|
|
187
|
+
subparsers.add_parser("check", help="检查项目结构和环境")
|
|
188
|
+
|
|
189
|
+
# run
|
|
190
|
+
subparsers.add_parser("run", help="运行项目")
|
|
191
|
+
|
|
192
|
+
# build
|
|
193
|
+
subparsers.add_parser("build", help="打包项目为 wheel")
|
|
194
|
+
|
|
195
|
+
args = parser.parse_args()
|
|
196
|
+
|
|
197
|
+
if args.command is None:
|
|
198
|
+
parser.print_help()
|
|
199
|
+
sys.exit(0)
|
|
200
|
+
|
|
201
|
+
commands = {
|
|
202
|
+
"create": cmd_create,
|
|
203
|
+
"check": cmd_check,
|
|
204
|
+
"run": cmd_run,
|
|
205
|
+
"build": cmd_build,
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
commands[args.command](args)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
if __name__ == "__main__":
|
|
212
|
+
main()
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import sys
|
|
3
|
+
import os
|
|
4
|
+
import platform
|
|
5
|
+
import logging
|
|
6
|
+
|
|
7
|
+
logger = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def check_poetry_installed() -> bool:
|
|
11
|
+
"""检查 Poetry 是否已安装"""
|
|
12
|
+
try:
|
|
13
|
+
subprocess.run(
|
|
14
|
+
["poetry", "--version"],
|
|
15
|
+
check=True,
|
|
16
|
+
stdout=subprocess.PIPE,
|
|
17
|
+
stderr=subprocess.PIPE,
|
|
18
|
+
text=True
|
|
19
|
+
)
|
|
20
|
+
return True
|
|
21
|
+
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
22
|
+
return False
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def install_poetry():
|
|
26
|
+
"""跨平台自动安装 Poetry"""
|
|
27
|
+
print("正在安装 Poetry...")
|
|
28
|
+
os_name = platform.system()
|
|
29
|
+
|
|
30
|
+
try:
|
|
31
|
+
if os_name == "Windows":
|
|
32
|
+
install_cmd = "(Invoke-WebRequest -Uri https://install.python-poetry.org -UseBasicParsing).Content | python -"
|
|
33
|
+
subprocess.run(["powershell", "-Command", install_cmd], check=True, shell=True)
|
|
34
|
+
else:
|
|
35
|
+
subprocess.run(["curl", "-sSL", "https://install.python-poetry.org"], check=True)
|
|
36
|
+
print("Poetry 安装成功!")
|
|
37
|
+
except subprocess.CalledProcessError:
|
|
38
|
+
try:
|
|
39
|
+
subprocess.run([sys.executable, "-m", "pip", "install", "poetry"], check=True)
|
|
40
|
+
except subprocess.CalledProcessError as e:
|
|
41
|
+
print(f"安装失败: {e}")
|
|
42
|
+
sys.exit(1)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def setup_current_directory(dependencies: list):
|
|
46
|
+
"""直接在当前目录初始化环境"""
|
|
47
|
+
current_dir = os.getcwd()
|
|
48
|
+
print(f"当前工作目录: {current_dir}")
|
|
49
|
+
|
|
50
|
+
# 1. 初始化 Poetry
|
|
51
|
+
print("正在初始化 Poetry...")
|
|
52
|
+
subprocess.run(["poetry", "init", "-n"],
|
|
53
|
+
capture_output=True,
|
|
54
|
+
text=True)
|
|
55
|
+
|
|
56
|
+
# 2. 配置虚拟环境在当前目录下 (.venv)
|
|
57
|
+
print("配置虚拟环境路径...")
|
|
58
|
+
subprocess.run(["poetry", "config", "virtualenvs.in-project", "true", "--local"], check=True)
|
|
59
|
+
|
|
60
|
+
# 3. 安装依赖
|
|
61
|
+
if dependencies:
|
|
62
|
+
print(f"正在安装依赖: {dependencies}")
|
|
63
|
+
subprocess.run(["poetry", "add"] + dependencies, capture_output=True, text=True)
|
|
64
|
+
|
|
65
|
+
print("\n环境配置完成!")
|
|
66
|
+
print(f"虚拟环境位置: {os.path.join(current_dir, '.venv')}")
|
|
67
|
+
|
|
68
|
+
def find_module(module_names: list) -> bool:
|
|
69
|
+
"""检查模块是否已安装"""
|
|
70
|
+
import importlib.util
|
|
71
|
+
|
|
72
|
+
for module_name in module_names:
|
|
73
|
+
spec = importlib.util.find_spec(module_name)
|
|
74
|
+
|
|
75
|
+
if spec is None:
|
|
76
|
+
return False
|
|
77
|
+
return True
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def check_environment():
|
|
81
|
+
"""检查运行环境"""
|
|
82
|
+
REQUIRED_LIBRARIES = ["python-dotenv", "pyyaml"]
|
|
83
|
+
REQUIRED_MODULES = ["dotenv", "yaml"]
|
|
84
|
+
if not find_module(REQUIRED_MODULES):
|
|
85
|
+
# Check for non-interactive mode
|
|
86
|
+
if os.getenv("PANCAKE_AUTO_INSTALL", "").lower() in ("1", "true", "yes"):
|
|
87
|
+
auto_install = True
|
|
88
|
+
elif not sys.stdin.isatty():
|
|
89
|
+
logger.error("Missing required libraries and running in non-interactive mode. Set PANCAKE_AUTO_INSTALL=1 to auto-install.")
|
|
90
|
+
sys.exit(1)
|
|
91
|
+
else:
|
|
92
|
+
auto_install = input("缺少必要的库,是否自动安装? (y/n)").lower().startswith("y")
|
|
93
|
+
|
|
94
|
+
if not auto_install:
|
|
95
|
+
sys.exit(1)
|
|
96
|
+
|
|
97
|
+
# 1. 检查 Poetry
|
|
98
|
+
if not check_poetry_installed():
|
|
99
|
+
install_poetry()
|
|
100
|
+
if platform.system() == "Windows":
|
|
101
|
+
import ctypes
|
|
102
|
+
ctypes.windll.user32.SendMessageTimeoutW(0xFFFF, 0x1A, 0, "Environment", 0x0002, 5000, None)
|
|
103
|
+
else:
|
|
104
|
+
os.environ["PATH"] = f"{os.path.expanduser('~/.local/bin')}:{os.environ['PATH']}"
|
|
105
|
+
|
|
106
|
+
# 2. 直接在当前目录搭建
|
|
107
|
+
setup_current_directory(REQUIRED_LIBRARIES)
|
|
108
|
+
|
|
109
|
+
print("环境配置完成!", f"虚拟环境位置: {os.path.join(os.getcwd(), '.venv')}")
|
|
110
|
+
print("请重新启动项目 例如: poetry run python main.py")
|
|
111
|
+
sys.exit(1)
|