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,99 @@
1
+ """
2
+ 彩色输出工具
3
+ """
4
+
5
+ from typing import Any
6
+
7
+ from rich.console import Console
8
+ from rich.panel import Panel
9
+ from rich.progress import Progress, SpinnerColumn, TextColumn
10
+ from rich.table import Table
11
+ from rich.tree import Tree
12
+
13
+ console = Console()
14
+
15
+
16
+ def print_success(message: str) -> None:
17
+ """打印成功消息"""
18
+ console.print(f"[bold green]✅ {message}[/bold green]")
19
+
20
+
21
+ def print_error(message: str) -> None:
22
+ """打印错误消息"""
23
+ console.print(f"[bold red]❌ {message}[/bold red]")
24
+
25
+
26
+ def print_warning(message: str) -> None:
27
+ """打印警告消息"""
28
+ console.print(f"[bold yellow]⚠️ {message}[/bold yellow]")
29
+
30
+
31
+ def print_info(message: str) -> None:
32
+ """打印信息消息"""
33
+ console.print(f"[bold blue]ℹ️ {message}[/bold blue]")
34
+
35
+
36
+ def print_step(message: str) -> None:
37
+ """打印步骤消息"""
38
+ console.print(f"[bold cyan]🔸 {message}[/bold cyan]")
39
+
40
+
41
+ def print_panel(title: str, content: str, style: str = "green") -> None:
42
+ """打印面板"""
43
+ console.print(Panel(content, title=title, style=style))
44
+
45
+
46
+ def print_table(title: str, columns: list[str], rows: list[list[str]]) -> None:
47
+ """打印表格"""
48
+ table = Table(title=title)
49
+
50
+ for col in columns:
51
+ table.add_column(col, style="cyan")
52
+
53
+ for row in rows:
54
+ table.add_row(*row)
55
+
56
+ console.print(table)
57
+
58
+
59
+ def print_tree(root_label: str, tree_data: dict[str, Any]) -> None:
60
+ """
61
+ 打印树形结构
62
+
63
+ Args:
64
+ root_label: 根节点标签
65
+ tree_data: 树形数据(字典或列表)
66
+ """
67
+ tree = Tree(f"[bold blue]{root_label}[/bold blue]")
68
+
69
+ def add_branch(parent: Tree, data: Any) -> None:
70
+ if isinstance(data, dict):
71
+ for key, value in data.items():
72
+ if isinstance(value, (dict, list)):
73
+ branch = parent.add(f"[cyan]{key}/[/cyan]")
74
+ add_branch(branch, value)
75
+ else:
76
+ parent.add(f"[green]{key}[/green]")
77
+ elif isinstance(data, list):
78
+ for item in data:
79
+ if isinstance(item, str):
80
+ parent.add(f"[green]{item}[/green]")
81
+ else:
82
+ add_branch(parent, item)
83
+
84
+ add_branch(tree, tree_data)
85
+ console.print(tree)
86
+
87
+
88
+ def create_progress() -> Progress:
89
+ """创建进度条"""
90
+ return Progress(
91
+ SpinnerColumn(),
92
+ TextColumn("[progress.description]{task.description}"),
93
+ console=console,
94
+ )
95
+
96
+
97
+ def print_divider(char: str = "━", length: int = 80) -> None:
98
+ """打印分割线"""
99
+ console.print(char * length, style="dim")
@@ -0,0 +1,171 @@
1
+ """
2
+ 配置加载器
3
+ """
4
+
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+ import toml
9
+
10
+
11
+ class ConfigLoader:
12
+ """配置加载器"""
13
+
14
+ def __init__(self, config_path: Path | str | None = None):
15
+ """
16
+ 初始化配置加载器
17
+
18
+ Args:
19
+ config_path: 配置文件路径
20
+ """
21
+ self.config_path = Path(config_path) if config_path else None
22
+ self.config: dict[str, Any] = {}
23
+
24
+ if self.config_path and self.config_path.exists():
25
+ self.load()
26
+
27
+ def load(self) -> dict[str, Any]:
28
+ """
29
+ 加载配置文件
30
+
31
+ Returns:
32
+ 配置字典
33
+ """
34
+ if not self.config_path or not self.config_path.exists():
35
+ return {}
36
+
37
+ self.config = toml.load(str(self.config_path))
38
+ return self.config
39
+
40
+ def get(self, key: str, default: Any = None) -> Any:
41
+ """
42
+ 获取配置项
43
+
44
+ 支持点号分隔的嵌套键,例如: "mpdt.check.level"
45
+
46
+ Args:
47
+ key: 配置键
48
+ default: 默认值
49
+
50
+ Returns:
51
+ 配置值
52
+ """
53
+ keys = key.split(".")
54
+ value = self.config
55
+
56
+ for k in keys:
57
+ if isinstance(value, dict) and k in value:
58
+ value = value[k]
59
+ else:
60
+ return default
61
+
62
+ return value
63
+
64
+ def set(self, key: str, value: Any) -> None:
65
+ """
66
+ 设置配置项
67
+
68
+ Args:
69
+ key: 配置键
70
+ value: 配置值
71
+ """
72
+ keys = key.split(".")
73
+ config = self.config
74
+
75
+ for k in keys[:-1]:
76
+ if k not in config:
77
+ config[k] = {}
78
+ config = config[k]
79
+
80
+ config[keys[-1]] = value
81
+
82
+ def save(self, path: Path | str | None = None) -> None:
83
+ """
84
+ 保存配置到文件
85
+
86
+ Args:
87
+ path: 保存路径,如果为 None 则使用初始化时的路径
88
+ """
89
+ save_path = Path(path) if path else self.config_path
90
+
91
+ if not save_path:
92
+ raise ValueError("未指定配置文件路径")
93
+
94
+ save_path.parent.mkdir(parents=True, exist_ok=True)
95
+
96
+ with open(save_path, "w", encoding="utf-8") as f:
97
+ toml.dump(self.config, f)
98
+
99
+
100
+ def load_mpdt_config(project_dir: Path | str = ".") -> ConfigLoader:
101
+ """
102
+ 加载 MPDT 配置文件
103
+
104
+ 优先级:
105
+ 1. .mpdtrc.toml
106
+ 2. pyproject.toml 中的 [tool.mpdt] 部分
107
+
108
+ Args:
109
+ project_dir: 项目目录
110
+
111
+ Returns:
112
+ ConfigLoader 对象
113
+ """
114
+ project_dir = Path(project_dir)
115
+
116
+ # 首先尝试加载 .mpdtrc.toml
117
+ mpdtrc_path = project_dir / ".mpdtrc.toml"
118
+ if mpdtrc_path.exists():
119
+ return ConfigLoader(mpdtrc_path)
120
+
121
+ # 然后尝试从 pyproject.toml 加载
122
+ pyproject_path = project_dir / "pyproject.toml"
123
+ if pyproject_path.exists():
124
+ config = toml.load(str(pyproject_path))
125
+ if "tool" in config and "mpdt" in config["tool"]:
126
+ loader = ConfigLoader()
127
+ loader.config = config["tool"]["mpdt"]
128
+ return loader
129
+
130
+ # 返回空配置
131
+ return ConfigLoader()
132
+
133
+
134
+ def get_default_config() -> dict[str, Any]:
135
+ """
136
+ 获取默认配置
137
+
138
+ Returns:
139
+ 默认配置字典
140
+ """
141
+ return {
142
+ "mpdt": {
143
+ "version": "0.1.2",
144
+ },
145
+ "check": {
146
+ "level": "warning",
147
+ "auto_fix": False,
148
+ "ignore_patterns": ["tests/*", "*.pyc", "__pycache__/*"],
149
+ },
150
+ "test": {
151
+ "coverage_threshold": 80,
152
+ "pytest_args": ["-v", "--tb=short"],
153
+ },
154
+ "build": {
155
+ "output_dir": "dist",
156
+ "include_docs": True,
157
+ "include_tests": False,
158
+ "format": "zip",
159
+ },
160
+ "dev": {
161
+ "port": 8080,
162
+ "host": "127.0.0.1",
163
+ "reload": True,
164
+ "check_on_save": True,
165
+ },
166
+ "templates": {
167
+ "author": "",
168
+ "license": "GPL-v3.0",
169
+ "python_version": "^3.11",
170
+ },
171
+ }
@@ -0,0 +1,297 @@
1
+ """
2
+ MPDT 配置管理器
3
+ 管理 mofox 路径、虚拟环境等配置信息
4
+ """
5
+
6
+ import os
7
+ from pathlib import Path
8
+ from typing import Literal
9
+
10
+ try:
11
+ import tomli
12
+ import tomli_w
13
+ except ImportError:
14
+ # 如果没有安装 tomli,使用 toml
15
+ try:
16
+ import toml as tomli
17
+ import toml as tomli_w
18
+ except ImportError:
19
+ tomli = None
20
+ tomli_w = None
21
+
22
+ VenvType = Literal["venv", "uv", "conda", "poetry", "none"]
23
+
24
+
25
+ class MPDTConfig:
26
+ """MPDT 配置管理器"""
27
+
28
+ def __init__(self, config_path: Path | None = None):
29
+ """初始化配置管理器
30
+
31
+ Args:
32
+ config_path: 配置文件路径,默认为 ~/.mpdt/config.toml
33
+ """
34
+ if config_path is None:
35
+ config_path = Path.home() / ".mpdt" / "config.toml"
36
+
37
+ self.config_path = config_path
38
+ self._config: dict = {}
39
+
40
+ # 加载配置
41
+ if self.config_path.exists():
42
+ self.load()
43
+
44
+ def load(self) -> None:
45
+ """加载配置文件"""
46
+ if not self.config_path.exists():
47
+ self._config = {}
48
+ return
49
+
50
+ if tomli is None:
51
+ raise ImportError("需要安装 tomli 库来读取配置文件")
52
+
53
+ with open(self.config_path, "rb") as f:
54
+ self._config = tomli.load(f)
55
+
56
+ def save(self) -> None:
57
+ """保存配置文件"""
58
+ if tomli_w is None:
59
+ raise ImportError("需要安装 tomli-w 库来写入配置文件")
60
+
61
+ # 确保目录存在
62
+ self.config_path.parent.mkdir(parents=True, exist_ok=True)
63
+
64
+ with open(self.config_path, "wb") as f:
65
+ tomli_w.dump(self._config, f)
66
+
67
+ @property
68
+ def mofox_path(self) -> Path | None:
69
+ """获取 mofox 主程序路径"""
70
+ path_str = self._config.get("mofox", {}).get("path")
71
+ return Path(path_str) if path_str else None
72
+
73
+ @mofox_path.setter
74
+ def mofox_path(self, path: Path | str) -> None:
75
+ """设置 mofox 主程序路径"""
76
+ if "mofox" not in self._config:
77
+ self._config["mofox"] = {}
78
+ self._config["mofox"]["path"] = str(Path(path).absolute())
79
+
80
+ @property
81
+ def venv_path(self) -> Path | None:
82
+ """获取虚拟环境路径"""
83
+ path_str = self._config.get("mofox", {}).get("venv_path")
84
+ return Path(path_str) if path_str else None
85
+
86
+ @venv_path.setter
87
+ def venv_path(self, path: Path | str | None) -> None:
88
+ """设置虚拟环境路径"""
89
+ if "mofox" not in self._config:
90
+ self._config["mofox"] = {}
91
+ if path is None:
92
+ self._config["mofox"]["venv_path"] = None
93
+ else:
94
+ self._config["mofox"]["venv_path"] = str(Path(path).absolute())
95
+
96
+ @property
97
+ def venv_type(self) -> VenvType:
98
+ """获取虚拟环境类型"""
99
+ return self._config.get("mofox", {}).get("venv_type", "venv")
100
+
101
+ @venv_type.setter
102
+ def venv_type(self, venv_type: VenvType) -> None:
103
+ """设置虚拟环境类型"""
104
+ if "mofox" not in self._config:
105
+ self._config["mofox"] = {}
106
+ self._config["mofox"]["venv_type"] = venv_type
107
+
108
+ @property
109
+ def auto_reload(self) -> bool:
110
+ """是否自动重载"""
111
+ return self._config.get("dev", {}).get("auto_reload", True)
112
+
113
+ @auto_reload.setter
114
+ def auto_reload(self, value: bool) -> None:
115
+ """设置是否自动重载"""
116
+ if "dev" not in self._config:
117
+ self._config["dev"] = {}
118
+ self._config["dev"]["auto_reload"] = value
119
+
120
+ @property
121
+ def reload_delay(self) -> float:
122
+ """重载延迟(秒)"""
123
+ return self._config.get("dev", {}).get("reload_delay", 0.3)
124
+
125
+ @reload_delay.setter
126
+ def reload_delay(self, value: float) -> None:
127
+ """设置重载延迟"""
128
+ if "dev" not in self._config:
129
+ self._config["dev"] = {}
130
+ self._config["dev"]["reload_delay"] = value
131
+
132
+ def get_python_command(self) -> list[str]:
133
+ """获取 Python 启动命令
134
+
135
+ Returns:
136
+ Python 命令列表,例如:
137
+ - ["E:/venv/Scripts/python.exe"]
138
+ - ["conda", "run", "-p", "E:/conda_env", "python"]
139
+ - ["poetry", "run", "python"]
140
+ """
141
+ venv_type = self.venv_type
142
+ venv_path = self.venv_path
143
+
144
+ if venv_type == "none" or not venv_path:
145
+ return ["python"]
146
+
147
+ if venv_type == "venv" or venv_type == "uv":
148
+ # venv 和 uv 使用相同的结构
149
+ if os.name == "nt": # Windows
150
+ python_exe = venv_path / "Scripts" / "python.exe"
151
+ else: # Unix-like
152
+ python_exe = venv_path / "bin" / "python"
153
+
154
+ if python_exe.exists():
155
+ return [str(python_exe)]
156
+ else:
157
+ # 降级到系统 Python
158
+ return ["python"]
159
+
160
+ elif venv_type == "conda":
161
+ return ["conda", "run", "-p", str(venv_path), "python"]
162
+
163
+ elif venv_type == "poetry":
164
+ # poetry 需要在 mofox 目录中执行
165
+ return ["poetry", "run", "python"]
166
+
167
+ else:
168
+ return ["python"]
169
+
170
+ def validate(self) -> tuple[bool, list[str]]:
171
+ """验证配置
172
+
173
+ Returns:
174
+ (是否有效, 错误消息列表)
175
+ """
176
+ errors = []
177
+
178
+ # 检查 mofox 路径
179
+ if not self.mofox_path:
180
+ errors.append("未配置 mofox 主程序路径")
181
+ elif not self.mofox_path.exists():
182
+ errors.append(f"mofox 路径不存在: {self.mofox_path}")
183
+ else:
184
+ # 检查是否有 bot.py
185
+ bot_file = self.mofox_path / "bot.py"
186
+ if not bot_file.exists():
187
+ errors.append(f"未找到 bot.py: {bot_file}")
188
+
189
+ # 检查虚拟环境
190
+ if self.venv_type != "none" and self.venv_path:
191
+ if not self.venv_path.exists():
192
+ errors.append(f"虚拟环境路径不存在: {self.venv_path}")
193
+ else:
194
+ # 检查 Python 可执行文件
195
+ python_cmd = self.get_python_command()
196
+ if self.venv_type in ["venv", "uv"]:
197
+ python_path = Path(python_cmd[0])
198
+ if not python_path.exists():
199
+ errors.append(f"Python 可执行文件不存在: {python_path}")
200
+
201
+ return len(errors) == 0, errors
202
+
203
+ def is_configured(self) -> bool:
204
+ """检查是否已配置"""
205
+ return self.mofox_path is not None
206
+
207
+
208
+ def get_default_config() -> MPDTConfig:
209
+ """获取默认配置实例"""
210
+ return MPDTConfig()
211
+
212
+
213
+ def interactive_config() -> MPDTConfig:
214
+ """交互式配置向导
215
+
216
+ Returns:
217
+ 配置好的 MPDTConfig 实例
218
+ """
219
+ from rich.console import Console
220
+ from rich.panel import Panel
221
+ from rich.prompt import Confirm, Prompt
222
+
223
+ console = Console()
224
+ config = MPDTConfig()
225
+
226
+ console.print(Panel.fit(
227
+ "[bold cyan]MPDT 配置向导[/bold cyan]\n\n"
228
+ "让我们配置 MoFox 主程序的路径和虚拟环境"
229
+ ))
230
+
231
+ # 配置 mofox 路径
232
+ while True:
233
+ mofox_path_str = Prompt.ask(
234
+ "\n[bold]请输入 mofox 主程序路径[/bold]",
235
+ default=str(Path.cwd().parent / "mofox") if Path.cwd().parent.name != "mofox" else str(Path.cwd())
236
+ )
237
+ mofox_path = Path(mofox_path_str).expanduser().absolute()
238
+
239
+ if not mofox_path.exists():
240
+ console.print(f"[red]路径不存在: {mofox_path}[/red]")
241
+ if not Confirm.ask("重新输入?", default=True):
242
+ break
243
+ continue
244
+
245
+ bot_file = mofox_path / "bot.py"
246
+ if not bot_file.exists():
247
+ console.print("[yellow]警告: 未找到 bot.py[/yellow]")
248
+ if not Confirm.ask("仍然使用此路径?", default=False):
249
+ continue
250
+
251
+ config.mofox_path = mofox_path
252
+ console.print(f"[green]✓ mofox 路径已设置: {mofox_path}[/green]")
253
+ break
254
+
255
+ # 配置虚拟环境
256
+ console.print("\n[bold]虚拟环境配置[/bold]")
257
+ venv_type_choice = Prompt.ask(
258
+ "请选择虚拟环境类型",
259
+ choices=["venv", "uv", "conda", "poetry", "none"],
260
+ default="venv"
261
+ )
262
+ config.venv_type = venv_type_choice
263
+
264
+ if venv_type_choice != "none":
265
+ if venv_type_choice == "poetry":
266
+ console.print("[cyan]使用 poetry,将在 mofox 目录中执行命令[/cyan]")
267
+ config.venv_path = config.mofox_path
268
+ else:
269
+ default_venv_path = str(config.mofox_path.parent / "venv")
270
+ if venv_type_choice == "uv":
271
+ default_venv_path = str(config.mofox_path.parent / ".venv")
272
+ elif venv_type_choice == "conda":
273
+ default_venv_path = str(config.mofox_path.parent / "conda_env")
274
+
275
+ venv_path_str = Prompt.ask(
276
+ f"请输入 {venv_type_choice} 虚拟环境路径",
277
+ default=default_venv_path
278
+ )
279
+ venv_path = Path(venv_path_str).expanduser().absolute()
280
+
281
+ if not venv_path.exists():
282
+ console.print(f"[yellow]警告: 虚拟环境路径不存在: {venv_path}[/yellow]")
283
+ if not Confirm.ask("仍然使用此路径?", default=True):
284
+ config.venv_type = "none"
285
+ else:
286
+ config.venv_path = venv_path
287
+ else:
288
+ config.venv_path = venv_path
289
+ console.print(f"[green]✓ 虚拟环境路径已设置: {venv_path}[/green]")
290
+ else:
291
+ console.print("[cyan]将使用系统 Python[/cyan]")
292
+
293
+ # 保存配置
294
+ config.save()
295
+ console.print(f"\n[bold green]✓ 配置已保存: {config.config_path}[/bold green]")
296
+
297
+ return config