pymud 0.21.0a4__py3-none-any.whl → 0.21.0a5__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.
pymud/session.py CHANGED
@@ -1,17 +1,17 @@
1
- import asyncio, logging, re, math, os, pickle, datetime, sysconfig, time, dataclasses, functools, traceback
1
+ import asyncio, logging, re, math, os, pickle, datetime, sysconfig, time, dataclasses
2
+ from pathlib import Path
2
3
  from collections.abc import Iterable
3
4
  from collections import OrderedDict
4
- import logging
5
5
  from prompt_toolkit.utils import get_cwidth
6
6
  from wcwidth import wcswidth, wcwidth
7
- from typing import Union, Optional, Any, List, Tuple, Dict
7
+ from typing import Union, Optional, Any, List, Tuple, Dict, Type
8
8
  from .logger import Logger
9
9
  from .extras import SessionBuffer, DotDict
10
10
  from .protocol import MudClientProtocol
11
- from .modules import ModuleInfo
11
+ from .modules import ModuleInfo, Plugin
12
12
  from .objects import BaseObject, Trigger, Alias, Command, Timer, SimpleAlias, SimpleTrigger, SimpleTimer, GMCPTrigger, CodeBlock, CodeLine
13
13
  from .settings import Settings
14
-
14
+ from .decorators import exception, async_exception
15
15
 
16
16
  class Session:
17
17
  """
@@ -102,6 +102,7 @@ class Session:
102
102
  "t+" : "ignore",
103
103
  "t-" : "ignore",
104
104
  "show": "test",
105
+ "echo": "test",
105
106
  }
106
107
 
107
108
  def __init__(self, app, name, host, port, encoding = None, after_connect = None, loop = None, **kwargs):
@@ -163,15 +164,28 @@ class Session:
163
164
 
164
165
  # 将变量加载和脚本加载调整到会话创建时刻
165
166
  if Settings.client["var_autoload"]:
166
- file = f"{self.name}.mud"
167
+ muddir = Path.cwd().joinpath('save')
168
+ if not muddir.exists() or not muddir.is_dir():
169
+ muddir.mkdir()
170
+
171
+ # 处理老版本当前目录的.mud文件,移动到save目录下
172
+ file = f"{self.name}.mud"
173
+ new_loc_file = muddir.joinpath(file)
174
+ if not os.path.exists(new_loc_file):
167
175
  if os.path.exists(file):
168
- with open(file, "rb") as fp:
169
- try:
170
- vars = pickle.load(fp)
171
- self._variables.update(vars)
172
- self.info(Settings.gettext("msg_var_autoload_success", file))
173
- except Exception as e:
174
- self.warning(Settings.gettext("msg_var_autoload_fail", file, e))
176
+ os.rename(file, new_loc_file)
177
+ else:
178
+ if os.path.exists(file):
179
+ os.remove(file)
180
+
181
+ if os.path.exists(new_loc_file):
182
+ with open(new_loc_file, "rb") as fp:
183
+ try:
184
+ vars = pickle.load(fp)
185
+ self._variables.update(vars)
186
+ self.info(Settings.gettext("msg_var_autoload_success", file))
187
+ except Exception as e:
188
+ self.warning(Settings.gettext("msg_var_autoload_fail", file, e))
175
189
 
176
190
 
177
191
  if self._auto_script:
@@ -209,7 +223,7 @@ class Session:
209
223
  async def connect(self):
210
224
  "创建到远程服务器的连接,异步非阻塞方式。"
211
225
  def _protocol_factory():
212
- return MudClientProtocol(self, onDisconnected = self.onDisconnected)
226
+ return MudClientProtocol(self, onDisconnected = self.onDisconnected, encoding = self.encoding, encoding_errors = Settings.server["encoding_errors"])
213
227
 
214
228
  try:
215
229
  #self.loop = asyncio.get_running_loop()
@@ -997,7 +1011,8 @@ class Session:
997
1011
 
998
1012
  :param cmdtext: 纯文本命令
999
1013
  """
1000
- isNotCmd = True
1014
+ keepEval = True
1015
+ notHandle = True
1001
1016
 
1002
1017
  # fix bugs, commands filter for enabled and sorted for priority
1003
1018
  avai_cmds = [cmd for cmd in self._commands.values() if isinstance(cmd, Command) and cmd.enabled]
@@ -1006,15 +1021,16 @@ class Session:
1006
1021
  for command in self._commands.values():
1007
1022
  state = command.match(cmdtext)
1008
1023
  if state.result == Command.SUCCESS:
1024
+ notHandle = False
1009
1025
  # 命令的任务名称采用命令id,以便于后续查错
1010
1026
  self.create_task(command.execute(cmdtext), name = "task-{0}".format(command.id))
1011
- isNotCmd = False
1012
- break
1013
1027
 
1014
- # 再判断是否是别名
1015
- if isNotCmd:
1016
- notAlias = True
1028
+ if not command.keepEval:
1029
+ keepEval = False
1030
+ break
1017
1031
 
1032
+ # 若持续匹配,再判断是否是别名
1033
+ if keepEval:
1018
1034
  # fix bugs, aliases filter for enabled and sorted for priority, and add oneShot, keepEval judge
1019
1035
  avai_alis = [ali for ali in self._aliases.values() if isinstance(ali, Alias) and ali.enabled]
1020
1036
  avai_alis.sort(key = lambda ali: ali.priority)
@@ -1022,16 +1038,16 @@ class Session:
1022
1038
  for alias in avai_alis:
1023
1039
  state = alias.match(cmdtext)
1024
1040
  if state.result == Alias.SUCCESS:
1025
- notAlias = False
1041
+ notHandle = False
1026
1042
  if alias.oneShot:
1027
1043
  self.delAlias(alias.id)
1028
1044
 
1029
1045
  if not alias.keepEval:
1030
1046
  break
1031
1047
 
1032
- # 都不是则是普通命令,直接发送
1033
- if notAlias:
1034
- self.writeline(cmdtext)
1048
+ # 都前面都未被处理,则直接发送
1049
+ if notHandle:
1050
+ self.writeline(cmdtext)
1035
1051
 
1036
1052
  async def exec_text_async(self, cmdtext: str):
1037
1053
  """
@@ -1040,7 +1056,8 @@ class Session:
1040
1056
  异步调用时,该函数要等待对应的代码执行完毕后才会返回。可以用于确保命令执行完毕。
1041
1057
  """
1042
1058
  result = None
1043
- isNotCmd = True
1059
+ keepEval = True
1060
+ notHandle = True
1044
1061
 
1045
1062
  # fix bugs, commands filter for enabled and sorted for priority
1046
1063
  avai_cmds = [cmd for cmd in self._commands.values() if isinstance(cmd, Command) and cmd.enabled]
@@ -1051,12 +1068,13 @@ class Session:
1051
1068
  if state.result == Command.SUCCESS:
1052
1069
  # 命令的任务名称采用命令id,以便于后续查错
1053
1070
  result = await self.create_task(command.execute(cmdtext), name = "task-{0}".format(command.id))
1054
- isNotCmd = False
1055
- break
1071
+ notHandle = False
1072
+ if not command.keepEval:
1073
+ keepEval = False
1074
+ break
1056
1075
 
1057
1076
  # 再判断是否是别名
1058
- if isNotCmd:
1059
- notAlias = True
1077
+ if keepEval:
1060
1078
 
1061
1079
  # fix bugs, aliases filter for enabled and sorted for priority, and add oneShot, keepEval judge
1062
1080
  avai_alis = [ali for ali in self._aliases.values() if isinstance(ali, Alias) and ali.enabled]
@@ -1065,16 +1083,16 @@ class Session:
1065
1083
  for alias in avai_alis:
1066
1084
  state = alias.match(cmdtext)
1067
1085
  if state.result == Alias.SUCCESS:
1068
- notAlias = False
1086
+ notHandle = False
1069
1087
  if alias.oneShot:
1070
1088
  self.delAlias(alias.id)
1071
1089
 
1072
1090
  if not alias.keepEval:
1073
1091
  break
1074
1092
 
1075
- # 都不是则是普通命令,直接发送
1076
- if notAlias:
1077
- self.writeline(cmdtext)
1093
+ # 若均为处理则是普通命令,直接发送
1094
+ if notHandle:
1095
+ self.writeline(cmdtext)
1078
1096
 
1079
1097
  return result
1080
1098
 
@@ -1168,39 +1186,49 @@ class Session:
1168
1186
  """
1169
1187
  return "{0}_{1}".format(prefix, self.getUniqueNumber())
1170
1188
 
1171
- def enableGroup(self, group: str, enabled = True):
1189
+ def enableGroup(self, group: str, enabled = True, subgroup = True, types: Union[Type, Union[Tuple, List]] = (Alias, Trigger, Command, Timer, GMCPTrigger)):
1172
1190
  """
1173
1191
  使能或禁用Group中所有对象, 返回组内各对象个数。
1174
1192
 
1175
1193
  :param group: 组名,即各对象的 group 属性的值
1176
1194
  :param enabled: 使能/禁用开关。为True时表示使能, False为禁用
1195
+ :param subgroup: 是否对子组同时生效,默认为True。子组是指名称在父组名之后的用.xxx命名的组。例如, 组 group1.group2 是 group1 的子组。
1196
+ :param types: 要使能的对象类型,默认为 (Alias, Trigger, Command, Timer, GMCPTrigger)。
1197
+ 可以指定为单个类型,也可以指定为类型列表或元组。
1198
+ 若指定为单个类型,则只使能该类型的对象。
1199
+ 若指定为类型列表或元组,则使能该类型列表或元组中的所有类型的对象。
1177
1200
  :return: 5个整数的列表,依次表示改组内操作的 别名,触发器,命令,定时器,GMCP 的个数
1178
1201
  """
1179
1202
  counts = [0, 0, 0, 0, 0]
1180
- for ali in self._aliases.values():
1181
- if isinstance(ali, Alias) and (ali.group == group):
1182
- ali.enabled = enabled
1183
- counts[0] += 1
1184
-
1185
- for tri in self._triggers.values():
1186
- if isinstance(tri, Trigger) and (tri.group == group):
1187
- tri.enabled = enabled
1188
- counts[1] += 1
1189
-
1190
- for cmd in self._commands.values():
1191
- if isinstance(cmd, Command) and (cmd.group == group):
1192
- cmd.enabled = enabled
1193
- counts[2] += 1
1194
-
1195
- for tmr in self._timers.values():
1196
- if isinstance(tmr, Timer) and (tmr.group == group):
1197
- tmr.enabled = enabled
1198
- counts[3] += 1
1199
-
1200
- for gmcp in self._gmcp.values():
1201
- if isinstance(gmcp, GMCPTrigger) and (gmcp.group == group):
1202
- gmcp.enabled = enabled
1203
- counts[4] += 1
1203
+ if (Alias == types) or (isinstance(types, Union[List, Tuple]) and (Alias in types)):
1204
+ for ali in self._aliases.values():
1205
+ if isinstance(ali, Alias) and ((ali.group == group) or (subgroup and ali.group.startswith(group + "."))):
1206
+ ali.enabled = enabled
1207
+ counts[0] += 1
1208
+
1209
+ if (Trigger == types) or (isinstance(types, Union[List, Tuple]) and (Trigger in types)):
1210
+ for tri in self._triggers.values():
1211
+ if isinstance(tri, Trigger) and ((tri.group == group) or (subgroup and tri.group.startswith(group + "."))):
1212
+ tri.enabled = enabled
1213
+ counts[1] += 1
1214
+
1215
+ if (Command == types) or (isinstance(types, Union[List, Tuple]) and (Command in types)):
1216
+ for cmd in self._commands.values():
1217
+ if isinstance(cmd, Command) and ((cmd.group == group) or (subgroup and cmd.group.startswith(group + "."))):
1218
+ cmd.enabled = enabled
1219
+ counts[2] += 1
1220
+
1221
+ if (Timer == types) or (isinstance(types, Union[List, Tuple]) and (Timer in types)):
1222
+ for tmr in self._timers.values():
1223
+ if isinstance(tmr, Timer) and ((tmr.group == group) or (subgroup and tmr.group.startswith(group + "."))):
1224
+ tmr.enabled = enabled
1225
+ counts[3] += 1
1226
+
1227
+ if (GMCPTrigger == types) or (isinstance(types, Union[List, Tuple]) and (GMCPTrigger in types)):
1228
+ for gmcp in self._gmcp.values():
1229
+ if isinstance(gmcp, GMCPTrigger) and ((gmcp.group == group) or (subgroup and gmcp.group.startswith(group + "."))):
1230
+ gmcp.enabled = enabled
1231
+ counts[4] += 1
1204
1232
 
1205
1233
  return counts
1206
1234
 
@@ -2234,6 +2262,10 @@ class Session:
2234
2262
  obj.enabled = False
2235
2263
  self.info(Settings.gettext("msg_object_disabled", obj.__repr__()))
2236
2264
  elif args[1] == "del":
2265
+ if hasattr(obj, "__unload__"):
2266
+ obj.__unload__()
2267
+ if hasattr(obj, "unload"):
2268
+ obj.unload()
2237
2269
  obj.enabled = False
2238
2270
  objs.pop(args[0])
2239
2271
  self.info(Settings.gettext("msg_object_deleted", obj.__repr__()))
@@ -2402,7 +2434,7 @@ class Session:
2402
2434
 
2403
2435
  self._handle_objs("Trigger", self._triggers, *code.code[2:])
2404
2436
 
2405
- def handle_task(self, code: CodeLine, *args, **kwargs):
2437
+ def handle_task(self, code: Optional[CodeLine] = None, *args, **kwargs):
2406
2438
  '''
2407
2439
  嵌入命令 #task 的执行函数,显示当前管理的所有任务清单(仅用于调试)。
2408
2440
  该函数不应该在代码中直接调用。
@@ -2441,6 +2473,9 @@ class Session:
2441
2473
  - ``#t+ mygroup``: 使能名称为 mygroup 的组内的所有对象,包括别名、触发器、命令、定时器、GMCPTrigger等
2442
2474
  - ``#t- mygroup``: 禁用名称为 mygroup 的组内的所有对象,包括别名、触发器、命令、定时器、GMCPTrigger等
2443
2475
 
2476
+ 注意:
2477
+ 使用#t+/#t-调用时,相当于enableGroup传递的默认参数,即subgroup为True, 且types为所有类型。
2478
+
2444
2479
  相关命令:
2445
2480
  - #trigger
2446
2481
  - #alias
@@ -2472,7 +2507,7 @@ class Session:
2472
2507
  cnts = self.enableGroup(groupname, False)
2473
2508
  self.info(Settings.gettext("msg_group_disabled", groupname, *cnts))
2474
2509
 
2475
- def handle_repeat(self, code: CodeLine, *args, **kwargs):
2510
+ def handle_repeat(self, code: Optional[CodeLine] = None, *args, **kwargs):
2476
2511
  '''
2477
2512
  嵌入命令 #repeat / #rep 的执行函数,重复向session输出上一次人工输入的命令。
2478
2513
  该函数不应该在代码中直接调用。
@@ -2676,7 +2711,13 @@ class Session:
2676
2711
  def _load_module(self, module_name):
2677
2712
  "加载指定名称模块"
2678
2713
  try:
2679
- if module_name not in self._modules.keys():
2714
+ if module_name in self.application.plugins.keys():
2715
+ plugin = self.application.plugins[module_name]
2716
+ if isinstance(plugin, Plugin):
2717
+ plugin.onSessionCreate(self)
2718
+ self.info(Settings.gettext("msg_plugin_loaded", module_name))
2719
+
2720
+ elif module_name not in self._modules.keys():
2680
2721
  self._modules[module_name] = ModuleInfo(module_name, self)
2681
2722
 
2682
2723
  else:
@@ -2714,6 +2755,12 @@ class Session:
2714
2755
  if isinstance(mod, ModuleInfo):
2715
2756
  mod.unload()
2716
2757
 
2758
+ elif module_name in self.application.plugins.keys():
2759
+ plugin = self.application.plugins[module_name]
2760
+ if isinstance(plugin, Plugin):
2761
+ plugin.onSessionDestroy(self)
2762
+ self.info(Settings.gettext("msg_plugin_unloaded", module_name))
2763
+
2717
2764
  else:
2718
2765
  self.warning(Settings.gettext("msg_module_not_loaded", module_name))
2719
2766
 
@@ -2830,7 +2877,7 @@ class Session:
2830
2877
 
2831
2878
  elif mod in self.plugins.keys():
2832
2879
  self.application.reload_plugin(self.plugins[mod])
2833
- self.info(Settings.gettext("msg_plugin_reloaded", mod))
2880
+ self.info(Settings.gettext("msg_plugins_reloaded", mod))
2834
2881
  else:
2835
2882
  self.warning(Settings.gettext("msg_name_not_found", mod))
2836
2883
 
@@ -2943,7 +2990,11 @@ class Session:
2943
2990
  - #variable
2944
2991
  '''
2945
2992
 
2946
- file = f"{self.name}.mud"
2993
+ muddir = Path.cwd().joinpath('save')
2994
+ if not muddir.exists() or not muddir.is_dir():
2995
+ muddir.mkdir()
2996
+
2997
+ file = muddir.joinpath(f"{self.name}.mud")
2947
2998
 
2948
2999
  with open(file, "wb") as fp:
2949
3000
  saved = dict()
@@ -2969,28 +3020,32 @@ class Session:
2969
3020
 
2970
3021
  self.buffer.text = ""
2971
3022
 
3023
+ @exception
2972
3024
  def handle_test(self, code: CodeLine, *args, **kwargs):
2973
3025
  '''
2974
- 嵌入命令 #test / #show 的执行函数,触发器测试命令。类似于zmud的#show命令。
3026
+ 嵌入命令 #test / #show / #echo 的执行函数,触发器测试命令。类似于zmud的#show命令。
2975
3027
  该函数不应该在代码中直接调用。
2976
3028
 
2977
3029
  使用:
2978
- - #show {some_text}: 测试服务器收到{some_text}时的触发器响应情况。此时,触发器不会真的响应。
3030
+ - #show {some_text}: 测试收到服务器{some_text}时的触发器响应情况。此时,触发器不会真的响应。
2979
3031
  - #test {some_test}: 与#show 的差异是,若存在匹配的触发器,无论其是否被使能,该触发器均会实际响应。
3032
+ - #echo {some_text}: 模拟收到服务器 {some_text}的情况,触发器按正常情况响应,但不会显示测试结果。
2980
3033
 
2981
3034
  示例:
2982
- - ``#show 你深深吸了口气,站了起来。`` : 模拟服务器收到“你深深吸了口气,站了起来。”时的情况进行触发测试(仅显示触发测试情况)
3035
+ - ``#show 你深深吸了口气,站了起来。`` : 模拟收到服务器“你深深吸了口气,站了起来。”时的情况进行触发测试(仅显示触发测试情况)
2983
3036
  - ``#test %copy``: 复制一句话,模拟服务器再次收到复制的这句内容时的情况进行触发器测试
2984
- - ``#test 你深深吸了口气,站了起来。`` : 模拟服务器收到“你深深吸了口气,站了起来。”时的情况进行触发测试(会实际导致触发器动作)
3037
+ - ``#test 你深深吸了口气,站了起来。`` : 模拟收到服务器“你深深吸了口气,站了起来。”时的情况进行触发测试(会实际导致触发器动作)
3038
+ - ``#echo 你深深吸了口气,站了起来。`` : 模拟收到服务器“你深深吸了口气,站了起来。”时的情况进行触发测试(不会显示测试结果)
2985
3039
 
2986
3040
  注意:
2987
3041
  - #show命令测试触发器时,触发器不会真的响应。
2988
3042
  - #test命令测试触发器时,触发器无论是否使能,均会真的响应。
3043
+ - #echo命令可以用来人工激发触发器。
2989
3044
  '''
2990
3045
  cmd = code.code[1].lower()
2991
- docallback = False
2992
- if cmd == "test":
2993
- docallback = True
3046
+ docallback = True
3047
+ if cmd == "show":
3048
+ docallback = False
2994
3049
 
2995
3050
  new_cmd_text, new_code = code.expand(self, *args, **kwargs)
2996
3051
  line = new_cmd_text[6:] # 取出#test 之后的所有内容
@@ -3016,6 +3071,10 @@ class Session:
3016
3071
  tris_disabled.sort(key = lambda tri: tri.priority)
3017
3072
 
3018
3073
  for raw_line in lines:
3074
+ # echo 模式下,直接将原始数据输出到窗口,并进行触发测试
3075
+ if cmd == "echo":
3076
+ self.writetobuffer(raw_line, True)
3077
+
3019
3078
  tri_line = self.getPlainText(raw_line)
3020
3079
 
3021
3080
  block = False
@@ -3025,7 +3084,7 @@ class Session:
3025
3084
  else:
3026
3085
  state = tri.match(tri_line, docallback = docallback)
3027
3086
 
3028
- if state.result == Trigger.SUCCESS:
3087
+ if state and (state.result == Trigger.SUCCESS):
3029
3088
  triggered_enabled += 1
3030
3089
  if not block:
3031
3090
  triggered += 1
@@ -3040,58 +3099,59 @@ class Session:
3040
3099
  info_enabled.append(Settings.gettext("msg_tri_ignored", tri.__detailed__(), Settings.WARN_STYLE, Settings.CLR_STYLE))
3041
3100
  # info_enabled.append(f" {Settings.WARN_STYLE}{tri.__detailed__()} 可以触发,但由于优先级与keepEval设定,触发器不会触发。{Settings.CLR_STYLE}")
3042
3101
 
3043
-
3044
- for tri in tris_disabled:
3045
- if tri.raw:
3046
- state = tri.match(raw_line, docallback = docallback)
3047
- else:
3048
- state = tri.match(tri_line, docallback = docallback)
3049
-
3050
- if state.result == Trigger.SUCCESS:
3051
- triggered_disabled += 1
3052
- #info_disabled.append(f" {tri.__detailed__()} 可以匹配触发。")
3053
- info_disabled.append(Settings.gettext("msg_tri_matched", tri.__detailed__()))
3102
+ if cmd != "echo":
3103
+ for tri in tris_disabled:
3104
+ if tri.raw:
3105
+ state = tri.match(raw_line, docallback = docallback)
3106
+ else:
3107
+ state = tri.match(tri_line, docallback = docallback)
3108
+
3109
+ if state and (state.result == Trigger.SUCCESS):
3110
+ triggered_disabled += 1
3111
+ #info_disabled.append(f" {tri.__detailed__()} 可以匹配触发。")
3112
+ info_disabled.append(Settings.gettext("msg_tri_matched", tri.__detailed__()))
3113
+
3114
+ if triggered_enabled + triggered_disabled == 0:
3115
+ info_all.append("")
3116
+
3117
+ if cmd != "echo":
3118
+ if triggered_enabled == 0:
3119
+ info_enabled.insert(0, Settings.gettext("msg_enabled_summary_0", Settings.INFO_STYLE))
3120
+ #info_enabled.insert(0, f"{Settings.INFO_STYLE} 使能的触发器中,没有可以触发的。")
3121
+ elif triggered < triggered_enabled:
3122
+ info_enabled.insert(0, Settings.gettext("msg_enabled_summary_1", Settings.INFO_STYLE, triggered_enabled, triggered, triggered_enabled - triggered))
3123
+ #info_enabled.insert(0, f"{Settings.INFO_STYLE} 使能的触发器中,共有 {triggered_enabled} 个可以触发,实际触发 {triggered} 个,另有 {triggered_enabled - triggered} 个由于 keepEval 原因实际不会触发。")
3124
+ else:
3125
+ info_enabled.insert(0, Settings.gettext("msg_enabled_summary_2", Settings.INFO_STYLE, triggered_enabled))
3126
+ #info_enabled.insert(0, f"{Settings.INFO_STYLE} 使能的触发器中,共有 {triggered_enabled} 个全部可以被正常触发。")
3054
3127
 
3128
+ if triggered_disabled > 0:
3129
+ info_disabled.insert(0, Settings.gettext("msg_disabled_summary_0", Settings.INFO_STYLE, triggered_disabled))
3130
+ #info_disabled.insert(0, f"{Settings.INFO_STYLE} 未使能的触发器中,共有 {triggered_disabled} 个可以匹配。")
3131
+ else:
3132
+ info_disabled.insert(0, Settings.gettext("msg_disabled_summary_1", Settings.INFO_STYLE))
3133
+ #info_disabled.insert(0, f"{Settings.INFO_STYLE} 未使能触发器,没有可以匹配的。")
3134
+
3135
+ info_all.append("")
3055
3136
  if triggered_enabled + triggered_disabled == 0:
3056
- info_all.append("")
3057
-
3058
- if triggered_enabled == 0:
3059
- info_enabled.insert(0, Settings.gettext("msg_enabled_summary_0", Settings.INFO_STYLE))
3060
- #info_enabled.insert(0, f"{Settings.INFO_STYLE} 使能的触发器中,没有可以触发的。")
3061
- elif triggered < triggered_enabled:
3062
- info_enabled.insert(0, Settings.gettext("msg_enabled_summary_1", Settings.INFO_STYLE, triggered_enabled, triggered, triggered_enabled - triggered))
3063
- #info_enabled.insert(0, f"{Settings.INFO_STYLE} 使能的触发器中,共有 {triggered_enabled} 个可以触发,实际触发 {triggered} 个,另有 {triggered_enabled - triggered} 个由于 keepEval 原因实际不会触发。")
3064
- else:
3065
- info_enabled.insert(0, Settings.gettext("msg_enabled_summary_2", Settings.INFO_STYLE, triggered_enabled))
3066
- #info_enabled.insert(0, f"{Settings.INFO_STYLE} 使能的触发器中,共有 {triggered_enabled} 个全部可以被正常触发。")
3067
-
3068
- if triggered_disabled > 0:
3069
- info_disabled.insert(0, Settings.gettext("msg_disabled_summary_0", Settings.INFO_STYLE, triggered_disabled))
3070
- #info_disabled.insert(0, f"{Settings.INFO_STYLE} 未使能的触发器中,共有 {triggered_disabled} 个可以匹配。")
3071
- else:
3072
- info_disabled.insert(0, Settings.gettext("msg_disabled_summary_1", Settings.INFO_STYLE))
3073
- #info_disabled.insert(0, f"{Settings.INFO_STYLE} 未使能触发器,没有可以匹配的。")
3074
-
3075
- info_all.append("")
3076
- if triggered_enabled + triggered_disabled == 0:
3077
- #info_all.append(f"PYMUD 触发器测试: {'响应模式' if docallback else '测试模式'}")
3078
- info_all.append(Settings.gettext("msg_test_summary_0", line))
3079
- info_all.append(Settings.gettext("msg_test_summary_1"))
3080
- #info_all.append(f" 测试内容: {line}")
3081
- #info_all.append(f" 测试结果: 没有可以匹配的触发器。")
3082
- else:
3083
- #info_all.append(f"PYMUD 触发器测试: {'响应模式' if docallback else '测试模式'}")
3084
- info_all.append(Settings.gettext("msg_test_summary_0", line))
3085
- info_all.append(Settings.gettext("msg_test_summary_2", triggered, triggered_enabled + triggered_disabled))
3086
- #info_all.append(f" 测试内容: {line}")
3087
- #info_all.append(f" 测试结果: 有{triggered}个触发器可以被正常触发,一共有{triggered_enabled + triggered_disabled}个满足匹配触发要求。")
3088
- info_all.extend(info_enabled)
3089
- info_all.extend(info_disabled)
3090
-
3091
- title = Settings.gettext("msg_test_title", Settings.gettext("msg_triggered_mode") if docallback else Settings.gettext("msg_matched_mode"))
3092
- #title = f"触发器测试 - {'响应模式' if docallback else '测试模式'}"
3093
- self.info("\n".join(info_all), title)
3094
- #self.info("PYMUD 触发器测试 完毕")
3137
+ #info_all.append(f"PYMUD 触发器测试: {'响应模式' if docallback else '测试模式'}")
3138
+ info_all.append(Settings.gettext("msg_test_summary_0", line))
3139
+ info_all.append(Settings.gettext("msg_test_summary_1"))
3140
+ #info_all.append(f" 测试内容: {line}")
3141
+ #info_all.append(f" 测试结果: 没有可以匹配的触发器。")
3142
+ else:
3143
+ #info_all.append(f"PYMUD 触发器测试: {'响应模式' if docallback else '测试模式'}")
3144
+ info_all.append(Settings.gettext("msg_test_summary_0", line))
3145
+ info_all.append(Settings.gettext("msg_test_summary_2", triggered, triggered_enabled + triggered_disabled))
3146
+ #info_all.append(f" 测试内容: {line}")
3147
+ #info_all.append(f" 测试结果: {triggered}个触发器可以被正常触发,一共有{triggered_enabled + triggered_disabled}个满足匹配触发要求。")
3148
+ info_all.extend(info_enabled)
3149
+ info_all.extend(info_disabled)
3150
+
3151
+ title = Settings.gettext("msg_test_title", Settings.gettext("msg_triggered_mode") if docallback else Settings.gettext("msg_matched_mode"))
3152
+ #title = f"触发器测试 - {'响应模式' if docallback else '测试模式'}"
3153
+ self.info("\n".join(info_all), title)
3154
+ #self.info("PYMUD 触发器测试 完毕")
3095
3155
 
3096
3156
  def handle_plugins(self, code: CodeLine, *args, **kwargs):
3097
3157
  '''
@@ -3152,7 +3212,7 @@ class Session:
3152
3212
  new_text, new_code = code.expand(self, *args, **kwargs)
3153
3213
  self.replace(new_text[9:])
3154
3214
 
3155
- def handle_gag(self, code: CodeLine, *args, **kwargs):
3215
+ def handle_gag(self, code: Optional[CodeLine] = None, *args, **kwargs):
3156
3216
  '''
3157
3217
  嵌入命令 #gag 的执行函数,在主窗口中不显示当前行内容,一般用于触发器中。
3158
3218
  该函数不应该在代码中直接调用。
@@ -3239,7 +3299,6 @@ class Session:
3239
3299
  def info2(self, msg, title = None, style = Settings.INFO_STYLE):
3240
3300
  title = title or Settings.gettext("title_msg")
3241
3301
  msg = f"{msg}"
3242
-
3243
3302
  self.writetobuffer("{}〔{}〕{}{}".format(style, title, msg, Settings.CLR_STYLE), newline = True)
3244
3303
 
3245
3304
  def info(self, msg, title = None, style = Settings.INFO_STYLE):
@@ -3387,34 +3446,3 @@ class Session:
3387
3446
  else:
3388
3447
  self.application.show_logSelectDialog()
3389
3448
 
3390
-
3391
- def exception(func):
3392
- """方法异常处理装饰器,捕获异常后通过会话的session.error打印相关信息"""
3393
- @functools.wraps(func)
3394
- def wrapper(self, *args, **kwargs):
3395
- try:
3396
- return func(self, *args, **kwargs)
3397
- except Exception as e:
3398
- # 调用类的错误处理方法
3399
- session = getattr(self, "session", None)
3400
- if isinstance(session, Session):
3401
- session.error(f"函数执行中遇到异常, {e}, 类型为 {type(e)}")
3402
- session.error(f"异常追踪为: {traceback.format_exc()}")
3403
- else:
3404
- raise # 当没有会话时,选择重新抛出异常
3405
- return wrapper
3406
-
3407
- def async_exception(func):
3408
- """异步方法异常处理装饰器,捕获异常后通过会话的session.error打印相关信息"""
3409
- @functools.wraps(func)
3410
- async def wrapper(self, *args, **kwargs):
3411
- try:
3412
- return await func(self, *args, **kwargs)
3413
- except Exception as e:
3414
- session = getattr(self, "session", None)
3415
- if isinstance(session, Session):
3416
- session.error(f"异步执行中遇到异常, {e}, 类型为 {type(e)}")
3417
- session.error(f"异常追踪为: {traceback.format_exc()}")
3418
- else:
3419
- raise # 当没有会话时,选择重新抛出异常
3420
- return wrapper
pymud/settings.py CHANGED
@@ -15,7 +15,7 @@ class Settings:
15
15
  "APP 简要描述"
16
16
  __version__ = importlib.metadata.version("pymud")
17
17
  "APP 当前版本"
18
- __release__ = "2025-05-17"
18
+ __release__ = "2025-05-20"
19
19
  "APP 当前版本发布日期"
20
20
  __author__ = "本牛(newstart)@北侠"
21
21
  "APP 作者"
@@ -35,7 +35,7 @@ class Settings:
35
35
  "SGA" : True, # Supress Go Ahead
36
36
  "ECHO" : False, # Echo
37
37
  "GMCP" : True, # Generic Mud Communication Protocol
38
- "MSDP" : False, # Mud Server Data Protocol (has bugs, please disable MSDP)
38
+ "MSDP" : True, # Mud Server Data Protocol
39
39
  "MSSP" : True, # Mud Server Status Protocol
40
40
  "MCCP2" : False, # Mud Compress Communication Protocol V2
41
41
  "MCCP3" : False, # Mud Compress Communication Protocol V3