ErisPulse 1.2.8__py3-none-any.whl → 2.1.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.
ErisPulse/__main__.py CHANGED
@@ -1,359 +1,209 @@
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
+ search: 搜索PyPI上的ErisPulse模块
9
+ install: 安装模块/适配器包
10
+ uninstall: 卸载模块/适配器包
11
+ list: 列出已安装的模块/适配器
12
+ list-remote: 列出远程PyPI上的ErisPulse模块和适配器
13
+ upgrade: 升级所有模块/适配器
14
+
15
+ ### 启动:
29
16
  run: 运行脚本
30
17
  --reload: 启用热重载
31
18
 
32
19
  ### 示例用法:
33
-
34
20
  ```
35
21
  # 安装模块
36
- epsdk install MyModule
22
+ epsdk install Yunhu
37
23
 
38
24
  # 启用热重载
39
25
  epsdk run main.py --reload
40
-
41
- # 管理源
42
- epsdk origin add https://example.com/map.json
43
26
  ```
44
-
45
27
  """
46
28
 
47
29
  import argparse
48
- import importlib
49
- import os
30
+ import importlib.metadata
31
+ import subprocess
50
32
  import sys
33
+ import os
51
34
  import time
52
- import shutil
53
- import aiohttp
54
- import zipfile
55
- import fnmatch
56
- import asyncio
57
- import subprocess
58
- import json
59
35
  import json
60
- from .db import env
61
- from .mods import mods
36
+ import asyncio
37
+ from urllib.parse import urlparse
38
+ from typing import List, Dict, Tuple, Optional
39
+ from importlib.metadata import version, PackageNotFoundError
62
40
  from watchdog.observers import Observer
63
41
  from watchdog.events import FileSystemEventHandler
42
+ from .Core.shellprint import shellprint, Shell_Printer
64
43
 
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"
146
-
147
- print(panel)
148
-
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}")
167
-
168
- separator = "├" + "┼".join("─" * (w+2) for w in col_widths) + "┤"
169
- print(f"{cls.DIM}{separator}{cls.RESET}")
170
-
171
- for row in rows:
172
- row_line = fmt.format(*row)
173
- print(f"│{row_line}│")
44
+ class PyPIManager:
45
+ """管理PyPI上的ErisPulse模块和适配器"""
46
+
47
+ REMOTE_SOURCES = [
48
+ "https://erisdev.com/packages.json",
49
+ "https://raw.githubusercontent.com/ErisPulse/ErisPulse-ModuleRepo/main/packages.json"
50
+ ]
51
+
52
+ @staticmethod
53
+ async def get_remote_packages() -> dict:
54
+ import aiohttp
55
+ from aiohttp import ClientError, ClientTimeout
174
56
 
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}]: "
57
+ timeout = ClientTimeout(total=5)
58
+ last_error = None
194
59
 
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 += ": "
60
+ try:
61
+ async with aiohttp.ClientSession(timeout=timeout) as session:
62
+ async with session.get(PyPIManager.REMOTE_SOURCES[0]) as response:
63
+ if response.status == 200:
64
+ data = await response.json()
65
+ return {
66
+ "modules": data.get("modules", {}),
67
+ "adapters": data.get("adapters", {})
68
+ }
69
+ except (ClientError, asyncio.TimeoutError) as e:
70
+ last_error = e
71
+ shellprint.panel(f"官方源请求失败,尝试备用源: {e}", "警告", "warning")
213
72
 
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"
73
+ try:
74
+ async with aiohttp.ClientSession(timeout=timeout) as session:
75
+ async with session.get(PyPIManager.REMOTE_SOURCES[1]) as response:
76
+ if response.status == 200:
77
+ data = await response.json()
78
+ return {
79
+ "modules": data.get("modules", {}),
80
+ "adapters": data.get("adapters", {})
81
+ }
82
+ except (ClientError, asyncio.TimeoutError) as e:
83
+ last_error = e
239
84
 
240
- shellprint.status("正在验证主源...")
241
- validated_url = asyncio.run(self._validate_url(primary_source))
85
+ if last_error:
86
+ shellprint.panel(f"获取远程模块列表失败: {last_error}", "错误", "error")
87
+ return {"modules": {}, "adapters": {}}
88
+
89
+ @staticmethod
90
+ def search_packages(query: str) -> List[Dict[str, str]]:
91
+ try:
92
+ result = subprocess.run(
93
+ [sys.executable, "-m", "pip", "search", query],
94
+ capture_output=True,
95
+ text=True
96
+ )
242
97
 
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"
98
+ packages = []
99
+ for line in result.stdout.split('\n'):
100
+ if "ErisPulse-" in line:
101
+ parts = line.split(' ', 1)
102
+ if len(parts) >= 1:
103
+ name = parts[0].strip()
104
+ desc = parts[1].strip() if len(parts) > 1 else ""
105
+ packages.append({"name": name, "description": desc})
106
+ return packages
107
+ except Exception as e:
108
+ shellprint.panel(f"搜索PyPI包失败: {e}", "错误", "error")
109
+ return []
110
+
111
+ @staticmethod
112
+ def get_installed_packages() -> Dict[str, Dict[str, str]]:
113
+ packages = {
114
+ "modules": {},
115
+ "adapters": {}
116
+ }
117
+
262
118
  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
119
+ # 查找模块
120
+ for dist in importlib.metadata.distributions():
121
+ if "ErisPulse-" in dist.metadata["Name"]:
122
+ entry_points = dist.entry_points
123
+ for ep in entry_points:
124
+ if ep.group == "erispulse.module":
125
+ packages["modules"][ep.name] = {
126
+ "package": dist.metadata["Name"],
127
+ "version": dist.version,
128
+ "summary": dist.metadata["Summary"]
129
+ }
130
+ elif ep.group == "erispulse.adapter":
131
+ packages["adapters"][ep.name] = {
132
+ "package": dist.metadata["Name"],
133
+ "version": dist.version,
134
+ "summary": dist.metadata["Summary"]
135
+ }
273
136
  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")
137
+ shellprint.panel(f"获取已安装包信息失败: {e}", "错误", "error")
138
+
139
+ return packages
140
+
141
+ @staticmethod
142
+ def install_package(package_name: str, upgrade: bool = False) -> bool:
143
+ try:
144
+ cmd = [sys.executable, "-m", "pip", "install"]
145
+ if upgrade:
146
+ cmd.append("--upgrade")
147
+ cmd.append(package_name)
148
+
149
+ shellprint.status(f"正在安装 {package_name}...")
150
+ result = subprocess.run(cmd, check=True)
151
+ if result.returncode == 0:
152
+ shellprint.panel(f"包 {package_name} 安装成功", "成功", "success")
153
+ return True
282
154
  return False
155
+ except subprocess.CalledProcessError as e:
156
+ shellprint.panel(f"安装包 {package_name} 失败: {e}", "错误", "error")
157
+ return False
158
+
159
+ @staticmethod
160
+ def uninstall_package(package_name: str) -> bool:
161
+ try:
162
+ shellprint.status(f"正在卸载 {package_name}...")
163
+ result = subprocess.run(
164
+ [sys.executable, "-m", "pip", "uninstall", "-y", package_name],
165
+ check=True
166
+ )
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 upgrade_all() -> bool:
177
+ try:
178
+ installed = PyPIManager.get_installed_packages()
179
+ all_packages = set()
180
+
181
+ for pkg_type in ["modules", "adapters"]:
182
+ for pkg_info in installed[pkg_type].values():
183
+ all_packages.add(pkg_info["package"])
283
184
 
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")
185
+ if not all_packages:
186
+ shellprint.panel("没有找到可升级的ErisPulse包", "提示", "info")
187
+ return False
188
+
189
+ shellprint.panel(
190
+ f"找到 {len(all_packages)} 个可升级的包:\n" +
191
+ "\n".join(f" - {pkg}" for pkg in all_packages),
192
+ "升级列表",
193
+ "info"
194
+ )
195
+
196
+ if not shellprint.confirm("确认升级所有包吗?", default=False):
197
+ return False
198
+
199
+ for pkg in all_packages:
200
+ PyPIManager.install_package(pkg, upgrade=True)
201
+
289
202
  return True
290
- else:
291
- shellprint.panel(f" {validated_url} 已存在,无需重复添加", "提示", "info")
203
+ except Exception as e:
204
+ shellprint.panel(f"升级包失败: {e}", "错误", "error")
292
205
  return False
293
206
 
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
207
  class ReloadHandler(FileSystemEventHandler):
358
208
  def __init__(self, script_path, *args, **kwargs):
359
209
  super().__init__(*args, **kwargs)
@@ -373,7 +223,6 @@ class ReloadHandler(FileSystemEventHandler):
373
223
 
374
224
  def on_modified(self, event):
375
225
  now = time.time()
376
- # 1秒后再次触发
377
226
  if now - self.last_reload < 1.0:
378
227
  return
379
228
 
@@ -407,661 +256,37 @@ def start_reloader(script_path):
407
256
  handler.process.terminate()
408
257
  observer.join()
409
258
 
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")
259
+ def run_script(script_path: str, reload: bool = False):
260
+ if not os.path.exists(script_path):
261
+ shellprint.panel(f"找不到指定文件: {script_path}", "错误", "error")
262
+ return
418
263
 
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")
264
+ if reload:
265
+ start_reloader(script_path)
425
266
  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))
267
+ shellprint.panel(f"运行脚本: {Shell_Printer.BOLD}{script_path}{Shell_Printer.RESET}", "执行", "info")
268
+ import runpy
569
269
  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
590
-
591
- # 复制模块到modules目录
592
- script_dir = os.path.dirname(os.path.abspath(__file__))
593
- target_dir = os.path.join(script_dir, 'modules', module_name)
594
-
270
+ runpy.run_path(script_path, run_name="__main__")
271
+ except KeyboardInterrupt:
272
+ shellprint.panel("脚本执行已中断", "中断", "info")
273
+ def get_erispulse_version():
595
274
  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
- ):
739
- 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
-
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")
850
- else:
851
- shellprint.panel(f"模块 {module_name} 不存在", "错误", "error")
852
-
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")
899
-
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")
980
-
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")
994
-
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
- )
1047
-
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
- )
275
+ return version("ErisPulse")
276
+ except PackageNotFoundError:
277
+ return "unknown version"
1056
278
 
1057
279
  def main():
1058
280
  parser = argparse.ArgumentParser(
1059
281
  prog="epsdk",
1060
- formatter_class=argparse.RawTextHelpFormatter
282
+ formatter_class=argparse.RawTextHelpFormatter,
283
+ description=f"{Shell_Printer.BOLD}ErisPulse SDK 命令行工具 {get_erispulse_version()}{Shell_Printer.RESET}"
1061
284
  )
1062
285
  parser._positionals.title = f"{Shell_Printer.BOLD}{Shell_Printer.CYAN}基本命令{Shell_Printer.RESET}"
1063
286
  parser._optionals.title = f"{Shell_Printer.BOLD}{Shell_Printer.MAGENTA}可选参数{Shell_Printer.RESET}"
1064
287
 
288
+ parser.add_argument("--version", action="store_true", help="显示版本信息并退出")
289
+
1065
290
  subparsers = parser.add_subparsers(
1066
291
  dest='command',
1067
292
  title='可用的命令',
@@ -1069,249 +294,159 @@ def main():
1069
294
  help='具体命令的帮助信息'
1070
295
  )
1071
296
 
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
297
+ # 搜索命令
298
+ search_parser = subparsers.add_parser('search', help='搜索PyPI上的ErisPulse模块')
299
+ search_parser.add_argument('query', type=str, help='搜索关键词')
1093
300
 
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', '安装指定模块')
301
+ # 安装命令
302
+ install_parser = subparsers.add_parser('install', help='安装模块/适配器包')
303
+ install_parser.add_argument('package', type=str, help='要安装的包名')
304
+ install_parser.add_argument('--upgrade', '-U', action='store_true', help='升级已安装的包')
1098
305
 
1099
- # 其他命令
1100
- list_parser = subparsers.add_parser('list', help='列出所有模块信息')
1101
- list_parser.add_argument('--module', '-m', type=str, help='指定要展示的模块名称')
306
+ # 卸载命令
307
+ uninstall_parser = subparsers.add_parser('uninstall', help='卸载模块/适配器包')
308
+ uninstall_parser.add_argument('package', type=str, help='要卸载的包名')
1102
309
 
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
- )
310
+ # 列表命令
311
+ list_parser = subparsers.add_parser('list', help='列出已安装的模块/适配器')
312
+ list_parser.add_argument('--type', '-t', choices=['modules', 'adapters', 'all'], default='all',
313
+ help='列出类型 (modules: 仅模块, adapters: 仅适配器, all: 全部)')
1117
314
 
1118
- add_origin_parser = origin_subparsers.add_parser('add', help='添加模块源')
1119
- add_origin_parser.add_argument('url', type=str, help='要添加的模块源URL')
315
+ # 远程列表命令
316
+ list_remote_parser = subparsers.add_parser('list-remote', help='列出远程可用的模块和适配器')
317
+ list_remote_parser.add_argument('--type', '-t', choices=['modules', 'adapters', 'all'], default='all',
318
+ help='列出类型 (modules: 仅模块, adapters: 仅适配器, all: 全部)')
319
+ # 升级命令
320
+ upgrade_parser = subparsers.add_parser('upgrade', help='升级所有模块/适配器')
321
+ upgrade_parser.add_argument('--force', '-f', action='store_true', help='跳过确认直接升级')
1120
322
 
1121
- list_origin_parser = origin_subparsers.add_parser('list', help='列出所有模块源')
1122
-
1123
- del_origin_parser = origin_subparsers.add_parser('del', help='删除模块源')
1124
- del_origin_parser.add_argument('url', type=str, help='要删除的模块源URL')
1125
-
323
+ # 运行命令
1126
324
  run_parser = subparsers.add_parser('run', help='运行指定主程序')
1127
325
  run_parser.add_argument('script', type=str, help='要运行的主程序路径')
1128
- run_parser.add_argument('--reload', action='store_true', help='启用热重载模式,自动检测代码变动并重启')
1129
-
326
+ run_parser.add_argument('--reload', action='store_true', help='启用热重载模式')
327
+
1130
328
  args = parser.parse_args()
1131
329
 
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}")
330
+ if args.version:
331
+ print(f"{Shell_Printer.GREEN}ErisPulse {get_erispulse_version()}{Shell_Printer.RESET}")
332
+ return
333
+
334
+ if not args.command:
335
+ parser.print_help()
336
+ return
1137
337
 
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)
338
+ try:
339
+ if args.command == "search":
340
+ packages = PyPIManager.search_packages(args.query)
341
+ if packages:
342
+ rows = [
343
+ [
344
+ f"{Shell_Printer.BLUE}{pkg['name']}{Shell_Printer.RESET}",
345
+ pkg['description']
346
+ ] for pkg in packages
347
+ ]
348
+ shellprint.table(["包名", "描述"], rows, "搜索结果", "info")
1167
349
  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
350
+ shellprint.panel(f"未找到匹配 '{args.query}' 的ErisPulse包", "提示", "info")
1175
351
 
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)
352
+ elif args.command == "install":
353
+ import asyncio
354
+ # 首先检查是否是远程模块/适配器的简称
355
+ remote_packages = asyncio.run(PyPIManager.get_remote_packages())
356
+ full_package_name = None
357
+
358
+ # 检查模块
359
+ if args.package in remote_packages["modules"]:
360
+ full_package_name = remote_packages["modules"][args.package]["package"]
361
+ # 检查适配器
362
+ elif args.package in remote_packages["adapters"]:
363
+ full_package_name = remote_packages["adapters"][args.package]["package"]
364
+
365
+ # 如果找到远程包,使用完整包名安装
366
+ if full_package_name:
367
+ shellprint.panel(
368
+ f"找到远程包: {Shell_Printer.BOLD}{args.package}{Shell_Printer.RESET} → {Shell_Printer.BLUE}{full_package_name}{Shell_Printer.RESET}",
369
+ "信息",
370
+ "info"
371
+ )
372
+ PyPIManager.install_package(full_package_name, args.upgrade)
1198
373
  else:
1199
- disable_module(module_name)
374
+ # 否则按原样安装
375
+ PyPIManager.install_package(args.package, args.upgrade)
376
+
377
+ elif args.command == "uninstall":
378
+ PyPIManager.uninstall_package(args.package)
379
+
380
+ elif args.command == "list":
381
+ installed = PyPIManager.get_installed_packages()
382
+
383
+ if args.type in ["modules", "all"] and installed["modules"]:
384
+ rows = [
385
+ [
386
+ f"{Shell_Printer.GREEN}{name}{Shell_Printer.RESET}",
387
+ f"{Shell_Printer.BLUE}{info['package']}{Shell_Printer.RESET}",
388
+ info["version"],
389
+ info["summary"]
390
+ ] for name, info in installed["modules"].items()
391
+ ]
392
+ shellprint.table(["模块名", "包名", "版本", "描述"], rows, "已安装模块", "info")
1200
393
 
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
394
+ if args.type in ["adapters", "all"] and installed["adapters"]:
395
+ rows = [
396
+ [
397
+ f"{Shell_Printer.YELLOW}{name}{Shell_Printer.RESET}",
398
+ f"{Shell_Printer.BLUE}{info['package']}{Shell_Printer.RESET}",
399
+ info["version"],
400
+ info["summary"]
401
+ ] for name, info in installed["adapters"].items()
402
+ ]
403
+ shellprint.table(["适配器名", "包名", "版本", "描述"], rows, "已安装适配器", "info")
1209
404
 
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)
405
+ if not installed["modules"] and not installed["adapters"]:
406
+ shellprint.panel("没有安装任何ErisPulse模块或适配器", "提示", "info")
1234
407
 
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
408
+ elif args.command == "upgrade":
409
+ if args.force or shellprint.confirm("确定要升级所有ErisPulse模块和适配器吗?", default=False):
410
+ PyPIManager.upgrade_all()
1240
411
 
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
412
+ elif args.command == "run":
413
+ run_script(args.script, args.reload)
414
+
415
+ elif args.command == "list-remote":
416
+ import asyncio
417
+ try:
418
+ remote_packages = asyncio.run(PyPIManager.get_remote_packages())
419
+
420
+ if args.type in ["modules", "all"] and remote_packages["modules"]:
421
+ rows = [
422
+ [
423
+ f"{Shell_Printer.GREEN}{name}{Shell_Printer.RESET}",
424
+ f"{Shell_Printer.BLUE}{info['package']}{Shell_Printer.RESET}",
425
+ info["version"],
426
+ info["description"]
427
+ ] for name, info in remote_packages["modules"].items()
428
+ ]
429
+ shellprint.table(["模块名", "包名", "版本", "描述"], rows, "远程模块", "info")
1252
430
 
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}")
431
+ if args.type in ["adapters", "all"] and remote_packages["adapters"]:
432
+ rows = [
433
+ [
434
+ f"{Shell_Printer.YELLOW}{name}{Shell_Printer.RESET}",
435
+ f"{Shell_Printer.BLUE}{info['package']}{Shell_Printer.RESET}",
436
+ info["version"],
437
+ info["description"]
438
+ ] for name, info in remote_packages["adapters"].items()
439
+ ]
440
+ shellprint.table(["适配器名", "包名", "版本", "描述"], rows, "远程适配器", "info")
1256
441
 
1257
- if not shellprint.confirm("是否安装所有匹配模块?", default=True):
1258
- print(f"{Shell_Printer.BLUE}安装已取消{Shell_Printer.RESET}")
1259
- continue
442
+ if not remote_packages["modules"] and not remote_packages["adapters"]:
443
+ shellprint.panel("没有找到远程模块或适配器", "提示", "info")
1260
444
 
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
445
+ except Exception as e:
446
+ shellprint.panel(f"获取远程列表失败: {e}", "错误", "error")
1277
447
 
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()
448
+ except Exception as e:
449
+ shellprint.panel(f"执行命令时出错: {e}", "错误", "error")
1315
450
 
1316
451
  if __name__ == "__main__":
1317
- main()
452
+ main()