kotonebot 0.5.0__py3-none-any.whl → 0.6.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.
Files changed (103) hide show
  1. kotonebot/__init__.py +39 -39
  2. kotonebot/backend/bot.py +312 -312
  3. kotonebot/backend/color.py +525 -525
  4. kotonebot/backend/context/__init__.py +3 -3
  5. kotonebot/backend/context/context.py +1002 -1002
  6. kotonebot/backend/context/task_action.py +183 -183
  7. kotonebot/backend/core.py +86 -129
  8. kotonebot/backend/debug/entry.py +89 -89
  9. kotonebot/backend/debug/mock.py +78 -78
  10. kotonebot/backend/debug/server.py +222 -222
  11. kotonebot/backend/debug/vars.py +351 -351
  12. kotonebot/backend/dispatch.py +227 -227
  13. kotonebot/backend/flow_controller.py +196 -196
  14. kotonebot/backend/image.py +36 -5
  15. kotonebot/backend/loop.py +222 -208
  16. kotonebot/backend/ocr.py +535 -535
  17. kotonebot/backend/preprocessor.py +103 -103
  18. kotonebot/client/__init__.py +9 -9
  19. kotonebot/client/device.py +369 -529
  20. kotonebot/client/fast_screenshot.py +377 -377
  21. kotonebot/client/host/__init__.py +43 -43
  22. kotonebot/client/host/adb_common.py +101 -107
  23. kotonebot/client/host/custom.py +118 -118
  24. kotonebot/client/host/leidian_host.py +196 -196
  25. kotonebot/client/host/mumu12_host.py +353 -353
  26. kotonebot/client/host/protocol.py +214 -214
  27. kotonebot/client/host/windows_common.py +58 -58
  28. kotonebot/client/implements/__init__.py +65 -70
  29. kotonebot/client/implements/adb.py +89 -89
  30. kotonebot/client/implements/nemu_ipc/__init__.py +11 -11
  31. kotonebot/client/implements/nemu_ipc/external_renderer_ipc.py +284 -284
  32. kotonebot/client/implements/nemu_ipc/nemu_ipc.py +327 -327
  33. kotonebot/client/implements/remote_windows.py +188 -188
  34. kotonebot/client/implements/uiautomator2.py +85 -85
  35. kotonebot/client/implements/windows.py +176 -176
  36. kotonebot/client/protocol.py +69 -69
  37. kotonebot/client/registration.py +24 -24
  38. kotonebot/client/scaler.py +467 -0
  39. kotonebot/config/base_config.py +96 -96
  40. kotonebot/config/config.py +61 -0
  41. kotonebot/config/manager.py +36 -36
  42. kotonebot/core/__init__.py +13 -0
  43. kotonebot/core/entities/base.py +182 -0
  44. kotonebot/core/entities/compound.py +75 -0
  45. kotonebot/core/entities/ocr.py +117 -0
  46. kotonebot/core/entities/template_match.py +198 -0
  47. kotonebot/devtools/__init__.py +42 -0
  48. kotonebot/devtools/cli/__init__.py +6 -0
  49. kotonebot/devtools/cli/main.py +53 -0
  50. kotonebot/{tools → devtools}/mirror.py +354 -354
  51. kotonebot/devtools/project/project.py +41 -0
  52. kotonebot/devtools/project/scanner.py +202 -0
  53. kotonebot/devtools/project/schema.py +99 -0
  54. kotonebot/devtools/resgen/__init__.py +42 -0
  55. kotonebot/devtools/resgen/codegen.py +331 -0
  56. kotonebot/devtools/resgen/core.py +94 -0
  57. kotonebot/devtools/resgen/parsers.py +360 -0
  58. kotonebot/devtools/resgen/utils.py +158 -0
  59. kotonebot/devtools/resgen/validation.py +115 -0
  60. kotonebot/devtools/web/dist/assets/bootstrap-icons-BOrJxbIo.woff +0 -0
  61. kotonebot/devtools/web/dist/assets/bootstrap-icons-BtvjY1KL.woff2 +0 -0
  62. kotonebot/devtools/web/dist/assets/ext-language_tools-CD021WJ2.js +2577 -0
  63. kotonebot/devtools/web/dist/assets/index-B_m5f2LF.js +2836 -0
  64. kotonebot/devtools/web/dist/assets/index-BlEDyGGa.css +9 -0
  65. kotonebot/devtools/web/dist/assets/language-client-C9muzqaq.js +128 -0
  66. kotonebot/devtools/web/dist/assets/mode-python-CtHp76XS.js +476 -0
  67. kotonebot/devtools/web/dist/icons/symbol-class.svg +3 -0
  68. kotonebot/devtools/web/dist/icons/symbol-file.svg +3 -0
  69. kotonebot/devtools/web/dist/icons/symbol-method.svg +3 -0
  70. kotonebot/devtools/web/dist/index.html +25 -0
  71. kotonebot/devtools/web/server/__init__.py +0 -0
  72. kotonebot/devtools/web/server/rest_api.py +217 -0
  73. kotonebot/devtools/web/server/server.py +85 -0
  74. kotonebot/errors.py +76 -76
  75. kotonebot/interop/win/__init__.py +11 -9
  76. kotonebot/interop/win/_mouse.py +310 -310
  77. kotonebot/interop/win/message_box.py +313 -313
  78. kotonebot/interop/win/reg.py +37 -37
  79. kotonebot/interop/win/shake_mouse.py +224 -0
  80. kotonebot/interop/win/shortcut.py +43 -43
  81. kotonebot/interop/win/task_dialog.py +513 -513
  82. kotonebot/logging/__init__.py +2 -2
  83. kotonebot/logging/log.py +17 -17
  84. kotonebot/primitives/__init__.py +19 -17
  85. kotonebot/primitives/geometry.py +1067 -862
  86. kotonebot/primitives/visual.py +143 -63
  87. kotonebot/ui/file_host/sensio.py +36 -36
  88. kotonebot/ui/file_host/tmp_send.py +54 -54
  89. kotonebot/ui/pushkit/__init__.py +3 -3
  90. kotonebot/ui/pushkit/image_host.py +88 -88
  91. kotonebot/ui/pushkit/protocol.py +13 -13
  92. kotonebot/ui/pushkit/wxpusher.py +54 -54
  93. kotonebot/ui/user.py +148 -148
  94. kotonebot/util.py +436 -436
  95. {kotonebot-0.5.0.dist-info → kotonebot-0.6.0.dist-info}/METADATA +84 -82
  96. kotonebot-0.6.0.dist-info/RECORD +105 -0
  97. kotonebot-0.6.0.dist-info/entry_points.txt +2 -0
  98. {kotonebot-0.5.0.dist-info → kotonebot-0.6.0.dist-info}/licenses/LICENSE +673 -673
  99. kotonebot/client/implements/adb_raw.py +0 -163
  100. kotonebot-0.5.0.dist-info/RECORD +0 -71
  101. /kotonebot/{tools → devtools/project}/__init__.py +0 -0
  102. {kotonebot-0.5.0.dist-info → kotonebot-0.6.0.dist-info}/WHEEL +0 -0
  103. {kotonebot-0.5.0.dist-info → kotonebot-0.6.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,331 @@
1
+ import os
2
+ from typing import Any, List, Callable
3
+
4
+ from .core import CodeWriter, ClassNode, ResourceNode, ImageAsset, BoxData, PointData, PrefabData
5
+ from .utils import unify_path
6
+
7
+ class StandardGenerator:
8
+ """标准 Python 生成器基类"""
9
+
10
+ def __init__(self, production: bool = False, ide_type: str | None = None,
11
+ path_transformer: Callable[[str], str] | None = None):
12
+ self.writer = CodeWriter()
13
+ self.production = production
14
+ self.ide_type = ide_type
15
+ self.path_transformer = path_transformer
16
+
17
+ def _transform_path(self, original_path: str, default_expr: str) -> str:
18
+ """Return the code expression for the image path.
19
+
20
+ If `path_transformer` is provided, call it with the original path and
21
+ use its return value verbatim as the expression to emit. Otherwise
22
+ fall back to `default_expr`.
23
+ """
24
+ if self.path_transformer:
25
+ return self.path_transformer(original_path)
26
+ return default_expr
27
+
28
+ def generate(self, root_nodes: List[ClassNode]) -> str:
29
+ self.render_header()
30
+ self.writer.write_empty_line()
31
+ for node in root_nodes:
32
+ self.render_class(node)
33
+ return self.writer.get_content()
34
+
35
+ def render_header(self):
36
+ """可被重写:文件头"""
37
+ w = self.writer
38
+ if not self.production:
39
+ w.write("####### 图片资源文件 #######")
40
+ w.write("####### 此文件为自动生成,请勿编辑 #######")
41
+ w.write("####### AUTO GENERATED. DO NOT EDIT. #######")
42
+ w.write("from kotonebot.backend.core import Image, HintBox, HintPoint")
43
+ w.write("from kotonebot.primitives import ImageSlice, Rect")
44
+
45
+ def render_class(self, node: ClassNode):
46
+ """递归渲染类"""
47
+ w = self.writer
48
+ w.write(f"class {node.name}:")
49
+ with w.indent():
50
+ if node.is_empty():
51
+ w.write("pass")
52
+ return
53
+
54
+ # 1. 渲染属性
55
+ for attr in node.attributes:
56
+ self.render_attribute(attr)
57
+ w.write_empty_line()
58
+
59
+ # 2. 渲染子类
60
+ for child in node.children:
61
+ self.render_class(child)
62
+ w.write_empty_line()
63
+
64
+ def render_attribute(self, attr: ResourceNode):
65
+ """渲染单个属性。根据 attr.value 的 IR 类型生成对应的代码字符串。"""
66
+ val = attr.value
67
+ code_str = ""
68
+
69
+ if isinstance(val, ImageAsset):
70
+ rect_expr: str
71
+ if val.rect is not None:
72
+ x1, y1, x2, y2 = val.rect
73
+ width = x2 - x1
74
+ height = y2 - y1
75
+ rect_expr = f"Rect(x={x1}, y={y1}, w={width}, h={height})"
76
+ else:
77
+ rect_expr = "None"
78
+ # 使用相对名作为资源引用(保留原来的 sprite_path 风格)
79
+ rel = os.path.basename(val.path)
80
+ display_name = attr.metadata.get('display_name', attr.name)
81
+ default = f'sprite_path("{rel}")'
82
+ path_expr = self._transform_path(val.path, default)
83
+ code_str = f'ImageSlice(file_path={path_expr}, name="{display_name}", slice_rect={rect_expr})'
84
+ elif isinstance(val, BoxData):
85
+ code_str = (f'HintBox(x1={val.x1}, y1={val.y1}, x2={val.x2}, y2={val.y2}, '
86
+ f'source_resolution=({val.resolution[0]}, {val.resolution[1]}))')
87
+ elif isinstance(val, PointData):
88
+ code_str = f'HintPoint(x={val.x}, y={val.y})'
89
+ else:
90
+ # fallback: str 转换
91
+ code_str = str(val)
92
+
93
+ self.writer.write(f"{attr.name} = {code_str}")
94
+ if not self.production:
95
+ self.render_docstring(attr)
96
+
97
+ def render_docstring(self, attr: ResourceNode):
98
+ """渲染 Docstring,包含图片标签生成逻辑"""
99
+ w = self.writer
100
+ base_doc = attr.docstring
101
+
102
+ # 构造 HTML 图片标签
103
+ img_tags = ""
104
+ # 1. 当前资源图片
105
+ if 'abs_path' in attr.metadata:
106
+ img_tags += self._make_img_tag(attr.metadata['abs_path'], attr.metadata.get('display_name', 'Img')) + '\\n'
107
+ elif 'preview_path' in attr.metadata:
108
+ img_tags += self._make_img_tag(attr.metadata['preview_path'], "Preview") + '\\n'
109
+
110
+ # 2. 原始大图 (可选)
111
+ if 'origin_file' in attr.metadata:
112
+ img_tags += "\nOriginal:\n" + self._make_img_tag(attr.metadata['origin_file'], "Original", height="200")
113
+
114
+ full_doc = f"{base_doc}\n\n{img_tags}"
115
+
116
+ # 写入
117
+ w.write('"""')
118
+ for line in full_doc.split('\n'):
119
+ w.write(line)
120
+ w.write('"""')
121
+
122
+ def _make_img_tag(self, path: str, title: str, height: str = "") -> str:
123
+ path = unify_path(path)
124
+ # 简单的 IDE 适配逻辑
125
+ if self.ide_type == 'vscode':
126
+ # VSCode 需要转义
127
+ path = path.replace('\\', '\\\\')
128
+ return f'<img src="vscode-file://vscode-app/{path}" title="{title}" height="{height}" />'
129
+ elif self.ide_type == 'pycharm':
130
+ return f'.. image:: http://localhost:6532/image?path={path}'
131
+ else:
132
+ return f'<img src="file:///{path}" title="{title}" height="{height}" />'
133
+
134
+
135
+ class EntityGenerator(StandardGenerator):
136
+ """
137
+ KotoneBot 实体代码生成器。
138
+
139
+ 输出规范:
140
+ 1. Template (图片) -> 生成继承自 TemplateMatchPrefab 的嵌套类。
141
+ 2. HintBox/Point -> 生成类的静态属性实例。
142
+ """
143
+
144
+ def render_header(self):
145
+ w = self.writer
146
+ w.write("####### 实体资源文件 #######")
147
+ w.write("####### 此文件为自动生成,请勿编辑 #######")
148
+ w.write("####### AUTO GENERATED. DO NOT EDIT. #######")
149
+ w.write_empty_line()
150
+ w.write("from kotonebot.core import TemplateMatchPrefab")
151
+ w.write("from kotonebot.primitives import Image, ImageSlice, Rect")
152
+ w.write("from kotonebot.backend.core import HintBox, HintPoint")
153
+ w.write_empty_line()
154
+
155
+ def render_attribute(self, attr: ResourceNode):
156
+ """
157
+ 核心分发逻辑:
158
+ 根据 ResourceNode 携带的 value 类型,决定生成策略。
159
+ """
160
+ data = attr.value
161
+
162
+ print(f'Writing: {attr.name} of type {type(data)}')
163
+ if isinstance(data, ImageAsset):
164
+ self._render_prefab_class(attr, data)
165
+ elif isinstance(data, PrefabData):
166
+ self._render_custom_prefab_class(attr, data)
167
+ elif isinstance(data, (BoxData, PointData)):
168
+ self._render_primitive_assignment(attr, data)
169
+ else:
170
+ # 兜底:如果 value 是未知类型或纯字符串,回退到默认赋值
171
+ super().render_attribute(attr)
172
+
173
+ def _render_custom_prefab_class(self, node: ResourceNode, data: PrefabData):
174
+ """
175
+ 渲染自定义基类的 Prefab 嵌套类
176
+ """
177
+ w = self.writer
178
+ class_name = node.name
179
+ if not getattr(data, 'prefab_id', None):
180
+ raise ValueError(f"PrefabData missing prefab_id for node {node.name}")
181
+ base_class = data.prefab_id
182
+
183
+ # 1. 类定义
184
+ w.write(f"class {class_name}({base_class}):")
185
+
186
+ with w.indent():
187
+ # 2. Docstring
188
+ if not self.production:
189
+ self.render_docstring(node)
190
+
191
+ # display_name 属性(用于 Image.name 参数)
192
+ display_name = node.metadata.get('display_name', node.name)
193
+
194
+ # 3. If PrefabData has an image, expose it as `template` for convenience
195
+ # so simple prefab definitions that only provide an image still
196
+ # produce a usable `template` attribute on the generated class.
197
+ # Only expose `template` automatically for prefabs that originated
198
+ # from a simple meta file (isSimple == True). Complex/v2 prefabs may
199
+ # define images via props and should not implicitly expose `template`.
200
+ if data.image is not None and node.metadata.get('isSimple'):
201
+ rect_expr: str
202
+ if data.image.rect is not None:
203
+ x1, y1, x2, y2 = data.image.rect
204
+ ix1, iy1, ix2, iy2 = map(int, (x1, y1, x2, y2))
205
+ rect_width = ix2 - ix1
206
+ rect_height = iy2 - iy1
207
+ rect_expr = f"Rect(x={ix1}, y={iy1}, w={rect_width}, h={rect_height})"
208
+ else:
209
+ rect_expr = "None"
210
+
211
+ clean_path = unify_path(data.image.path)
212
+ default = f'"{clean_path}"'
213
+ path_expr = self._transform_path(clean_path, default)
214
+ w.write(f'template = ImageSlice(file_path={path_expr}, name="{display_name}", slice_rect={rect_expr})')
215
+ w.write_empty_line()
216
+
217
+ # 4. V2 Props
218
+ for key, value in data.props.items():
219
+ if isinstance(value, ImageAsset):
220
+ rect_expr: str
221
+ if value.rect is not None:
222
+ x1, y1, x2, y2 = value.rect
223
+ ix1, iy1, ix2, iy2 = map(int, (x1, y1, x2, y2))
224
+ rect_width = ix2 - ix1
225
+ rect_height = iy2 - iy1
226
+ rect_expr = f"Rect(x={ix1}, y={iy1}, w={rect_width}, h={rect_height})"
227
+ else:
228
+ rect_expr = "None"
229
+ clean_path = unify_path(value.path)
230
+ default = f'"{clean_path}"'
231
+ path_expr = self._transform_path(clean_path, default)
232
+ w.write(f'{key} = ImageSlice(file_path={path_expr}, name="{display_name}", slice_rect={rect_expr})')
233
+ elif isinstance(value, (int, float, str, bool)):
234
+ w.write(f'{key} = {repr(value)}')
235
+
236
+ # 5. display_name 属性
237
+ display_name = node.metadata.get('display_name', node.name)
238
+ w.write(f'display_name = "{display_name}"')
239
+
240
+ def _render_prefab_class(self, node: ResourceNode, data: ImageAsset):
241
+ """
242
+ 渲染 TemplateMatchPrefab 嵌套类
243
+ """
244
+ w = self.writer
245
+ class_name = node.name
246
+
247
+ # 1. 类定义
248
+ w.write(f"class {class_name}(TemplateMatchPrefab):")
249
+
250
+ with w.indent():
251
+ # 2. Docstring
252
+ if not self.production:
253
+ self.render_docstring(node)
254
+
255
+ # 3. template 属性 (Image)
256
+ # 确保路径分隔符统一,避免 Windows 反斜杠问题
257
+ clean_path = unify_path(data.path)
258
+ rect_expr: str
259
+ if data.rect is not None:
260
+ x1, y1, x2, y2 = data.rect
261
+ ix1, iy1, ix2, iy2 = map(int, (x1, y1, x2, y2))
262
+ rect_width = ix2 - ix1
263
+ rect_height = iy2 - iy1
264
+ rect_expr = f"Rect(x={ix1}, y={iy1}, w={rect_width}, h={rect_height})"
265
+ else:
266
+ rect_expr = "None"
267
+ display_name = node.metadata.get('display_name', node.name)
268
+ default = f'"{clean_path}"'
269
+ path_expr = self._transform_path(clean_path, default)
270
+ w.write(f'template = ImageSlice(file_path={path_expr}, name="{display_name}", slice_rect={rect_expr})')
271
+
272
+ # 4. display_name 属性
273
+ # 优先从 metadata 取,如果没有则用变量名
274
+ w.write(f'display_name = "{display_name}"')
275
+
276
+ def _render_primitive_assignment(self, node: ResourceNode, data: Any):
277
+ """
278
+ 渲染 HintBox 或 HintPoint 的赋值语句
279
+ Example: MyBox = HintBox(x1=1, y1=2...)
280
+ """
281
+ # 1. 生成 Docstring (如果是非生产模式)
282
+ if not self.production:
283
+ # 对于属性赋值,docstring 通常写在上方,或者不写
284
+ # Python 标准是将 docstring 写在赋值语句下方,但这在类属性中不太常见
285
+ # 这里我们选择不为 HintBox 生成复杂的 docstring,或者作为注释生成
286
+ pass
287
+
288
+ # 2. 构造构造函数字符串
289
+ constructor_str = ""
290
+
291
+ if isinstance(data, BoxData):
292
+ constructor_str = (
293
+ f"HintBox("
294
+ f"x1={data.x1}, y1={data.y1}, "
295
+ f"x2={data.x2}, y2={data.y2}, "
296
+ f"source_resolution={data.resolution})"
297
+ )
298
+
299
+ elif isinstance(data, PointData):
300
+ constructor_str = f"HintPoint(x={data.x}, y={data.y})"
301
+
302
+ # 3. 写入代码
303
+ self.writer.write(f"{node.name} = {constructor_str}")
304
+
305
+ def render_docstring(self, attr: ResourceNode):
306
+ """
307
+ 重写文档渲染逻辑,支持 markdown 图片预览
308
+ """
309
+ w = self.writer
310
+ lines = []
311
+
312
+ # 基础描述
313
+ if attr.docstring:
314
+ lines.extend(attr.docstring.split('\n'))
315
+
316
+ # 图片预览 (仅当它是 ImageAsset 且有绝对路径用于 IDE 预览时)
317
+ # 注意:这里的 abs_path 需要 Parser 在 metadata 里额外塞进去,
318
+ # 因为 ImageAsset.path 可能已经是相对路径了。
319
+ if self.ide_type and isinstance(attr.value, ImageAsset):
320
+ preview_path = attr.metadata.get('origin_file') or attr.metadata.get('abs_path')
321
+ if preview_path:
322
+ lines.append("")
323
+ lines.append(self._make_img_tag(preview_path, "Preview"))
324
+
325
+ if not lines:
326
+ return
327
+
328
+ w.write('"""')
329
+ for line in lines:
330
+ w.write(line)
331
+ w.write('"""')
@@ -0,0 +1,94 @@
1
+ import contextlib
2
+ from dataclasses import dataclass, field
3
+ from typing import List, Any, Protocol, Dict, Optional, Union, Tuple
4
+
5
+ # --- 工具类: CodeWriter ---
6
+ class CodeWriter:
7
+ def __init__(self):
8
+ self._lines = []
9
+ self._indent_level = 0
10
+ self._indent_str = " "
11
+
12
+ def write(self, text: str):
13
+ """写入一行代码,自动处理缩进"""
14
+ self._lines.append((self._indent_str * self._indent_level) + text)
15
+
16
+ def write_empty_line(self):
17
+ self._lines.append("")
18
+
19
+ @contextlib.contextmanager
20
+ def indent(self):
21
+ """缩进上下文管理器"""
22
+ self._indent_level += 1
23
+ yield
24
+ self._indent_level -= 1
25
+
26
+ def get_content(self) -> str:
27
+ return "\n".join(self._lines)
28
+
29
+ # --- 中间表示 (IR) 数据结构 ---
30
+
31
+
32
+ @dataclass
33
+ class ImageAsset:
34
+ """代表图片资源的结构化数据"""
35
+ path: str
36
+ rect: Tuple[int, int, int, int] | None # (x1, y1, x2, y2)
37
+
38
+
39
+ @dataclass
40
+ class PrefabData:
41
+ """代表自定义 Prefab 资源的结构化数据
42
+ """
43
+ image: Optional[ImageAsset]
44
+ prefab_id: str
45
+ props: Dict[str, Any] = field(default_factory=dict)
46
+
47
+
48
+ @dataclass
49
+ class BoxData:
50
+ """代表矩形区域的结构化数据"""
51
+ x1: int
52
+ y1: int
53
+ x2: int
54
+ y2: int
55
+ resolution: Tuple[int, int] = (720, 1280)
56
+
57
+
58
+ @dataclass
59
+ class PointData:
60
+ """代表点的结构化数据"""
61
+ x: int
62
+ y: int
63
+
64
+
65
+ @dataclass
66
+ class ResourceNode:
67
+ """资源的最小单元 (Sprite, HintBox 等)。value 存放 IR 对象,而不是代码字符串。"""
68
+ name: str
69
+ type: str # 'template', 'hint-box', 'hint-point', 'prefab'
70
+ value: Union[ImageAsset, BoxData, PointData, PrefabData, Any]
71
+ docstring: str = ""
72
+ metadata: Dict[str, Any] = field(default_factory=dict) # 原始数据备份,用于扩展
73
+
74
+ @dataclass
75
+ class ClassNode:
76
+ """表示一个生成的类节点"""
77
+ name: str
78
+ children: List['ClassNode'] = field(default_factory=list)
79
+ attributes: List[ResourceNode] = field(default_factory=list)
80
+
81
+ def is_empty(self) -> bool:
82
+ return not self.children and not self.attributes
83
+
84
+ # --- 接口定义 ---
85
+
86
+ class SchemaParser(Protocol):
87
+ """解析器协议"""
88
+ def can_parse(self, file_path: str) -> bool:
89
+ """判断该解析器是否能处理此文件"""
90
+ ...
91
+
92
+ def parse(self, file_path: str, context: Dict[str, Any]) -> List[ResourceNode]:
93
+ """解析文件并返回资源列表。Context 可包含输出目录等配置"""
94
+ ...