pytest-dsl 0.13.0__py3-none-any.whl → 0.15.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.
- pytest_dsl/__init__.py +190 -3
- pytest_dsl/cli.py +37 -260
- pytest_dsl/core/custom_keyword_manager.py +114 -14
- pytest_dsl/core/dsl_executor.py +549 -166
- pytest_dsl/core/dsl_executor_utils.py +21 -22
- pytest_dsl/core/execution_tracker.py +291 -0
- pytest_dsl/core/hook_manager.py +87 -0
- pytest_dsl/core/hookable_executor.py +134 -0
- pytest_dsl/core/hookable_keyword_manager.py +106 -0
- pytest_dsl/core/hookspecs.py +175 -0
- pytest_dsl/core/keyword_loader.py +402 -0
- pytest_dsl/core/keyword_manager.py +29 -23
- pytest_dsl/core/parser.py +94 -18
- pytest_dsl/core/validator.py +417 -0
- pytest_dsl/core/yaml_loader.py +142 -42
- pytest_dsl/core/yaml_vars.py +90 -7
- pytest_dsl/plugin.py +10 -1
- {pytest_dsl-0.13.0.dist-info → pytest_dsl-0.15.0.dist-info}/METADATA +1 -1
- {pytest_dsl-0.13.0.dist-info → pytest_dsl-0.15.0.dist-info}/RECORD +23 -16
- {pytest_dsl-0.13.0.dist-info → pytest_dsl-0.15.0.dist-info}/WHEEL +0 -0
- {pytest_dsl-0.13.0.dist-info → pytest_dsl-0.15.0.dist-info}/entry_points.txt +0 -0
- {pytest_dsl-0.13.0.dist-info → pytest_dsl-0.15.0.dist-info}/licenses/LICENSE +0 -0
- {pytest_dsl-0.13.0.dist-info → pytest_dsl-0.15.0.dist-info}/top_level.txt +0 -0
pytest_dsl/__init__.py
CHANGED
@@ -2,9 +2,196 @@
|
|
2
2
|
pytest-dsl - 基于pytest的DSL测试框架
|
3
3
|
|
4
4
|
使用自定义的领域特定语言(DSL)来编写测试用例,使测试更加直观、易读和易维护。
|
5
|
+
|
6
|
+
主要功能:
|
7
|
+
- DSL语法解析和执行
|
8
|
+
- 关键字管理和注册
|
9
|
+
- Hook机制支持插件扩展
|
10
|
+
- DSL格式校验
|
11
|
+
- 远程关键字支持
|
12
|
+
- 自定义关键字支持
|
5
13
|
"""
|
6
14
|
|
7
|
-
__version__ = "0.1.0"
|
15
|
+
__version__ = "0.1.0"
|
16
|
+
|
17
|
+
# 核心执行器
|
18
|
+
from pytest_dsl.core.dsl_executor import DSLExecutor
|
19
|
+
|
20
|
+
# 关键字管理器
|
21
|
+
from pytest_dsl.core.keyword_manager import keyword_manager, KeywordManager
|
22
|
+
|
23
|
+
# Hook系统
|
24
|
+
from pytest_dsl.core.hookspecs import hookimpl, hookspec, DSLHookSpecs
|
25
|
+
from pytest_dsl.core.hook_manager import hook_manager, DSLHookManager
|
26
|
+
|
27
|
+
# DSL格式校验
|
28
|
+
from pytest_dsl.core.validator import (
|
29
|
+
DSLValidator,
|
30
|
+
DSLValidationError,
|
31
|
+
validate_dsl,
|
32
|
+
check_dsl_syntax
|
33
|
+
)
|
34
|
+
|
35
|
+
# 自动装饰器
|
36
|
+
from pytest_dsl.core.auto_decorator import auto_dsl
|
37
|
+
|
38
|
+
# 核心工具类
|
39
|
+
from pytest_dsl.core.parser import Node, get_parser
|
40
|
+
from pytest_dsl.core.lexer import get_lexer
|
41
|
+
from pytest_dsl.core.context import TestContext
|
42
|
+
from pytest_dsl.core.global_context import global_context
|
43
|
+
|
44
|
+
# 变量工具
|
45
|
+
from pytest_dsl.core.variable_utils import VariableReplacer
|
46
|
+
|
47
|
+
# 自定义关键字管理器
|
48
|
+
from pytest_dsl.core.custom_keyword_manager import custom_keyword_manager
|
49
|
+
|
50
|
+
# 关键字加载器
|
51
|
+
from pytest_dsl.core.keyword_loader import (
|
52
|
+
keyword_loader, KeywordLoader,
|
53
|
+
load_all_keywords, categorize_keyword, get_keyword_source_info,
|
54
|
+
group_keywords_by_source, scan_project_custom_keywords
|
55
|
+
)
|
56
|
+
|
57
|
+
# 便捷导入的别名
|
58
|
+
Executor = DSLExecutor
|
59
|
+
Validator = DSLValidator
|
60
|
+
HookManager = DSLHookManager
|
61
|
+
KeywordLoader = KeywordLoader
|
62
|
+
|
63
|
+
# 导出所有公共接口
|
64
|
+
__all__ = [
|
65
|
+
# 版本信息
|
66
|
+
'__version__',
|
67
|
+
|
68
|
+
# 核心执行器
|
69
|
+
'DSLExecutor', 'Executor',
|
70
|
+
|
71
|
+
# 关键字管理
|
72
|
+
'keyword_manager', 'KeywordManager',
|
73
|
+
'custom_keyword_manager',
|
74
|
+
|
75
|
+
# 关键字加载器
|
76
|
+
'keyword_loader', 'KeywordLoader',
|
77
|
+
'load_all_keywords', 'categorize_keyword', 'get_keyword_source_info',
|
78
|
+
'group_keywords_by_source', 'scan_project_custom_keywords',
|
79
|
+
|
80
|
+
# Hook系统
|
81
|
+
'hookimpl', 'hookspec', 'DSLHookSpecs',
|
82
|
+
'hook_manager', 'DSLHookManager', 'HookManager',
|
83
|
+
|
84
|
+
# DSL校验
|
85
|
+
'DSLValidator', 'Validator',
|
86
|
+
'DSLValidationError',
|
87
|
+
'validate_dsl',
|
88
|
+
'check_dsl_syntax',
|
89
|
+
|
90
|
+
# 自动装饰器
|
91
|
+
'auto_dsl',
|
92
|
+
|
93
|
+
# 核心组件
|
94
|
+
'Node', 'get_parser', 'get_lexer',
|
95
|
+
'TestContext', 'global_context',
|
96
|
+
'VariableReplacer',
|
97
|
+
]
|
98
|
+
|
99
|
+
# 快捷函数
|
100
|
+
|
101
|
+
|
102
|
+
def create_executor(enable_hooks: bool = True) -> DSLExecutor:
|
103
|
+
"""创建DSL执行器实例
|
104
|
+
|
105
|
+
Args:
|
106
|
+
enable_hooks: 是否启用hook机制
|
107
|
+
|
108
|
+
Returns:
|
109
|
+
DSL执行器实例
|
110
|
+
"""
|
111
|
+
return DSLExecutor(enable_hooks=enable_hooks)
|
112
|
+
|
113
|
+
|
114
|
+
def parse_dsl(content: str) -> Node:
|
115
|
+
"""解析DSL内容为AST
|
116
|
+
|
117
|
+
Args:
|
118
|
+
content: DSL内容
|
119
|
+
|
120
|
+
Returns:
|
121
|
+
解析后的AST根节点
|
122
|
+
"""
|
123
|
+
lexer = get_lexer()
|
124
|
+
parser = get_parser()
|
125
|
+
return parser.parse(content, lexer=lexer)
|
126
|
+
|
127
|
+
|
128
|
+
def execute_dsl(content: str, context: dict = None, enable_hooks: bool = True) -> any:
|
129
|
+
"""执行DSL内容的便捷函数
|
130
|
+
|
131
|
+
Args:
|
132
|
+
content: DSL内容
|
133
|
+
context: 执行上下文(可选)
|
134
|
+
enable_hooks: 是否启用hook机制
|
135
|
+
|
136
|
+
Returns:
|
137
|
+
执行结果
|
138
|
+
"""
|
139
|
+
executor = create_executor(enable_hooks=enable_hooks)
|
140
|
+
if context:
|
141
|
+
executor.variables.update(context)
|
142
|
+
for key, value in context.items():
|
143
|
+
executor.test_context.set(key, value)
|
144
|
+
|
145
|
+
ast = parse_dsl(content)
|
146
|
+
return executor.execute(ast)
|
147
|
+
|
148
|
+
|
149
|
+
def register_keyword(name: str, parameters: list = None, source_type: str = "external",
|
150
|
+
source_name: str = "user_defined"):
|
151
|
+
"""注册关键字的装饰器
|
152
|
+
|
153
|
+
Args:
|
154
|
+
name: 关键字名称
|
155
|
+
parameters: 参数列表
|
156
|
+
source_type: 来源类型
|
157
|
+
source_name: 来源名称
|
158
|
+
|
159
|
+
Returns:
|
160
|
+
装饰器函数
|
161
|
+
"""
|
162
|
+
if parameters is None:
|
163
|
+
parameters = []
|
164
|
+
|
165
|
+
return keyword_manager.register_with_source(
|
166
|
+
name=name,
|
167
|
+
parameters=parameters,
|
168
|
+
source_type=source_type,
|
169
|
+
source_name=source_name
|
170
|
+
)
|
171
|
+
|
172
|
+
|
173
|
+
# 版本兼容性检查
|
174
|
+
def check_version_compatibility():
|
175
|
+
"""检查版本兼容性"""
|
176
|
+
try:
|
177
|
+
import sys
|
178
|
+
if sys.version_info < (3, 7):
|
179
|
+
import warnings
|
180
|
+
warnings.warn(
|
181
|
+
"pytest-dsl 需要 Python 3.7 或更高版本",
|
182
|
+
UserWarning,
|
183
|
+
stacklevel=2
|
184
|
+
)
|
185
|
+
except Exception:
|
186
|
+
pass
|
187
|
+
|
188
|
+
|
189
|
+
# 初始化时进行版本检查
|
190
|
+
check_version_compatibility()
|
8
191
|
|
9
|
-
#
|
10
|
-
|
192
|
+
# 自动初始化hook管理器
|
193
|
+
try:
|
194
|
+
hook_manager.initialize()
|
195
|
+
except Exception as e:
|
196
|
+
import warnings
|
197
|
+
warnings.warn(f"Hook管理器初始化失败: {e}", UserWarning, stacklevel=2)
|
pytest_dsl/cli.py
CHANGED
@@ -16,8 +16,9 @@ from pytest_dsl.core.yaml_loader import load_yaml_variables_from_args
|
|
16
16
|
from pytest_dsl.core.auto_directory import (
|
17
17
|
SETUP_FILE_NAME, TEARDOWN_FILE_NAME, execute_hook_file
|
18
18
|
)
|
19
|
-
from pytest_dsl.core.
|
20
|
-
|
19
|
+
from pytest_dsl.core.keyword_loader import (
|
20
|
+
load_all_keywords, categorize_keyword, get_keyword_source_info,
|
21
|
+
group_keywords_by_source, scan_project_custom_keywords
|
21
22
|
)
|
22
23
|
from pytest_dsl.core.keyword_manager import keyword_manager
|
23
24
|
|
@@ -138,164 +139,7 @@ def parse_args():
|
|
138
139
|
return args
|
139
140
|
|
140
141
|
|
141
|
-
def load_all_keywords(include_remote=False):
|
142
|
-
"""加载所有可用的关键字
|
143
142
|
|
144
|
-
Args:
|
145
|
-
include_remote: 是否包含远程关键字,默认为False
|
146
|
-
"""
|
147
|
-
# 首先导入内置关键字模块,确保内置关键字被注册
|
148
|
-
try:
|
149
|
-
import pytest_dsl.keywords # noqa: F401
|
150
|
-
print("内置关键字模块加载完成")
|
151
|
-
except ImportError as e:
|
152
|
-
print(f"加载内置关键字模块失败: {e}")
|
153
|
-
|
154
|
-
# 加载已安装的关键字插件
|
155
|
-
load_all_plugins()
|
156
|
-
|
157
|
-
# 扫描本地关键字
|
158
|
-
scan_local_keywords()
|
159
|
-
|
160
|
-
# 自动导入项目中的resources目录
|
161
|
-
try:
|
162
|
-
from pytest_dsl.core.custom_keyword_manager import (
|
163
|
-
custom_keyword_manager)
|
164
|
-
|
165
|
-
# 尝试从多个可能的项目根目录位置导入resources
|
166
|
-
possible_roots = [
|
167
|
-
os.getcwd(), # 当前工作目录
|
168
|
-
os.path.dirname(os.getcwd()), # 上级目录
|
169
|
-
]
|
170
|
-
|
171
|
-
# 尝试每个可能的根目录
|
172
|
-
for project_root in possible_roots:
|
173
|
-
if project_root and os.path.exists(project_root):
|
174
|
-
resources_dir = os.path.join(project_root, "resources")
|
175
|
-
if (os.path.exists(resources_dir) and
|
176
|
-
os.path.isdir(resources_dir)):
|
177
|
-
custom_keyword_manager.auto_import_resources_directory(
|
178
|
-
project_root)
|
179
|
-
break
|
180
|
-
except Exception as e:
|
181
|
-
print(f"自动导入resources目录时出现警告: {str(e)}")
|
182
|
-
|
183
|
-
# 扫描项目中的自定义关键字(.resource文件中定义的)
|
184
|
-
project_custom_keywords = scan_project_custom_keywords()
|
185
|
-
if project_custom_keywords:
|
186
|
-
print(f"发现 {len(project_custom_keywords)} 个项目自定义关键字")
|
187
|
-
|
188
|
-
# 加载.resource文件中的关键字到关键字管理器
|
189
|
-
from pytest_dsl.core.custom_keyword_manager import (
|
190
|
-
custom_keyword_manager
|
191
|
-
)
|
192
|
-
from pathlib import Path
|
193
|
-
|
194
|
-
project_root = Path(os.getcwd())
|
195
|
-
resource_files = list(project_root.glob('**/*.resource'))
|
196
|
-
|
197
|
-
for resource_file in resource_files:
|
198
|
-
try:
|
199
|
-
custom_keyword_manager.load_resource_file(str(resource_file))
|
200
|
-
print(f"已加载资源文件: {resource_file}")
|
201
|
-
except Exception as e:
|
202
|
-
print(f"加载资源文件失败 {resource_file}: {e}")
|
203
|
-
|
204
|
-
# 根据参数决定是否加载远程关键字
|
205
|
-
if include_remote:
|
206
|
-
print("正在扫描远程关键字...")
|
207
|
-
# 这里可以添加远程关键字的扫描逻辑
|
208
|
-
# 目前远程关键字是通过DSL文件中的@remote导入指令动态加载的
|
209
|
-
else:
|
210
|
-
print("跳过远程关键字扫描")
|
211
|
-
|
212
|
-
return project_custom_keywords
|
213
|
-
|
214
|
-
|
215
|
-
def categorize_keyword(keyword_name, keyword_info,
|
216
|
-
project_custom_keywords=None):
|
217
|
-
"""判断关键字的类别"""
|
218
|
-
# 优先使用存储的来源信息
|
219
|
-
source_type = keyword_info.get('source_type')
|
220
|
-
if source_type:
|
221
|
-
if source_type == 'builtin':
|
222
|
-
return 'builtin'
|
223
|
-
elif source_type == 'plugin':
|
224
|
-
return 'plugin'
|
225
|
-
elif source_type in ['external', 'local']:
|
226
|
-
return 'custom'
|
227
|
-
elif source_type == 'project_custom':
|
228
|
-
return 'project_custom'
|
229
|
-
|
230
|
-
# 向后兼容:使用原有的判断逻辑
|
231
|
-
if keyword_info.get('remote', False):
|
232
|
-
return 'remote'
|
233
|
-
|
234
|
-
# 检查是否是项目自定义关键字(DSL文件中定义的)
|
235
|
-
if project_custom_keywords and keyword_name in project_custom_keywords:
|
236
|
-
return 'project_custom'
|
237
|
-
|
238
|
-
# 检查是否是内置关键字(通过检查函数所在模块)
|
239
|
-
func = keyword_info.get('func')
|
240
|
-
if func and hasattr(func, '__module__'):
|
241
|
-
module_name = func.__module__
|
242
|
-
if module_name and module_name.startswith('pytest_dsl.keywords'):
|
243
|
-
return 'builtin'
|
244
|
-
|
245
|
-
return 'custom'
|
246
|
-
|
247
|
-
|
248
|
-
def get_keyword_source_info(keyword_info):
|
249
|
-
"""获取关键字的详细来源信息"""
|
250
|
-
source_type = keyword_info.get('source_type', 'unknown')
|
251
|
-
source_name = keyword_info.get('source_name', '未知')
|
252
|
-
|
253
|
-
return {
|
254
|
-
'type': source_type,
|
255
|
-
'name': source_name,
|
256
|
-
'display_name': source_name,
|
257
|
-
'module': keyword_info.get('module_name', ''),
|
258
|
-
'plugin_module': keyword_info.get('plugin_module', '')
|
259
|
-
}
|
260
|
-
|
261
|
-
|
262
|
-
def group_keywords_by_source(keywords_dict, project_custom_keywords=None):
|
263
|
-
"""按来源分组关键字
|
264
|
-
|
265
|
-
Returns:
|
266
|
-
dict: 格式为 {source_group: {source_name: [keywords]}}
|
267
|
-
"""
|
268
|
-
groups = {
|
269
|
-
'builtin': {},
|
270
|
-
'plugin': {},
|
271
|
-
'custom': {},
|
272
|
-
'project_custom': {},
|
273
|
-
'remote': {}
|
274
|
-
}
|
275
|
-
|
276
|
-
for keyword_name, keyword_info in keywords_dict.items():
|
277
|
-
category = categorize_keyword(
|
278
|
-
keyword_name, keyword_info, project_custom_keywords
|
279
|
-
)
|
280
|
-
source_info = get_keyword_source_info(keyword_info)
|
281
|
-
|
282
|
-
# 特殊处理项目自定义关键字
|
283
|
-
if category == 'project_custom' and project_custom_keywords:
|
284
|
-
custom_info = project_custom_keywords[keyword_name]
|
285
|
-
source_name = custom_info['file']
|
286
|
-
else:
|
287
|
-
source_name = source_info['name']
|
288
|
-
|
289
|
-
if source_name not in groups[category]:
|
290
|
-
groups[category][source_name] = []
|
291
|
-
|
292
|
-
groups[category][source_name].append({
|
293
|
-
'name': keyword_name,
|
294
|
-
'info': keyword_info,
|
295
|
-
'source_info': source_info
|
296
|
-
})
|
297
|
-
|
298
|
-
return groups
|
299
143
|
|
300
144
|
|
301
145
|
def format_keyword_info_text(keyword_name, keyword_info, show_category=True,
|
@@ -724,12 +568,17 @@ def list_keywords(output_format='json', name_filter=None,
|
|
724
568
|
|
725
569
|
def load_yaml_variables(args):
|
726
570
|
"""从命令行参数加载YAML变量"""
|
727
|
-
#
|
571
|
+
# 使用统一的加载函数,包含远程服务器自动连接功能和hook支持
|
728
572
|
try:
|
573
|
+
# 尝试从环境变量获取环境名称
|
574
|
+
environment = (os.environ.get('PYTEST_DSL_ENVIRONMENT') or
|
575
|
+
os.environ.get('ENVIRONMENT'))
|
576
|
+
|
729
577
|
load_yaml_variables_from_args(
|
730
578
|
yaml_files=args.yaml_vars,
|
731
579
|
yaml_vars_dir=args.yaml_vars_dir,
|
732
|
-
project_root=os.getcwd() # CLI模式下使用当前工作目录作为项目根目录
|
580
|
+
project_root=os.getcwd(), # CLI模式下使用当前工作目录作为项目根目录
|
581
|
+
environment=environment
|
733
582
|
)
|
734
583
|
except Exception as e:
|
735
584
|
print(f"加载YAML变量失败: {str(e)}")
|
@@ -774,6 +623,33 @@ def run_dsl_tests(args):
|
|
774
623
|
# 加载YAML变量(包括远程服务器自动连接)
|
775
624
|
load_yaml_variables(args)
|
776
625
|
|
626
|
+
# 支持hook机制的执行
|
627
|
+
from pytest_dsl.core.hookable_executor import hookable_executor
|
628
|
+
|
629
|
+
# 检查是否有hook提供的用例列表
|
630
|
+
hook_cases = hookable_executor.list_dsl_cases()
|
631
|
+
if hook_cases:
|
632
|
+
# 如果有hook提供的用例,优先执行这些用例
|
633
|
+
print(f"通过Hook发现 {len(hook_cases)} 个DSL用例")
|
634
|
+
failures = 0
|
635
|
+
for case in hook_cases:
|
636
|
+
case_id = case.get('id') or case.get('name', 'unknown')
|
637
|
+
try:
|
638
|
+
print(f"执行用例: {case.get('name', case_id)}")
|
639
|
+
hookable_executor.execute_dsl(str(case_id))
|
640
|
+
print(f"✓ 用例 {case.get('name', case_id)} 执行成功")
|
641
|
+
except Exception as e:
|
642
|
+
print(f"✗ 用例 {case.get('name', case_id)} 执行失败: {e}")
|
643
|
+
failures += 1
|
644
|
+
|
645
|
+
if failures > 0:
|
646
|
+
print(f"总计 {failures}/{len(hook_cases)} 个测试失败")
|
647
|
+
sys.exit(1)
|
648
|
+
else:
|
649
|
+
print(f"所有 {len(hook_cases)} 个测试成功完成")
|
650
|
+
return
|
651
|
+
|
652
|
+
# 如果没有hook用例,使用传统的文件执行方式
|
777
653
|
lexer = get_lexer()
|
778
654
|
parser = get_parser()
|
779
655
|
executor = DSLExecutor()
|
@@ -900,106 +776,7 @@ def main_list_keywords():
|
|
900
776
|
)
|
901
777
|
|
902
778
|
|
903
|
-
def scan_project_custom_keywords(project_root=None):
|
904
|
-
"""扫描项目中.resource文件中的自定义关键字
|
905
|
-
|
906
|
-
Args:
|
907
|
-
project_root: 项目根目录,默认为当前工作目录
|
908
|
-
|
909
|
-
Returns:
|
910
|
-
dict: 自定义关键字信息,格式为
|
911
|
-
{keyword_name: {'file': file_path, 'node': ast_node}}
|
912
|
-
"""
|
913
|
-
if project_root is None:
|
914
|
-
project_root = os.getcwd()
|
915
|
-
|
916
|
-
project_root = Path(project_root)
|
917
|
-
custom_keywords = {}
|
918
|
-
|
919
|
-
# 查找所有.resource文件
|
920
|
-
resource_files = list(project_root.glob('**/*.resource'))
|
921
|
-
|
922
|
-
if not resource_files:
|
923
|
-
return custom_keywords
|
924
|
-
|
925
|
-
lexer = get_lexer()
|
926
|
-
parser = get_parser()
|
927
|
-
|
928
|
-
for file_path in resource_files:
|
929
|
-
try:
|
930
|
-
# 读取并解析文件
|
931
|
-
content = read_file(str(file_path))
|
932
|
-
ast = parser.parse(content, lexer=lexer)
|
933
|
-
|
934
|
-
# 查找自定义关键字定义
|
935
|
-
keywords_in_file = extract_custom_keywords_from_ast(
|
936
|
-
ast, str(file_path)
|
937
|
-
)
|
938
|
-
custom_keywords.update(keywords_in_file)
|
939
|
-
|
940
|
-
except Exception as e:
|
941
|
-
print(f"解析资源文件 {file_path} 时出错: {e}")
|
942
|
-
|
943
|
-
return custom_keywords
|
944
|
-
|
945
|
-
|
946
|
-
def extract_custom_keywords_from_ast(ast, file_path):
|
947
|
-
"""从AST中提取自定义关键字定义
|
948
|
-
|
949
|
-
Args:
|
950
|
-
ast: 抽象语法树
|
951
|
-
file_path: 文件路径
|
952
|
-
|
953
|
-
Returns:
|
954
|
-
dict: 自定义关键字信息
|
955
|
-
"""
|
956
|
-
custom_keywords = {}
|
957
|
-
|
958
|
-
if ast.type != 'Start' or len(ast.children) < 2:
|
959
|
-
return custom_keywords
|
960
|
-
|
961
|
-
# 遍历语句节点
|
962
|
-
statements_node = ast.children[1]
|
963
|
-
if statements_node.type != 'Statements':
|
964
|
-
return custom_keywords
|
965
|
-
|
966
|
-
for node in statements_node.children:
|
967
|
-
# 支持两种格式:CustomKeyword(旧格式)和Function(新格式)
|
968
|
-
if node.type in ['CustomKeyword', 'Function']:
|
969
|
-
keyword_name = node.value
|
970
|
-
|
971
|
-
# 提取参数信息
|
972
|
-
params_node = node.children[0] if node.children else None
|
973
|
-
parameters = []
|
974
|
-
|
975
|
-
if params_node:
|
976
|
-
for param in params_node:
|
977
|
-
param_name = param.value
|
978
|
-
param_default = None
|
979
|
-
|
980
|
-
# 检查是否有默认值
|
981
|
-
if param.children and param.children[0]:
|
982
|
-
param_default = param.children[0].value
|
983
|
-
|
984
|
-
param_info = {
|
985
|
-
'name': param_name,
|
986
|
-
'mapping': param_name,
|
987
|
-
'description': f'自定义关键字参数 {param_name}'
|
988
|
-
}
|
989
|
-
|
990
|
-
if param_default is not None:
|
991
|
-
param_info['default'] = param_default
|
992
|
-
|
993
|
-
parameters.append(param_info)
|
994
|
-
|
995
|
-
custom_keywords[keyword_name] = {
|
996
|
-
'file': file_path,
|
997
|
-
'node': node,
|
998
|
-
'type': 'project_custom',
|
999
|
-
'parameters': parameters
|
1000
|
-
}
|
1001
779
|
|
1002
|
-
return custom_keywords
|
1003
780
|
|
1004
781
|
|
1005
782
|
if __name__ == '__main__':
|
@@ -200,23 +200,37 @@ class CustomKeywordManager:
|
|
200
200
|
with open(file_path, 'r', encoding='utf-8') as f:
|
201
201
|
content = f.read()
|
202
202
|
|
203
|
-
#
|
204
|
-
lexer = get_lexer()
|
205
|
-
parser = get_parser()
|
206
|
-
ast = parser.parse(content, lexer=lexer)
|
207
|
-
|
208
|
-
# 标记为已加载
|
203
|
+
# 标记为已加载(在解析前标记,避免循环导入)
|
209
204
|
self.resource_cache[absolute_path] = True
|
210
205
|
|
211
|
-
#
|
212
|
-
self.
|
206
|
+
# 使用公共方法解析和处理资源文件内容
|
207
|
+
self._process_resource_file_content(content, file_path)
|
213
208
|
|
214
|
-
# 注册关键字
|
215
|
-
self._register_keywords(ast, file_path)
|
216
209
|
except Exception as e:
|
210
|
+
# 如果处理失败,移除缓存标记
|
211
|
+
self.resource_cache.pop(absolute_path, None)
|
217
212
|
print(f"资源文件 {file_path} 加载失败: {str(e)}")
|
218
213
|
raise
|
219
214
|
|
215
|
+
def _process_resource_file_content(self, content: str,
|
216
|
+
file_path: str) -> None:
|
217
|
+
"""处理资源文件内容
|
218
|
+
|
219
|
+
Args:
|
220
|
+
content: 文件内容
|
221
|
+
file_path: 文件路径
|
222
|
+
"""
|
223
|
+
# 解析资源文件
|
224
|
+
lexer = get_lexer()
|
225
|
+
parser = get_parser()
|
226
|
+
ast = parser.parse(content, lexer=lexer)
|
227
|
+
|
228
|
+
# 处理导入指令
|
229
|
+
self._process_imports(ast, os.path.dirname(file_path))
|
230
|
+
|
231
|
+
# 注册关键字
|
232
|
+
self._register_keywords_from_ast(ast, file_path)
|
233
|
+
|
220
234
|
def _process_imports(self, ast: Node, base_dir: str) -> None:
|
221
235
|
"""处理资源文件中的导入指令
|
222
236
|
|
@@ -244,12 +258,13 @@ class CustomKeywordManager:
|
|
244
258
|
# 递归加载导入的资源文件
|
245
259
|
self.load_resource_file(imported_file)
|
246
260
|
|
247
|
-
def
|
248
|
-
|
261
|
+
def _register_keywords_from_ast(self, ast: Node,
|
262
|
+
source_name: str) -> None:
|
263
|
+
"""从AST中注册关键字(重构后的版本)
|
249
264
|
|
250
265
|
Args:
|
251
266
|
ast: 抽象语法树
|
252
|
-
|
267
|
+
source_name: 来源名称
|
253
268
|
"""
|
254
269
|
if ast.type != 'Start' or len(ast.children) < 2:
|
255
270
|
return
|
@@ -261,7 +276,7 @@ class CustomKeywordManager:
|
|
261
276
|
|
262
277
|
for node in statements_node.children:
|
263
278
|
if node.type in ['CustomKeyword', 'Function']:
|
264
|
-
self._register_custom_keyword(node,
|
279
|
+
self._register_custom_keyword(node, source_name)
|
265
280
|
|
266
281
|
def _register_custom_keyword(self, node: Node, file_path: str) -> None:
|
267
282
|
"""注册自定义关键字
|
@@ -342,6 +357,91 @@ class CustomKeywordManager:
|
|
342
357
|
|
343
358
|
print(f"已注册自定义关键字: {keyword_name} 来自文件: {file_path}")
|
344
359
|
|
360
|
+
def register_keyword_from_dsl_content(self, dsl_content: str,
|
361
|
+
source_name: str = "DSL内容") -> list:
|
362
|
+
"""从DSL内容注册关键字(公共方法)
|
363
|
+
|
364
|
+
Args:
|
365
|
+
dsl_content: DSL文本内容
|
366
|
+
source_name: 来源名称,用于日志显示
|
367
|
+
|
368
|
+
Returns:
|
369
|
+
list: 注册成功的关键字名称列表
|
370
|
+
|
371
|
+
Raises:
|
372
|
+
Exception: 解析或注册失败时抛出异常
|
373
|
+
"""
|
374
|
+
try:
|
375
|
+
# 解析DSL内容
|
376
|
+
lexer = get_lexer()
|
377
|
+
parser = get_parser()
|
378
|
+
ast = parser.parse(dsl_content, lexer=lexer)
|
379
|
+
|
380
|
+
# 收集注册前的关键字列表
|
381
|
+
existing_keywords = (
|
382
|
+
set(keyword_manager._keywords.keys())
|
383
|
+
if hasattr(keyword_manager, '_keywords')
|
384
|
+
else set()
|
385
|
+
)
|
386
|
+
|
387
|
+
# 使用统一的注册方法
|
388
|
+
self._register_keywords_from_ast(ast, source_name)
|
389
|
+
|
390
|
+
# 计算新注册的关键字
|
391
|
+
new_keywords = (
|
392
|
+
set(keyword_manager._keywords.keys())
|
393
|
+
if hasattr(keyword_manager, '_keywords')
|
394
|
+
else set()
|
395
|
+
)
|
396
|
+
registered_keywords = list(new_keywords - existing_keywords)
|
397
|
+
|
398
|
+
if not registered_keywords:
|
399
|
+
raise ValueError("在DSL内容中未找到任何关键字定义")
|
400
|
+
|
401
|
+
return registered_keywords
|
402
|
+
|
403
|
+
except Exception as e:
|
404
|
+
print(f"从DSL内容注册关键字失败(来源:{source_name}): {e}")
|
405
|
+
raise
|
406
|
+
|
407
|
+
def register_specific_keyword_from_dsl_content(
|
408
|
+
self, keyword_name: str, dsl_content: str,
|
409
|
+
source_name: str = "DSL内容") -> bool:
|
410
|
+
"""从DSL内容注册指定的关键字(公共方法)
|
411
|
+
|
412
|
+
Args:
|
413
|
+
keyword_name: 要注册的关键字名称
|
414
|
+
dsl_content: DSL文本内容
|
415
|
+
source_name: 来源名称,用于日志显示
|
416
|
+
|
417
|
+
Returns:
|
418
|
+
bool: 是否注册成功
|
419
|
+
|
420
|
+
Raises:
|
421
|
+
Exception: 解析失败或未找到指定关键字时抛出异常
|
422
|
+
"""
|
423
|
+
try:
|
424
|
+
# 解析DSL内容
|
425
|
+
lexer = get_lexer()
|
426
|
+
parser = get_parser()
|
427
|
+
ast = parser.parse(dsl_content, lexer=lexer)
|
428
|
+
|
429
|
+
# 查找指定的关键字定义
|
430
|
+
if ast.type == 'Start' and len(ast.children) >= 2:
|
431
|
+
statements_node = ast.children[1]
|
432
|
+
if statements_node.type == 'Statements':
|
433
|
+
for node in statements_node.children:
|
434
|
+
if (node.type in ['CustomKeyword', 'Function'] and
|
435
|
+
node.value == keyword_name):
|
436
|
+
self._register_custom_keyword(node, source_name)
|
437
|
+
return True
|
438
|
+
|
439
|
+
raise ValueError(f"在DSL内容中未找到关键字定义: {keyword_name}")
|
440
|
+
|
441
|
+
except Exception as e:
|
442
|
+
print(f"从DSL内容注册关键字失败 {keyword_name}(来源:{source_name}): {e}")
|
443
|
+
raise
|
444
|
+
|
345
445
|
|
346
446
|
# 创建全局自定义关键字管理器实例
|
347
447
|
custom_keyword_manager = CustomKeywordManager()
|