ErisPulse 1.2.9__py3-none-any.whl → 2.1.1__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.
ErisPulse/__main__.py CHANGED
@@ -1,360 +1,253 @@
1
1
  """
2
2
  # CLI 入口
3
3
 
4
- 提供命令行界面(CLI)用于模块管理、源管理和开发调试。
5
-
6
- ## 主要功能
7
- - 模块管理: 安装/卸载/启用/禁用
8
- - 源管理: 添加/删除/更新源
9
- - 热重载: 开发时自动重启
10
- - 彩色终端输出
4
+ 提供命令行界面(CLI)用于包管理和启动入口。
11
5
 
12
6
  ## 主要命令
13
- ### 模块管理:
14
- init: 初始化SDK
15
- install: 安装模块
16
- uninstall: 卸载模块
17
- enable: 启用模块
18
- disable: 禁用模块
19
- list: 列出模块
20
- update: 更新模块列表
21
- upgrade: 升级模块
22
-
23
- ### 源管理:
24
- origin add: 添加源
25
- origin del: 删除源
26
- origin list: 列出源
27
-
28
- ### 开发调试:
7
+ ### 包管理:
8
+ install: 安装模块/适配器包
9
+ uninstall: 卸载模块/适配器包
10
+ list: 列出已安装的模块/适配器
11
+ list-remote: 列出远程PyPI上的ErisPulse模块和适配器
12
+ upgrade: 升级所有模块/适配器
13
+
14
+ ### 启动:
29
15
  run: 运行脚本
30
16
  --reload: 启用热重载
31
17
 
32
18
  ### 示例用法:
33
-
34
19
  ```
35
20
  # 安装模块
36
- epsdk install MyModule
21
+ epsdk install Yunhu
37
22
 
38
23
  # 启用热重载
39
24
  epsdk run main.py --reload
40
-
41
- # 管理源
42
- epsdk origin add https://example.com/map.json
43
25
  ```
44
-
45
26
  """
46
27
 
47
28
  import argparse
48
- import importlib
49
- import os
29
+ import importlib.metadata
30
+ import subprocess
50
31
  import sys
32
+ import os
51
33
  import time
52
- import shutil
53
- import aiohttp
54
- import zipfile
55
- import fnmatch
56
- import asyncio
57
- import subprocess
58
- import json
59
34
  import json
60
- from .db import env
61
- from .mods import mods
35
+ import asyncio
36
+ from urllib.parse import urlparse
37
+ from typing import List, Dict, Tuple, Optional
38
+ from importlib.metadata import version, PackageNotFoundError
62
39
  from watchdog.observers import Observer
63
40
  from watchdog.events import FileSystemEventHandler
41
+ from .Core.shellprint import shellprint, Shell_Printer
64
42
 
65
- class Shell_Printer:
66
- # ANSI 颜色代码
67
- RESET = "\033[0m"
68
- BOLD = "\033[1m"
69
- RED = "\033[91m"
70
- GREEN = "\033[92m"
71
- YELLOW = "\033[93m"
72
- BLUE = "\033[94m"
73
- MAGENTA = "\033[95m"
74
- CYAN = "\033[96m"
75
- WHITE = "\033[97m"
76
- DIM = "\033[2m"
77
- UNDERLINE = "\033[4m"
78
- BG_BLUE = "\033[44m"
79
- BG_GREEN = "\033[42m"
80
- BG_YELLOW = "\033[43m"
81
- BG_RED = "\033[41m"
82
-
83
- def __init__(self):
84
- pass
85
-
86
- @classmethod
87
- def _get_color(cls, level):
88
- return {
89
- "info": cls.CYAN,
90
- "success": cls.GREEN,
91
- "warning": cls.YELLOW,
92
- "error": cls.RED,
93
- "title": cls.MAGENTA,
94
- "default": cls.RESET,
95
- }.get(level, cls.RESET)
96
-
97
- @classmethod
98
- def panel(cls, msg: str, title: str = None, level: str = "info") -> None:
99
- color = cls._get_color(level)
100
- width = 70
101
- border_char = "─" * width
102
-
103
- if level == "error":
104
- border_char = "═" * width
105
- msg = f"{cls.RED}✗ {msg}{cls.RESET}"
106
- elif level == "warning":
107
- border_char = "─" * width
108
- msg = f"{cls.YELLOW}⚠ {msg}{cls.RESET}"
109
-
110
- title_line = ""
111
- if title:
112
- title = f" {title.upper()} "
113
- title_padding = (width - len(title)) // 2
114
- left_pad = " " * title_padding
115
- right_pad = " " * (width - len(title) - title_padding)
116
- title_line = f"{cls.DIM}┌{left_pad}{cls.BOLD}{color}{title}{cls.RESET}{cls.DIM}{right_pad}┐{cls.RESET}\n"
117
-
118
- lines = []
119
- for line in msg.split("\n"):
120
- if len(line) > width - 4:
121
- words = line.split()
122
- current_line = ""
123
- for word in words:
124
- if len(current_line) + len(word) + 1 > width - 4:
125
- lines.append(f"{cls.DIM}│{cls.RESET} {current_line.ljust(width-4)} {cls.DIM}│{cls.RESET}")
126
- current_line = word
127
- else:
128
- current_line += (" " + word) if current_line else word
129
- if current_line:
130
- lines.append(f"{cls.DIM}│{cls.RESET} {current_line.ljust(width-4)} {cls.DIM}│{cls.RESET}")
131
- else:
132
- lines.append(f"{cls.DIM}│{cls.RESET} {line.ljust(width-4)} {cls.DIM}│{cls.RESET}")
133
-
134
- if level == "error":
135
- border_style = "╘"
136
- elif level == "warning":
137
- border_style = "╧"
138
- else:
139
- border_style = "└"
140
- bottom_border = f"{cls.DIM}{border_style}{border_char}┘{cls.RESET}"
141
-
142
- panel = f"{title_line}"
143
- panel += f"{cls.DIM}├{border_char}┤{cls.RESET}\n"
144
- panel += "\n".join(lines) + "\n"
145
- panel += f"{bottom_border}\n"
43
+ class PyPIManager:
44
+ """
45
+ PyPI包管理器
46
+
47
+ 负责与PyPI交互,包括搜索、安装、卸载和升级ErisPulse模块/适配器
48
+
49
+ {!--< tips >!--}
50
+ 1. 支持多个远程源作为备份
51
+ 2. 自动区分模块和适配器
52
+ 3. 提供详细的错误处理
53
+ {!--< /tips >!--}
54
+ """
55
+
56
+ REMOTE_SOURCES = [
57
+ "https://erisdev.com/packages.json",
58
+ "https://raw.githubusercontent.com/ErisPulse/ErisPulse-ModuleRepo/main/packages.json"
59
+ ]
60
+
61
+ @staticmethod
62
+ async def get_remote_packages() -> dict:
63
+ """
64
+ 获取远程包列表
146
65
 
147
- print(panel)
66
+ 从配置的远程源获取所有可用的ErisPulse模块和适配器
148
67
 
149
- @classmethod
150
- def table(cls, headers, rows, title=None, level="info") -> None:
151
- color = cls._get_color(level)
152
- if title:
153
- print(f"{cls.BOLD}{color}== {title} =={cls.RESET}")
154
-
155
- col_widths = [len(h) for h in headers]
156
- for row in rows:
157
- for i, cell in enumerate(row):
158
- col_widths[i] = max(col_widths[i], len(str(cell)))
159
-
160
- fmt = "│".join(f" {{:<{w}}} " for w in col_widths)
161
-
162
- top_border = "┌" + "┬".join("─" * (w+2) for w in col_widths) + "┐"
163
- print(f"{cls.DIM}{top_border}{cls.RESET}")
164
-
165
- header_line = fmt.format(*headers)
166
- print(f"{cls.BOLD}{color}│{header_line}│{cls.RESET}")
68
+ :return:
69
+ Dict[str, Dict]: 包含模块和适配器的字典
70
+ - modules: 模块字典 {模块名: 模块信息}
71
+ - adapters: 适配器字典 {适配器名: 适配器信息}
72
+
73
+ :raises ClientError: 当网络请求失败时抛出
74
+ :raises asyncio.TimeoutError: 当请求超时时抛出
75
+ """
76
+
77
+ import aiohttp
78
+ from aiohttp import ClientError, ClientTimeout
167
79
 
168
- separator = "├" + "┼".join("─" * (w+2) for w in col_widths) + "┤"
169
- print(f"{cls.DIM}{separator}{cls.RESET}")
80
+ timeout = ClientTimeout(total=5)
81
+ last_error = None
170
82
 
171
- for row in rows:
172
- row_line = fmt.format(*row)
173
- print(f"│{row_line}│")
83
+ try:
84
+ async with aiohttp.ClientSession(timeout=timeout) as session:
85
+ async with session.get(PyPIManager.REMOTE_SOURCES[0]) as response:
86
+ if response.status == 200:
87
+ data = await response.json()
88
+ return {
89
+ "modules": data.get("modules", {}),
90
+ "adapters": data.get("adapters", {})
91
+ }
92
+ except (ClientError, asyncio.TimeoutError) as e:
93
+ last_error = e
94
+ shellprint.panel(f"官方源请求失败,尝试备用源: {e}", "警告", "warning")
174
95
 
175
- bottom_border = "└" + "┴".join("─" * (w+2) for w in col_widths) + "┘"
176
- print(f"{cls.DIM}{bottom_border}{cls.RESET}")
177
-
178
- @classmethod
179
- def progress_bar(cls, current, total, prefix="", suffix="", length=50):
180
- filled_length = int(length * current // total)
181
- percent = min(100.0, 100 * (current / float(total)))
182
- bar = f"{cls.GREEN}{'█' * filled_length}{cls.WHITE}{'░' * (length - filled_length)}{cls.RESET}"
183
- sys.stdout.write(f"\r{cls.BOLD}{prefix}{cls.RESET} {bar} {cls.BOLD}{percent:.1f}%{cls.RESET} {suffix}")
184
- sys.stdout.flush()
185
- if current == total:
186
- print()
187
-
188
- @classmethod
189
- def confirm(cls, msg, default=False) -> bool:
190
- yes_options = {'y', 'yes'}
191
- no_options = {'n', 'no'}
192
- default_str = "Y/n" if default else "y/N"
193
- prompt = f"{cls.BOLD}{msg}{cls.RESET} [{cls.CYAN}{default_str}{cls.RESET}]: "
96
+ try:
97
+ async with aiohttp.ClientSession(timeout=timeout) as session:
98
+ async with session.get(PyPIManager.REMOTE_SOURCES[1]) as response:
99
+ if response.status == 200:
100
+ data = await response.json()
101
+ return {
102
+ "modules": data.get("modules", {}),
103
+ "adapters": data.get("adapters", {})
104
+ }
105
+ except (ClientError, asyncio.TimeoutError) as e:
106
+ last_error = e
107
+
108
+ if last_error:
109
+ shellprint.panel(f"获取远程模块列表失败: {last_error}", "错误", "error")
110
+ return {"modules": {}, "adapters": {}}
111
+
112
+ @staticmethod
113
+ def get_installed_packages() -> Dict[str, Dict[str, str]]:
114
+ """
115
+ 获取已安装的包信息
116
+
117
+ :return:
118
+ Dict[str, Dict[str, Dict[str, str]]]: 已安装包字典
119
+ - modules: 已安装模块 {模块名: 模块信息}
120
+ - adapters: 已安装适配器 {适配器名: 适配器信息}
121
+ """
122
+ packages = {
123
+ "modules": {},
124
+ "adapters": {}
125
+ }
194
126
 
195
- while True:
196
- ans = input(prompt).strip().lower()
197
- if not ans:
198
- return default
199
- if ans in yes_options:
200
- return True
201
- if ans in no_options:
202
- return False
203
- print(f"{cls.YELLOW}请输入 'y' 或 'n'{cls.RESET}")
204
-
205
- @classmethod
206
- def ask(cls, msg, choices=None, default="") -> str:
207
- prompt = f"{cls.BOLD}{msg}{cls.RESET}"
208
- if choices:
209
- prompt += f" ({cls.CYAN}{'/'.join(choices)}{cls.RESET})"
210
- if default:
211
- prompt += f" [{cls.BLUE}默认: {default}{cls.RESET}]"
212
- prompt += ": "
127
+ try:
128
+ # 查找模块
129
+ for dist in importlib.metadata.distributions():
130
+ if "ErisPulse-" in dist.metadata["Name"]:
131
+ entry_points = dist.entry_points
132
+ for ep in entry_points:
133
+ if ep.group == "erispulse.module":
134
+ packages["modules"][ep.name] = {
135
+ "package": dist.metadata["Name"],
136
+ "version": dist.version,
137
+ "summary": dist.metadata["Summary"]
138
+ }
139
+ elif ep.group == "erispulse.adapter":
140
+ packages["adapters"][ep.name] = {
141
+ "package": dist.metadata["Name"],
142
+ "version": dist.version,
143
+ "summary": dist.metadata["Summary"]
144
+ }
145
+ except Exception as e:
146
+ shellprint.panel(f"获取已安装包信息失败: {e}", "错误", "error")
213
147
 
214
- while True:
215
- ans = input(prompt).strip()
216
- if not ans and default:
217
- return default
218
- if not choices or ans in choices:
219
- return ans
220
- print(f"{cls.YELLOW}请输入有效选项: {', '.join(choices)}{cls.RESET}")
221
-
222
- @classmethod
223
- def status(cls, msg, success=True):
224
- symbol = f"{cls.GREEN}✓" if success else f"{cls.RED}✗"
225
- print(f"\r{symbol}{cls.RESET} {msg}")
226
-
227
- shellprint = Shell_Printer()
228
-
229
- class SourceManager:
230
- def __init__(self):
231
- self._init_sources()
232
-
233
- def _init_sources(self):
234
- if not env.get('origins'):
235
- env.set('origins', [])
236
-
237
- primary_source = "https://erisdev.com/map.json"
238
- secondary_source = "https://raw.githubusercontent.com/ErisPulse/ErisPulse-ModuleRepo/refs/heads/main/map.json"
239
-
240
- shellprint.status("正在验证主源...")
241
- validated_url = asyncio.run(self._validate_url(primary_source))
148
+ return packages
149
+
150
+ @staticmethod
151
+ def install_package(package_name: str, upgrade: bool = False) -> bool:
152
+ """
153
+ 安装指定包
154
+
155
+ :param package_name: str 要安装的包名
156
+ :param upgrade: bool 是否升级已安装的包 (默认: False)
157
+ :return: bool 安装是否成功
158
+ """
159
+ try:
160
+ cmd = [sys.executable, "-m", "pip", "install"]
161
+ if upgrade:
162
+ cmd.append("--upgrade")
163
+ cmd.append(package_name)
242
164
 
243
- if validated_url:
244
- env.set('origins', [validated_url])
245
- shellprint.status(f"主源 {validated_url} 已成功添加")
246
- else:
247
- if secondary_source not in env.get('origins', []):
248
- env.set('origins', [secondary_source])
249
- shellprint.panel(
250
- f"主源不可用,已添加备用源 {secondary_source}\n\n"
251
- f"{Shell_Printer.YELLOW}提示:{Shell_Printer.RESET} 建议尽快升级 ErisPulse SDK 版本",
252
- "源初始化",
253
- "warning"
254
- )
255
-
256
- async def _validate_url(self, url):
257
- if not url.startswith(('http://', 'https://')):
258
- protocol = shellprint.ask("未指定协议,请输入使用的协议", choices=['http', 'https'], default="https")
259
- url = f"{protocol}://{url}"
260
- if not url.endswith('.json'):
261
- url = f"{url}/map.json"
165
+ shellprint.status(f"正在安装 {package_name}...")
166
+ result = subprocess.run(cmd, check=True)
167
+ if result.returncode == 0:
168
+ shellprint.panel(f"包 {package_name} 安装成功", "成功", "success")
169
+ return True
170
+ return False
171
+ except subprocess.CalledProcessError as e:
172
+ shellprint.panel(f"安装包 {package_name} 失败: {e}", "错误", "error")
173
+ return False
174
+
175
+ @staticmethod
176
+ def uninstall_package(package_name: str) -> bool:
177
+ """
178
+ 卸载指定包
179
+
180
+ :param package_name: str 要卸载的包名
181
+ :return: bool 卸载是否成功
182
+ """
262
183
  try:
263
- async with aiohttp.ClientSession() as session:
264
- async with session.get(url) as response:
265
- response.raise_for_status()
266
- try:
267
- content = await response.text()
268
- json.loads(content)
269
- return url
270
- except (ValueError, json.JSONDecodeError) as e:
271
- shellprint.panel(f"源 {url} 返回的内容不是有效的 JSON 格式: {e}", "错误", "error")
272
- return None
273
- except Exception as e:
274
- shellprint.panel(f"访问源 {url} 失败: {e}", "错误", "error")
275
- return None
276
-
277
- def add_source(self, value):
278
- shellprint.status(f"验证源: {value}")
279
- validated_url = asyncio.run(self._validate_url(value))
280
- if not validated_url:
281
- shellprint.panel("提供的源不是一个有效源,请检查后重试", "错误", "error")
184
+ shellprint.status(f"正在卸载 {package_name}...")
185
+ result = subprocess.run(
186
+ [sys.executable, "-m", "pip", "uninstall", "-y", package_name],
187
+ check=True
188
+ )
189
+ if result.returncode == 0:
190
+ shellprint.panel(f"包 {package_name} 卸载成功", "成功", "success")
191
+ return True
282
192
  return False
193
+ except subprocess.CalledProcessError as e:
194
+ shellprint.panel(f"卸载包 {package_name} 失败: {e}", "错误", "error")
195
+ return False
196
+
197
+ @staticmethod
198
+ def upgrade_all() -> bool:
199
+ """
200
+ 升级所有已安装的ErisPulse包
201
+
202
+ :return: bool 升级是否成功
203
+
204
+ {!--< tips >!--}
205
+ 1. 会先列出所有可升级的包
206
+ 2. 需要用户确认才会执行升级
207
+ {!--< /tips >!--}
208
+ """
209
+ try:
210
+ installed = PyPIManager.get_installed_packages()
211
+ all_packages = set()
212
+
213
+ for pkg_type in ["modules", "adapters"]:
214
+ for pkg_info in installed[pkg_type].values():
215
+ all_packages.add(pkg_info["package"])
283
216
 
284
- origins = env.get('origins')
285
- if validated_url not in origins:
286
- origins.append(validated_url)
287
- env.set('origins', origins)
288
- shellprint.panel(f"源 {validated_url} 已成功添加", "成功", "success")
217
+ if not all_packages:
218
+ shellprint.panel("没有找到可升级的ErisPulse包", "提示", "info")
219
+ return False
220
+
221
+ shellprint.panel(
222
+ f"找到 {len(all_packages)} 个可升级的包:\n" +
223
+ "\n".join(f" - {pkg}" for pkg in all_packages),
224
+ "升级列表",
225
+ "info"
226
+ )
227
+
228
+ if not shellprint.confirm("确认升级所有包吗?", default=False):
229
+ return False
230
+
231
+ for pkg in all_packages:
232
+ PyPIManager.install_package(pkg, upgrade=True)
233
+
289
234
  return True
290
- else:
291
- shellprint.panel(f" {validated_url} 已存在,无需重复添加", "提示", "info")
235
+ except Exception as e:
236
+ shellprint.panel(f"升级包失败: {e}", "错误", "error")
292
237
  return False
293
238
 
294
- def update_sources(self):
295
- shellprint.status("更新模块源...")
296
- origins = env.get('origins')
297
- providers = {}
298
- modules = {}
299
- module_alias = {}
300
- table_rows = []
301
- async def fetch_source_data():
302
- for i, origin in enumerate(origins):
303
- shellprint.status(f"获取源数据 ({i+1}/{len(origins)}): {origin}")
304
- try:
305
- async with aiohttp.ClientSession() as session:
306
- async with session.get(origin) as response:
307
- response.raise_for_status()
308
- try:
309
- text = await response.text()
310
- content = json.loads(text)
311
- providers[content["name"]] = content["base"]
312
- for module in content["modules"].keys():
313
- module_content = content["modules"][module]
314
- modules[f'{module}@{content["name"]}'] = module_content
315
- module_origin_name = module_content["path"]
316
- module_alias_name = module
317
- module_alias[f'{module_origin_name}@{content["name"]}'] = module_alias_name
318
- table_rows.append([
319
- content['name'],
320
- module,
321
- f"{providers[content['name']]}{module_origin_name}"
322
- ])
323
- except (ValueError, json.JSONDecodeError) as e:
324
- shellprint.panel(f"源 {origin} 返回的内容不是有效的 JSON 格式: {e}", "错误", "error")
325
- except Exception as e:
326
- shellprint.panel(f"获取 {origin} 时出错: {e}", "错误", "error")
327
-
328
- asyncio.run(fetch_source_data())
329
- shellprint.table(["源", "模块", "地址"], table_rows, "源更新状态", "success")
330
- from datetime import datetime
331
- env.set('providers', providers)
332
- env.set('modules', modules)
333
- env.set('module_alias', module_alias)
334
- env.set('last_origin_update_time', datetime.now().isoformat())
335
- shellprint.panel("源更新完成", "成功", "success")
336
-
337
- def list_sources(self):
338
- origins = env.get('origins')
339
- if not origins:
340
- shellprint.panel("当前没有配置任何源", "提示", "info")
341
- return
342
-
343
- rows = [[str(idx), origin] for idx, origin in enumerate(origins, 1)]
344
- shellprint.table(["序号", "源地址"], rows, "已配置的源", "info")
345
-
346
- def del_source(self, value):
347
- origins = env.get('origins')
348
- if value in origins:
349
- origins.remove(value)
350
- env.set('origins', origins)
351
- shellprint.panel(f"源 {value} 已成功删除", "成功", "success")
352
- else:
353
- shellprint.panel(f"源 {value} 不存在", "错误", "error")
354
-
355
- source_manager = SourceManager()
356
-
357
239
  class ReloadHandler(FileSystemEventHandler):
240
+ """
241
+ 热重载处理器
242
+
243
+ 监控文件变化并自动重启脚本
244
+
245
+ {!--< tips >!--}
246
+ 1. 基于watchdog实现文件监控
247
+ 2. 有1秒的防抖延迟
248
+ 3. 会终止旧进程并启动新进程
249
+ {!--< /tips >!--}
250
+ """
358
251
  def __init__(self, script_path, *args, **kwargs):
359
252
  super().__init__(*args, **kwargs)
360
253
  self.script_path = script_path
@@ -363,6 +256,11 @@ class ReloadHandler(FileSystemEventHandler):
363
256
  self.start_process()
364
257
 
365
258
  def start_process(self):
259
+ """
260
+ 启动/重启被监控的进程
261
+
262
+ {!--< internal-use >!--}
263
+ """
366
264
  if self.process:
367
265
  self.process.terminate()
368
266
  self.process.wait()
@@ -372,8 +270,12 @@ class ReloadHandler(FileSystemEventHandler):
372
270
  self.last_reload = time.time()
373
271
 
374
272
  def on_modified(self, event):
273
+ """
274
+ 文件修改事件处理
275
+
276
+ :param event: FileSystemEvent 文件系统事件对象
277
+ """
375
278
  now = time.time()
376
- # 1秒后再次触发
377
279
  if now - self.last_reload < 1.0:
378
280
  return
379
281
 
@@ -382,6 +284,16 @@ class ReloadHandler(FileSystemEventHandler):
382
284
  self.start_process()
383
285
 
384
286
  def start_reloader(script_path):
287
+ """
288
+ 启动热重载监控
289
+
290
+ :param script_path: str 要监控的脚本路径
291
+
292
+ {!--< tips >!--}
293
+ 1. 监控脚本所在目录和modules目录
294
+ 2. 按Ctrl+C可停止监控
295
+ {!--< /tips >!--}
296
+ """
385
297
  project_root = os.path.dirname(os.path.abspath(__file__))
386
298
  watch_dirs = [
387
299
  os.path.dirname(os.path.abspath(script_path)),
@@ -407,911 +319,206 @@ def start_reloader(script_path):
407
319
  handler.process.terminate()
408
320
  observer.join()
409
321
 
410
- def enable_module(module_name):
411
- shellprint.status(f"启用模块: {module_name}")
412
- module_info = mods.get_module(module_name)
413
- if module_info:
414
- mods.set_module_status(module_name, True)
415
- shellprint.panel(f"模块 {module_name} 已成功启用", "成功", "success")
416
- else:
417
- shellprint.panel(f"模块 {module_name} 不存在", "错误", "error")
418
-
419
- def disable_module(module_name):
420
- shellprint.status(f"禁用模块: {module_name}")
421
- module_info = mods.get_module(module_name)
422
- if module_info:
423
- mods.set_module_status(module_name, False)
424
- shellprint.panel(f"模块 {module_name} 已成功禁用", "成功", "success")
425
- else:
426
- shellprint.panel(f"模块 {module_name} 不存在", "错误", "error")
427
-
428
- async def fetch_url(session, url, progress_callback=None):
429
- try:
430
- async with session.get(url) as response:
431
- response.raise_for_status()
432
- total_size = int(response.headers.get('Content-Length', 0))
433
- downloaded = 0
434
- data = b''
435
-
436
- async for chunk in response.content.iter_any():
437
- data += chunk
438
- downloaded += len(chunk)
439
- if total_size and progress_callback:
440
- progress_callback(downloaded, total_size)
441
-
442
- return data
443
- except Exception as e:
444
- print(f"请求失败: {e}")
445
- return None
446
-
447
- def extract_and_setup_module(module_name, module_url, zip_path, module_dir):
448
- try:
449
- print(f"正在下载模块: {Shell_Printer.BOLD}{module_name}{Shell_Printer.RESET}")
450
- current_downloaded = [0]
451
- total_size = [0]
452
-
453
- def progress_callback(downloaded, total):
454
- if total > 0 and total_size[0] == 0:
455
- total_size[0] = total
456
- if downloaded > current_downloaded[0]:
457
- current_downloaded[0] = downloaded
458
- prefix = f"{Shell_Printer.BOLD}{module_name}{Shell_Printer.RESET} {Shell_Printer.DIM}下载中{Shell_Printer.RESET}"
459
- suffix = f"{downloaded/1024:.1f}KB/{total/1024:.1f}KB" if total > 0 else f"{downloaded/1024:.1f}KB"
460
- shellprint.progress_bar(downloaded, total or downloaded, prefix, suffix, 40)
461
-
462
- async def download_module():
463
- async with aiohttp.ClientSession() as session:
464
- content = await fetch_url(session, module_url, progress_callback)
465
- if content is None:
466
- return False
467
-
468
- with open(zip_path, 'wb') as zip_file:
469
- zip_file.write(content)
470
-
471
- if total_size[0] > 0:
472
- shellprint.progress_bar(total_size[0], total_size[0],
473
- f"{Shell_Printer.BOLD}{module_name}{Shell_Printer.RESET}",
474
- "下载完成", 40)
475
-
476
- if not os.path.exists(module_dir):
477
- os.makedirs(module_dir)
478
-
479
- shellprint.status(f"解压模块: {module_name}")
480
- with zipfile.ZipFile(zip_path, 'r') as zip_ref:
481
- file_list = zip_ref.namelist()
482
- for i, file in enumerate(file_list):
483
- zip_ref.extract(file, module_dir)
484
- if len(file_list) > 10 and i % (len(file_list) // 10) == 0:
485
- shellprint.progress_bar(i, len(file_list),
486
- f"{Shell_Printer.BOLD}{module_name}{Shell_Printer.RESET}",
487
- "解压中", 30)
488
- if len(file_list) > 10:
489
- shellprint.progress_bar(len(file_list), len(file_list),
490
- f"{Shell_Printer.BOLD}{module_name}{Shell_Printer.RESET}",
491
- "解压完成", 30)
492
- print()
493
-
494
- init_file_path = os.path.join(module_dir, '__init__.py')
495
- if not os.path.exists(init_file_path):
496
- sub_module_dir = os.path.join(module_dir, module_name)
497
- m_sub_module_dir = os.path.join(module_dir, f"m_{module_name}")
498
- for sub_dir in [sub_module_dir, m_sub_module_dir]:
499
- if os.path.exists(sub_dir) and os.path.isdir(sub_dir):
500
- for item in os.listdir(sub_dir):
501
- source_item = os.path.join(sub_dir, item)
502
- target_item = os.path.join(module_dir, item)
503
- if os.path.exists(target_item):
504
- os.remove(target_item)
505
- shutil.move(source_item, module_dir)
506
- os.rmdir(sub_dir)
507
- print(f"模块 {module_name} 文件已成功解压并设置")
508
- return True
509
-
510
- return asyncio.run(download_module())
511
- except Exception as e:
512
- shellprint.panel(f"处理模块 {module_name} 文件失败: {e}", "错误", "error")
513
- if os.path.exists(zip_path):
514
- try:
515
- os.remove(zip_path)
516
- except Exception as cleanup_error:
517
- print(f"清理失败: {cleanup_error}")
518
- return False
519
- finally:
520
- if os.path.exists(zip_path):
521
- try:
522
- os.remove(zip_path)
523
- except Exception as cleanup_error:
524
- print(f"清理失败: {cleanup_error}")
525
-
526
- def install_pip_dependencies(dependencies):
527
- if not dependencies:
528
- return True
529
-
530
- print(f"{Shell_Printer.CYAN}正在安装pip依赖: {', '.join(dependencies)}{Shell_Printer.RESET}")
531
- try:
532
- # 使用子进程获取安装进度
533
- process = subprocess.Popen(
534
- [sys.executable, "-m", "pip", "install"] + dependencies,
535
- stdout=subprocess.PIPE,
536
- stderr=subprocess.STDOUT,
537
- universal_newlines=True
538
- )
539
-
540
- # 实时输出安装过程
541
- while True:
542
- output = process.stdout.readline()
543
- if output == '' and process.poll() is not None:
544
- break
545
- if output:
546
- print(f"{Shell_Printer.DIM}{output.strip()}{Shell_Printer.RESET}")
547
-
548
- return process.returncode == 0
549
-
550
- except subprocess.CalledProcessError as e:
551
- shellprint.panel(f"安装pip依赖失败: {e.stderr}", "错误", "error")
552
- return False
553
-
554
- def install_local_module(module_path, force=False):
555
- try:
556
- module_path = os.path.abspath(module_path)
557
- if not os.path.exists(module_path):
558
- shellprint.panel(f"路径不存在: {module_path}", "错误", "error")
559
- return False
560
-
561
- module_name = os.path.basename(module_path.rstrip('/\\'))
562
- init_py = os.path.join(module_path, '__init__.py')
563
-
564
- if not os.path.exists(init_py):
565
- shellprint.panel(f"目录 {module_path} 不是一个有效的Python模块", "错误", "error")
566
- return False
567
- import sys
568
- sys.path.insert(0, os.path.dirname(module_path))
569
- try:
570
- module = importlib.import_module(module_name)
571
- if not hasattr(module, 'moduleInfo'):
572
- shellprint.panel(f"模块 {module_name} 缺少 moduleInfo 定义", "错误", "error")
573
- return False
574
- finally:
575
- sys.path.remove(os.path.dirname(module_path))
576
- except Exception as e:
577
- shellprint.panel(f"导入模块 {module_name} 失败: {e}", "错误", "error")
578
- return False
579
-
580
- module_info = mods.get_module(module_name)
581
- if module_info and not force:
582
- meta = module_info.get('info', {}).get('meta', {})
583
- shellprint.panel(
584
- f"{Shell_Printer.BOLD}{module_name}{Shell_Printer.RESET}\n版本: {meta.get('version', '未知')}\n描述: {meta.get('description', '无描述')}",
585
- "模块已存在",
586
- "info"
587
- )
588
- if not shellprint.confirm("是否要强制重新安装?", default=False):
589
- return False
322
+ def run_script(script_path: str, reload: bool = False):
323
+ """
324
+ 运行指定脚本
590
325
 
591
- # 复制模块到modules目录
592
- script_dir = os.path.dirname(os.path.abspath(__file__))
593
- target_dir = os.path.join(script_dir, 'modules', module_name)
326
+ :param script_path: str 要运行的脚本路径
327
+ :param reload: bool 是否启用热重载 (默认: False)
594
328
 
595
- try:
596
- if os.path.exists(target_dir):
597
- shutil.rmtree(target_dir)
598
- shutil.copytree(module_path, target_dir)
599
- except Exception as e:
600
- shellprint.panel(f"复制模块文件失败: {e}", "错误", "error")
601
- return False
602
-
603
- # 安装依赖
604
- dependencies = module.moduleInfo.get('dependencies', {})
605
- for dep in dependencies.get('requires', []):
606
- print(f"\n{Shell_Printer.BOLD}处理依赖: {dep}{Shell_Printer.RESET}")
607
- install_module(dep)
608
-
609
- # 安装pip依赖
610
- pip_dependencies = dependencies.get('pip', [])
611
- if pip_dependencies:
612
- print(f"{Shell_Printer.YELLOW}模块 {module_name} 需要以下pip依赖: {', '.join(pip_dependencies)}{Shell_Printer.RESET}")
613
- if not install_pip_dependencies(pip_dependencies):
614
- print(f"{Shell_Printer.RED}无法安装模块 {module_name} 的pip依赖,安装终止{Shell_Printer.RESET}")
615
- return False
616
-
617
- # 注册模块信息
618
- mods.set_module(module_name, {
619
- 'status': True,
620
- 'info': {
621
- 'meta': module.moduleInfo.get('meta', {}),
622
- 'dependencies': module.moduleInfo.get('dependencies', {})
623
- }
624
- })
625
-
626
- shellprint.panel(f"本地模块 {Shell_Printer.BOLD}{module_name}{Shell_Printer.RESET} 安装成功", "成功", "success")
627
- return True
628
-
629
- def install_module(module_name, force=False):
630
- # 检查是否是本地路径
631
- if module_name.startswith('.') or os.path.isabs(module_name):
632
- return install_local_module(module_name, force)
633
-
634
- shellprint.panel(f"准备安装模块: {Shell_Printer.BOLD}{module_name}{Shell_Printer.RESET}", "安装摘要", "info")
635
- last_update_time = env.get('last_origin_update_time', None)
636
- if last_update_time:
637
- from datetime import datetime, timedelta
638
- last_update = datetime.fromisoformat(last_update_time)
639
- if datetime.now() - last_update > timedelta(hours=720):
640
- shellprint.panel("距离上次源更新已超过30天,源内可能有新模块或更新。", "提示", "warning")
641
- if shellprint.confirm("是否在安装模块前更新源?", default=True):
642
- source_manager.update_sources()
643
- env.set('last_origin_update_time', datetime.now().isoformat())
644
- shellprint.status("源更新完成")
645
-
646
- module_info = mods.get_module(module_name)
647
- if module_info and not force:
648
- meta = module_info.get('info', {}).get('meta', {})
649
- shellprint.panel(
650
- f"{Shell_Printer.BOLD}{module_name}{Shell_Printer.RESET}\n版本: {meta.get('version', '未知')}\n描述: {meta.get('description', '无描述')}",
651
- "模块已存在",
652
- "info"
653
- )
654
- if not shellprint.confirm("是否要强制重新安装?", default=False):
655
- return
656
-
657
- providers = env.get('providers', {})
658
- if isinstance(providers, str):
659
- providers = json.loads(providers)
660
- module_info_list = []
661
-
662
- for provider, url in providers.items():
663
- module_key = f"{module_name}@{provider}"
664
- modules_data = env.get('modules', {})
665
- if isinstance(modules_data, str):
666
- modules_data = json.loads(modules_data)
667
- if module_key in modules_data:
668
- module_data = modules_data[module_key]
669
- meta = module_data.get("meta", {})
670
- depsinfo = module_data.get("dependencies", {})
671
- module_info_list.append({
672
- 'provider': provider,
673
- 'url': url,
674
- 'path': module_data.get('path', ''),
675
- 'version': meta.get('version', '未知'),
676
- 'description': meta.get('description', '无描述'),
677
- 'author': meta.get('author', '未知'),
678
- 'dependencies': depsinfo.get("requires", []),
679
- 'optional_dependencies': depsinfo.get("optional", []),
680
- 'pip_dependencies': depsinfo.get("pip", [])
681
- })
682
-
683
- if not module_info_list:
684
- shellprint.panel(f"未找到模块 {module_name}", "错误", "error")
685
- if providers:
686
- print(f"{Shell_Printer.BOLD}当前可用源:{Shell_Printer.RESET}")
687
- for provider in providers:
688
- print(f" {Shell_Printer.CYAN}- {provider}{Shell_Printer.RESET}")
689
- return
690
-
691
- if len(module_info_list) > 1:
692
- print(f"找到 {Shell_Printer.BOLD}{len(module_info_list)}{Shell_Printer.RESET} 个源的 {Shell_Printer.BOLD}{module_name}{Shell_Printer.RESET} 模块:")
693
- rows = []
694
- for i, info in enumerate(module_info_list):
695
- rows.append([
696
- f"{Shell_Printer.CYAN}{i+1}{Shell_Printer.RESET}",
697
- info['provider'],
698
- info['version'],
699
- info['description'],
700
- info['author']
701
- ])
702
- shellprint.table(["编号", "源", "版本", "描述", "作者"], rows, "可选模块源", "info")
703
-
704
- while True:
705
- choice = shellprint.ask("请选择要安装的源 (输入编号)", [str(i) for i in range(1, len(module_info_list)+1)])
706
- if choice and 1 <= int(choice) <= len(module_info_list):
707
- selected_module = module_info_list[int(choice)-1]
708
- break
709
- else:
710
- print(f"{Shell_Printer.YELLOW}输入无效,请重新选择{Shell_Printer.RESET}")
711
- else:
712
- selected_module = module_info_list[0]
713
-
714
- # 安装依赖
715
- for dep in selected_module['dependencies']:
716
- print(f"\n{Shell_Printer.BOLD}处理依赖: {dep}{Shell_Printer.RESET}")
717
- install_module(dep)
718
-
719
- # 安装pip依赖
720
- third_party_deps = selected_module.get('pip_dependencies', [])
721
- if third_party_deps:
722
- print(f"{Shell_Printer.YELLOW}模块 {module_name} 需要以下pip依赖: {', '.join(third_party_deps)}{Shell_Printer.RESET}")
723
- if not install_pip_dependencies(third_party_deps):
724
- print(f"{Shell_Printer.RED}无法安装模块 {module_name} 的pip依赖,安装终止{Shell_Printer.RESET}")
725
- return
726
-
727
- # 下载并安装模块
728
- module_url = selected_module['url'] + selected_module['path']
729
- script_dir = os.path.dirname(os.path.abspath(__file__))
730
- module_dir = os.path.join(script_dir, 'modules', module_name)
731
- zip_path = os.path.join(script_dir, f"{module_name}.zip")
732
-
733
- if not extract_and_setup_module(
734
- module_name=module_name,
735
- module_url=module_url,
736
- zip_path=zip_path,
737
- module_dir=module_dir
738
- ):
329
+ :raises FileNotFoundError: 当脚本不存在时抛出
330
+ """
331
+ if not os.path.exists(script_path):
332
+ shellprint.panel(f"找不到指定文件: {script_path}", "错误", "error")
739
333
  return
740
-
741
- # 注册模块信息
742
- mods.set_module(module_name, {
743
- 'status': True,
744
- 'info': {
745
- 'meta': {
746
- 'version': selected_module['version'],
747
- 'description': selected_module['description'],
748
- 'author': selected_module['author'],
749
- 'pip_dependencies': selected_module['pip_dependencies']
750
- },
751
- 'dependencies': {
752
- 'requires': selected_module['dependencies'],
753
- 'optional': selected_module['optional_dependencies'],
754
- 'pip': selected_module['pip_dependencies']
755
- }
756
- }
757
- })
758
-
759
- shellprint.panel(f"模块 {Shell_Printer.BOLD}{module_name}{Shell_Printer.RESET} 安装成功", "成功", "success")
760
334
 
761
- def uninstall_module(module_name):
762
- shellprint.panel(f"准备卸载模块: {Shell_Printer.BOLD}{module_name}{Shell_Printer.RESET}", "卸载摘要", "warning")
763
- module_info = mods.get_module(module_name)
764
- if not module_info:
765
- shellprint.panel(f"模块 {module_name} 不存在", "错误", "error")
766
- return
767
-
768
- meta = module_info.get('info', {}).get('meta', {})
769
- depsinfo = module_info.get('info', {}).get('dependencies', {})
770
- shellprint.panel(
771
- f"版本: {Shell_Printer.BOLD}{meta.get('version', '未知')}{Shell_Printer.RESET}\n"
772
- f"描述: {meta.get('description', '无描述')}\n"
773
- f"pip依赖: {Shell_Printer.YELLOW}{', '.join(depsinfo.get('pip', [])) or '无'}{Shell_Printer.RESET}",
774
- "模块信息",
775
- "info"
776
- )
777
-
778
- if not shellprint.confirm("确认要卸载此模块吗?", default=False):
779
- print(f"{Shell_Printer.BLUE}卸载已取消{Shell_Printer.RESET}")
780
- return
781
-
782
- script_dir = os.path.dirname(os.path.abspath(__file__))
783
- module_path = os.path.join(script_dir, 'modules', module_name)
784
- module_file_path = module_path + '.py'
785
-
786
- if os.path.exists(module_file_path):
787
- try:
788
- shellprint.status(f"删除模块文件: {module_name}.py")
789
- os.remove(module_file_path)
790
- except Exception as e:
791
- shellprint.panel(f"删除模块文件 {module_name} 时出错: {e}", "错误", "error")
792
- elif os.path.exists(module_path) and os.path.isdir(module_path):
793
- try:
794
- shellprint.status(f"删除模块目录: {module_name}")
795
- shutil.rmtree(module_path)
796
- except Exception as e:
797
- shellprint.panel(f"删除模块目录 {module_name} 时出错: {e}", "错误", "error")
798
- else:
799
- shellprint.panel(f"模块 {module_name} 文件不存在", "错误", "error")
800
- return
801
-
802
- pip_dependencies = depsinfo.get('pip', [])
803
- if pip_dependencies:
804
- all_modules = mods.get_all_modules()
805
- unused_pip_dependencies = []
806
- essential_packages = {'aiohttp'}
807
- for dep in pip_dependencies:
808
- if dep in essential_packages:
809
- print(f"{Shell_Printer.CYAN}跳过必要模块 {dep} 的卸载{Shell_Printer.RESET}")
810
- continue
811
-
812
- is_dependency_used = False
813
- for name, info in all_modules.items():
814
- if name != module_name and dep in info.get('info', {}).get('dependencies', {}).get('pip', []):
815
- is_dependency_used = True
816
- break
817
-
818
- if not is_dependency_used:
819
- unused_pip_dependencies.append(dep)
820
-
821
- if unused_pip_dependencies:
822
- shellprint.panel(
823
- f"以下 pip 依赖不再被其他模块使用:\n{Shell_Printer.YELLOW}{', '.join(unused_pip_dependencies)}{Shell_Printer.RESET}",
824
- "可卸载依赖",
825
- "info"
826
- )
827
- if shellprint.confirm("是否卸载这些 pip 依赖?", default=False):
828
- try:
829
- shellprint.status(f"卸载pip依赖: {', '.join(unused_pip_dependencies)}")
830
- subprocess.run(
831
- [sys.executable, "-m", "pip", "uninstall", "-y"] + unused_pip_dependencies,
832
- check=True,
833
- stdout=subprocess.PIPE,
834
- stderr=subprocess.PIPE
835
- )
836
- shellprint.panel(
837
- f"成功卸载 pip 依赖: {', '.join(unused_pip_dependencies)}",
838
- "成功",
839
- "success"
840
- )
841
- except subprocess.CalledProcessError as e:
842
- shellprint.panel(
843
- f"卸载 pip 依赖失败: {e.stderr.decode()}",
844
- "错误",
845
- "error"
846
- )
847
-
848
- if mods.remove_module(module_name):
849
- shellprint.panel(f"模块 {Shell_Printer.BOLD}{module_name}{Shell_Printer.RESET} 已成功卸载", "成功", "success")
335
+ if reload:
336
+ start_reloader(script_path)
850
337
  else:
851
- shellprint.panel(f"模块 {module_name} 不存在", "错误", "error")
338
+ shellprint.panel(f"运行脚本: {Shell_Printer.BOLD}{script_path}{Shell_Printer.RESET}", "执行", "info")
339
+ import runpy
340
+ try:
341
+ runpy.run_path(script_path, run_name="__main__")
342
+ except KeyboardInterrupt:
343
+ shellprint.panel("脚本执行已中断", "中断", "info")
852
344
 
853
- def upgrade_all_modules(force=False):
854
- all_modules = mods.get_all_modules()
855
- if not all_modules:
856
- print(f"{Shell_Printer.YELLOW}未找到任何模块,无法更新{Shell_Printer.RESET}")
857
- return
858
-
859
- providers = env.get('providers', {})
860
- if isinstance(providers, str):
861
- providers = json.loads(providers)
862
- modules_data = env.get('modules', {})
863
- if isinstance(modules_data, str):
864
- modules_data = json.loads(modules_data)
865
-
866
- updates_available = []
867
- for module_name, module_info in all_modules.items():
868
- shellprint.status(f"检查更新: {module_name}")
869
- local_version = module_info.get('info', {}).get('meta', {}).get('version', '0.0.0')
870
- for provider, url in providers.items():
871
- module_key = f"{module_name}@{provider}"
872
- if module_key in modules_data:
873
- remote_module = modules_data[module_key]
874
- remote_version = remote_module.get('meta', {}).get('version', '1.14.514')
875
- if remote_version > local_version:
876
- updates_available.append({
877
- 'name': module_name,
878
- 'local_version': local_version,
879
- 'remote_version': remote_version,
880
- 'provider': provider,
881
- 'url': url,
882
- 'path': remote_module.get('path', ''),
883
- })
884
-
885
- if not updates_available:
886
- print(f"{Shell_Printer.GREEN}所有模块已是最新版本,无需更新{Shell_Printer.RESET}")
887
- return
888
-
889
- print(f"\n{Shell_Printer.BOLD}以下模块有可用更新:{Shell_Printer.RESET}")
890
- rows = []
891
- for update in updates_available:
892
- rows.append([
893
- update['name'],
894
- update['local_version'],
895
- f"{Shell_Printer.GREEN}{update['remote_version']}{Shell_Printer.RESET}",
896
- update['provider']
897
- ])
898
- shellprint.table(["模块", "当前版本", "最新版本", "源"], rows, "可用更新", "info")
345
+ def get_erispulse_version():
346
+ """
347
+ 获取当前安装的ErisPulse版本
899
348
 
900
- if not force:
901
- warning_msg = (
902
- f"{Shell_Printer.BOLD}{Shell_Printer.RED}警告:{Shell_Printer.RESET} "
903
- "更新模块可能会导致兼容性问题,请在更新前查看插件作者的相关声明。\n"
904
- "是否继续?"
905
- )
906
- if not shellprint.confirm(warning_msg, default=False):
907
- print(f"{Shell_Printer.BLUE}更新已取消{Shell_Printer.RESET}")
908
- return
909
-
910
- for i, update in enumerate(updates_available, 1):
911
- print(f"\n{Shell_Printer.BOLD}[{i}/{len(updates_available)}]{Shell_Printer.RESET} 更新模块 {Shell_Printer.BOLD}{update['name']}{Shell_Printer.RESET}")
912
-
913
- # 检查新版本的依赖
914
- module_key = f"{update['name']}@{update['provider']}"
915
- new_module_info = modules_data[module_key]
916
- new_dependencies = new_module_info.get('dependencies', {}).get('requires', [])
917
-
918
- # 检查缺失的依赖
919
- missing_deps = []
920
- for dep in new_dependencies:
921
- if dep not in all_modules or not all_modules[dep].get('status', True):
922
- missing_deps.append(dep)
923
-
924
- if missing_deps:
925
- shellprint.panel(
926
- f"模块 {update['name']} 需要以下依赖:\n{Shell_Printer.YELLOW}{', '.join(missing_deps)}{Shell_Printer.RESET}",
927
- "缺失依赖",
928
- "warning"
929
- )
930
- if not shellprint.confirm("是否安装这些依赖?", default=True):
931
- print(f"{Shell_Printer.BLUE}跳过模块 {update['name']} 的更新{Shell_Printer.RESET}")
932
- continue
933
-
934
- for dep in missing_deps:
935
- print(f"\n{Shell_Printer.BOLD}安装依赖: {dep}{Shell_Printer.RESET}")
936
- install_module(dep)
937
-
938
- module_url = update['url'] + update['path']
939
- script_dir = os.path.dirname(os.path.abspath(__file__))
940
- module_dir = os.path.join(script_dir, 'modules', update['name'])
941
- zip_path = os.path.join(script_dir, f"{update['name']}.zip")
942
-
943
- # 检查新版本的pip依赖
944
- new_pip_deps = new_module_info.get('dependencies', {}).get('pip', [])
945
- current_pip_deps = all_modules[update['name']].get('info', {}).get('dependencies', {}).get('pip', [])
946
- added_pip_deps = [dep for dep in new_pip_deps if dep not in current_pip_deps]
947
-
948
- if added_pip_deps:
949
- shellprint.panel(
950
- f"模块 {update['name']} 需要以下新的pip依赖:\n{Shell_Printer.YELLOW}{', '.join(added_pip_deps)}{Shell_Printer.RESET}",
951
- "新增pip依赖",
952
- "warning"
953
- )
954
- if not shellprint.confirm("是否安装这些pip依赖?", default=True):
955
- print(f"{Shell_Printer.BLUE}跳过模块 {update['name']} 的更新{Shell_Printer.RESET}")
956
- continue
957
-
958
- if not install_pip_dependencies(added_pip_deps):
959
- print(f"{Shell_Printer.RED}无法安装模块 {update['name']} 的pip依赖,更新终止{Shell_Printer.RESET}")
960
- continue
961
-
962
- if not extract_and_setup_module(
963
- module_name=update['name'],
964
- module_url=module_url,
965
- zip_path=zip_path,
966
- module_dir=module_dir
967
- ):
968
- continue
969
-
970
- # 更新模块信息,包括新的依赖
971
- all_modules[update['name']]['info']['version'] = update['remote_version']
972
- all_modules[update['name']]['info']['dependencies'] = {
973
- 'requires': new_dependencies,
974
- 'pip': new_pip_deps
975
- }
976
- mods.set_all_modules(all_modules)
977
- print(f"{Shell_Printer.GREEN}模块 {update['name']} 已更新至版本 {update['remote_version']}{Shell_Printer.RESET}")
978
-
979
- shellprint.panel("所有可用更新已处理完成", "完成", "success")
349
+ :return: str ErisPulse版本号或"unknown version"
350
+ """
351
+ try:
352
+ return version("ErisPulse")
353
+ except PackageNotFoundError:
354
+ return "unknown version"
980
355
 
981
- def list_modules(module_name=None):
982
- all_modules = mods.get_all_modules()
983
- if not all_modules:
984
- shellprint.panel("未在数据库中发现注册模块,正在初始化模块列表...", "提示", "info")
985
- from . import init as init_module
986
- init_module()
987
- all_modules = mods.get_all_modules()
988
-
989
- if not all_modules:
990
- shellprint.panel("未找到任何模块", "错误", "error")
991
- return
992
-
993
- shellprint.panel(f"找到 {Shell_Printer.BOLD}{len(all_modules)}{Shell_Printer.RESET} 个模块", "统计", "info")
356
+ def main():
357
+ """
358
+ CLI主入口
994
359
 
995
- rows = []
996
- for name, info in all_modules.items():
997
- # 根据状态设置颜色
998
- status_color = Shell_Printer.GREEN if info.get("status", True) else Shell_Printer.RED
999
- status = f"{status_color}✓" if info.get("status", True) else f"{Shell_Printer.RED}✗"
1000
-
1001
- meta = info.get('info', {}).get('meta', {})
1002
- depsinfo = info.get('info', {}).get('dependencies', {})
1003
- optional_deps = depsinfo.get('optional', [])
1004
-
1005
- available_optional_deps = []
1006
- missing_optional_deps = []
1007
- if optional_deps:
1008
- for dep in optional_deps:
1009
- if isinstance(dep, list):
1010
- available_deps = [d for d in dep if d in all_modules]
1011
- if available_deps:
1012
- available_optional_deps.extend(available_deps)
1013
- else:
1014
- missing_optional_deps.extend(dep)
1015
- elif dep in all_modules:
1016
- available_optional_deps.append(dep)
1017
- else:
1018
- missing_optional_deps.append(dep)
1019
-
1020
- if missing_optional_deps:
1021
- optional_dependencies = f"可用: {', '.join(available_optional_deps)} 缺失: {Shell_Printer.RED}{', '.join(missing_optional_deps)}{Shell_Printer.RESET}"
1022
- else:
1023
- optional_dependencies = ', '.join(available_optional_deps) or '无'
1024
- else:
1025
- optional_dependencies = '无'
1026
-
1027
- # 依赖项使用不同颜色标识
1028
- dependencies = Shell_Printer.YELLOW + ', '.join(depsinfo.get('requires', [])) + Shell_Printer.RESET or '无'
1029
- pip_dependencies = Shell_Printer.CYAN + ', '.join(depsinfo.get('pip', [])) + Shell_Printer.RESET or '无'
1030
-
1031
- rows.append([
1032
- Shell_Printer.BOLD + name + Shell_Printer.RESET,
1033
- status,
1034
- Shell_Printer.BLUE + meta.get('version', '未知') + Shell_Printer.RESET,
1035
- meta.get('description', '无描述'),
1036
- dependencies,
1037
- optional_dependencies,
1038
- pip_dependencies
1039
- ])
1040
-
1041
- shellprint.table(
1042
- ["模块名称", "状态", "版本", "描述", "依赖", "可选依赖", "pip依赖"],
1043
- rows,
1044
- "模块列表",
1045
- "info"
1046
- )
360
+ 解析命令行参数并执行相应命令
361
+
362
+ {!--< tips >!--}
363
+ 1. 使用argparse处理命令行参数
364
+ 2. 支持彩色输出和表格显示
365
+ 3. 提供详细的错误处理
366
+ {!--< /tips >!--}
367
+ """
1047
368
 
1048
- enabled_count = sum(1 for m in all_modules.values() if m.get("status", True))
1049
- disabled_count = len(all_modules) - enabled_count
1050
- shellprint.panel(
1051
- f"{Shell_Printer.GREEN}已启用: {enabled_count}{Shell_Printer.RESET} "
1052
- f"{Shell_Printer.RED}已禁用: {disabled_count}{Shell_Printer.RESET}",
1053
- "模块状态统计",
1054
- "info"
1055
- )
1056
-
1057
- def main():
1058
369
  parser = argparse.ArgumentParser(
1059
370
  prog="epsdk",
1060
- formatter_class=argparse.RawTextHelpFormatter
371
+ formatter_class=argparse.RawTextHelpFormatter,
372
+ description=f"{Shell_Printer.BOLD}ErisPulse SDK 命令行工具 {get_erispulse_version()}{Shell_Printer.RESET}"
1061
373
  )
1062
374
  parser._positionals.title = f"{Shell_Printer.BOLD}{Shell_Printer.CYAN}基本命令{Shell_Printer.RESET}"
1063
375
  parser._optionals.title = f"{Shell_Printer.BOLD}{Shell_Printer.MAGENTA}可选参数{Shell_Printer.RESET}"
1064
376
 
377
+ parser.add_argument("--version", action="store_true", help="显示版本信息并退出")
378
+
1065
379
  subparsers = parser.add_subparsers(
1066
380
  dest='command',
1067
381
  title='可用的命令',
1068
382
  metavar=f"{Shell_Printer.GREEN}<命令>{Shell_Printer.RESET}",
1069
383
  help='具体命令的帮助信息'
1070
384
  )
385
+
386
+ # 安装命令
387
+ install_parser = subparsers.add_parser('install', help='安装模块/适配器包')
388
+ install_parser.add_argument('package', type=str, help='要安装的包名')
389
+ install_parser.add_argument('--upgrade', '-U', action='store_true', help='升级已安装的包')
1071
390
 
1072
- command_help = {
1073
- 'enable': '启用指定模块',
1074
- 'disable': '禁用指定模块',
1075
- 'list': '列出所有模块信息',
1076
- 'update': '更新模块列表',
1077
- 'upgrade': '升级所有可用模块',
1078
- 'uninstall': '删除指定模块',
1079
- 'install': '安装指定模块',
1080
- 'origin': '管理模块源',
1081
- 'run': '运行指定主程序'
1082
- }
1083
-
1084
- # 模块管理
1085
- def add_module_command(name, help_text):
1086
- cmd = subparsers.add_parser(name, help=help_text, description=help_text)
1087
- cmd.add_argument('module_names', nargs='+', help='模块名称')
1088
- if name in ['enable', 'disable', 'install']:
1089
- cmd.add_argument('--init', action='store_true', help='在操作前初始化模块数据库')
1090
- if name in ['install']:
1091
- cmd.add_argument('--force', action='store_true', help='强制重新安装模块')
1092
- return cmd
1093
-
1094
- enable_parser = add_module_command('enable', '启用指定模块')
1095
- disable_parser = add_module_command('disable', '禁用指定模块')
1096
- uninstall_parser = add_module_command('uninstall', '删除指定模块')
1097
- install_parser = add_module_command('install', '安装指定模块')
1098
-
1099
- # 其他命令
1100
- list_parser = subparsers.add_parser('list', help='列出所有模块信息')
1101
- list_parser.add_argument('--module', '-m', type=str, help='指定要展示的模块名称')
1102
-
1103
- update_parser = subparsers.add_parser('update', help='更新模块列表')
1104
-
1105
- upgrade_parser = subparsers.add_parser('upgrade', help='升级模块列表')
1106
- upgrade_parser.add_argument('--force', action='store_true', help='跳过二次确认,强制更新')
1107
-
1108
- # 初始化命令
1109
- init_parser = subparsers.add_parser('init', help='初始化模块数据库')
1110
-
1111
- origin_parser = subparsers.add_parser('origin', help='管理模块源')
1112
- origin_subparsers = origin_parser.add_subparsers(
1113
- dest='origin_command',
1114
- title='源管理命令',
1115
- metavar=f"{Shell_Printer.CYAN}<子命令>{Shell_Printer.RESET}"
1116
- )
391
+ # 卸载命令
392
+ uninstall_parser = subparsers.add_parser('uninstall', help='卸载模块/适配器包')
393
+ uninstall_parser.add_argument('package', type=str, help='要卸载的包名')
1117
394
 
1118
- add_origin_parser = origin_subparsers.add_parser('add', help='添加模块源')
1119
- add_origin_parser.add_argument('url', type=str, help='要添加的模块源URL')
395
+ # 列表命令
396
+ list_parser = subparsers.add_parser('list', help='列出已安装的模块/适配器')
397
+ list_parser.add_argument('--type', '-t', choices=['modules', 'adapters', 'all'], default='all',
398
+ help='列出类型 (modules: 仅模块, adapters: 仅适配器, all: 全部)')
1120
399
 
1121
- list_origin_parser = origin_subparsers.add_parser('list', help='列出所有模块源')
400
+ # 远程列表命令
401
+ list_remote_parser = subparsers.add_parser('list-remote', help='列出远程可用的模块和适配器')
402
+ list_remote_parser.add_argument('--type', '-t', choices=['modules', 'adapters', 'all'], default='all',
403
+ help='列出类型 (modules: 仅模块, adapters: 仅适配器, all: 全部)')
404
+ # 升级命令
405
+ upgrade_parser = subparsers.add_parser('upgrade', help='升级所有模块/适配器')
406
+ upgrade_parser.add_argument('--force', '-f', action='store_true', help='跳过确认直接升级')
1122
407
 
1123
- del_origin_parser = origin_subparsers.add_parser('del', help='删除模块源')
1124
- del_origin_parser.add_argument('url', type=str, help='要删除的模块源URL')
1125
-
408
+ # 运行命令
1126
409
  run_parser = subparsers.add_parser('run', help='运行指定主程序')
1127
410
  run_parser.add_argument('script', type=str, help='要运行的主程序路径')
1128
- run_parser.add_argument('--reload', action='store_true', help='启用热重载模式,自动检测代码变动并重启')
1129
-
411
+ run_parser.add_argument('--reload', action='store_true', help='启用热重载模式')
412
+
1130
413
  args = parser.parse_args()
1131
414
 
1132
- if hasattr(args, 'init') and args.init:
1133
- print(f"{Shell_Printer.GREEN}正在初始化模块列表...{Shell_Printer.RESET}")
1134
- from . import init as init_module
1135
- init_module()
1136
- print(f"{Shell_Printer.GREEN}模块列表初始化完成{Shell_Printer.RESET}")
415
+ if args.version:
416
+ print(f"{Shell_Printer.GREEN}ErisPulse {get_erispulse_version()}{Shell_Printer.RESET}")
417
+ return
418
+
419
+ if not args.command:
420
+ parser.print_help()
421
+ return
1137
422
 
1138
- # 处理命令
1139
- if args.command == 'enable':
1140
- for module_name in args.module_names:
1141
- module_name = module_name.strip()
1142
- if not module_name:
1143
- continue
1144
-
1145
- if '*' in module_name or '?' in module_name:
1146
- shellprint.status(f"匹配模块模式: {module_name}")
1147
- all_modules = mods.get_all_modules()
1148
- if not all_modules:
1149
- shellprint.panel("未找到任何模块,请先更新源或检查配置", "错误", "error")
1150
- continue
1151
-
1152
- matched_modules = [name for name in all_modules.keys() if fnmatch.fnmatch(name, module_name)]
1153
- if not matched_modules:
1154
- shellprint.panel(f"未找到匹配模块模式 {module_name} 的模块", "错误", "error")
1155
- continue
1156
-
1157
- print(f"{Shell_Printer.GREEN}找到 {len(matched_modules)} 个匹配模块:{Shell_Printer.RESET}")
1158
- for i, matched_module in enumerate(matched_modules, start=1):
1159
- print(f" {Shell_Printer.CYAN}{i}. {matched_module}{Shell_Printer.RESET}")
1160
-
1161
- if not shellprint.confirm("是否启用所有匹配模块?", default=True):
1162
- print(f"{Shell_Printer.BLUE}操作已取消{Shell_Printer.RESET}")
1163
- continue
1164
-
1165
- for matched_module in matched_modules:
1166
- enable_module(matched_module)
423
+ try:
424
+ if args.command == "install":
425
+ import asyncio
426
+ # 首先检查是否是远程模块/适配器的简称
427
+ remote_packages = asyncio.run(PyPIManager.get_remote_packages())
428
+ full_package_name = None
429
+
430
+ # 检查模块
431
+ if args.package in remote_packages["modules"]:
432
+ full_package_name = remote_packages["modules"][args.package]["package"]
433
+ # 检查适配器
434
+ elif args.package in remote_packages["adapters"]:
435
+ full_package_name = remote_packages["adapters"][args.package]["package"]
436
+
437
+ # 如果找到远程包,使用完整包名安装
438
+ if full_package_name:
439
+ shellprint.panel(
440
+ f"找到远程包: {Shell_Printer.BOLD}{args.package}{Shell_Printer.RESET} → {Shell_Printer.BLUE}{full_package_name}{Shell_Printer.RESET}",
441
+ "信息",
442
+ "info"
443
+ )
444
+ PyPIManager.install_package(full_package_name, args.upgrade)
1167
445
  else:
1168
- enable_module(module_name)
1169
-
1170
- elif args.command == 'disable':
1171
- for module_name in args.module_names:
1172
- module_name = module_name.strip()
1173
- if not module_name:
1174
- continue
446
+ # 否则按原样安装
447
+ PyPIManager.install_package(args.package, args.upgrade)
448
+
449
+ elif args.command == "uninstall":
450
+ PyPIManager.uninstall_package(args.package)
451
+
452
+ elif args.command == "list":
453
+ installed = PyPIManager.get_installed_packages()
454
+
455
+ if args.type in ["modules", "all"] and installed["modules"]:
456
+ rows = [
457
+ [
458
+ f"{Shell_Printer.GREEN}{name}{Shell_Printer.RESET}",
459
+ f"{Shell_Printer.BLUE}{info['package']}{Shell_Printer.RESET}",
460
+ info["version"],
461
+ info["summary"]
462
+ ] for name, info in installed["modules"].items()
463
+ ]
464
+ shellprint.table(["模块名", "包名", "版本", "描述"], rows, "已安装模块", "info")
1175
465
 
1176
- if '*' in module_name or '?' in module_name:
1177
- shellprint.status(f"匹配模块模式: {module_name}")
1178
- all_modules = mods.get_all_modules()
1179
- if not all_modules:
1180
- shellprint.panel("未找到任何模块,请先更新源或检查配置", "错误", "error")
1181
- continue
1182
-
1183
- matched_modules = [name for name in all_modules.keys() if fnmatch.fnmatch(name, module_name)]
1184
- if not matched_modules:
1185
- shellprint.panel(f"未找到匹配模块模式 {module_name} 的模块", "错误", "error")
1186
- continue
1187
-
1188
- print(f"{Shell_Printer.GREEN}找到 {len(matched_modules)} 个匹配模块:{Shell_Printer.RESET}")
1189
- for i, matched_module in enumerate(matched_modules, start=1):
1190
- print(f" {Shell_Printer.CYAN}{i}. {matched_module}{Shell_Printer.RESET}")
1191
-
1192
- if not shellprint.confirm("是否禁用所有匹配模块?", default=True):
1193
- print(f"{Shell_Printer.BLUE}操作已取消{Shell_Printer.RESET}")
1194
- continue
1195
-
1196
- for matched_module in matched_modules:
1197
- disable_module(matched_module)
1198
- else:
1199
- disable_module(module_name)
466
+ if args.type in ["adapters", "all"] and installed["adapters"]:
467
+ rows = [
468
+ [
469
+ f"{Shell_Printer.YELLOW}{name}{Shell_Printer.RESET}",
470
+ f"{Shell_Printer.BLUE}{info['package']}{Shell_Printer.RESET}",
471
+ info["version"],
472
+ info["summary"]
473
+ ] for name, info in installed["adapters"].items()
474
+ ]
475
+ shellprint.table(["适配器名", "包名", "版本", "描述"], rows, "已安装适配器", "info")
1200
476
 
1201
- elif args.command == 'list':
1202
- list_modules(args.module)
1203
-
1204
- elif args.command == 'uninstall':
1205
- for module_name in args.module_names:
1206
- module_name = module_name.strip()
1207
- if not module_name:
1208
- continue
477
+ if not installed["modules"] and not installed["adapters"]:
478
+ shellprint.panel("没有安装任何ErisPulse模块或适配器", "提示", "info")
1209
479
 
1210
- if '*' in module_name or '?' in module_name:
1211
- shellprint.status(f"匹配模块模式: {module_name}")
1212
- all_modules = mods.get_all_modules()
1213
- if not all_modules:
1214
- shellprint.panel("未找到任何模块,请先更新源或检查配置", "错误", "error")
1215
- continue
1216
-
1217
- matched_modules = [name for name in all_modules.keys() if fnmatch.fnmatch(name, module_name)]
1218
- if not matched_modules:
1219
- shellprint.panel(f"未找到匹配模块模式 {module_name} 的模块", "错误", "error")
1220
- continue
1221
-
1222
- print(f"{Shell_Printer.GREEN}找到 {len(matched_modules)} 个匹配模块:{Shell_Printer.RESET}")
1223
- for i, matched_module in enumerate(matched_modules, start=1):
1224
- print(f" {Shell_Printer.CYAN}{i}. {matched_module}{Shell_Printer.RESET}")
1225
-
1226
- if not shellprint.confirm("是否卸载所有匹配模块?", default=True):
1227
- print(f"{Shell_Printer.BLUE}操作已取消{Shell_Printer.RESET}")
1228
- continue
1229
-
1230
- for matched_module in matched_modules:
1231
- uninstall_module(matched_module)
1232
- else:
1233
- uninstall_module(module_name)
480
+ elif args.command == "upgrade":
481
+ if args.force or shellprint.confirm("确定要升级所有ErisPulse模块和适配器吗?", default=False):
482
+ PyPIManager.upgrade_all()
1234
483
 
1235
- elif args.command == 'install':
1236
- for module_name in args.module_names:
1237
- module_name = module_name.strip()
1238
- if not module_name:
1239
- continue
484
+ elif args.command == "run":
485
+ run_script(args.script, args.reload)
486
+
487
+ elif args.command == "list-remote":
488
+ import asyncio
489
+ try:
490
+ remote_packages = asyncio.run(PyPIManager.get_remote_packages())
1240
491
 
1241
- if '*' in module_name or '?' in module_name:
1242
- shellprint.status(f"匹配模块模式: {module_name}")
1243
- all_modules = mods.get_all_modules()
1244
- if not all_modules:
1245
- shellprint.panel("未找到任何模块,请先更新源或检查配置", "错误", "error")
1246
- continue
1247
-
1248
- matched_modules = [name for name in all_modules.keys() if fnmatch.fnmatch(name, module_name)]
1249
- if not matched_modules:
1250
- shellprint.panel(f"未找到匹配模块模式 {module_name} 的模块", "错误", "error")
1251
- continue
492
+ if args.type in ["modules", "all"] and remote_packages["modules"]:
493
+ rows = [
494
+ [
495
+ f"{Shell_Printer.GREEN}{name}{Shell_Printer.RESET}",
496
+ f"{Shell_Printer.BLUE}{info['package']}{Shell_Printer.RESET}",
497
+ info["version"],
498
+ info["description"]
499
+ ] for name, info in remote_packages["modules"].items()
500
+ ]
501
+ shellprint.table(["模块名", "包名", "版本", "描述"], rows, "远程模块", "info")
1252
502
 
1253
- print(f"{Shell_Printer.GREEN}找到 {len(matched_modules)} 个匹配模块:{Shell_Printer.RESET}")
1254
- for i, matched_module in enumerate(matched_modules, start=1):
1255
- print(f" {Shell_Printer.CYAN}{i}. {matched_module}{Shell_Printer.RESET}")
503
+ if args.type in ["adapters", "all"] and remote_packages["adapters"]:
504
+ rows = [
505
+ [
506
+ f"{Shell_Printer.YELLOW}{name}{Shell_Printer.RESET}",
507
+ f"{Shell_Printer.BLUE}{info['package']}{Shell_Printer.RESET}",
508
+ info["version"],
509
+ info["description"]
510
+ ] for name, info in remote_packages["adapters"].items()
511
+ ]
512
+ shellprint.table(["适配器名", "包名", "版本", "描述"], rows, "远程适配器", "info")
1256
513
 
1257
- if not shellprint.confirm("是否安装所有匹配模块?", default=True):
1258
- print(f"{Shell_Printer.BLUE}安装已取消{Shell_Printer.RESET}")
1259
- continue
514
+ if not remote_packages["modules"] and not remote_packages["adapters"]:
515
+ shellprint.panel("没有找到远程模块或适配器", "提示", "info")
1260
516
 
1261
- for matched_module in matched_modules:
1262
- install_module(matched_module, args.force)
1263
- else:
1264
- install_module(module_name, args.force)
1265
-
1266
- elif args.command == 'update':
1267
- source_manager.update_sources()
1268
-
1269
- elif args.command == 'upgrade':
1270
- upgrade_all_modules(args.force)
1271
-
1272
- elif args.command == 'run':
1273
- script_path = args.script
1274
- if not os.path.exists(script_path):
1275
- shellprint.panel(f"找不到指定文件: {script_path}", "错误", "error")
1276
- return
517
+ except Exception as e:
518
+ shellprint.panel(f"获取远程列表失败: {e}", "错误", "error")
1277
519
 
1278
- if args.reload:
1279
- start_reloader(script_path)
1280
- else:
1281
- shellprint.panel(f"运行脚本: {Shell_Printer.BOLD}{script_path}{Shell_Printer.RESET}", "执行", "info")
1282
- import runpy
1283
-
1284
- # 添加KeyboardInterrupt异常捕捉
1285
- try:
1286
- runpy.run_path(script_path, run_name="__main__")
1287
- except KeyboardInterrupt:
1288
- shellprint.panel("脚本执行已中断", "中断", "info")
1289
-
1290
- elif args.command == 'init':
1291
- print(f"{Shell_Printer.GREEN}正在初始化项目...{Shell_Printer.RESET}")
1292
- from . import init as init_module
1293
- try:
1294
- init_module()
1295
- print(f"{Shell_Printer.GREEN}项目初始化完成{Shell_Printer.RESET}")
1296
- except Exception as e:
1297
- print(f"{Shell_Printer.RED}项目初始化失败: {e}{Shell_Printer.RESET}")
1298
-
1299
- elif args.command == 'origin':
1300
- if args.origin_command == 'add':
1301
- success = source_manager.add_source(args.url)
1302
- if success and shellprint.confirm("源已添加,是否立即更新源以获取最新模块信息?", default=True):
1303
- source_manager.update_sources()
1304
-
1305
- elif args.origin_command == 'list':
1306
- source_manager.list_sources()
1307
-
1308
- elif args.origin_command == 'del':
1309
- source_manager.del_source(args.url)
1310
-
1311
- else:
1312
- origin_parser.print_help()
1313
- else:
1314
- parser.print_help()
520
+ except Exception as e:
521
+ shellprint.panel(f"执行命令时出错: {e}", "错误", "error")
1315
522
 
1316
523
  if __name__ == "__main__":
1317
- main()
524
+ main()