pymud 0.21.0a1__py3-none-any.whl → 0.21.0a3__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/__init__.py +16 -15
- pymud/__main__.py +3 -3
- pymud/dialogs.py +160 -160
- pymud/extras.py +942 -942
- pymud/i18n.py +41 -41
- pymud/lang/i18n_chs.py +204 -204
- pymud/lang/i18n_eng.py +42 -42
- pymud/logger.py +162 -162
- pymud/main.py +206 -206
- pymud/modules.py +431 -371
- pymud/objects.py +1030 -1028
- pymud/pkuxkx.py +263 -67
- pymud/protocol.py +1008 -1008
- pymud/pymud.py +1292 -1292
- pymud/session.py +3392 -3361
- pymud/settings.py +193 -191
- {pymud-0.21.0a1.dist-info → pymud-0.21.0a3.dist-info}/METADATA +369 -370
- pymud-0.21.0a3.dist-info/RECORD +22 -0
- {pymud-0.21.0a1.dist-info → pymud-0.21.0a3.dist-info}/licenses/LICENSE.txt +674 -674
- pymud-0.21.0a1.dist-info/RECORD +0 -22
- {pymud-0.21.0a1.dist-info → pymud-0.21.0a3.dist-info}/WHEEL +0 -0
- {pymud-0.21.0a1.dist-info → pymud-0.21.0a3.dist-info}/entry_points.txt +0 -0
- {pymud-0.21.0a1.dist-info → pymud-0.21.0a3.dist-info}/top_level.txt +0 -0
pymud/objects.py
CHANGED
@@ -1,1028 +1,1030 @@
|
|
1
|
-
"""
|
2
|
-
MUD会话(session)中, 支持的对象列表
|
3
|
-
"""
|
4
|
-
|
5
|
-
import asyncio, logging, re, importlib
|
6
|
-
from abc import ABC, ABCMeta, abstractmethod
|
7
|
-
from collections.abc import Iterable
|
8
|
-
from collections import namedtuple
|
9
|
-
from typing import Any
|
10
|
-
from .settings import Settings
|
11
|
-
|
12
|
-
class CodeLine:
|
13
|
-
"""
|
14
|
-
PyMUD中可执行的代码块(单行),不应由脚本直接调用。
|
15
|
-
若脚本需要生成自己的代码块,应使用 CodeBlock。
|
16
|
-
"""
|
17
|
-
|
18
|
-
@classmethod
|
19
|
-
def create_line(cls, line: str):
|
20
|
-
hasvar = False
|
21
|
-
code_params = []
|
22
|
-
arg = ""
|
23
|
-
brace_count, single_quote, double_quote = 0, 0, 0
|
24
|
-
|
25
|
-
if len(line)> 0:
|
26
|
-
if line[0] == "#":
|
27
|
-
start_idx = 1
|
28
|
-
code_params.append("#")
|
29
|
-
else:
|
30
|
-
start_idx = 0
|
31
|
-
|
32
|
-
for i in range(start_idx, len(line)):
|
33
|
-
ch = line[i]
|
34
|
-
if ch == "{":
|
35
|
-
brace_count += 1
|
36
|
-
arg += ch
|
37
|
-
elif ch == "}":
|
38
|
-
brace_count -= 1
|
39
|
-
if brace_count < 0:
|
40
|
-
raise Exception(Settings.gettext("excpetion_brace_not_matched"))
|
41
|
-
arg += ch
|
42
|
-
elif ch == "'":
|
43
|
-
if single_quote == 0:
|
44
|
-
single_quote = 1
|
45
|
-
elif single_quote == 1:
|
46
|
-
single_quote = 0
|
47
|
-
elif ch == '"':
|
48
|
-
if double_quote == 0:
|
49
|
-
double_quote = 1
|
50
|
-
elif double_quote == 1:
|
51
|
-
double_quote = 0
|
52
|
-
|
53
|
-
elif ch == " ":
|
54
|
-
if (brace_count == 0) and (double_quote == 0) and (single_quote == 0):
|
55
|
-
code_params.append(arg)
|
56
|
-
arg = ""
|
57
|
-
else:
|
58
|
-
arg += ch
|
59
|
-
else:
|
60
|
-
arg += ch
|
61
|
-
|
62
|
-
if (single_quote > 0) or (double_quote > 0):
|
63
|
-
raise Exception(Settings.gettext("exception_quote_not_matched"))
|
64
|
-
|
65
|
-
if arg:
|
66
|
-
code_params.append(arg)
|
67
|
-
if arg[0] in ("@", "%"):
|
68
|
-
hasvar = True
|
69
|
-
|
70
|
-
syncmode = "dontcare"
|
71
|
-
if len(code_params) >= 2:
|
72
|
-
if (code_params[0] == "#"):
|
73
|
-
if code_params[1] in ("gag", "replace"):
|
74
|
-
syncmode = "sync"
|
75
|
-
elif code_params[1] in ("wa", "wait"):
|
76
|
-
syncmode = "async"
|
77
|
-
|
78
|
-
return syncmode, hasvar, tuple(code_params),
|
79
|
-
else:
|
80
|
-
return syncmode, hasvar, tuple()
|
81
|
-
|
82
|
-
def __init__(self, _code: str) -> None:
|
83
|
-
self.__code = _code
|
84
|
-
self.__syncmode, self.__hasvar, self.code = CodeLine.create_line(_code)
|
85
|
-
|
86
|
-
@property
|
87
|
-
def length(self):
|
88
|
-
return len(self.code)
|
89
|
-
|
90
|
-
@property
|
91
|
-
def hasvar(self):
|
92
|
-
return self.__hasvar
|
93
|
-
|
94
|
-
@property
|
95
|
-
def commandText(self):
|
96
|
-
return self.__code
|
97
|
-
|
98
|
-
@property
|
99
|
-
def syncMode(self):
|
100
|
-
return self.__syncmode
|
101
|
-
|
102
|
-
def execute(self, session, *args, **kwargs):
|
103
|
-
session.exec_code(self, *args, **kwargs)
|
104
|
-
|
105
|
-
def expand(self, session, *args, **kwargs):
|
106
|
-
new_code_str = self.__code
|
107
|
-
new_code = []
|
108
|
-
|
109
|
-
line = kwargs.get("line", None) or session.getVariable("%line", "None")
|
110
|
-
raw = kwargs.get("raw", None) or session.getVariable("%raw", "None")
|
111
|
-
wildcards = kwargs.get("wildcards", None)
|
112
|
-
|
113
|
-
for item in self.code:
|
114
|
-
if len(item) == 0: continue
|
115
|
-
# %1~%9,特指捕获中的匹配内容
|
116
|
-
if item in (f"%{i}" for i in range(1, 10)):
|
117
|
-
idx = int(item[1:])
|
118
|
-
if idx <= len(wildcards):
|
119
|
-
item_val = wildcards[idx-1]
|
120
|
-
else:
|
121
|
-
item_val = "None"
|
122
|
-
new_code.append(item_val)
|
123
|
-
new_code_str = new_code_str.replace(item, f"{item_val}", 1)
|
124
|
-
|
125
|
-
# 系统变量,%开头
|
126
|
-
elif item == "%line":
|
127
|
-
new_code.append(line)
|
128
|
-
new_code_str = new_code_str.replace(item, f"{line}", 1)
|
129
|
-
|
130
|
-
elif item == "%raw":
|
131
|
-
new_code.append(raw)
|
132
|
-
new_code_str = new_code_str.replace(item, f"{raw}", 1)
|
133
|
-
|
134
|
-
elif item[0] == "%":
|
135
|
-
item_val = session.getVariable(item, "")
|
136
|
-
new_code.append(item_val)
|
137
|
-
new_code_str = new_code_str.replace(item, f"{item_val}", 1)
|
138
|
-
|
139
|
-
# 非系统变量,@开头,在变量明前加@引用
|
140
|
-
elif item[0] == "@":
|
141
|
-
item_val = session.getVariable(item[1:], "")
|
142
|
-
new_code.append(item_val)
|
143
|
-
new_code_str = new_code_str.replace(item, f"{item_val}", 1)
|
144
|
-
|
145
|
-
else:
|
146
|
-
new_code.append(item)
|
147
|
-
|
148
|
-
return new_code_str, new_code
|
149
|
-
|
150
|
-
async def async_execute(self, session, *args, **kwargs):
|
151
|
-
return await session.exec_code_async(self, *args, **kwargs)
|
152
|
-
|
153
|
-
class CodeBlock:
|
154
|
-
"""
|
155
|
-
PyMUD中可以执行的代码块,可以进行命令、别名检测,以及完成变量替代。
|
156
|
-
|
157
|
-
但一般情况下,不需要手动创建 CodeBlock 对象,而是在 SimpleTrigger, SimpleAlias 等类型中直接使用字符串进行创建。或者在命令行输入文本将自动创建。
|
158
|
-
|
159
|
-
:param code: 代码块的代码本身。可以单行、多行、以及多层代码块
|
160
|
-
"""
|
161
|
-
|
162
|
-
@classmethod
|
163
|
-
def create_block(cls, code: str) -> tuple:
|
164
|
-
"创建代码块,并返回对象自身"
|
165
|
-
#若block由{}包裹,则去掉大括号直接分解
|
166
|
-
|
167
|
-
if (len(code) >= 2) and (code[0] == '{') and (code[-1] == '}'):
|
168
|
-
code = code[1:-1]
|
169
|
-
|
170
|
-
code_lines = []
|
171
|
-
line = ""
|
172
|
-
brace_count = 0
|
173
|
-
for i in range(0, len(code)):
|
174
|
-
ch = code[i]
|
175
|
-
if ch == "{":
|
176
|
-
brace_count += 1
|
177
|
-
line += ch
|
178
|
-
elif ch == "}":
|
179
|
-
brace_count -= 1
|
180
|
-
if brace_count < 0:
|
181
|
-
raise Exception(Settings.gettext("excpetion_brace_not_matched"))
|
182
|
-
line += ch
|
183
|
-
elif ch == ";":
|
184
|
-
if brace_count == 0:
|
185
|
-
code_lines.append(line)
|
186
|
-
line = ""
|
187
|
-
else:
|
188
|
-
line += ch
|
189
|
-
else:
|
190
|
-
line += ch
|
191
|
-
|
192
|
-
if line:
|
193
|
-
code_lines.append(line)
|
194
|
-
|
195
|
-
if len(code_lines) == 1:
|
196
|
-
return (CodeLine(code),)
|
197
|
-
else:
|
198
|
-
codes = []
|
199
|
-
for line in code_lines:
|
200
|
-
codes.extend(CodeBlock.create_block(line))
|
201
|
-
|
202
|
-
return tuple(codes)
|
203
|
-
|
204
|
-
def __init__(self, code) -> None:
|
205
|
-
self.__code = code
|
206
|
-
self.codes = CodeBlock.create_block(code)
|
207
|
-
|
208
|
-
self.__syncmode = "dontcare"
|
209
|
-
|
210
|
-
for code in self.codes:
|
211
|
-
if isinstance(code, CodeLine):
|
212
|
-
if code.syncMode == "dontcare":
|
213
|
-
continue
|
214
|
-
elif code.syncMode == "sync":
|
215
|
-
if self.__syncmode in ("dontcare", "sync"):
|
216
|
-
self.__syncmode = "sync"
|
217
|
-
elif self.__syncmode == "async":
|
218
|
-
self.__syncmode = "conflict"
|
219
|
-
break
|
220
|
-
|
221
|
-
elif code.syncMode == "async":
|
222
|
-
if self.__syncmode in ("dontcare", "async"):
|
223
|
-
self.__syncmode = "async"
|
224
|
-
elif self.__syncmode == "sync":
|
225
|
-
self.__syncmode = "conflict"
|
226
|
-
break
|
227
|
-
|
228
|
-
@property
|
229
|
-
def syncmode(self):
|
230
|
-
"""
|
231
|
-
只读属性: 同步模式。在创建代码块时,根据代码内容自动判定模式。
|
232
|
-
|
233
|
-
该属性有四个可能值
|
234
|
-
- ``dontcare``: 同步异步均可,既不存在强制同步命令,也不存在强制异步命令
|
235
|
-
- ``sync``: 强制同步,仅存在强制同步模式命令及其他非同步异步命令
|
236
|
-
- ``async``: 强制异步,仅存在强制异步模式命令及其他非同步异步命令
|
237
|
-
- ``conflict``: 模式冲突,同时存在强制同步和强制异步命令
|
238
|
-
|
239
|
-
强制同步模式命令包括:
|
240
|
-
- #gag
|
241
|
-
- #replace
|
242
|
-
|
243
|
-
强制异步模式命令包括:
|
244
|
-
- #wait
|
245
|
-
"""
|
246
|
-
|
247
|
-
return self.__syncmode
|
248
|
-
|
249
|
-
def execute(self, session, *args, **kwargs):
|
250
|
-
"""
|
251
|
-
执行该 CodeBlock。执行前判断 syncmode。
|
252
|
-
- 仅当 syncmode 为 sync 时,才使用同步方式执行。
|
253
|
-
- 当 syncmode 为其他值时,均使用异步方式执行
|
254
|
-
- 当 syncmode 为 conflict 时,同步命令失效,并打印警告
|
255
|
-
|
256
|
-
:param session: 命令执行的会话实例
|
257
|
-
:param args: 兼容与扩展所需,用于变量替代及其他用途
|
258
|
-
:param kwargs: 兼容与扩展所需,用于变量替代及其他用途
|
259
|
-
"""
|
260
|
-
sync = kwargs.get("sync", None)
|
261
|
-
if sync == None:
|
262
|
-
if self.syncmode in ("dontcare", "async"):
|
263
|
-
sync = False
|
264
|
-
elif self.syncmode == "sync":
|
265
|
-
sync = True
|
266
|
-
elif self.syncmode == "conflict":
|
267
|
-
session.warning(Settings.gettext("exception_forced_async"))
|
268
|
-
sync = False
|
269
|
-
|
270
|
-
if sync:
|
271
|
-
for code in self.codes:
|
272
|
-
if isinstance(code, CodeLine):
|
273
|
-
code.execute(session, *args, **kwargs)
|
274
|
-
else:
|
275
|
-
session.create_task(self.async_execute(session, *args, **kwargs))
|
276
|
-
|
277
|
-
async def async_execute(self, session, *args, **kwargs):
|
278
|
-
"""
|
279
|
-
以异步方式执行该 CodeBlock。参数与 execute 相同。
|
280
|
-
"""
|
281
|
-
result = None
|
282
|
-
for code in self.codes:
|
283
|
-
if isinstance(code, CodeLine):
|
284
|
-
result = await code.async_execute(session, *args, **kwargs)
|
285
|
-
|
286
|
-
if Settings.client["interval"] > 0:
|
287
|
-
await asyncio.sleep(Settings.client["interval"] / 1000.0)
|
288
|
-
|
289
|
-
session.clean_finished_tasks()
|
290
|
-
return result
|
291
|
-
|
292
|
-
class BaseObject:
|
293
|
-
"""
|
294
|
-
MUD会话支持的对象基类。
|
295
|
-
|
296
|
-
:param session: 所属会话对象
|
297
|
-
:param args: 兼容与扩展所需
|
298
|
-
:param kwargs: 兼容与扩展所需
|
299
|
-
|
300
|
-
kwargs支持的关键字:
|
301
|
-
:id: 唯一ID。不指定时,默认使用 __abbr__ + UniqueID 来生成
|
302
|
-
:group: 所属的组名。不指定时,默认使用空字符串
|
303
|
-
:enabled: 使能状态。不指定时,默认使用 True
|
304
|
-
:priority: 优先级,越小优先级越高。不指定时,默认使用 100
|
305
|
-
:timeout: 超时时间,单位为秒。不指定时,默认使用 10
|
306
|
-
:sync: 同步模式。不指定时,默认为 True
|
307
|
-
:oneShot: 仅执行一次标识。不指定时,默认为 False
|
308
|
-
:onSuccess: 成功时的同步回调函数。不指定时,默认使用 self.onSuccess
|
309
|
-
:onFailure: 失败时的同步回调函数。不指定时,默认使用 self.onFailure
|
310
|
-
:onTimeout: 超时时的同步回调函数。不指定时,默认使用 self.onTimeout
|
311
|
-
"""
|
312
|
-
|
313
|
-
State = namedtuple("State", ("result", "id", "line", "wildcards"))
|
314
|
-
|
315
|
-
NOTSET = N = -1
|
316
|
-
FAILURE = F = 0
|
317
|
-
SUCCESS = S = 1
|
318
|
-
TIMEOUT = T = 2
|
319
|
-
ABORT = A = 3
|
320
|
-
|
321
|
-
__abbr__ = "obj"
|
322
|
-
"内部缩写代码前缀"
|
323
|
-
|
324
|
-
def __init__(self, session, *args, **kwargs):
|
325
|
-
from .session import Session
|
326
|
-
if isinstance(session, Session):
|
327
|
-
self.session = session
|
328
|
-
else:
|
329
|
-
assert(Settings.gettext("exception_session_type_fail"))
|
330
|
-
|
331
|
-
self._enabled = True # give a default value
|
332
|
-
self.log = logging.getLogger(f"pymud.{self.__class__.__name__}")
|
333
|
-
self.id = kwargs.get("id", session.getUniqueID(self.__class__.__abbr__))
|
334
|
-
self.group = kwargs.get("group", "") # 组
|
335
|
-
self.enabled = kwargs.get("enabled", True) # 使能与否
|
336
|
-
self.priority = kwargs.get("priority", 100) # 优先级
|
337
|
-
self.timeout = kwargs.get("timeout", 10) # 超时时间
|
338
|
-
self.sync = kwargs.get("sync", True) # 同步模式,默认
|
339
|
-
self.oneShot = kwargs.get("oneShot", False) # 单次执行,非默认
|
340
|
-
|
341
|
-
self.args = args
|
342
|
-
self.kwarg = kwargs
|
343
|
-
|
344
|
-
# 成功完成,失败完成,超时的处理函数(若有指定),否则使用类的自定义函数
|
345
|
-
self._onSuccess = kwargs.get("onSuccess", self.onSuccess)
|
346
|
-
self._onFailure = kwargs.get("onFailure", self.onFailure)
|
347
|
-
self._onTimeout = kwargs.get("onTimeout", self.onTimeout)
|
348
|
-
|
349
|
-
self.log.debug(f"对象实例 {self} 创建成功.")
|
350
|
-
|
351
|
-
self.session.addObject(self)
|
352
|
-
|
353
|
-
@property
|
354
|
-
def enabled(self):
|
355
|
-
"可读写属性,使能或取消使能本对象"
|
356
|
-
return self._enabled
|
357
|
-
|
358
|
-
@enabled.setter
|
359
|
-
def enabled(self, en: bool):
|
360
|
-
self._enabled = en
|
361
|
-
|
362
|
-
def onSuccess(self, *args, **kwargs):
|
363
|
-
"成功后执行的默认回调函数"
|
364
|
-
self.log.debug(f"{self} 缺省成功回调函数被执行.")
|
365
|
-
|
366
|
-
def onFailure(self, *args, **kwargs):
|
367
|
-
"失败后执行的默认回调函数"
|
368
|
-
self.log.debug(f"{self} 缺省失败回调函数被执行.")
|
369
|
-
|
370
|
-
def onTimeout(self, *args, **kwargs):
|
371
|
-
"超时后执行的默认回调函数"
|
372
|
-
self.log.debug(f"{self} 缺省超时回调函数被执行.")
|
373
|
-
|
374
|
-
def debug(self, msg):
|
375
|
-
"在logging中记录debug信息"
|
376
|
-
self.log.debug(msg)
|
377
|
-
|
378
|
-
def info(self, msg, *args):
|
379
|
-
"若session存在,session中输出info;不存在则在logging中输出info"
|
380
|
-
if self.session:
|
381
|
-
self.session.info(msg, *args)
|
382
|
-
else:
|
383
|
-
self.log.info(msg)
|
384
|
-
|
385
|
-
def warning(self, msg, *args):
|
386
|
-
"若session存在,session中输出warning;不存在则在logging中输出warning"
|
387
|
-
if self.session:
|
388
|
-
self.session.warning(msg, *args)
|
389
|
-
else:
|
390
|
-
self.log.warning(msg)
|
391
|
-
|
392
|
-
def error(self, msg, *args):
|
393
|
-
"若session存在,session中输出error;同时在logging中输出error"
|
394
|
-
if self.session:
|
395
|
-
self.session.error(msg, *args)
|
396
|
-
else:
|
397
|
-
self.log.error(msg)
|
398
|
-
|
399
|
-
def __repr__(self) -> str:
|
400
|
-
return self.__detailed__()
|
401
|
-
|
402
|
-
def __detailed__(self) -> str:
|
403
|
-
group = f'group = "{self.group}" ' if self.group else ''
|
404
|
-
return f'<{self.__class__.__name__}> id = "{self.id}" {group}enabled = {self.enabled}'
|
405
|
-
|
406
|
-
class GMCPTrigger(BaseObject):
|
407
|
-
"""
|
408
|
-
GMCP触发器 GMCPTrigger 类型,继承自 BaseObject。
|
409
|
-
|
410
|
-
GMCP触发器处于基于 GMCP 协议的数据,其使用方法类似于 Trigger 对象
|
411
|
-
|
412
|
-
但 GMCPTrigger 必定以指定name为触发,触发时,其值直接传递给对象本身
|
413
|
-
|
414
|
-
:param session: 本对象所属的会话
|
415
|
-
:param name: 触发对应的 GMCP 的 name
|
416
|
-
"""
|
417
|
-
def __init__(self, session, name, *args, **kwargs):
|
418
|
-
self.event = asyncio.Event()
|
419
|
-
self.value = None
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
"""
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
self.
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
value_exp = value
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
:param
|
464
|
-
:param
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
:
|
470
|
-
:
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
self.
|
478
|
-
self.
|
479
|
-
self.
|
480
|
-
|
481
|
-
self.
|
482
|
-
|
483
|
-
self.
|
484
|
-
|
485
|
-
self.
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
self.
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
self.
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
:
|
544
|
-
|
545
|
-
result
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
if self.patterns
|
562
|
-
|
563
|
-
|
564
|
-
self.
|
565
|
-
self.
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
self.
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
self._mline
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
self.
|
607
|
-
elif state.result == self.
|
608
|
-
self.
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
self.reset()
|
627
|
-
|
628
|
-
self.
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
:param
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
|
664
|
-
|
665
|
-
|
666
|
-
|
667
|
-
|
668
|
-
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
|
697
|
-
|
698
|
-
|
699
|
-
|
700
|
-
|
701
|
-
:param
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
|
708
|
-
|
709
|
-
|
710
|
-
|
711
|
-
|
712
|
-
|
713
|
-
|
714
|
-
|
715
|
-
|
716
|
-
|
717
|
-
|
718
|
-
|
719
|
-
|
720
|
-
|
721
|
-
|
722
|
-
|
723
|
-
|
724
|
-
|
725
|
-
|
726
|
-
|
727
|
-
|
728
|
-
|
729
|
-
|
730
|
-
|
731
|
-
|
732
|
-
|
733
|
-
|
734
|
-
|
735
|
-
|
736
|
-
|
737
|
-
|
738
|
-
|
739
|
-
|
740
|
-
|
741
|
-
|
742
|
-
|
743
|
-
|
744
|
-
|
745
|
-
|
746
|
-
|
747
|
-
|
748
|
-
|
749
|
-
"""
|
750
|
-
|
751
|
-
|
752
|
-
|
753
|
-
|
754
|
-
|
755
|
-
|
756
|
-
|
757
|
-
|
758
|
-
|
759
|
-
|
760
|
-
|
761
|
-
|
762
|
-
self.
|
763
|
-
|
764
|
-
|
765
|
-
|
766
|
-
|
767
|
-
|
768
|
-
|
769
|
-
|
770
|
-
|
771
|
-
|
772
|
-
|
773
|
-
|
774
|
-
|
775
|
-
|
776
|
-
|
777
|
-
|
778
|
-
|
779
|
-
|
780
|
-
|
781
|
-
|
782
|
-
"""
|
783
|
-
|
784
|
-
|
785
|
-
|
786
|
-
|
787
|
-
|
788
|
-
|
789
|
-
|
790
|
-
|
791
|
-
|
792
|
-
|
793
|
-
|
794
|
-
|
795
|
-
|
796
|
-
|
797
|
-
|
798
|
-
|
799
|
-
|
800
|
-
|
801
|
-
|
802
|
-
|
803
|
-
|
804
|
-
|
805
|
-
|
806
|
-
|
807
|
-
|
808
|
-
|
809
|
-
:param
|
810
|
-
|
811
|
-
|
812
|
-
|
813
|
-
|
814
|
-
|
815
|
-
|
816
|
-
|
817
|
-
|
818
|
-
|
819
|
-
|
820
|
-
|
821
|
-
|
822
|
-
self.
|
823
|
-
self.
|
824
|
-
|
825
|
-
|
826
|
-
|
827
|
-
|
828
|
-
|
829
|
-
|
830
|
-
|
831
|
-
|
832
|
-
|
833
|
-
|
834
|
-
|
835
|
-
|
836
|
-
|
837
|
-
|
838
|
-
|
839
|
-
|
840
|
-
|
841
|
-
|
842
|
-
|
843
|
-
|
844
|
-
|
845
|
-
|
846
|
-
|
847
|
-
|
848
|
-
|
849
|
-
|
850
|
-
|
851
|
-
|
852
|
-
|
853
|
-
|
854
|
-
|
855
|
-
|
856
|
-
:
|
857
|
-
|
858
|
-
|
859
|
-
|
860
|
-
|
861
|
-
#
|
862
|
-
|
863
|
-
#
|
864
|
-
|
865
|
-
|
866
|
-
|
867
|
-
|
868
|
-
|
869
|
-
|
870
|
-
|
871
|
-
|
872
|
-
tr.
|
873
|
-
|
874
|
-
|
875
|
-
tr.
|
876
|
-
|
877
|
-
|
878
|
-
|
879
|
-
|
880
|
-
|
881
|
-
|
882
|
-
|
883
|
-
|
884
|
-
|
885
|
-
|
886
|
-
|
887
|
-
|
888
|
-
|
889
|
-
|
890
|
-
|
891
|
-
|
892
|
-
|
893
|
-
|
894
|
-
|
895
|
-
|
896
|
-
|
897
|
-
|
898
|
-
|
899
|
-
|
900
|
-
|
901
|
-
|
902
|
-
|
903
|
-
|
904
|
-
|
905
|
-
|
906
|
-
|
907
|
-
|
908
|
-
|
909
|
-
|
910
|
-
|
911
|
-
|
912
|
-
|
913
|
-
|
914
|
-
|
915
|
-
|
916
|
-
|
917
|
-
|
918
|
-
|
919
|
-
|
920
|
-
|
921
|
-
|
922
|
-
|
923
|
-
|
924
|
-
|
925
|
-
|
926
|
-
|
927
|
-
|
928
|
-
|
929
|
-
|
930
|
-
|
931
|
-
|
932
|
-
|
933
|
-
|
934
|
-
|
935
|
-
|
936
|
-
|
937
|
-
|
938
|
-
|
939
|
-
|
940
|
-
|
941
|
-
|
942
|
-
|
943
|
-
-
|
944
|
-
-
|
945
|
-
-
|
946
|
-
|
947
|
-
|
948
|
-
|
949
|
-
|
950
|
-
|
951
|
-
|
952
|
-
|
953
|
-
|
954
|
-
|
955
|
-
|
956
|
-
|
957
|
-
|
958
|
-
|
959
|
-
|
960
|
-
|
961
|
-
|
962
|
-
|
963
|
-
|
964
|
-
|
965
|
-
|
966
|
-
|
967
|
-
|
968
|
-
|
969
|
-
|
970
|
-
|
971
|
-
|
972
|
-
|
973
|
-
|
974
|
-
|
975
|
-
|
976
|
-
|
977
|
-
|
978
|
-
|
979
|
-
|
980
|
-
|
981
|
-
|
982
|
-
|
983
|
-
|
984
|
-
|
985
|
-
|
986
|
-
|
987
|
-
|
988
|
-
|
989
|
-
|
990
|
-
|
991
|
-
|
992
|
-
|
993
|
-
|
994
|
-
|
995
|
-
|
996
|
-
|
997
|
-
|
998
|
-
|
999
|
-
|
1000
|
-
self.
|
1001
|
-
|
1002
|
-
|
1003
|
-
|
1004
|
-
|
1005
|
-
|
1006
|
-
|
1007
|
-
|
1008
|
-
|
1009
|
-
|
1010
|
-
|
1011
|
-
|
1012
|
-
|
1013
|
-
|
1014
|
-
|
1015
|
-
|
1016
|
-
|
1017
|
-
|
1018
|
-
|
1019
|
-
|
1020
|
-
|
1021
|
-
|
1022
|
-
|
1023
|
-
|
1024
|
-
|
1025
|
-
|
1026
|
-
|
1027
|
-
|
1028
|
-
|
1
|
+
"""
|
2
|
+
MUD会话(session)中, 支持的对象列表
|
3
|
+
"""
|
4
|
+
|
5
|
+
import asyncio, logging, re, importlib
|
6
|
+
from abc import ABC, ABCMeta, abstractmethod
|
7
|
+
from collections.abc import Iterable
|
8
|
+
from collections import namedtuple
|
9
|
+
from typing import Any
|
10
|
+
from .settings import Settings
|
11
|
+
|
12
|
+
class CodeLine:
|
13
|
+
"""
|
14
|
+
PyMUD中可执行的代码块(单行),不应由脚本直接调用。
|
15
|
+
若脚本需要生成自己的代码块,应使用 CodeBlock。
|
16
|
+
"""
|
17
|
+
|
18
|
+
@classmethod
|
19
|
+
def create_line(cls, line: str):
|
20
|
+
hasvar = False
|
21
|
+
code_params = []
|
22
|
+
arg = ""
|
23
|
+
brace_count, single_quote, double_quote = 0, 0, 0
|
24
|
+
|
25
|
+
if len(line)> 0:
|
26
|
+
if line[0] == "#":
|
27
|
+
start_idx = 1
|
28
|
+
code_params.append("#")
|
29
|
+
else:
|
30
|
+
start_idx = 0
|
31
|
+
|
32
|
+
for i in range(start_idx, len(line)):
|
33
|
+
ch = line[i]
|
34
|
+
if ch == "{":
|
35
|
+
brace_count += 1
|
36
|
+
arg += ch
|
37
|
+
elif ch == "}":
|
38
|
+
brace_count -= 1
|
39
|
+
if brace_count < 0:
|
40
|
+
raise Exception(Settings.gettext("excpetion_brace_not_matched"))
|
41
|
+
arg += ch
|
42
|
+
elif ch == "'":
|
43
|
+
if single_quote == 0:
|
44
|
+
single_quote = 1
|
45
|
+
elif single_quote == 1:
|
46
|
+
single_quote = 0
|
47
|
+
elif ch == '"':
|
48
|
+
if double_quote == 0:
|
49
|
+
double_quote = 1
|
50
|
+
elif double_quote == 1:
|
51
|
+
double_quote = 0
|
52
|
+
|
53
|
+
elif ch == " ":
|
54
|
+
if (brace_count == 0) and (double_quote == 0) and (single_quote == 0):
|
55
|
+
code_params.append(arg)
|
56
|
+
arg = ""
|
57
|
+
else:
|
58
|
+
arg += ch
|
59
|
+
else:
|
60
|
+
arg += ch
|
61
|
+
|
62
|
+
if (single_quote > 0) or (double_quote > 0):
|
63
|
+
raise Exception(Settings.gettext("exception_quote_not_matched"))
|
64
|
+
|
65
|
+
if arg:
|
66
|
+
code_params.append(arg)
|
67
|
+
if arg[0] in ("@", "%"):
|
68
|
+
hasvar = True
|
69
|
+
|
70
|
+
syncmode = "dontcare"
|
71
|
+
if len(code_params) >= 2:
|
72
|
+
if (code_params[0] == "#"):
|
73
|
+
if code_params[1] in ("gag", "replace"):
|
74
|
+
syncmode = "sync"
|
75
|
+
elif code_params[1] in ("wa", "wait"):
|
76
|
+
syncmode = "async"
|
77
|
+
|
78
|
+
return syncmode, hasvar, tuple(code_params),
|
79
|
+
else:
|
80
|
+
return syncmode, hasvar, tuple()
|
81
|
+
|
82
|
+
def __init__(self, _code: str) -> None:
|
83
|
+
self.__code = _code
|
84
|
+
self.__syncmode, self.__hasvar, self.code = CodeLine.create_line(_code)
|
85
|
+
|
86
|
+
@property
|
87
|
+
def length(self):
|
88
|
+
return len(self.code)
|
89
|
+
|
90
|
+
@property
|
91
|
+
def hasvar(self):
|
92
|
+
return self.__hasvar
|
93
|
+
|
94
|
+
@property
|
95
|
+
def commandText(self):
|
96
|
+
return self.__code
|
97
|
+
|
98
|
+
@property
|
99
|
+
def syncMode(self):
|
100
|
+
return self.__syncmode
|
101
|
+
|
102
|
+
def execute(self, session, *args, **kwargs):
|
103
|
+
session.exec_code(self, *args, **kwargs)
|
104
|
+
|
105
|
+
def expand(self, session, *args, **kwargs):
|
106
|
+
new_code_str = self.__code
|
107
|
+
new_code = []
|
108
|
+
|
109
|
+
line = kwargs.get("line", None) or session.getVariable("%line", "None")
|
110
|
+
raw = kwargs.get("raw", None) or session.getVariable("%raw", "None")
|
111
|
+
wildcards = kwargs.get("wildcards", None)
|
112
|
+
|
113
|
+
for item in self.code:
|
114
|
+
if len(item) == 0: continue
|
115
|
+
# %1~%9,特指捕获中的匹配内容
|
116
|
+
if item in (f"%{i}" for i in range(1, 10)):
|
117
|
+
idx = int(item[1:])
|
118
|
+
if idx <= len(wildcards):
|
119
|
+
item_val = wildcards[idx-1]
|
120
|
+
else:
|
121
|
+
item_val = "None"
|
122
|
+
new_code.append(item_val)
|
123
|
+
new_code_str = new_code_str.replace(item, f"{item_val}", 1)
|
124
|
+
|
125
|
+
# 系统变量,%开头
|
126
|
+
elif item == "%line":
|
127
|
+
new_code.append(line)
|
128
|
+
new_code_str = new_code_str.replace(item, f"{line}", 1)
|
129
|
+
|
130
|
+
elif item == "%raw":
|
131
|
+
new_code.append(raw)
|
132
|
+
new_code_str = new_code_str.replace(item, f"{raw}", 1)
|
133
|
+
|
134
|
+
elif item[0] == "%":
|
135
|
+
item_val = session.getVariable(item, "")
|
136
|
+
new_code.append(item_val)
|
137
|
+
new_code_str = new_code_str.replace(item, f"{item_val}", 1)
|
138
|
+
|
139
|
+
# 非系统变量,@开头,在变量明前加@引用
|
140
|
+
elif item[0] == "@":
|
141
|
+
item_val = session.getVariable(item[1:], "")
|
142
|
+
new_code.append(item_val)
|
143
|
+
new_code_str = new_code_str.replace(item, f"{item_val}", 1)
|
144
|
+
|
145
|
+
else:
|
146
|
+
new_code.append(item)
|
147
|
+
|
148
|
+
return new_code_str, new_code
|
149
|
+
|
150
|
+
async def async_execute(self, session, *args, **kwargs):
|
151
|
+
return await session.exec_code_async(self, *args, **kwargs)
|
152
|
+
|
153
|
+
class CodeBlock:
|
154
|
+
"""
|
155
|
+
PyMUD中可以执行的代码块,可以进行命令、别名检测,以及完成变量替代。
|
156
|
+
|
157
|
+
但一般情况下,不需要手动创建 CodeBlock 对象,而是在 SimpleTrigger, SimpleAlias 等类型中直接使用字符串进行创建。或者在命令行输入文本将自动创建。
|
158
|
+
|
159
|
+
:param code: 代码块的代码本身。可以单行、多行、以及多层代码块
|
160
|
+
"""
|
161
|
+
|
162
|
+
@classmethod
|
163
|
+
def create_block(cls, code: str) -> tuple:
|
164
|
+
"创建代码块,并返回对象自身"
|
165
|
+
#若block由{}包裹,则去掉大括号直接分解
|
166
|
+
|
167
|
+
if (len(code) >= 2) and (code[0] == '{') and (code[-1] == '}'):
|
168
|
+
code = code[1:-1]
|
169
|
+
|
170
|
+
code_lines = []
|
171
|
+
line = ""
|
172
|
+
brace_count = 0
|
173
|
+
for i in range(0, len(code)):
|
174
|
+
ch = code[i]
|
175
|
+
if ch == "{":
|
176
|
+
brace_count += 1
|
177
|
+
line += ch
|
178
|
+
elif ch == "}":
|
179
|
+
brace_count -= 1
|
180
|
+
if brace_count < 0:
|
181
|
+
raise Exception(Settings.gettext("excpetion_brace_not_matched"))
|
182
|
+
line += ch
|
183
|
+
elif ch == ";":
|
184
|
+
if brace_count == 0:
|
185
|
+
code_lines.append(line)
|
186
|
+
line = ""
|
187
|
+
else:
|
188
|
+
line += ch
|
189
|
+
else:
|
190
|
+
line += ch
|
191
|
+
|
192
|
+
if line:
|
193
|
+
code_lines.append(line)
|
194
|
+
|
195
|
+
if len(code_lines) == 1:
|
196
|
+
return (CodeLine(code),)
|
197
|
+
else:
|
198
|
+
codes = []
|
199
|
+
for line in code_lines:
|
200
|
+
codes.extend(CodeBlock.create_block(line))
|
201
|
+
|
202
|
+
return tuple(codes)
|
203
|
+
|
204
|
+
def __init__(self, code) -> None:
|
205
|
+
self.__code = code
|
206
|
+
self.codes = CodeBlock.create_block(code)
|
207
|
+
|
208
|
+
self.__syncmode = "dontcare"
|
209
|
+
|
210
|
+
for code in self.codes:
|
211
|
+
if isinstance(code, CodeLine):
|
212
|
+
if code.syncMode == "dontcare":
|
213
|
+
continue
|
214
|
+
elif code.syncMode == "sync":
|
215
|
+
if self.__syncmode in ("dontcare", "sync"):
|
216
|
+
self.__syncmode = "sync"
|
217
|
+
elif self.__syncmode == "async":
|
218
|
+
self.__syncmode = "conflict"
|
219
|
+
break
|
220
|
+
|
221
|
+
elif code.syncMode == "async":
|
222
|
+
if self.__syncmode in ("dontcare", "async"):
|
223
|
+
self.__syncmode = "async"
|
224
|
+
elif self.__syncmode == "sync":
|
225
|
+
self.__syncmode = "conflict"
|
226
|
+
break
|
227
|
+
|
228
|
+
@property
|
229
|
+
def syncmode(self):
|
230
|
+
"""
|
231
|
+
只读属性: 同步模式。在创建代码块时,根据代码内容自动判定模式。
|
232
|
+
|
233
|
+
该属性有四个可能值
|
234
|
+
- ``dontcare``: 同步异步均可,既不存在强制同步命令,也不存在强制异步命令
|
235
|
+
- ``sync``: 强制同步,仅存在强制同步模式命令及其他非同步异步命令
|
236
|
+
- ``async``: 强制异步,仅存在强制异步模式命令及其他非同步异步命令
|
237
|
+
- ``conflict``: 模式冲突,同时存在强制同步和强制异步命令
|
238
|
+
|
239
|
+
强制同步模式命令包括:
|
240
|
+
- #gag
|
241
|
+
- #replace
|
242
|
+
|
243
|
+
强制异步模式命令包括:
|
244
|
+
- #wait
|
245
|
+
"""
|
246
|
+
|
247
|
+
return self.__syncmode
|
248
|
+
|
249
|
+
def execute(self, session, *args, **kwargs):
|
250
|
+
"""
|
251
|
+
执行该 CodeBlock。执行前判断 syncmode。
|
252
|
+
- 仅当 syncmode 为 sync 时,才使用同步方式执行。
|
253
|
+
- 当 syncmode 为其他值时,均使用异步方式执行
|
254
|
+
- 当 syncmode 为 conflict 时,同步命令失效,并打印警告
|
255
|
+
|
256
|
+
:param session: 命令执行的会话实例
|
257
|
+
:param args: 兼容与扩展所需,用于变量替代及其他用途
|
258
|
+
:param kwargs: 兼容与扩展所需,用于变量替代及其他用途
|
259
|
+
"""
|
260
|
+
sync = kwargs.get("sync", None)
|
261
|
+
if sync == None:
|
262
|
+
if self.syncmode in ("dontcare", "async"):
|
263
|
+
sync = False
|
264
|
+
elif self.syncmode == "sync":
|
265
|
+
sync = True
|
266
|
+
elif self.syncmode == "conflict":
|
267
|
+
session.warning(Settings.gettext("exception_forced_async"))
|
268
|
+
sync = False
|
269
|
+
|
270
|
+
if sync:
|
271
|
+
for code in self.codes:
|
272
|
+
if isinstance(code, CodeLine):
|
273
|
+
code.execute(session, *args, **kwargs)
|
274
|
+
else:
|
275
|
+
session.create_task(self.async_execute(session, *args, **kwargs))
|
276
|
+
|
277
|
+
async def async_execute(self, session, *args, **kwargs):
|
278
|
+
"""
|
279
|
+
以异步方式执行该 CodeBlock。参数与 execute 相同。
|
280
|
+
"""
|
281
|
+
result = None
|
282
|
+
for code in self.codes:
|
283
|
+
if isinstance(code, CodeLine):
|
284
|
+
result = await code.async_execute(session, *args, **kwargs)
|
285
|
+
|
286
|
+
if Settings.client["interval"] > 0:
|
287
|
+
await asyncio.sleep(Settings.client["interval"] / 1000.0)
|
288
|
+
|
289
|
+
session.clean_finished_tasks()
|
290
|
+
return result
|
291
|
+
|
292
|
+
class BaseObject:
|
293
|
+
"""
|
294
|
+
MUD会话支持的对象基类。
|
295
|
+
|
296
|
+
:param session: 所属会话对象
|
297
|
+
:param args: 兼容与扩展所需
|
298
|
+
:param kwargs: 兼容与扩展所需
|
299
|
+
|
300
|
+
kwargs支持的关键字:
|
301
|
+
:id: 唯一ID。不指定时,默认使用 __abbr__ + UniqueID 来生成
|
302
|
+
:group: 所属的组名。不指定时,默认使用空字符串
|
303
|
+
:enabled: 使能状态。不指定时,默认使用 True
|
304
|
+
:priority: 优先级,越小优先级越高。不指定时,默认使用 100
|
305
|
+
:timeout: 超时时间,单位为秒。不指定时,默认使用 10
|
306
|
+
:sync: 同步模式。不指定时,默认为 True
|
307
|
+
:oneShot: 仅执行一次标识。不指定时,默认为 False
|
308
|
+
:onSuccess: 成功时的同步回调函数。不指定时,默认使用 self.onSuccess
|
309
|
+
:onFailure: 失败时的同步回调函数。不指定时,默认使用 self.onFailure
|
310
|
+
:onTimeout: 超时时的同步回调函数。不指定时,默认使用 self.onTimeout
|
311
|
+
"""
|
312
|
+
|
313
|
+
State = namedtuple("State", ("result", "id", "line", "wildcards"))
|
314
|
+
|
315
|
+
NOTSET = N = -1
|
316
|
+
FAILURE = F = 0
|
317
|
+
SUCCESS = S = 1
|
318
|
+
TIMEOUT = T = 2
|
319
|
+
ABORT = A = 3
|
320
|
+
|
321
|
+
__abbr__ = "obj"
|
322
|
+
"内部缩写代码前缀"
|
323
|
+
|
324
|
+
def __init__(self, session, *args, **kwargs):
|
325
|
+
from .session import Session
|
326
|
+
if isinstance(session, Session):
|
327
|
+
self.session = session
|
328
|
+
else:
|
329
|
+
assert(Settings.gettext("exception_session_type_fail"))
|
330
|
+
|
331
|
+
self._enabled = True # give a default value
|
332
|
+
self.log = logging.getLogger(f"pymud.{self.__class__.__name__}")
|
333
|
+
self.id = kwargs.get("id", session.getUniqueID(self.__class__.__abbr__))
|
334
|
+
self.group = kwargs.get("group", "") # 组
|
335
|
+
self.enabled = kwargs.get("enabled", True) # 使能与否
|
336
|
+
self.priority = kwargs.get("priority", 100) # 优先级
|
337
|
+
self.timeout = kwargs.get("timeout", 10) # 超时时间
|
338
|
+
self.sync = kwargs.get("sync", True) # 同步模式,默认
|
339
|
+
self.oneShot = kwargs.get("oneShot", False) # 单次执行,非默认
|
340
|
+
|
341
|
+
self.args = args
|
342
|
+
self.kwarg = kwargs
|
343
|
+
|
344
|
+
# 成功完成,失败完成,超时的处理函数(若有指定),否则使用类的自定义函数
|
345
|
+
self._onSuccess = kwargs.get("onSuccess", self.onSuccess)
|
346
|
+
self._onFailure = kwargs.get("onFailure", self.onFailure)
|
347
|
+
self._onTimeout = kwargs.get("onTimeout", self.onTimeout)
|
348
|
+
|
349
|
+
self.log.debug(f"对象实例 {self} 创建成功.")
|
350
|
+
|
351
|
+
self.session.addObject(self)
|
352
|
+
|
353
|
+
@property
|
354
|
+
def enabled(self):
|
355
|
+
"可读写属性,使能或取消使能本对象"
|
356
|
+
return self._enabled
|
357
|
+
|
358
|
+
@enabled.setter
|
359
|
+
def enabled(self, en: bool):
|
360
|
+
self._enabled = en
|
361
|
+
|
362
|
+
def onSuccess(self, *args, **kwargs):
|
363
|
+
"成功后执行的默认回调函数"
|
364
|
+
self.log.debug(f"{self} 缺省成功回调函数被执行.")
|
365
|
+
|
366
|
+
def onFailure(self, *args, **kwargs):
|
367
|
+
"失败后执行的默认回调函数"
|
368
|
+
self.log.debug(f"{self} 缺省失败回调函数被执行.")
|
369
|
+
|
370
|
+
def onTimeout(self, *args, **kwargs):
|
371
|
+
"超时后执行的默认回调函数"
|
372
|
+
self.log.debug(f"{self} 缺省超时回调函数被执行.")
|
373
|
+
|
374
|
+
def debug(self, msg):
|
375
|
+
"在logging中记录debug信息"
|
376
|
+
self.log.debug(msg)
|
377
|
+
|
378
|
+
def info(self, msg, *args):
|
379
|
+
"若session存在,session中输出info;不存在则在logging中输出info"
|
380
|
+
if self.session:
|
381
|
+
self.session.info(msg, *args)
|
382
|
+
else:
|
383
|
+
self.log.info(msg)
|
384
|
+
|
385
|
+
def warning(self, msg, *args):
|
386
|
+
"若session存在,session中输出warning;不存在则在logging中输出warning"
|
387
|
+
if self.session:
|
388
|
+
self.session.warning(msg, *args)
|
389
|
+
else:
|
390
|
+
self.log.warning(msg)
|
391
|
+
|
392
|
+
def error(self, msg, *args):
|
393
|
+
"若session存在,session中输出error;同时在logging中输出error"
|
394
|
+
if self.session:
|
395
|
+
self.session.error(msg, *args)
|
396
|
+
else:
|
397
|
+
self.log.error(msg)
|
398
|
+
|
399
|
+
def __repr__(self) -> str:
|
400
|
+
return self.__detailed__()
|
401
|
+
|
402
|
+
def __detailed__(self) -> str:
|
403
|
+
group = f'group = "{self.group}" ' if self.group else ''
|
404
|
+
return f'<{self.__class__.__name__}> id = "{self.id}" {group}enabled = {self.enabled}'
|
405
|
+
|
406
|
+
class GMCPTrigger(BaseObject):
|
407
|
+
"""
|
408
|
+
GMCP触发器 GMCPTrigger 类型,继承自 BaseObject。
|
409
|
+
|
410
|
+
GMCP触发器处于基于 GMCP 协议的数据,其使用方法类似于 Trigger 对象
|
411
|
+
|
412
|
+
但 GMCPTrigger 必定以指定name为触发,触发时,其值直接传递给对象本身
|
413
|
+
|
414
|
+
:param session: 本对象所属的会话
|
415
|
+
:param name: 触发对应的 GMCP 的 name
|
416
|
+
"""
|
417
|
+
def __init__(self, session, name, *args, **kwargs):
|
418
|
+
self.event = asyncio.Event()
|
419
|
+
self.value = None
|
420
|
+
# 确保不要有重复的id
|
421
|
+
kwargs.pop("id", None)
|
422
|
+
super().__init__(session, id = name, *args, **kwargs)
|
423
|
+
|
424
|
+
def __del__(self):
|
425
|
+
self.reset()
|
426
|
+
|
427
|
+
def reset(self):
|
428
|
+
"复位事件,用于async执行"
|
429
|
+
self.event.clear()
|
430
|
+
|
431
|
+
async def triggered(self):
|
432
|
+
"""
|
433
|
+
异步触发的可等待函数。其使用方法和 Trigger.triggered() 类似,且参数与返回值均与之兼容。
|
434
|
+
"""
|
435
|
+
self.reset()
|
436
|
+
await self.event.wait()
|
437
|
+
state = BaseObject.State(True, self.id, self.line, self.value)
|
438
|
+
self.reset()
|
439
|
+
return state
|
440
|
+
|
441
|
+
def __call__(self, value) -> Any:
|
442
|
+
try:
|
443
|
+
#import json
|
444
|
+
value_exp = eval(value)
|
445
|
+
except:
|
446
|
+
value_exp = value
|
447
|
+
|
448
|
+
self.line = value
|
449
|
+
self.value = value_exp
|
450
|
+
|
451
|
+
if callable(self._onSuccess):
|
452
|
+
self.event.set()
|
453
|
+
self._onSuccess(self.id, value, value_exp)
|
454
|
+
|
455
|
+
def __detailed__(self) -> str:
|
456
|
+
group = f'group = "{self.group}" ' if self.group else ''
|
457
|
+
return f'<{self.__class__.__name__}> name = "{self.id}" value = "{self.value}" {group}enabled = {self.enabled} '
|
458
|
+
|
459
|
+
class MatchObject(BaseObject):
|
460
|
+
"""
|
461
|
+
支持匹配内容的对象,包括Alias, Trigger, Command 等对象以及其子类对象。继承自 BaseObject
|
462
|
+
|
463
|
+
:param session: 同 BaseObject , 本对象所属的会话
|
464
|
+
:param patterns: 用于匹配的模式。详见 patterns 属性
|
465
|
+
:param args: 兼容与扩展所需
|
466
|
+
:param kwargs: 兼容与扩展所需
|
467
|
+
|
468
|
+
MatchObject 新增了部分 kwargs 关键字,包括:
|
469
|
+
:ignoreCase: 忽略大小写,默认为 False
|
470
|
+
:isRegExp: 是否是正则表达式,默认为 True
|
471
|
+
:keepEval: 是否持续匹配,默认为 False
|
472
|
+
:raw: 是否匹配含有VT100 ANSI标记的原始数据,默认为 False
|
473
|
+
"""
|
474
|
+
|
475
|
+
__abbr__ = "mob"
|
476
|
+
def __init__(self, session, patterns, *args, **kwargs):
|
477
|
+
self.ignoreCase = kwargs.get("ignoreCase", False) # 忽略大小写,非默认
|
478
|
+
self.isRegExp = kwargs.get("isRegExp", True) # 正则表达式,默认
|
479
|
+
self.expandVar = kwargs.get("expandVar", True) # 扩展变量(将变量用值替代),默认
|
480
|
+
self.keepEval = kwargs.get("keepEval", False) # 不中断,非默认
|
481
|
+
self.raw = kwargs.get("raw", False) # 原始数据匹配。当原始数据匹配时,不对VT100指令进行解析
|
482
|
+
|
483
|
+
self.wildcards = []
|
484
|
+
self.lines = []
|
485
|
+
self.event = asyncio.Event()
|
486
|
+
|
487
|
+
self.patterns = patterns
|
488
|
+
|
489
|
+
super().__init__(session, patterns = patterns, *args, **kwargs)
|
490
|
+
|
491
|
+
def __del__(self):
|
492
|
+
pass
|
493
|
+
|
494
|
+
@property
|
495
|
+
def patterns(self):
|
496
|
+
"""
|
497
|
+
可读写属性, 本对象的匹配模式。该属性可以在运行时动态更改,改后即时生效。
|
498
|
+
|
499
|
+
- 构造函数中的 patterns 用于指定初始的匹配模式。
|
500
|
+
- 该属性支持字符串和其他可迭代对象(如元组、列表)两种形式。
|
501
|
+
- 当为字符串时,使用单行匹配模式
|
502
|
+
- 当为可迭代对象时,使用多行匹配模式。多行的行数由可迭代对象所确定。
|
503
|
+
"""
|
504
|
+
return self._patterns
|
505
|
+
|
506
|
+
@patterns.setter
|
507
|
+
def patterns(self, patterns):
|
508
|
+
self._patterns = patterns
|
509
|
+
|
510
|
+
if isinstance(patterns, str):
|
511
|
+
self.multiline = False
|
512
|
+
self.linesToMatch = 1
|
513
|
+
elif isinstance(patterns, Iterable):
|
514
|
+
self.multiline = True
|
515
|
+
self.linesToMatch = len(patterns)
|
516
|
+
|
517
|
+
if self.isRegExp:
|
518
|
+
flag = 0
|
519
|
+
if self.ignoreCase: flag = re.I
|
520
|
+
if not self.multiline:
|
521
|
+
self._regExp = re.compile(self.patterns, flag) # 此处可考虑增加flags
|
522
|
+
else:
|
523
|
+
self._regExps = []
|
524
|
+
for line in self.patterns:
|
525
|
+
self._regExps.append(re.compile(line, flag))
|
526
|
+
|
527
|
+
self.linesToMatch = len(self._regExps)
|
528
|
+
self._mline = 0
|
529
|
+
|
530
|
+
def reset(self):
|
531
|
+
"复位事件,用于async执行未等待结果时,对事件的复位。仅异步有效。"
|
532
|
+
self.event.clear()
|
533
|
+
|
534
|
+
def set(self):
|
535
|
+
"设置事件标记,可以用于人工强制触发,仅在异步触发器下生效。"
|
536
|
+
self.event.set()
|
537
|
+
|
538
|
+
def match(self, line: str, docallback = True) -> BaseObject.State:
|
539
|
+
"""
|
540
|
+
匹配函数。由 Session 调用。
|
541
|
+
|
542
|
+
:param line: 匹配的数据行
|
543
|
+
:param docallback: 匹配成功后是否执行回调函数,默认为 True
|
544
|
+
|
545
|
+
:return: BaseObject.State 类型,一个包含 result, id, name, line, wildcards 的命名元组对象
|
546
|
+
"""
|
547
|
+
result = self.NOTSET
|
548
|
+
|
549
|
+
if not self.multiline: # 非多行
|
550
|
+
if self.isRegExp:
|
551
|
+
m = self._regExp.match(line)
|
552
|
+
if m:
|
553
|
+
result = self.SUCCESS
|
554
|
+
self.wildcards.clear()
|
555
|
+
if len(m.groups()) > 0:
|
556
|
+
self.wildcards.extend(m.groups())
|
557
|
+
|
558
|
+
self.lines.clear()
|
559
|
+
self.lines.append(line)
|
560
|
+
else:
|
561
|
+
#if line.find(self.patterns) >= 0:
|
562
|
+
#if line == self.patterns:
|
563
|
+
if self.patterns in line:
|
564
|
+
result = self.SUCCESS
|
565
|
+
self.lines.clear()
|
566
|
+
self.lines.append(line)
|
567
|
+
self.wildcards.clear()
|
568
|
+
|
569
|
+
else: # 多行匹配情况
|
570
|
+
# multilines match. 多行匹配时,受限于行的捕获方式,必须一行一行来,设置状态标志进行处理。
|
571
|
+
if self._mline == 0: # 当尚未开始匹配时,匹配第1行
|
572
|
+
m = self._regExps[0].match(line)
|
573
|
+
if m:
|
574
|
+
self.lines.clear()
|
575
|
+
self.lines.append(line)
|
576
|
+
self.wildcards.clear()
|
577
|
+
if len(m.groups()) > 0:
|
578
|
+
self.wildcards.extend(m.groups())
|
579
|
+
self._mline = 1 # 下一状态 (中间行)
|
580
|
+
elif (self._mline > 0) and (self._mline < self.linesToMatch - 1):
|
581
|
+
m = self._regExps[self._mline].match(line)
|
582
|
+
if m:
|
583
|
+
self.lines.append(line)
|
584
|
+
if len(m.groups()) > 0:
|
585
|
+
self.wildcards.extend(m.groups())
|
586
|
+
self._mline += 1
|
587
|
+
else:
|
588
|
+
self._mline = 0
|
589
|
+
elif self._mline == self.linesToMatch - 1: # 最终行
|
590
|
+
m = self._regExps[self._mline].match(line)
|
591
|
+
if m:
|
592
|
+
self.lines.append(line)
|
593
|
+
if len(m.groups()) > 0:
|
594
|
+
self.wildcards.extend(m.groups())
|
595
|
+
result = self.SUCCESS
|
596
|
+
|
597
|
+
self._mline = 0
|
598
|
+
|
599
|
+
state = BaseObject.State(result, self.id, "\n".join(self.lines), tuple(self.wildcards))
|
600
|
+
|
601
|
+
# 采用回调方式执行的时候,执行函数回调(仅当self.sync和docallback均为真时才执行同步
|
602
|
+
# 当docallback为真时,是真正的进行匹配和触发,为false时,仅返回匹配结果,不实际触发
|
603
|
+
if docallback:
|
604
|
+
if self.sync:
|
605
|
+
if state.result == self.SUCCESS:
|
606
|
+
self._onSuccess(state.id, state.line, state.wildcards)
|
607
|
+
elif state.result == self.FAILURE:
|
608
|
+
self._onFailure(state.id, state.line, state.wildcards)
|
609
|
+
elif state.result == self.TIMEOUT:
|
610
|
+
self._onTimeout(state.id, state.line, state.wildcards)
|
611
|
+
|
612
|
+
if state.result == self.SUCCESS:
|
613
|
+
self.event.set()
|
614
|
+
|
615
|
+
self.state = state
|
616
|
+
return state
|
617
|
+
|
618
|
+
async def matched(self) -> BaseObject.State:
|
619
|
+
"""
|
620
|
+
匹配函数的异步模式,等待匹配成功之后才返回。返回值 BaseObject.state
|
621
|
+
|
622
|
+
异步匹配模式用于 Trigger 的异步模式以及 Command 的匹配中。
|
623
|
+
"""
|
624
|
+
# 等待,再复位
|
625
|
+
try:
|
626
|
+
self.reset()
|
627
|
+
await self.event.wait()
|
628
|
+
self.reset()
|
629
|
+
except Exception as e:
|
630
|
+
self.error(Settings.gettext("exception_in_async", e))
|
631
|
+
|
632
|
+
return self.state
|
633
|
+
|
634
|
+
def __detailed__(self) -> str:
|
635
|
+
group = f'group = "{self.group}" ' if self.group else ''
|
636
|
+
return f'<{self.__class__.__name__}> id = "{self.id}" {group}enabled = {self.enabled} patterns = "{self.patterns}"'
|
637
|
+
|
638
|
+
class Alias(MatchObject):
|
639
|
+
"""
|
640
|
+
别名 Alias 类型,继承自 MatchObject。
|
641
|
+
|
642
|
+
其内涵与 MatchObject 完全相同,仅对缩写进行了覆盖。
|
643
|
+
"""
|
644
|
+
|
645
|
+
__abbr__ = "ali"
|
646
|
+
|
647
|
+
class SimpleAlias(Alias):
|
648
|
+
"""
|
649
|
+
简单别名 SimpleAlias 类型,继承自 Alias, 包含了 Alias 的全部功能, 并使用 CodeBlock 对象创建了 onSuccess 的使用场景。
|
650
|
+
|
651
|
+
:param session: 本对象所属的会话, 同 MatchObject
|
652
|
+
:param patterns: 匹配模式,同 MatchObject
|
653
|
+
:param code: str, 当匹配成功时执行的代码, 使用 CodeBlock 进行实现
|
654
|
+
"""
|
655
|
+
|
656
|
+
def __init__(self, session, patterns, code, *args, **kwargs):
|
657
|
+
self._code = code
|
658
|
+
self._codeblock = CodeBlock(code)
|
659
|
+
super().__init__(session, patterns, *args, **kwargs)
|
660
|
+
|
661
|
+
def onSuccess(self, id, line, wildcards):
|
662
|
+
"覆盖了基类的默认 onSuccess方法,使用 CodeBlock 执行构造函数中传入的 code 参数"
|
663
|
+
self._codeblock.execute(self.session, id = id, line = line, wildcards = wildcards)
|
664
|
+
|
665
|
+
def __detailed__(self) -> str:
|
666
|
+
group = f'group = "{self.group}" ' if self.group else ''
|
667
|
+
return f'<{self.__class__.__name__}> id = "{self.id}" {group}enabled = {self.enabled} patterns = "{self.patterns}" code = "{self._code}"'
|
668
|
+
|
669
|
+
def __repr__(self) -> str:
|
670
|
+
return self.__detailed__()
|
671
|
+
|
672
|
+
class Trigger(MatchObject):
|
673
|
+
"""
|
674
|
+
触发器 Trigger 类型,继承自 MatchObject。
|
675
|
+
|
676
|
+
其内涵与 MatchObject 完全相同,仅对缩写进行了覆盖,并增写了 triggered 异步方法。
|
677
|
+
"""
|
678
|
+
|
679
|
+
__abbr__ = "tri"
|
680
|
+
|
681
|
+
def __init__(self, session, patterns, *args, **kwargs):
|
682
|
+
super().__init__(session, patterns, *args, **kwargs)
|
683
|
+
self._task = None
|
684
|
+
|
685
|
+
async def triggered(self):
|
686
|
+
"""
|
687
|
+
异步触发的可等待函数。内部通过 MatchObject.matched 实现
|
688
|
+
|
689
|
+
差异在于对创建的 matched 任务进行了管理。
|
690
|
+
"""
|
691
|
+
if isinstance(self._task, asyncio.Task) and (not self._task.done()):
|
692
|
+
self._task.cancel()
|
693
|
+
|
694
|
+
self._task = self.session.create_task(self.matched())
|
695
|
+
return await self._task
|
696
|
+
|
697
|
+
class SimpleTrigger(Trigger):
|
698
|
+
"""
|
699
|
+
简单别名 SimpleTrigger 类型,继承自 Trigger, 包含了 Trigger 的全部功能, 并使用 CodeBlock 对象创建了 onSuccess 的使用场景。
|
700
|
+
|
701
|
+
:param session: 本对象所属的会话, 同 MatchObject
|
702
|
+
:param patterns: 匹配模式,同 MatchObject
|
703
|
+
:param code: str, 当匹配成功时执行的代码, 使用 CodeBlock 进行实现
|
704
|
+
"""
|
705
|
+
|
706
|
+
def __init__(self, session, patterns, code, *args, **kwargs):
|
707
|
+
self._code = code
|
708
|
+
self._codeblock = CodeBlock(code)
|
709
|
+
super().__init__(session, patterns, *args, **kwargs)
|
710
|
+
|
711
|
+
def onSuccess(self, id, line, wildcards):
|
712
|
+
"覆盖了基类的默认 onSuccess方法,使用 CodeBlock 执行构造函数中传入的 code 参数"
|
713
|
+
|
714
|
+
raw = self.session.getVariable("%raw")
|
715
|
+
self._codeblock.execute(self.session, id = id, line = line, raw = raw, wildcards = wildcards)
|
716
|
+
|
717
|
+
def __detailed__(self) -> str:
|
718
|
+
group = f'group = "{self.group}" ' if self.group else ''
|
719
|
+
return f'<{self.__class__.__name__}> id = "{self.id}" {group}enabled = {self.enabled} patterns = "{self.patterns}" code = "{self._code}"'
|
720
|
+
|
721
|
+
def __repr__(self) -> str:
|
722
|
+
return self.__detailed__()
|
723
|
+
|
724
|
+
class Command(MatchObject):
|
725
|
+
"""
|
726
|
+
命令 Command 类型,继承自 MatchObject。
|
727
|
+
命令是 PYMUD 的最大特色,它是一组归纳了同步/异步执行、等待响应、处理的集成对象。
|
728
|
+
要使用命令,不能直接使用 Command 类型,应总是继承并使用其子类,务必覆盖基类的 execute 方法。
|
729
|
+
|
730
|
+
有关 Command 的使用帮助,请查看帮助页面
|
731
|
+
|
732
|
+
:param session: 本对象所属的会话
|
733
|
+
:param patterns: 匹配模式
|
734
|
+
"""
|
735
|
+
__abbr__ = "cmd"
|
736
|
+
def __init__(self, session, patterns, *args, **kwargs):
|
737
|
+
super().__init__(session, patterns, sync = False, *args, **kwargs)
|
738
|
+
self._tasks = set()
|
739
|
+
|
740
|
+
def __unload__(self):
|
741
|
+
"""
|
742
|
+
当从会话中移除任务时,会自动调用该函数。
|
743
|
+
可以将命令管理的各子类对象在此处清除。
|
744
|
+
该函数需要在子类中覆盖重写。
|
745
|
+
"""
|
746
|
+
pass
|
747
|
+
|
748
|
+
def unload(self):
|
749
|
+
"""
|
750
|
+
与__unload__方法相同,子类仅需覆盖一种方法就可以
|
751
|
+
"""
|
752
|
+
pass
|
753
|
+
|
754
|
+
def create_task(self, coro, *args, name = None):
|
755
|
+
"""
|
756
|
+
创建并管理任务。由 Command 创建的任务,同时也被 Session 所管理。
|
757
|
+
其内部是调用 asyncio.create_task 进行任务创建。
|
758
|
+
|
759
|
+
:param coro: 任务包含的协程或可等待对象
|
760
|
+
:param name: 任务名称, Python 3.10 才支持的参数
|
761
|
+
"""
|
762
|
+
task = self.session.create_task(coro, *args, name)
|
763
|
+
task.add_done_callback(self._tasks.discard)
|
764
|
+
self._tasks.add(task)
|
765
|
+
return task
|
766
|
+
|
767
|
+
def remove_task(self, task: asyncio.Task, msg = None):
|
768
|
+
"""
|
769
|
+
取消任务并从管理任务清单中移除。由 Command 取消和移除的任务,同时也被 Session 所取消和移除。
|
770
|
+
|
771
|
+
:param task: 要取消的任务
|
772
|
+
:param msg: 取消任务时提供的消息, Python 3.10 才支持的参数
|
773
|
+
"""
|
774
|
+
|
775
|
+
result = self.session.remove_task(task, msg)
|
776
|
+
self._tasks.discard(task)
|
777
|
+
# if task in self._tasks:
|
778
|
+
# self._tasks.remove(task)
|
779
|
+
return result
|
780
|
+
|
781
|
+
def reset(self):
|
782
|
+
"""
|
783
|
+
复位命令,并取消和清除所有本对象管理的任务。
|
784
|
+
"""
|
785
|
+
|
786
|
+
super().reset()
|
787
|
+
|
788
|
+
for task in list(self._tasks):
|
789
|
+
if isinstance(task, asyncio.Task) and (not task.done()):
|
790
|
+
self.remove_task(task)
|
791
|
+
|
792
|
+
async def execute(self, cmd, *args, **kwargs):
|
793
|
+
"""
|
794
|
+
命令调用的入口函数。该函数由 Session 进行自动调用。
|
795
|
+
通过 ``Session.exec`` 系列方法调用的命令,最终是执行该命令的 execute 方法。
|
796
|
+
|
797
|
+
子类必须实现并覆盖该方法。
|
798
|
+
"""
|
799
|
+
self.reset()
|
800
|
+
return
|
801
|
+
|
802
|
+
class SimpleCommand(Command):
|
803
|
+
"""
|
804
|
+
对命令的基本应用进行了基础封装的一种可以直接使用的命令类型,继承自 Command。
|
805
|
+
|
806
|
+
SimpleCommand 并不能理解为 “简单” 命令,因为其使用并不简单。
|
807
|
+
只有在熟练使用 Command 建立自己的命令子类之后,对于某些场景的应用才可以简化代码使用 SimpleCommand 类型。
|
808
|
+
|
809
|
+
:param session: 本对象所属的会话
|
810
|
+
:param patterns: 匹配模式
|
811
|
+
:param succ_tri: 代表成功的响应触发器清单,可以为单个触发器,或一组触发器,必须指定
|
812
|
+
|
813
|
+
kwargs关键字参数特殊支持:
|
814
|
+
:fail_tri: 代表失败的响应触发器清单,可以为单个触发器,或一组触发器,可以为 None
|
815
|
+
:retry_tri: 代表重试的响应触发器清单,可以为单个触发器,或一组触发器,可以为 None
|
816
|
+
"""
|
817
|
+
|
818
|
+
MAX_RETRY = 20
|
819
|
+
|
820
|
+
def __init__(self, session, patterns, succ_tri, *args, **kwargs):
|
821
|
+
super().__init__(session, patterns, succ_tri, *args, **kwargs)
|
822
|
+
self._succ_tris = list()
|
823
|
+
self._fail_tris = list()
|
824
|
+
self._retry_tris = list()
|
825
|
+
self._executed_cmd = ""
|
826
|
+
|
827
|
+
if isinstance(succ_tri, Iterable):
|
828
|
+
self._succ_tris.extend(succ_tri)
|
829
|
+
else:
|
830
|
+
if isinstance(succ_tri, Trigger):
|
831
|
+
self._succ_tris.append(succ_tri)
|
832
|
+
|
833
|
+
fail_tri = kwargs.get("fail_tri", None)
|
834
|
+
if fail_tri:
|
835
|
+
if isinstance(fail_tri, Iterable):
|
836
|
+
self._fail_tris.extend(fail_tri)
|
837
|
+
else:
|
838
|
+
if isinstance(fail_tri, Trigger):
|
839
|
+
self._fail_tris.append(fail_tri)
|
840
|
+
|
841
|
+
retry_tri = kwargs.get("retry_tri", None)
|
842
|
+
if retry_tri:
|
843
|
+
if isinstance(retry_tri, Iterable):
|
844
|
+
self._retry_tris.extend(retry_tri)
|
845
|
+
else:
|
846
|
+
if isinstance(retry_tri, Trigger):
|
847
|
+
self._retry_tris.append(retry_tri)
|
848
|
+
|
849
|
+
async def execute(self, cmd, *args, **kwargs):
|
850
|
+
"""
|
851
|
+
覆盖基类的 execute 方法, SimpleCommand 的默认实现。
|
852
|
+
|
853
|
+
:param cmd: 执行时输入的实际指令
|
854
|
+
|
855
|
+
kwargs接受指定以下参数,在执行中进行一次调用:
|
856
|
+
:onSuccess: 成功时的回调
|
857
|
+
:onFailure: 失败时的回调
|
858
|
+
:onTimeout: 超时时的回调
|
859
|
+
"""
|
860
|
+
self.reset()
|
861
|
+
# 0. check command
|
862
|
+
cmd = cmd or self.patterns
|
863
|
+
# 1. save the command, to use later.
|
864
|
+
self._executed_cmd = cmd
|
865
|
+
# 2. writer command
|
866
|
+
retry_times = 0
|
867
|
+
while True:
|
868
|
+
# 1. create awaitables
|
869
|
+
tasklist = list()
|
870
|
+
for tr in self._succ_tris:
|
871
|
+
tr.reset()
|
872
|
+
tasklist.append(self.session.create_task(tr.triggered()))
|
873
|
+
for tr in self._fail_tris:
|
874
|
+
tr.reset()
|
875
|
+
tasklist.append(self.session.create_task(tr.triggered()))
|
876
|
+
for tr in self._retry_tris:
|
877
|
+
tr.reset()
|
878
|
+
tasklist.append(self.session.create_task(tr.triggered()))
|
879
|
+
|
880
|
+
await asyncio.sleep(0.1)
|
881
|
+
self.session.writeline(cmd)
|
882
|
+
|
883
|
+
done, pending = await asyncio.wait(tasklist, timeout = self.timeout, return_when = "FIRST_COMPLETED")
|
884
|
+
|
885
|
+
tasks_done = list(done)
|
886
|
+
|
887
|
+
tasks_pending = list(pending)
|
888
|
+
for t in tasks_pending:
|
889
|
+
t.cancel()
|
890
|
+
|
891
|
+
result = self.NOTSET
|
892
|
+
if len(tasks_done) > 0:
|
893
|
+
task = tasks_done[0]
|
894
|
+
_, name, line, wildcards = task.result()
|
895
|
+
# success
|
896
|
+
if name in (tri.id for tri in self._succ_tris):
|
897
|
+
result = self.SUCCESS
|
898
|
+
break
|
899
|
+
|
900
|
+
elif name in (tri.id for tri in self._fail_tris):
|
901
|
+
result = self.FAILURE
|
902
|
+
break
|
903
|
+
|
904
|
+
elif name in (tri.id for tri in self._retry_tris):
|
905
|
+
retry_times += 1
|
906
|
+
if retry_times > self.MAX_RETRY:
|
907
|
+
result = self.FAILURE
|
908
|
+
break
|
909
|
+
|
910
|
+
await asyncio.sleep(2)
|
911
|
+
|
912
|
+
else:
|
913
|
+
result = self.TIMEOUT
|
914
|
+
break
|
915
|
+
|
916
|
+
if result == self.SUCCESS:
|
917
|
+
self._onSuccess(name = self.id, cmd = cmd, line = line, wildcards = wildcards)
|
918
|
+
_outer_onSuccess = kwargs.get("onSuccess", None)
|
919
|
+
if callable(_outer_onSuccess):
|
920
|
+
_outer_onSuccess(name = self.id, cmd = cmd, line = line, wildcards = wildcards)
|
921
|
+
|
922
|
+
elif result == self.FAILURE:
|
923
|
+
self._onFailure(name = self.id, cmd = cmd, line = line, wildcards = wildcards)
|
924
|
+
_outer_onFailure = kwargs.get("onFailure", None)
|
925
|
+
if callable(_outer_onFailure):
|
926
|
+
_outer_onFailure(name = self.id, cmd = cmd, line = line, wildcards = wildcards)
|
927
|
+
|
928
|
+
elif result == self.TIMEOUT:
|
929
|
+
self._onTimeout(name = self.id, cmd = cmd, timeout = self.timeout)
|
930
|
+
_outer_onTimeout = kwargs.get("onTimeout", None)
|
931
|
+
if callable(_outer_onTimeout):
|
932
|
+
_outer_onTimeout(name = self.id, cmd = cmd, timeout = self.timeout)
|
933
|
+
|
934
|
+
return result
|
935
|
+
|
936
|
+
class Timer(BaseObject):
|
937
|
+
"""
|
938
|
+
定时器 Timer 类型,继承自 MatchObject。PYMUD 支持同时任意多个定时器。
|
939
|
+
|
940
|
+
:param session: 对象所属会话
|
941
|
+
|
942
|
+
Timer 中使用的 kwargs 均继承自 BaseObject,包括:
|
943
|
+
- id: 标识
|
944
|
+
- group: 组名
|
945
|
+
- enabled: 使能状态
|
946
|
+
- timeout: 定时时间
|
947
|
+
- onSuccess: 定时到期执行的函数
|
948
|
+
"""
|
949
|
+
__abbr__ = "ti"
|
950
|
+
|
951
|
+
def __init__(self, session, *args, **kwargs):
|
952
|
+
self._task = None
|
953
|
+
self._halt = False
|
954
|
+
super().__init__(session, *args, **kwargs)
|
955
|
+
|
956
|
+
def __del__(self):
|
957
|
+
self.reset()
|
958
|
+
|
959
|
+
def startTimer(self):
|
960
|
+
"启动定时器"
|
961
|
+
if not isinstance(self._task, asyncio.Task):
|
962
|
+
self._halt = False
|
963
|
+
self._task = asyncio.create_task(self.onTimerTask())
|
964
|
+
|
965
|
+
asyncio.ensure_future(self._task)
|
966
|
+
|
967
|
+
async def onTimerTask(self):
|
968
|
+
"定时任务的调用方法,脚本中无需调用"
|
969
|
+
|
970
|
+
while self._enabled:
|
971
|
+
await asyncio.sleep(self.timeout)
|
972
|
+
|
973
|
+
if callable(self._onSuccess):
|
974
|
+
self._onSuccess(self.id)
|
975
|
+
|
976
|
+
if self.oneShot or self._halt:
|
977
|
+
break
|
978
|
+
|
979
|
+
|
980
|
+
def reset(self):
|
981
|
+
"复位定时器,清除所创建的定时任务"
|
982
|
+
try:
|
983
|
+
self._halt = True
|
984
|
+
if isinstance(self._task, asyncio.Task) and (not self._task.done()):
|
985
|
+
self._task.cancel()
|
986
|
+
|
987
|
+
self._task = None
|
988
|
+
except asyncio.CancelledError:
|
989
|
+
pass
|
990
|
+
|
991
|
+
@property
|
992
|
+
def enabled(self):
|
993
|
+
"可读写属性,定时器使能状态"
|
994
|
+
return self._enabled
|
995
|
+
|
996
|
+
@enabled.setter
|
997
|
+
def enabled(self, en: bool):
|
998
|
+
self._enabled = en
|
999
|
+
if not en:
|
1000
|
+
self.reset()
|
1001
|
+
else:
|
1002
|
+
self.startTimer()
|
1003
|
+
|
1004
|
+
def __detailed__(self) -> str:
|
1005
|
+
group = f'group = "{self.group}" ' if self.group else ''
|
1006
|
+
return f'<{self.__class__.__name__}> id = "{self.id}" {group}enabled = {self.enabled} timeout = {self.timeout}'
|
1007
|
+
|
1008
|
+
def __repr__(self) -> str:
|
1009
|
+
return self.__detailed__()
|
1010
|
+
|
1011
|
+
class SimpleTimer(Timer):
|
1012
|
+
"""
|
1013
|
+
简单定时器 SimpleTimer 类型,继承自 Timer, 包含了 Timer 的全部功能, 并使用 CodeBlock 对象创建了 onSuccess 的使用场景。
|
1014
|
+
|
1015
|
+
:param session: 本对象所属的会话, 同 MatchObject
|
1016
|
+
:param code: str, 当定时任务到期时执行的代码, 使用 CodeBlock 实现
|
1017
|
+
"""
|
1018
|
+
def __init__(self, session, code, *args, **kwargs):
|
1019
|
+
self._code = code
|
1020
|
+
self._codeblock = CodeBlock(code)
|
1021
|
+
super().__init__(session, *args, **kwargs)
|
1022
|
+
|
1023
|
+
def onSuccess(self, id):
|
1024
|
+
"覆盖了基类的默认 onSuccess方法,使用 CodeBlock 执行构造函数中传入的 code 参数"
|
1025
|
+
self._codeblock.execute(self.session, id = id)
|
1026
|
+
|
1027
|
+
def __detailed__(self) -> str:
|
1028
|
+
group = f'group = "{self.group}" ' if self.group else ''
|
1029
|
+
return f'<{self.__class__.__name__}> id = "{self.id}" {group}enabled = {self.enabled} timeout = {self.timeout} code = "{self._code}"'
|
1030
|
+
|