pymud 0.21.0a5__py3-none-any.whl → 0.21.1__py3-none-any.whl

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