ErisPulse 2.1.5__py3-none-any.whl → 2.1.7__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/Core/mods.py CHANGED
@@ -26,8 +26,8 @@ class ModuleManager:
26
26
  {!--< /tips >!--}
27
27
  """
28
28
 
29
- DEFAULT_MODULE_PREFIX = "erispulse.module.data:"
30
- DEFAULT_STATUS_PREFIX = "erispulse.module.status:"
29
+ DEFAULT_MODULE_PREFIX = "erispulse.data.modules.info:"
30
+ DEFAULT_STATUS_PREFIX = "erispulse.data.modules.status:"
31
31
 
32
32
  def __init__(self):
33
33
  from .env import env
@@ -75,12 +75,9 @@ class ModuleManager:
75
75
  >>> # 禁用模块
76
76
  >>> mods.set_module_status("MyModule", False)
77
77
  """
78
+ from .logger import logger
78
79
  self.env.set(f"{self.status_prefix}{module_name}", bool(status))
79
-
80
- module_info = self.get_module(module_name)
81
- if module_info:
82
- module_info["status"] = bool(status)
83
- self.env.set(f"{self.module_prefix}{module_name}", module_info)
80
+ logger.debug(f"模块 {module_name} 状态已设置为 {status}")
84
81
 
85
82
  def get_module_status(self, module_name: str) -> bool:
86
83
  """
@@ -95,7 +92,7 @@ class ModuleManager:
95
92
  """
96
93
  status = self.env.get(f"{self.status_prefix}{module_name}", True)
97
94
  if isinstance(status, str):
98
- return status.lower() == 'true'
95
+ return status.lower() not in ('false', '0', 'no', 'off')
99
96
  return bool(status)
100
97
 
101
98
  def set_module(self, module_name: str, module_info: Dict[str, Any]) -> None:
@@ -109,11 +106,9 @@ class ModuleManager:
109
106
  >>> mods.set_module("MyModule", {
110
107
  >>> "version": "1.0.0",
111
108
  >>> "description": "我的模块",
112
- >>> "status": True
113
109
  >>> })
114
110
  """
115
111
  self.env.set(f"{self.module_prefix}{module_name}", module_info)
116
- self.set_module_status(module_name, module_info.get('status', True))
117
112
 
118
113
  def get_module(self, module_name: str) -> Optional[Dict[str, Any]]:
119
114
  """
@@ -164,8 +159,6 @@ class ModuleManager:
164
159
  module_name = key[prefix_len:]
165
160
  module_info = self.get_module(module_name)
166
161
  if module_info:
167
- status = self.get_module_status(module_name)
168
- module_info['status'] = bool(status)
169
162
  modules_info[module_name] = module_info
170
163
  return modules_info
171
164
 
ErisPulse/__init__.py CHANGED
@@ -27,9 +27,6 @@ from .Core import mods
27
27
  from .Core import adapter, AdapterFather, SendDSL
28
28
  from .Core import adapter_server
29
29
 
30
- # 确保windows下的shell能正确显示颜色
31
- os.system('')
32
-
33
30
  sdk = sys.modules[__name__]
34
31
 
35
32
  BaseModules = {
@@ -244,62 +241,47 @@ class AdapterLoader:
244
241
 
245
242
  :raises ImportError: 当适配器加载失败时抛出
246
243
  """
244
+ meta_name = entry_point.name
245
+ adapter_status = mods.get_module_status(meta_name)
246
+ logger.debug(f"适配器 {meta_name} 状态: {adapter_status}")
247
+
248
+ if adapter_status is False:
249
+ disabled_adapters.append(meta_name)
250
+ logger.warning(f"适配器 {meta_name} 已禁用,跳过加载")
251
+ return adapter_objs, enabled_adapters, disabled_adapters
252
+
247
253
  try:
248
- loaded_obj = entry_point.load()
249
- adapter_obj = sys.modules[loaded_obj.__module__]
254
+ loaded_class = entry_point.load()
255
+ adapter_obj = sys.modules[loaded_class.__module__]
250
256
  dist = importlib.metadata.distribution(entry_point.dist.name)
251
257
 
252
- # 创建adapterInfo
253
258
  adapter_info = {
254
259
  "meta": {
255
- "name": entry_point.name,
260
+ "name": meta_name,
256
261
  "version": getattr(adapter_obj, "__version__", dist.version if dist else "1.0.0"),
257
262
  "description": getattr(adapter_obj, "__description__", ""),
258
263
  "author": getattr(adapter_obj, "__author__", ""),
259
264
  "license": getattr(adapter_obj, "__license__", ""),
260
265
  "package": entry_point.dist.name
261
266
  },
262
- "adapter_class": loaded_obj
267
+ "adapter_class": loaded_class
263
268
  }
264
269
 
265
- # 检查是否已经加载过这个适配器类
266
- existing_instance = None
267
- for existing_adapter in adapter_objs.values():
268
- if hasattr(existing_adapter, 'adapterInfo'):
269
- for existing_adapter_info in existing_adapter.adapterInfo.values():
270
- if isinstance(existing_adapter_info, dict) and existing_adapter_info["adapter_class"] == loaded_obj:
271
- existing_instance = existing_adapter_info["adapter_class"]
272
- break
273
-
274
- # 如果已经存在实例,则复用
275
- if existing_instance is not None:
276
- adapter_info["adapter_class"] = existing_instance
277
-
278
270
  if not hasattr(adapter_obj, 'adapterInfo'):
279
271
  adapter_obj.adapterInfo = {}
280
272
 
281
- adapter_obj.adapterInfo[entry_point.name] = adapter_info
282
-
283
- # 检查适配器状态
284
- meta_name = entry_point.name
285
- stored_info = mods.get_module(meta_name) or {
286
- "status": True,
287
- "info": adapter_info
288
- }
289
- mods.set_module(meta_name, stored_info)
273
+ adapter_obj.adapterInfo[meta_name] = adapter_info
290
274
 
291
- if not stored_info.get('status', True):
292
- disabled_adapters.append(meta_name)
293
- logger.warning(f"适配器 {meta_name} 已禁用,跳过加载")
294
- return adapter_objs, enabled_adapters, disabled_adapters
275
+ # 存储适配器信息
276
+ mods.set_module(meta_name, adapter_info)
295
277
 
296
278
  adapter_objs[meta_name] = adapter_obj
297
279
  enabled_adapters.append(meta_name)
298
- logger.debug(f"从PyPI包加载适配器: {meta_name}")
280
+ logger.debug(f"从PyPI包发现适配器: {meta_name}")
299
281
 
300
282
  except Exception as e:
301
- logger.warning(f"从entry-point加载适配器 {entry_point.name} 失败: {e}")
302
- raise ImportError(f"无法加载适配器 {entry_point.name}: {e}")
283
+ logger.warning(f"从entry-point加载适配器 {meta_name} 失败: {e}")
284
+ raise ImportError(f"无法加载适配器 {meta_name}: {e}")
303
285
 
304
286
  return adapter_objs, enabled_adapters, disabled_adapters
305
287
 
@@ -374,18 +356,26 @@ class ModuleLoader:
374
356
 
375
357
  :raises ImportError: 当模块加载失败时抛出
376
358
  """
359
+ meta_name = entry_point.name
360
+ module_status = mods.get_module_status(meta_name)
361
+ logger.debug(f"模块 {meta_name} 状态: {module_status}")
362
+
363
+ # 首先检查模块状态,如果明确为False则直接跳过
364
+ if module_status is False:
365
+ disabled_modules.append(meta_name)
366
+ logger.warning(f"模块 {meta_name} 已禁用,跳过加载")
367
+ return module_objs, enabled_modules, disabled_modules
368
+
377
369
  try:
378
370
  loaded_obj = entry_point.load()
379
371
  module_obj = sys.modules[loaded_obj.__module__]
380
372
  dist = importlib.metadata.distribution(entry_point.dist.name)
381
373
 
382
- # 从pyproject.toml读取加载策略
383
- lazy_load = ModuleLoader._should_lazy_load(dist)
374
+ lazy_load = ModuleLoader._should_lazy_load(loaded_obj)
384
375
 
385
- # 创建moduleInfo
386
376
  module_info = {
387
377
  "meta": {
388
- "name": entry_point.name,
378
+ "name": meta_name,
389
379
  "version": getattr(module_obj, "__version__", dist.version if dist else "1.0.0"),
390
380
  "description": getattr(module_obj, "__description__", ""),
391
381
  "author": getattr(module_obj, "__author__", ""),
@@ -398,26 +388,16 @@ class ModuleLoader:
398
388
 
399
389
  module_obj.moduleInfo = module_info
400
390
 
401
- # 检查模块状态
402
- meta_name = entry_point.name
403
- stored_info = mods.get_module(meta_name) or {
404
- "status": True,
405
- "info": module_info
406
- }
407
- mods.set_module(meta_name, stored_info)
408
-
409
- if not stored_info.get('status', True):
410
- disabled_modules.append(meta_name)
411
- logger.warning(f"模块 {meta_name} 已禁用,跳过加载")
412
- return module_objs, enabled_modules, disabled_modules
391
+ # 存储模块信息
392
+ mods.set_module(meta_name, module_info)
413
393
 
414
394
  module_objs[meta_name] = module_obj
415
395
  enabled_modules.append(meta_name)
416
396
  logger.debug(f"从PyPI包加载模块: {meta_name}")
417
397
 
418
398
  except Exception as e:
419
- logger.warning(f"从entry-point加载模块 {entry_point.name} 失败: {e}")
420
- raise ImportError(f"无法加载模块 {entry_point.name}: {e}")
399
+ logger.warning(f"从entry-point加载模块 {meta_name} 失败: {e}")
400
+ raise ImportError(f"无法加载模块 {meta_name}: {e}")
421
401
 
422
402
  return module_objs, enabled_modules, disabled_modules
423
403
 
@@ -512,7 +492,7 @@ class ModuleInitializer:
512
492
 
513
493
  :return: bool 模块初始化是否成功
514
494
  """
515
- # 第一阶段:将所有模块挂载到LazyModule代理上
495
+ # 将所有模块挂载到LazyModule代理上
516
496
  for module_name in modules:
517
497
  module_obj = module_objs[module_name]
518
498
  meta_name = module_obj.moduleInfo["meta"]["name"]
@@ -544,7 +524,7 @@ class ModuleInitializer:
544
524
  setattr(sdk, meta_name, None)
545
525
  return False
546
526
 
547
- # 第二阶段:检查并初始化需要立即加载的模块
527
+ # 检查并初始化需要立即加载的模块
548
528
  for module_name in modules:
549
529
  module_obj = module_objs[module_name]
550
530
  meta_name = module_obj.moduleInfo["meta"]["name"]
@@ -577,7 +557,6 @@ class ModuleInitializer:
577
557
  return False
578
558
 
579
559
  return True
580
-
581
560
  @staticmethod
582
561
  def _register_adapters(adapters: List[str], adapter_objs: Dict[str, Any]) -> bool:
583
562
  """
@@ -590,9 +569,6 @@ class ModuleInitializer:
590
569
  :return: bool 适配器注册是否成功
591
570
  """
592
571
  success = True
593
- # 存储平台名到适配器类的映射
594
- platform_to_adapter = {}
595
- # 存储已注册的适配器类到实例的映射
596
572
  registered_classes = {}
597
573
 
598
574
  for adapter_name in adapters:
@@ -601,28 +577,31 @@ class ModuleInitializer:
601
577
  try:
602
578
  if hasattr(adapter_obj, "adapterInfo") and isinstance(adapter_obj.adapterInfo, dict):
603
579
  for platform, adapter_info in adapter_obj.adapterInfo.items():
604
- # 如果这个平台已经注册过,跳过
605
- if platform in platform_to_adapter:
580
+ if platform in sdk.adapter._adapters:
606
581
  continue
607
582
 
608
583
  adapter_class = adapter_info["adapter_class"]
609
584
 
610
- # 检查是否已经注册过这个适配器类
611
585
  if adapter_class in registered_classes:
612
- # 获取已注册的实例
613
- existing_instance = registered_classes[adapter_class]
614
- # 将新平台名指向已有实例
615
- sdk.adapter._adapters[platform] = existing_instance
616
- sdk.adapter._platform_to_instance[platform] = existing_instance
586
+ instance = registered_classes[adapter_class]
587
+ # 改为直接操作适配器字典而不是调用register
588
+ sdk.adapter._adapters[platform] = instance
589
+ sdk.adapter._platform_to_instance[platform] = instance
590
+ logger.debug(f"复用适配器实例 {adapter_class.__name__} 到平台别称 {platform}")
617
591
  else:
618
- # 注册新适配器
619
- sdk.adapter.register(platform, adapter_class)
620
- # 记录已注册的类和实例
621
- registered_classes[adapter_class] = sdk.adapter._adapters[platform]
622
-
623
- # 记录平台到适配器的映射
624
- platform_to_adapter[platform] = adapter_class
625
- logger.info(f"注册适配器: {platform}")
592
+ init_signature = inspect.signature(adapter_class.__init__)
593
+ params = init_signature.parameters
594
+
595
+ if 'sdk' in params:
596
+ instance = adapter_class(sdk)
597
+ else:
598
+ instance = adapter_class()
599
+
600
+ # 直接操作适配器字典
601
+ sdk.adapter._adapters[platform] = instance
602
+ sdk.adapter._platform_to_instance[platform] = instance
603
+ registered_classes[adapter_class] = instance
604
+ logger.info(f"注册适配器: {platform} ({adapter_class.__name__})")
626
605
  except Exception as e:
627
606
  logger.error(f"适配器 {adapter_name} 注册失败: {e}")
628
607
  success = False
ErisPulse/__main__.py CHANGED
@@ -1,30 +1,3 @@
1
- """
2
- # CLI 入口
3
-
4
- 提供命令行界面(CLI)用于包管理和启动入口。
5
-
6
- ## 主要命令
7
- ### 包管理:
8
- install: 安装模块/适配器包
9
- uninstall: 卸载模块/适配器包
10
- list: 列出已安装的模块/适配器
11
- list-remote: 列出远程PyPI上的ErisPulse模块和适配器
12
- upgrade: 升级所有模块/适配器
13
-
14
- ### 启动:
15
- run: 运行脚本
16
- --reload: 启用热重载
17
-
18
- ### 示例用法:
19
- ```
20
- # 安装模块
21
- epsdk install Yunhu
22
-
23
- # 启用热重载
24
- epsdk run main.py --reload
25
- ```
26
- """
27
-
28
1
  import argparse
29
2
  import importlib.metadata
30
3
  import subprocess
@@ -38,21 +11,45 @@ from typing import List, Dict, Tuple, Optional
38
11
  from importlib.metadata import version, PackageNotFoundError
39
12
  from watchdog.observers import Observer
40
13
  from watchdog.events import FileSystemEventHandler
41
- from .Core.shellprint import shellprint, Shell_Printer
14
+
15
+ # Rich console setup
16
+ from rich.console import Console
17
+ from rich.panel import Panel
18
+ from rich.table import Table
19
+ from rich.progress import Progress, BarColumn, TextColumn
20
+ from rich.prompt import Confirm, Prompt
21
+ from rich.text import Text
22
+ from rich.box import SIMPLE, ROUNDED, DOUBLE
23
+ from rich.style import Style
24
+ from rich.theme import Theme
25
+ from rich.layout import Layout
26
+ from rich.live import Live
27
+
28
+ # 确保在Windows上启用颜色
29
+ import sys
30
+ if sys.platform == "win32":
31
+ from colorama import init
32
+ init()
33
+
34
+ theme = Theme({
35
+ "info": "cyan",
36
+ "success": "green",
37
+ "warning": "yellow",
38
+ "error": "red",
39
+ "title": "magenta",
40
+ "default": "default",
41
+ "progress": "green",
42
+ "progress.remaining": "white",
43
+ })
44
+
45
+ console = Console(theme=theme, color_system="auto", force_terminal=True)
42
46
 
43
47
  class PyPIManager:
44
48
  """
45
49
  PyPI包管理器
46
50
 
47
51
  负责与PyPI交互,包括搜索、安装、卸载和升级ErisPulse模块/适配器
48
-
49
- {!--< tips >!--}
50
- 1. 支持多个远程源作为备份
51
- 2. 自动区分模块和适配器
52
- 3. 提供详细的错误处理
53
- {!--< /tips >!--}
54
52
  """
55
-
56
53
  REMOTE_SOURCES = [
57
54
  "https://erisdev.com/packages.json",
58
55
  "https://raw.githubusercontent.com/ErisPulse/ErisPulse-ModuleRepo/main/packages.json"
@@ -73,7 +70,6 @@ class PyPIManager:
73
70
  :raises ClientError: 当网络请求失败时抛出
74
71
  :raises asyncio.TimeoutError: 当请求超时时抛出
75
72
  """
76
-
77
73
  import aiohttp
78
74
  from aiohttp import ClientError, ClientTimeout
79
75
 
@@ -92,7 +88,11 @@ class PyPIManager:
92
88
  }
93
89
  except (ClientError, asyncio.TimeoutError) as e:
94
90
  last_error = e
95
- shellprint.panel(f"官方源请求失败,尝试备用源: {e}", "警告", "warning")
91
+ console.print(Panel(
92
+ f"官方源请求失败,尝试备用源: {e}",
93
+ title="警告",
94
+ style="warning"
95
+ ))
96
96
 
97
97
  try:
98
98
  async with aiohttp.ClientSession(timeout=timeout) as session:
@@ -108,7 +108,11 @@ class PyPIManager:
108
108
  last_error = e
109
109
 
110
110
  if last_error:
111
- shellprint.panel(f"获取远程模块列表失败: {last_error}", "错误", "error")
111
+ console.print(Panel(
112
+ f"获取远程模块列表失败: {last_error}",
113
+ title="错误",
114
+ style="error"
115
+ ))
112
116
  return {"modules": {}, "adapters": {}}
113
117
 
114
118
  @staticmethod
@@ -145,33 +149,81 @@ class PyPIManager:
145
149
  "summary": dist.metadata["Summary"]
146
150
  }
147
151
  except Exception as e:
148
- shellprint.panel(f"获取已安装包信息失败: {e}", "错误", "error")
152
+ console.print(Panel(
153
+ f"获取已安装包信息失败: {e}",
154
+ title="错误",
155
+ style="error"
156
+ ))
149
157
 
150
158
  return packages
151
159
 
160
+ @staticmethod
161
+ def uv_install_package(package_name: str, upgrade: bool = False) -> bool:
162
+ """
163
+ 优先使用uv安装包
164
+
165
+ :param package_name: str 要安装的包名
166
+ :param upgrade: bool 是否升级已安装的包 (默认: False)
167
+ :return: bool 安装是否成功
168
+ """
169
+ try:
170
+ # 检查uv是否可用
171
+ uv_check = subprocess.run(["uv", "--version"], capture_output=True)
172
+ if uv_check.returncode != 0:
173
+ return False # uv不可用
174
+
175
+ cmd = ["uv", "pip", "install"]
176
+ if upgrade:
177
+ cmd.append("--upgrade")
178
+ cmd.append(package_name)
179
+
180
+ with console.status(f"使用uv安装 {package_name}..."):
181
+ result = subprocess.run(cmd, check=True)
182
+ return result.returncode == 0
183
+ except (subprocess.CalledProcessError, FileNotFoundError):
184
+ return False
185
+
152
186
  @staticmethod
153
187
  def install_package(package_name: str, upgrade: bool = False) -> bool:
154
188
  """
155
- 安装指定包
189
+ 安装指定包 (修改后优先尝试uv)
156
190
 
157
191
  :param package_name: str 要安装的包名
158
192
  :param upgrade: bool 是否升级已安装的包 (默认: False)
159
193
  :return: bool 安装是否成功
160
194
  """
195
+ # 优先尝试uv安装
196
+ if PyPIManager.uv_install_package(package_name, upgrade):
197
+ console.print(Panel(
198
+ f"包 {package_name} 安装成功 (使用uv)",
199
+ title="成功",
200
+ style="success"
201
+ ))
202
+ return True
203
+
204
+ # 回退到pip安装
161
205
  try:
162
206
  cmd = [sys.executable, "-m", "pip", "install"]
163
207
  if upgrade:
164
208
  cmd.append("--upgrade")
165
209
  cmd.append(package_name)
166
210
 
167
- shellprint.status(f"正在安装 {package_name}...")
168
- result = subprocess.run(cmd, check=True)
169
- if result.returncode == 0:
170
- shellprint.panel(f"包 {package_name} 安装成功", "成功", "success")
171
- return True
172
- return False
211
+ with console.status(f"使用pip安装 {package_name}..."):
212
+ result = subprocess.run(cmd, check=True)
213
+ if result.returncode == 0:
214
+ console.print(Panel(
215
+ f"包 {package_name} 安装成功",
216
+ title="成功",
217
+ style="success"
218
+ ))
219
+ return True
220
+ return False
173
221
  except subprocess.CalledProcessError as e:
174
- shellprint.panel(f"安装包 {package_name} 失败: {e}", "错误", "error")
222
+ console.print(Panel(
223
+ f"安装包 {package_name} 失败: {e}",
224
+ title="错误",
225
+ style="error"
226
+ ))
175
227
  return False
176
228
 
177
229
  @staticmethod
@@ -183,17 +235,25 @@ class PyPIManager:
183
235
  :return: bool 卸载是否成功
184
236
  """
185
237
  try:
186
- shellprint.status(f"正在卸载 {package_name}...")
187
- result = subprocess.run(
188
- [sys.executable, "-m", "pip", "uninstall", "-y", package_name],
189
- check=True
190
- )
191
- if result.returncode == 0:
192
- shellprint.panel(f"包 {package_name} 卸载成功", "成功", "success")
193
- return True
194
- return False
238
+ with console.status(f"正在卸载 {package_name}..."):
239
+ result = subprocess.run(
240
+ [sys.executable, "-m", "pip", "uninstall", "-y", package_name],
241
+ check=True
242
+ )
243
+ if result.returncode == 0:
244
+ console.print(Panel(
245
+ f"包 {package_name} 卸载成功",
246
+ title="成功",
247
+ style="success"
248
+ ))
249
+ return True
250
+ return False
195
251
  except subprocess.CalledProcessError as e:
196
- shellprint.panel(f"卸载包 {package_name} 失败: {e}", "错误", "error")
252
+ console.print(Panel(
253
+ f"卸载包 {package_name} 失败: {e}",
254
+ title="错误",
255
+ style="error"
256
+ ))
197
257
  return False
198
258
 
199
259
  @staticmethod
@@ -202,11 +262,6 @@ class PyPIManager:
202
262
  升级所有已安装的ErisPulse包
203
263
 
204
264
  :return: bool 升级是否成功
205
-
206
- {!--< tips >!--}
207
- 1. 会先列出所有可升级的包
208
- 2. 需要用户确认才会执行升级
209
- {!--< /tips >!--}
210
265
  """
211
266
  try:
212
267
  installed = PyPIManager.get_installed_packages()
@@ -217,17 +272,21 @@ class PyPIManager:
217
272
  all_packages.add(pkg_info["package"])
218
273
 
219
274
  if not all_packages:
220
- shellprint.panel("没有找到可升级的ErisPulse包", "提示", "info")
275
+ console.print(Panel(
276
+ "没有找到可升级的ErisPulse包",
277
+ title="提示",
278
+ style="info"
279
+ ))
221
280
  return False
222
281
 
223
- shellprint.panel(
282
+ console.print(Panel(
224
283
  f"找到 {len(all_packages)} 个可升级的包:\n" +
225
284
  "\n".join(f" - {pkg}" for pkg in all_packages),
226
- "升级列表",
227
- "info"
228
- )
285
+ title="升级列表",
286
+ style="info"
287
+ ))
229
288
 
230
- if not shellprint.confirm("确认升级所有包吗?", default=False):
289
+ if not Confirm.ask("确认升级所有包吗?", default=False):
231
290
  return False
232
291
 
233
292
  for pkg in all_packages:
@@ -235,7 +294,11 @@ class PyPIManager:
235
294
 
236
295
  return True
237
296
  except Exception as e:
238
- shellprint.panel(f"升级包失败: {e}", "错误", "error")
297
+ console.print(Panel(
298
+ f"升级包失败: {e}",
299
+ title="错误",
300
+ style="error"
301
+ ))
239
302
  return False
240
303
 
241
304
  class ReloadHandler(FileSystemEventHandler):
@@ -243,31 +306,24 @@ class ReloadHandler(FileSystemEventHandler):
243
306
  热重载处理器
244
307
 
245
308
  监控文件变化并自动重启脚本
246
-
247
- {!--< tips >!--}
248
- 1. 基于watchdog实现文件监控
249
- 2. 有1秒的防抖延迟
250
- 3. 会终止旧进程并启动新进程
251
- {!--< /tips >!--}
252
309
  """
253
- def __init__(self, script_path, *args, **kwargs):
310
+ def __init__(self, script_path, reload_mode=False, *args, **kwargs):
254
311
  super().__init__(*args, **kwargs)
255
312
  self.script_path = script_path
256
313
  self.process = None
257
314
  self.last_reload = time.time()
315
+ self.reload_mode = reload_mode
258
316
  self.start_process()
259
317
 
260
318
  def start_process(self):
261
319
  """
262
320
  启动/重启被监控的进程
263
-
264
- {!--< internal-use >!--}
265
321
  """
266
322
  if self.process:
267
323
  self.process.terminate()
268
324
  self.process.wait()
269
325
 
270
- shellprint.status(f"启动进程: {self.script_path}")
326
+ console.print(f"[bold green]启动进程:[/] {self.script_path}")
271
327
  self.process = subprocess.Popen([sys.executable, self.script_path])
272
328
  self.last_reload = time.time()
273
329
 
@@ -281,68 +337,67 @@ class ReloadHandler(FileSystemEventHandler):
281
337
  if now - self.last_reload < 1.0:
282
338
  return
283
339
 
284
- if event.src_path.endswith(".py"):
285
- print(f"\n{Shell_Printer.CYAN}[热重载] 检测到文件变动: {event.src_path}{Shell_Printer.RESET}")
340
+ if event.src_path.endswith(".py") and self.reload_mode:
341
+ console.print(f"\n[cyan][热重载] 文件发生变动: {event.src_path}[/]")
342
+ self.start_process()
343
+ elif event.src_path.endswith("config.toml"):
344
+ console.print(f"\n[cyan][热重载] 配置发生变动: {event.src_path}[/]")
286
345
  self.start_process()
287
346
 
288
- def start_reloader(script_path):
347
+ def start_reloader(script_path, reload_mode=False):
289
348
  """
290
349
  启动热重载监控
291
350
 
292
351
  :param script_path: str 要监控的脚本路径
293
-
294
- {!--< tips >!--}
295
- 1. 监控脚本所在目录和modules目录
296
- 2. 按Ctrl+C可停止监控
297
- {!--< /tips >!--}
352
+ :param reload_mode: bool 是否启用完整重载模式 (默认: False)
298
353
  """
299
- project_root = os.path.dirname(os.path.abspath(__file__))
354
+ if not os.path.exists(script_path):
355
+ console.print(Panel(
356
+ f"找不到指定文件: {script_path}",
357
+ title="错误",
358
+ style="error"
359
+ ))
360
+ return
300
361
  watch_dirs = [
301
362
  os.path.dirname(os.path.abspath(script_path)),
302
- os.path.join(project_root, "modules")
303
363
  ]
304
364
 
305
- handler = ReloadHandler(script_path)
365
+ handler = ReloadHandler(script_path, reload_mode)
306
366
  observer = Observer()
307
367
 
308
368
  for d in watch_dirs:
309
369
  if os.path.exists(d):
310
- observer.schedule(handler, d, recursive=True)
370
+ if reload_mode:
371
+ # 完整重载模式:监控所有.py文件
372
+ observer.schedule(handler, d, recursive=True)
373
+ else:
374
+ # 普通模式:只监控config.toml
375
+ observer.schedule(handler, d, recursive=False)
311
376
 
312
377
  observer.start()
313
- print(f"\n{Shell_Printer.GREEN}{Shell_Printer.BOLD}[热重载] 已启动{Shell_Printer.RESET}")
314
- print(f"{Shell_Printer.DIM}监控目录: {', '.join(watch_dirs)}{Shell_Printer.RESET}\n")
378
+ console.print("\n[bold green][热重载] 已启动[/]")
379
+ mode_desc = "开发重载模式" if reload_mode else "配置监控模式"
380
+ console.print(f"[dim]模式: {mode_desc}\n监控目录: {', '.join(watch_dirs)}[/]\n")
315
381
  try:
382
+ first_interrupt = True
316
383
  while True:
317
384
  time.sleep(1)
318
385
  except KeyboardInterrupt:
319
- observer.stop()
320
- if handler.process:
321
- handler.process.terminate()
322
- observer.join()
323
-
324
- def run_script(script_path: str, reload: bool = False):
325
- """
326
- 运行指定脚本
327
-
328
- :param script_path: str 要运行的脚本路径
329
- :param reload: bool 是否启用热重载 (默认: False)
330
-
331
- :raises FileNotFoundError: 当脚本不存在时抛出
332
- """
333
- if not os.path.exists(script_path):
334
- shellprint.panel(f"找不到指定文件: {script_path}", "错误", "error")
335
- return
336
-
337
- if reload:
338
- start_reloader(script_path)
339
- else:
340
- shellprint.panel(f"运行脚本: {Shell_Printer.BOLD}{script_path}{Shell_Printer.RESET}", "执行", "info")
341
- import runpy
342
- try:
343
- runpy.run_path(script_path, run_name="__main__")
344
- except KeyboardInterrupt:
345
- shellprint.panel("脚本执行已中断", "中断", "info")
386
+ if first_interrupt:
387
+ first_interrupt = False
388
+ console.print("\n[bold green]正在安全关闭热重载...[/]")
389
+ console.print("[bold red]再次按下Ctrl+C以强制关闭[/]")
390
+ try:
391
+ observer.stop()
392
+ if handler.process:
393
+ handler.process.terminate()
394
+ observer.join()
395
+ except KeyboardInterrupt:
396
+ console.print("[bold red]强制关闭热重载[/]")
397
+ raise
398
+ else:
399
+ console.print(Panel("[bold red]强制关闭热重载[/]"))
400
+ raise
346
401
 
347
402
  def get_erispulse_version():
348
403
  """
@@ -360,31 +415,46 @@ def main():
360
415
  CLI主入口
361
416
 
362
417
  解析命令行参数并执行相应命令
363
-
364
- {!--< tips >!--}
365
- 1. 使用argparse处理命令行参数
366
- 2. 支持彩色输出和表格显示
367
- 3. 提供详细的错误处理
368
- {!--< /tips >!--}
369
418
  """
370
419
 
371
420
  parser = argparse.ArgumentParser(
372
421
  prog="epsdk",
373
422
  formatter_class=argparse.RawTextHelpFormatter,
374
- description=f"{Shell_Printer.BOLD}ErisPulse SDK 命令行工具 {get_erispulse_version()}{Shell_Printer.RESET}"
423
+ description=f"ErisPulse SDK 命令行工具 {get_erispulse_version()}"
375
424
  )
376
- parser._positionals.title = f"{Shell_Printer.BOLD}{Shell_Printer.CYAN}基本命令{Shell_Printer.RESET}"
377
- parser._optionals.title = f"{Shell_Printer.BOLD}{Shell_Printer.MAGENTA}可选参数{Shell_Printer.RESET}"
425
+ parser._positionals.title = "基本"
426
+ parser._optionals.title = "可选"
378
427
 
379
- parser.add_argument("--version", action="store_true", help="显示版本信息并退出")
428
+ parser.add_argument("--version", action="store_true", help="显示版本信息")
380
429
 
381
430
  subparsers = parser.add_subparsers(
382
431
  dest='command',
383
- title='可用的命令',
384
- metavar=f"{Shell_Printer.GREEN}<命令>{Shell_Printer.RESET}",
385
- help='具体命令的帮助信息'
432
+ metavar="<命令>",
386
433
  )
387
434
 
435
+ # 在main()函数中修改第三方命令加载部分
436
+ try:
437
+ entry_points = importlib.metadata.entry_points()
438
+ if hasattr(entry_points, 'select'):
439
+ cli_entries = entry_points.select(group='erispulse.cli')
440
+ else:
441
+ cli_entries = entry_points.get('erispulse.cli', [])
442
+
443
+ for entry in cli_entries:
444
+ try:
445
+ cli_func = entry.load()
446
+ if callable(cli_func):
447
+ # 直接调用函数并传入subparsers和console
448
+ cli_func(subparsers, console)
449
+ else:
450
+ console.print(f"[yellow]模块 {entry.name} 的入口点不是可调用对象[/]")
451
+ except Exception as e:
452
+ console.print(f"[red]加载第三方命令 {entry.name} 失败: {e}[/]")
453
+ import traceback
454
+ console.print(traceback.format_exc())
455
+ except Exception as e:
456
+ console.print(f"[yellow]加载第三方CLI命令失败: {e}[/]", style="warning")
457
+
388
458
  # 安装命令
389
459
  install_parser = subparsers.add_parser('install', help='安装模块/适配器包')
390
460
  install_parser.add_argument('package', type=str, help='要安装的包名')
@@ -394,6 +464,14 @@ def main():
394
464
  uninstall_parser = subparsers.add_parser('uninstall', help='卸载模块/适配器包')
395
465
  uninstall_parser.add_argument('package', type=str, help='要卸载的包名')
396
466
 
467
+ # 启用命令
468
+ enable_parser = subparsers.add_parser('enable', help='启用模块')
469
+ enable_parser.add_argument('module', type=str, help='要启用的模块名')
470
+
471
+ # 禁用命令
472
+ disable_parser = subparsers.add_parser('disable', help='禁用模块')
473
+ disable_parser.add_argument('module', type=str, help='要禁用的模块名')
474
+
397
475
  # 列表命令
398
476
  list_parser = subparsers.add_parser('list', help='列出已安装的模块/适配器')
399
477
  list_parser.add_argument('--type', '-t', choices=['modules', 'adapters', 'all'], default='all',
@@ -411,18 +489,19 @@ def main():
411
489
  run_parser = subparsers.add_parser('run', help='运行指定主程序')
412
490
  run_parser.add_argument('script', type=str, help='要运行的主程序路径')
413
491
  run_parser.add_argument('--reload', action='store_true', help='启用热重载模式')
414
-
492
+
415
493
  args = parser.parse_args()
416
494
 
417
495
  if args.version:
418
- print(f"{Shell_Printer.GREEN}ErisPulse {get_erispulse_version()}{Shell_Printer.RESET}")
496
+ console.print(f"[green]ErisPulse {get_erispulse_version()}[/]")
419
497
  return
420
498
 
421
499
  if not args.command:
422
500
  parser.print_help()
423
- return
424
501
 
425
502
  try:
503
+ if hasattr(args, 'func'):
504
+ args.func(args)
426
505
  if args.command == "install":
427
506
  import asyncio
428
507
  # 首先检查是否是远程模块/适配器的简称
@@ -438,11 +517,11 @@ def main():
438
517
 
439
518
  # 如果找到远程包,使用完整包名安装
440
519
  if full_package_name:
441
- shellprint.panel(
442
- f"找到远程包: {Shell_Printer.BOLD}{args.package}{Shell_Printer.RESET} → {Shell_Printer.BLUE}{full_package_name}{Shell_Printer.RESET}",
443
- "信息",
444
- "info"
445
- )
520
+ console.print(Panel(
521
+ f"找到远程包: [bold]{args.package}[/][blue]{full_package_name}[/]",
522
+ title="信息",
523
+ style="info"
524
+ ))
446
525
  PyPIManager.install_package(full_package_name, args.upgrade)
447
526
  else:
448
527
  # 否则按原样安装
@@ -450,77 +529,132 @@ def main():
450
529
 
451
530
  elif args.command == "uninstall":
452
531
  PyPIManager.uninstall_package(args.package)
453
-
532
+
533
+ elif args.command == "enable":
534
+ from ErisPulse.Core import mods
535
+ installed = PyPIManager.get_installed_packages()
536
+ if args.module not in installed["modules"]:
537
+ console.print(Panel(
538
+ f"模块 [red]{args.module}[/] 不存在或未安装",
539
+ title="错误",
540
+ style="error"
541
+ ))
542
+ else:
543
+ mods.set_module_status(args.module, True)
544
+ console.print(Panel(
545
+ f"模块 [green]{args.module}[/] 已启用",
546
+ title="成功",
547
+ style="success"
548
+ ))
549
+
550
+ elif args.command == "disable":
551
+ from ErisPulse.Core import mods
552
+ installed = PyPIManager.get_installed_packages()
553
+ if args.module not in installed["modules"]:
554
+ console.print(Panel(
555
+ f"模块 [red]{args.module}[/] 不存在或未安装",
556
+ title="错误",
557
+ style="error"
558
+ ))
559
+ else:
560
+ mods.set_module_status(args.module, False)
561
+ console.print(Panel(
562
+ f"模块 [yellow]{args.module}[/] 已禁用",
563
+ title="成功",
564
+ style="warning"
565
+ ))
566
+
454
567
  elif args.command == "list":
455
568
  installed = PyPIManager.get_installed_packages()
456
569
 
457
570
  if args.type in ["modules", "all"] and installed["modules"]:
458
- rows = [
459
- [
460
- f"{Shell_Printer.GREEN}{name}{Shell_Printer.RESET}",
461
- f"{Shell_Printer.BLUE}{info['package']}{Shell_Printer.RESET}",
462
- info["version"],
463
- info["summary"]
464
- ] for name, info in installed["modules"].items()
465
- ]
466
- shellprint.table(["模块名", "包名", "版本", "描述"], rows, "已安装模块", "info")
571
+ table = Table(title="已安装模块", box=SIMPLE)
572
+ table.add_column("模块名", style="green")
573
+ table.add_column("包名", style="blue")
574
+ table.add_column("版本")
575
+ table.add_column("描述")
576
+
577
+ for name, info in installed["modules"].items():
578
+ table.add_row(name, info["package"], info["version"], info["summary"])
579
+
580
+ console.print(table)
467
581
 
468
582
  if args.type in ["adapters", "all"] and installed["adapters"]:
469
- rows = [
470
- [
471
- f"{Shell_Printer.YELLOW}{name}{Shell_Printer.RESET}",
472
- f"{Shell_Printer.BLUE}{info['package']}{Shell_Printer.RESET}",
473
- info["version"],
474
- info["summary"]
475
- ] for name, info in installed["adapters"].items()
476
- ]
477
- shellprint.table(["适配器名", "包名", "版本", "描述"], rows, "已安装适配器", "info")
583
+ table = Table(title="已安装适配器", box=SIMPLE)
584
+ table.add_column("适配器名", style="yellow")
585
+ table.add_column("包名", style="blue")
586
+ table.add_column("版本")
587
+ table.add_column("描述")
588
+
589
+ for name, info in installed["adapters"].items():
590
+ table.add_row(name, info["package"], info["version"], info["summary"])
591
+
592
+ console.print(table)
478
593
 
479
594
  if not installed["modules"] and not installed["adapters"]:
480
- shellprint.panel("没有安装任何ErisPulse模块或适配器", "提示", "info")
595
+ console.print(Panel(
596
+ "没有安装任何ErisPulse模块或适配器",
597
+ title="提示",
598
+ style="info"
599
+ ))
481
600
 
482
601
  elif args.command == "upgrade":
483
- if args.force or shellprint.confirm("确定要升级所有ErisPulse模块和适配器吗?", default=False):
602
+ if args.force or Confirm.ask("确定要升级所有ErisPulse模块和适配器吗?", default=False):
484
603
  PyPIManager.upgrade_all()
485
604
 
486
605
  elif args.command == "run":
487
- run_script(args.script, args.reload)
488
-
606
+ start_reloader(args.script, args.reload)
489
607
  elif args.command == "list-remote":
490
608
  import asyncio
491
609
  try:
492
610
  remote_packages = asyncio.run(PyPIManager.get_remote_packages())
493
611
 
494
612
  if args.type in ["modules", "all"] and remote_packages["modules"]:
495
- rows = [
496
- [
497
- f"{Shell_Printer.GREEN}{name}{Shell_Printer.RESET}",
498
- f"{Shell_Printer.BLUE}{info['package']}{Shell_Printer.RESET}",
499
- info["version"],
500
- info["description"]
501
- ] for name, info in remote_packages["modules"].items()
502
- ]
503
- shellprint.table(["模块名", "包名", "版本", "描述"], rows, "远程模块", "info")
613
+ table = Table(title="远程模块", box=SIMPLE)
614
+ table.add_column("模块名", style="green")
615
+ table.add_column("包名", style="blue")
616
+ table.add_column("版本")
617
+ table.add_column("描述")
618
+
619
+ for name, info in remote_packages["modules"].items():
620
+ table.add_row(name, info["package"], info["version"], info["description"])
621
+
622
+ console.print(table)
504
623
 
505
624
  if args.type in ["adapters", "all"] and remote_packages["adapters"]:
506
- rows = [
507
- [
508
- f"{Shell_Printer.YELLOW}{name}{Shell_Printer.RESET}",
509
- f"{Shell_Printer.BLUE}{info['package']}{Shell_Printer.RESET}",
510
- info["version"],
511
- info["description"]
512
- ] for name, info in remote_packages["adapters"].items()
513
- ]
514
- shellprint.table(["适配器名", "包名", "版本", "描述"], rows, "远程适配器", "info")
625
+ table = Table(title="远程适配器", box=SIMPLE)
626
+ table.add_column("适配器名", style="yellow")
627
+ table.add_column("包名", style="blue")
628
+ table.add_column("版本")
629
+ table.add_column("描述")
630
+
631
+ for name, info in remote_packages["adapters"].items():
632
+ table.add_row(name, info["package"], info["version"], info["description"])
633
+
634
+ console.print(table)
515
635
 
516
636
  if not remote_packages["modules"] and not remote_packages["adapters"]:
517
- shellprint.panel("没有找到远程模块或适配器", "提示", "info")
637
+ console.print(Panel(
638
+ "没有找到远程模块或适配器",
639
+ title="提示",
640
+ style="info"
641
+ ))
518
642
 
519
643
  except Exception as e:
520
- shellprint.panel(f"获取远程列表失败: {e}", "错误", "error")
521
-
644
+ console.print(Panel(
645
+ f"获取远程列表失败: {e}",
646
+ title="错误",
647
+ style="error"
648
+ ))
649
+
650
+ except KeyboardInterrupt as e:
651
+ pass
522
652
  except Exception as e:
523
- shellprint.panel(f"执行命令时出错: {e}", "错误", "error")
653
+ console.print(Panel(
654
+ f"执行命令时出错: {e}",
655
+ title="错误",
656
+ style="error"
657
+ ))
524
658
 
525
659
  if __name__ == "__main__":
526
660
  main()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ErisPulse
3
- Version: 2.1.5
3
+ Version: 2.1.7
4
4
  Summary: ErisPulse 是一个模块化、可扩展的异步 Python SDK 框架,主要用于构建高效、可维护的机器人应用程序。
5
5
  Author-email: "艾莉丝·格雷拉特(WSu2059)" <wsu2059@qq.com>, runoneall <runoobsteve@gmail.com>
6
6
  License: MIT License
@@ -49,11 +49,14 @@ Classifier: Programming Language :: Python :: 3.12
49
49
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
50
50
  Requires-Python: >=3.8
51
51
  Requires-Dist: aiohttp
52
+ Requires-Dist: colorama
52
53
  Requires-Dist: fastapi>=0.116.1
53
54
  Requires-Dist: hypercorn>=0.14.0
55
+ Requires-Dist: keyboard
54
56
  Requires-Dist: pip
55
57
  Requires-Dist: pydantic>=2.10.6
56
58
  Requires-Dist: python-multipart>=0.0.20
59
+ Requires-Dist: rich
57
60
  Requires-Dist: toml
58
61
  Requires-Dist: watchdog
59
62
  Description-Content-Type: text/markdown
@@ -1,16 +1,15 @@
1
- ErisPulse/__init__.py,sha256=hsgvpT1GTBvjHpUlGSljVzuTwGgtlraQ4WnLMUFm5EM,27013
2
- ErisPulse/__main__.py,sha256=qqx1F8bPRKhjalKVapI3RtLupHuLlYPRk503oduYADY,19947
1
+ ErisPulse/__init__.py,sha256=T-N56UQyBmpvlqwH2wGi5ptPzaJpbgF5tKkJt0DlkaM,26099
2
+ ErisPulse/__main__.py,sha256=QwHMu8lmWbjS3EyOYV-wea3I0xcGmH_McWNx7JSGwts,24853
3
3
  ErisPulse/Core/__init__.py,sha256=XnQFMn4HMKzxa6KHJEvOpwt0v8jp21vAbw6aG0O8sPs,722
4
4
  ErisPulse/Core/adapter.py,sha256=lw5T336EsNtjUgSPdevcOZReZNkA8oYvvTRCHk_fo_o,18149
5
5
  ErisPulse/Core/env.py,sha256=D2BwtSbKggbeAsOzwwVv-kd5UkVmYlDUHtgLzFZqKUI,19841
6
6
  ErisPulse/Core/logger.py,sha256=VYhWKPXn88ib7S_OKCr_MibdnMH8k5sHkwLtNDZP3QM,7034
7
- ErisPulse/Core/mods.py,sha256=5SPutuzbMrA-VZwiXeNxYWfrdbpLRdYfQ0RvEkFuQgg,7308
7
+ ErisPulse/Core/mods.py,sha256=2yIq8t9Ca9CBPRiZU0yr8Lc0XGmmkB7LlH-5FWqXjw4,7023
8
8
  ErisPulse/Core/raiserr.py,sha256=TQCKbYrfqu8tjX7dJiJemK0lZCQ4-5875nUGXgb8HUk,5606
9
9
  ErisPulse/Core/server.py,sha256=FkDTeLuHD5IBnWVxvYU8pHb6yCt8GzyvC1bpOiJ7G7I,9217
10
- ErisPulse/Core/shellprint.py,sha256=-BFoyFho_D3XEhxIoKt6x5gO4C62LKwmJWKDUGiPjNY,5908
11
10
  ErisPulse/Core/util.py,sha256=7rdMmn6sBFqYd4znxBCcJjuv2eyTExdeKyZopgds868,3796
12
- erispulse-2.1.5.dist-info/METADATA,sha256=9R7jlHvOoAtXb7irzBiubt-Yub3ynOVvQGzw2MtDy0U,6190
13
- erispulse-2.1.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
14
- erispulse-2.1.5.dist-info/entry_points.txt,sha256=Jss71M6nEha0TA-DyVZugPYdcL14s9QpiOeIlgWxzOc,182
15
- erispulse-2.1.5.dist-info/licenses/LICENSE,sha256=4jyqikiB0G0n06CEEMMTzTXjE4IShghSlB74skMSPQs,1464
16
- erispulse-2.1.5.dist-info/RECORD,,
11
+ erispulse-2.1.7.dist-info/METADATA,sha256=4xIp6y-QyDEaC13715lWFeEYK2D965Ij1EuToHiJ37U,6258
12
+ erispulse-2.1.7.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
13
+ erispulse-2.1.7.dist-info/entry_points.txt,sha256=Jss71M6nEha0TA-DyVZugPYdcL14s9QpiOeIlgWxzOc,182
14
+ erispulse-2.1.7.dist-info/licenses/LICENSE,sha256=4jyqikiB0G0n06CEEMMTzTXjE4IShghSlB74skMSPQs,1464
15
+ erispulse-2.1.7.dist-info/RECORD,,
@@ -1,165 +0,0 @@
1
- import sys
2
-
3
- class Shell_Printer:
4
- # ANSI 颜色代码
5
- RESET = "\033[0m"
6
- BOLD = "\033[1m"
7
- RED = "\033[91m"
8
- GREEN = "\033[92m"
9
- YELLOW = "\033[93m"
10
- BLUE = "\033[94m"
11
- MAGENTA = "\033[95m"
12
- CYAN = "\033[96m"
13
- WHITE = "\033[97m"
14
- DIM = "\033[2m"
15
- UNDERLINE = "\033[4m"
16
- BG_BLUE = "\033[44m"
17
- BG_GREEN = "\033[42m"
18
- BG_YELLOW = "\033[43m"
19
- BG_RED = "\033[41m"
20
-
21
- def __init__(self):
22
- pass
23
-
24
- @classmethod
25
- def _get_color(cls, level):
26
- return {
27
- "info": cls.CYAN,
28
- "success": cls.GREEN,
29
- "warning": cls.YELLOW,
30
- "error": cls.RED,
31
- "title": cls.MAGENTA,
32
- "default": cls.RESET,
33
- }.get(level, cls.RESET)
34
-
35
- @classmethod
36
- def panel(cls, msg: str, title: str = None, level: str = "info") -> None:
37
- color = cls._get_color(level)
38
- width = 70
39
- border_char = "─" * width
40
-
41
- if level == "error":
42
- border_char = "═" * width
43
- msg = f"{cls.RED}✗ {msg}{cls.RESET}"
44
- elif level == "warning":
45
- border_char = "─" * width
46
- msg = f"{cls.YELLOW}⚠ {msg}{cls.RESET}"
47
-
48
- title_line = ""
49
- if title:
50
- title = f" {title.upper()} "
51
- title_padding = (width - len(title)) // 2
52
- left_pad = " " * title_padding
53
- right_pad = " " * (width - len(title) - title_padding)
54
- title_line = f"{cls.DIM}┌{left_pad}{cls.BOLD}{color}{title}{cls.RESET}{cls.DIM}{right_pad}┐{cls.RESET}\n"
55
-
56
- lines = []
57
- for line in msg.split("\n"):
58
- if len(line) > width - 4:
59
- words = line.split()
60
- current_line = ""
61
- for word in words:
62
- if len(current_line) + len(word) + 1 > width - 4:
63
- lines.append(f"{cls.DIM}│{cls.RESET} {current_line.ljust(width-4)} {cls.DIM}│{cls.RESET}")
64
- current_line = word
65
- else:
66
- current_line += (" " + word) if current_line else word
67
- if current_line:
68
- lines.append(f"{cls.DIM}│{cls.RESET} {current_line.ljust(width-4)} {cls.DIM}│{cls.RESET}")
69
- else:
70
- lines.append(f"{cls.DIM}│{cls.RESET} {line.ljust(width-4)} {cls.DIM}│{cls.RESET}")
71
-
72
- if level == "error":
73
- border_style = "╘"
74
- elif level == "warning":
75
- border_style = "╧"
76
- else:
77
- border_style = "└"
78
- bottom_border = f"{cls.DIM}{border_style}{border_char}┘{cls.RESET}"
79
-
80
- panel = f"{title_line}"
81
- panel += f"{cls.DIM}├{border_char}┤{cls.RESET}\n"
82
- panel += "\n".join(lines) + "\n"
83
- panel += f"{bottom_border}\n"
84
-
85
- print(panel)
86
-
87
- @classmethod
88
- def table(cls, headers, rows, title=None, level="info") -> None:
89
- color = cls._get_color(level)
90
- if title:
91
- print(f"{cls.BOLD}{color}== {title} =={cls.RESET}")
92
-
93
- col_widths = [len(h) for h in headers]
94
- for row in rows:
95
- for i, cell in enumerate(row):
96
- col_widths[i] = max(col_widths[i], len(str(cell)))
97
-
98
- fmt = "│".join(f" {{:<{w}}} " for w in col_widths)
99
-
100
- top_border = "┌" + "┬".join("─" * (w+2) for w in col_widths) + "┐"
101
- print(f"{cls.DIM}{top_border}{cls.RESET}")
102
-
103
- header_line = fmt.format(*headers)
104
- print(f"{cls.BOLD}{color}│{header_line}│{cls.RESET}")
105
-
106
- separator = "├" + "┼".join("─" * (w+2) for w in col_widths) + "┤"
107
- print(f"{cls.DIM}{separator}{cls.RESET}")
108
-
109
- for row in rows:
110
- row_line = fmt.format(*row)
111
- print(f"│{row_line}│")
112
-
113
- bottom_border = "└" + "┴".join("─" * (w+2) for w in col_widths) + "┘"
114
- print(f"{cls.DIM}{bottom_border}{cls.RESET}")
115
-
116
- @classmethod
117
- def progress_bar(cls, current, total, prefix="", suffix="", length=50):
118
- filled_length = int(length * current // total)
119
- percent = min(100.0, 100 * (current / float(total)))
120
- bar = f"{cls.GREEN}{'█' * filled_length}{cls.WHITE}{'░' * (length - filled_length)}{cls.RESET}"
121
- sys.stdout.write(f"\r{cls.BOLD}{prefix}{cls.RESET} {bar} {cls.BOLD}{percent:.1f}%{cls.RESET} {suffix}")
122
- sys.stdout.flush()
123
- if current == total:
124
- print()
125
-
126
- @classmethod
127
- def confirm(cls, msg, default=False) -> bool:
128
- yes_options = {'y', 'yes'}
129
- no_options = {'n', 'no'}
130
- default_str = "Y/n" if default else "y/N"
131
- prompt = f"{cls.BOLD}{msg}{cls.RESET} [{cls.CYAN}{default_str}{cls.RESET}]: "
132
-
133
- while True:
134
- ans = input(prompt).strip().lower()
135
- if not ans:
136
- return default
137
- if ans in yes_options:
138
- return True
139
- if ans in no_options:
140
- return False
141
- print(f"{cls.YELLOW}请输入 'y' 或 'n'{cls.RESET}")
142
-
143
- @classmethod
144
- def ask(cls, msg, choices=None, default="") -> str:
145
- prompt = f"{cls.BOLD}{msg}{cls.RESET}"
146
- if choices:
147
- prompt += f" ({cls.CYAN}{'/'.join(choices)}{cls.RESET})"
148
- if default:
149
- prompt += f" [{cls.BLUE}默认: {default}{cls.RESET}]"
150
- prompt += ": "
151
-
152
- while True:
153
- ans = input(prompt).strip()
154
- if not ans and default:
155
- return default
156
- if not choices or ans in choices:
157
- return ans
158
- print(f"{cls.YELLOW}请输入有效选项: {', '.join(choices)}{cls.RESET}")
159
-
160
- @classmethod
161
- def status(cls, msg, success=True):
162
- symbol = f"{cls.GREEN}✓" if success else f"{cls.RED}✗"
163
- print(f"\r{symbol}{cls.RESET} {msg}")
164
-
165
- shellprint = Shell_Printer()