mofox-plugin-dev-toolkit 0.3.3__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 (46) hide show
  1. mofox_plugin_dev_toolkit-0.3.3.dist-info/METADATA +730 -0
  2. mofox_plugin_dev_toolkit-0.3.3.dist-info/RECORD +46 -0
  3. mofox_plugin_dev_toolkit-0.3.3.dist-info/WHEEL +5 -0
  4. mofox_plugin_dev_toolkit-0.3.3.dist-info/entry_points.txt +2 -0
  5. mofox_plugin_dev_toolkit-0.3.3.dist-info/licenses/LICENSE +674 -0
  6. mofox_plugin_dev_toolkit-0.3.3.dist-info/top_level.txt +1 -0
  7. mpdt/__init__.py +15 -0
  8. mpdt/__main__.py +8 -0
  9. mpdt/cli.py +316 -0
  10. mpdt/commands/__init__.py +9 -0
  11. mpdt/commands/check.py +498 -0
  12. mpdt/commands/dev.py +318 -0
  13. mpdt/commands/generate.py +448 -0
  14. mpdt/commands/init.py +686 -0
  15. mpdt/dev/bridge_plugin/__init__.py +17 -0
  16. mpdt/dev/bridge_plugin/cleanup_handler.py +65 -0
  17. mpdt/dev/bridge_plugin/dev_config.py +24 -0
  18. mpdt/dev/bridge_plugin/file_watcher.py +169 -0
  19. mpdt/dev/bridge_plugin/plugin.py +219 -0
  20. mpdt/templates/__init__.py +165 -0
  21. mpdt/templates/action_template.py +102 -0
  22. mpdt/templates/adapter_template.py +129 -0
  23. mpdt/templates/chatter_template.py +103 -0
  24. mpdt/templates/event_template.py +116 -0
  25. mpdt/templates/plus_command_template.py +150 -0
  26. mpdt/templates/prompt_template.py +92 -0
  27. mpdt/templates/router_template.py +175 -0
  28. mpdt/templates/tool_template.py +98 -0
  29. mpdt/utils/__init__.py +10 -0
  30. mpdt/utils/code_parser.py +401 -0
  31. mpdt/utils/color_printer.py +99 -0
  32. mpdt/utils/config_loader.py +171 -0
  33. mpdt/utils/config_manager.py +297 -0
  34. mpdt/utils/file_ops.py +207 -0
  35. mpdt/utils/license_generator.py +980 -0
  36. mpdt/utils/plugin_parser.py +195 -0
  37. mpdt/utils/template_engine.py +112 -0
  38. mpdt/validators/__init__.py +26 -0
  39. mpdt/validators/auto_fix_validator.py +990 -0
  40. mpdt/validators/base.py +129 -0
  41. mpdt/validators/component_validator.py +842 -0
  42. mpdt/validators/config_validator.py +119 -0
  43. mpdt/validators/metadata_validator.py +107 -0
  44. mpdt/validators/structure_validator.py +72 -0
  45. mpdt/validators/style_validator.py +117 -0
  46. mpdt/validators/type_validator.py +206 -0
@@ -0,0 +1,195 @@
1
+ """
2
+ 插件名称解析器
3
+ 使用 AST 解析插件文件,提取运行时插件名称
4
+ """
5
+
6
+ import ast
7
+ from pathlib import Path
8
+
9
+
10
+ def extract_plugin_name(plugin_path: Path) -> str | None:
11
+ """从插件目录提取运行时插件名称
12
+
13
+ Args:
14
+ plugin_path: 插件目录路径
15
+
16
+ Returns:
17
+ 插件名称,如果解析失败返回 None
18
+
19
+ Example:
20
+ >>> extract_plugin_name(Path("my_awesome_plugin"))
21
+ "awesome_plugin" # 从 plugin.py 中的 plugin_name 属性读取
22
+ """
23
+ plugin_file = plugin_path / "plugin.py"
24
+
25
+ if not plugin_file.exists():
26
+ return None
27
+
28
+ try:
29
+ with open(plugin_file, encoding="utf-8") as f:
30
+ source = f.read()
31
+
32
+ tree = ast.parse(source)
33
+
34
+ # 查找 BasePlugin 的子类
35
+ for node in ast.walk(tree):
36
+ if isinstance(node, ast.ClassDef):
37
+ # 检查是否继承自 BasePlugin
38
+ is_base_plugin = False
39
+ for base in node.bases:
40
+ if isinstance(base, ast.Name) and base.id == "BasePlugin":
41
+ is_base_plugin = True
42
+ break
43
+
44
+ if not is_base_plugin:
45
+ continue
46
+
47
+ # 查找 plugin_name 属性
48
+ for item in node.body:
49
+ # 处理普通赋值: plugin_name = "xxx"
50
+ if isinstance(item, ast.Assign):
51
+ for target in item.targets:
52
+ if isinstance(target, ast.Name) and target.id == "plugin_name":
53
+ if isinstance(item.value, ast.Constant):
54
+ return item.value.value
55
+
56
+ # 处理带类型注解的赋值: plugin_name: str = "xxx"
57
+ elif isinstance(item, ast.AnnAssign):
58
+ if isinstance(item.target, ast.Name) and item.target.id == "plugin_name":
59
+ if item.value and isinstance(item.value, ast.Constant):
60
+ return item.value.value
61
+
62
+ return None
63
+
64
+ except Exception:
65
+ return None
66
+
67
+
68
+ def get_plugin_info(plugin_path: Path) -> dict:
69
+ """获取插件详细信息
70
+
71
+ Args:
72
+ plugin_path: 插件目录路径
73
+
74
+ Returns:
75
+ 包含插件信息的字典:
76
+ {
77
+ "dir_name": "my_awesome_plugin",
78
+ "plugin_name": "awesome_plugin",
79
+ "class_name": "MyAwesomePlugin",
80
+ "path": "/path/to/plugin",
81
+ "has_plugin_file": True,
82
+ "parse_success": True
83
+ }
84
+ """
85
+ info = {
86
+ "dir_name": plugin_path.name,
87
+ "plugin_name": None,
88
+ "class_name": None,
89
+ "path": str(plugin_path.absolute()),
90
+ "has_plugin_file": False,
91
+ "parse_success": False,
92
+ }
93
+
94
+ plugin_file = plugin_path / "plugin.py"
95
+
96
+ if not plugin_file.exists():
97
+ return info
98
+
99
+ info["has_plugin_file"] = True
100
+
101
+ try:
102
+ with open(plugin_file, encoding="utf-8") as f:
103
+ source = f.read()
104
+
105
+ tree = ast.parse(source)
106
+
107
+ # 查找 BasePlugin 的子类
108
+ for node in ast.walk(tree):
109
+ if isinstance(node, ast.ClassDef):
110
+ # 检查是否继承自 BasePlugin
111
+ is_base_plugin = False
112
+ for base in node.bases:
113
+ if isinstance(base, ast.Name) and base.id == "BasePlugin":
114
+ is_base_plugin = True
115
+ break
116
+
117
+ if not is_base_plugin:
118
+ continue
119
+
120
+ info["class_name"] = node.name
121
+
122
+ # 查找 plugin_name 属性
123
+ for item in node.body:
124
+ # 处理普通赋值: plugin_name = "xxx"
125
+ if isinstance(item, ast.Assign):
126
+ for target in item.targets:
127
+ if isinstance(target, ast.Name) and target.id == "plugin_name":
128
+ if isinstance(item.value, ast.Constant):
129
+ info["plugin_name"] = item.value.value
130
+ info["parse_success"] = True
131
+ break
132
+
133
+ # 处理带类型注解的赋值: plugin_name: str = "xxx"
134
+ elif isinstance(item, ast.AnnAssign):
135
+ if isinstance(item.target, ast.Name) and item.target.id == "plugin_name":
136
+ if item.value and isinstance(item.value, ast.Constant):
137
+ info["plugin_name"] = item.value.value
138
+ info["parse_success"] = True
139
+ break
140
+
141
+ # 找到 BasePlugin 子类后跳出
142
+ if info["class_name"]:
143
+ break
144
+
145
+ return info
146
+
147
+ except Exception as e:
148
+ info["error"] = str(e)
149
+ return info
150
+
151
+
152
+ def validate_plugin_structure(plugin_path: Path) -> tuple[bool, list[str]]:
153
+ """验证插件目录结构
154
+
155
+ Args:
156
+ plugin_path: 插件目录路径
157
+
158
+ Returns:
159
+ (是否有效, 错误/警告消息列表)
160
+ """
161
+ messages = []
162
+
163
+ if not plugin_path.is_dir():
164
+ return False, ["路径不是一个目录"]
165
+
166
+ # 检查必需文件
167
+ required_files = {"plugin.py": "插件主文件", "__init__.py": "包初始化文件"}
168
+
169
+ for filename, description in required_files.items():
170
+ file_path = plugin_path / filename
171
+ if not file_path.exists():
172
+ messages.append(f"缺少 {description}: {filename}")
173
+
174
+ # 如果缺少必需文件,直接返回
175
+ if messages:
176
+ return False, messages
177
+
178
+ # 检查 plugin.py 是否可以解析
179
+ plugin_name = extract_plugin_name(plugin_path)
180
+ if not plugin_name:
181
+ messages.append("无法从 plugin.py 中提取 plugin_name")
182
+ messages.append("请确保有一个继承自 BasePlugin 的类,并定义了 plugin_name 属性")
183
+ return False, messages
184
+
185
+ # 检查推荐文件
186
+ recommended_files = {"config.toml": "配置文件", "README.md": "说明文档"}
187
+
188
+ for filename, description in recommended_files.items():
189
+ file_path = plugin_path / filename
190
+ if not file_path.exists():
191
+ messages.append(f"建议添加 {description}: {filename}")
192
+
193
+ # 如果只有建议性消息,仍然返回有效
194
+ has_errors = any("缺少" in msg or "无法" in msg for msg in messages)
195
+ return not has_errors, messages
@@ -0,0 +1,112 @@
1
+ """
2
+ 模板引擎
3
+ """
4
+
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+ from jinja2 import Environment, FileSystemLoader, Template
9
+
10
+
11
+ class TemplateEngine:
12
+ """模板引擎类"""
13
+
14
+ def __init__(self, template_dir: Path | str | None = None):
15
+ """
16
+ 初始化模板引擎
17
+
18
+ Args:
19
+ template_dir: 模板目录路径
20
+ """
21
+ if template_dir:
22
+ self.template_dir = Path(template_dir)
23
+ self.env = Environment(
24
+ loader=FileSystemLoader(str(self.template_dir)),
25
+ trim_blocks=True,
26
+ lstrip_blocks=True,
27
+ )
28
+ else:
29
+ # 使用默认模板目录
30
+ default_template_dir = Path(__file__).parent.parent / "templates"
31
+ self.template_dir = default_template_dir
32
+
33
+ if default_template_dir.exists():
34
+ self.env = Environment(
35
+ loader=FileSystemLoader(str(default_template_dir)),
36
+ trim_blocks=True,
37
+ lstrip_blocks=True,
38
+ )
39
+ else:
40
+ self.env = None
41
+
42
+ def render_string(self, template_string: str, context: dict[str, Any]) -> str:
43
+ """
44
+ 渲染字符串模板
45
+
46
+ Args:
47
+ template_string: 模板字符串
48
+ context: 模板变量
49
+
50
+ Returns:
51
+ 渲染后的字符串
52
+ """
53
+ template = Template(template_string, trim_blocks=True, lstrip_blocks=True)
54
+ return template.render(**context)
55
+
56
+ def render_file(self, template_name: str, context: dict[str, Any]) -> str:
57
+ """
58
+ 渲染文件模板
59
+
60
+ Args:
61
+ template_name: 模板文件名
62
+ context: 模板变量
63
+
64
+ Returns:
65
+ 渲染后的字符串
66
+ """
67
+ if self.env is None:
68
+ raise ValueError("模板目录不存在,无法加载文件模板")
69
+
70
+ template = self.env.get_template(template_name)
71
+ return template.render(**context)
72
+
73
+ def render_to_file(self, template_name: str, context: dict[str, Any], output_path: Path | str) -> None:
74
+ """
75
+ 渲染模板并写入文件
76
+
77
+ Args:
78
+ template_name: 模板文件名
79
+ context: 模板变量
80
+ output_path: 输出文件路径
81
+ """
82
+ content = self.render_file(template_name, context)
83
+ output_path = Path(output_path)
84
+ output_path.parent.mkdir(parents=True, exist_ok=True)
85
+ output_path.write_text(content, encoding="utf-8")
86
+
87
+
88
+ def prepare_common_context(**kwargs: Any) -> dict[str, Any]:
89
+ """
90
+ 准备通用模板变量
91
+
92
+ Args:
93
+ **kwargs: 额外的变量
94
+
95
+ Returns:
96
+ 模板变量字典
97
+ """
98
+ from datetime import datetime
99
+
100
+ from mpdt.utils.file_ops import get_git_user_info
101
+
102
+ git_info = get_git_user_info()
103
+
104
+ context = {
105
+ "timestamp": datetime.now().isoformat(),
106
+ "year": datetime.now().year,
107
+ "author": git_info.get("name", ""),
108
+ "author_email": git_info.get("email", ""),
109
+ }
110
+
111
+ context.update(kwargs)
112
+ return context
@@ -0,0 +1,26 @@
1
+ """
2
+ 验证器模块
3
+ """
4
+
5
+ from .auto_fix_validator import AutoFixValidator
6
+ from .base import BaseValidator, ValidationIssue, ValidationLevel, ValidationResult
7
+ from .component_validator import ComponentValidator
8
+ from .config_validator import ConfigValidator
9
+ from .metadata_validator import MetadataValidator
10
+ from .structure_validator import StructureValidator
11
+ from .style_validator import StyleValidator
12
+ from .type_validator import TypeValidator
13
+
14
+ __all__ = [
15
+ "BaseValidator",
16
+ "ValidationResult",
17
+ "ValidationIssue",
18
+ "ValidationLevel",
19
+ "StructureValidator",
20
+ "MetadataValidator",
21
+ "ComponentValidator",
22
+ "ConfigValidator",
23
+ "StyleValidator",
24
+ "TypeValidator",
25
+ "AutoFixValidator",
26
+ ]