pymud 0.20.0a4__py3-none-any.whl → 0.20.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/__init__.py +2 -1
- pymud/dialogs.py +11 -6
- pymud/extras.py +32 -75
- pymud/logger.py +9 -3
- pymud/modules.py +188 -0
- pymud/objects.py +37 -20
- pymud/pymud.py +43 -8
- pymud/session.py +192 -116
- pymud/settings.py +2 -2
- {pymud-0.20.0a4.dist-info → pymud-0.20.1.dist-info}/METADATA +26 -15
- pymud-0.20.1.dist-info/RECORD +19 -0
- {pymud-0.20.0a4.dist-info → pymud-0.20.1.dist-info}/WHEEL +1 -1
- pymud-0.20.0a4.dist-info/RECORD +0 -18
- {pymud-0.20.0a4.dist-info → pymud-0.20.1.dist-info}/LICENSE.txt +0 -0
- {pymud-0.20.0a4.dist-info → pymud-0.20.1.dist-info}/entry_points.txt +0 -0
- {pymud-0.20.0a4.dist-info → pymud-0.20.1.dist-info}/top_level.txt +0 -0
pymud/__init__.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
from .settings import Settings
|
2
2
|
from .pymud import PyMudApp
|
3
|
+
from .modules import IConfig
|
3
4
|
from .objects import CodeBlock, Alias, SimpleAlias, Trigger, SimpleTrigger, Command, SimpleCommand, Timer, SimpleTimer, GMCPTrigger
|
4
5
|
from .extras import DotDict
|
5
6
|
from .session import Session
|
@@ -7,5 +8,5 @@ from .logger import Logger
|
|
7
8
|
from .main import main
|
8
9
|
|
9
10
|
__all__ = [
|
10
|
-
"PyMudApp", "Settings", "CodeBlock", "Alias", "SimpleAlias", "Trigger", "SimpleTrigger", "Command", "SimpleCommand", "Timer", "SimpleTimer", "GMCPTrigger", "Session", "PyMudApp", "DotDict", "Logger", "main"
|
11
|
+
"IConfig", "PyMudApp", "Settings", "CodeBlock", "Alias", "SimpleAlias", "Trigger", "SimpleTrigger", "Command", "SimpleCommand", "Timer", "SimpleTimer", "GMCPTrigger", "Session", "PyMudApp", "DotDict", "Logger", "main"
|
11
12
|
]
|
pymud/dialogs.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
import asyncio, webbrowser
|
2
2
|
|
3
3
|
from prompt_toolkit.layout import AnyContainer, ConditionalContainer, Float, VSplit, HSplit, Window, WindowAlign, ScrollablePane, ScrollOffsets
|
4
|
-
from prompt_toolkit.widgets import Button, Dialog, Label, MenuContainer, MenuItem, TextArea, SystemToolbar, Frame, RadioList
|
4
|
+
from prompt_toolkit.widgets import Button, Dialog, Label, MenuContainer, MenuItem, TextArea, SystemToolbar, Frame, RadioList
|
5
5
|
from prompt_toolkit.layout.dimension import Dimension, D
|
6
6
|
from prompt_toolkit import ANSI, HTML
|
7
7
|
from prompt_toolkit.mouse_events import MouseEvent, MouseEventType
|
@@ -133,12 +133,14 @@ class LogSelectionDialog(BasicDialog):
|
|
133
133
|
def __init__(self, text, values, modal=True):
|
134
134
|
self._header_text = text
|
135
135
|
self._selection_values = values
|
136
|
-
self.
|
136
|
+
self._itemsCount = len(values)
|
137
|
+
if len(values) > 0:
|
138
|
+
self._radio_list = RadioList(values = self._selection_values)
|
139
|
+
else:
|
140
|
+
self._radio_list = Label('无记录'.center(13))
|
137
141
|
super().__init__('选择查看的记录', modal)
|
138
142
|
|
139
143
|
def create_body(self) -> AnyContainer:
|
140
|
-
|
141
|
-
|
142
144
|
body=HSplit([
|
143
145
|
Label(text = self._header_text, dont_extend_height=True),
|
144
146
|
self._radio_list
|
@@ -151,6 +153,9 @@ class LogSelectionDialog(BasicDialog):
|
|
151
153
|
return [ok_button, cancel_button]
|
152
154
|
|
153
155
|
def btn_ok_clicked(self):
|
154
|
-
|
155
|
-
|
156
|
+
if self._itemsCount:
|
157
|
+
result = self._radio_list.current_value
|
158
|
+
self.set_done(result)
|
159
|
+
else:
|
160
|
+
self.set_done(False)
|
156
161
|
|
pymud/extras.py
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
# External Libraries
|
2
2
|
from unicodedata import east_asian_width
|
3
3
|
from wcwidth import wcwidth
|
4
|
-
|
5
|
-
import time, datetime
|
4
|
+
import time, re, logging
|
6
5
|
|
7
6
|
from typing import Iterable
|
8
7
|
from prompt_toolkit import ANSI
|
@@ -65,6 +64,10 @@ class MudFormatProcessor(Processor):
|
|
65
64
|
self.FULL_BLOCKS = set("▂▃▅▆▇▄█")
|
66
65
|
self.SINGLE_LINES = set("┌└├┬┼┴╭╰─")
|
67
66
|
self.DOUBLE_LINES = set("╔╚╠╦╪╩═")
|
67
|
+
self.START_COLOR_REGX = re.compile(r"^\[[\d;]+m")
|
68
|
+
self.COLOR_REGX = re.compile(r"\[[\d;]+m")
|
69
|
+
self._color_start = ""
|
70
|
+
self._color_correction = False
|
68
71
|
|
69
72
|
def width_correction(self, line: str) -> str:
|
70
73
|
new_str = []
|
@@ -88,11 +91,36 @@ class MudFormatProcessor(Processor):
|
|
88
91
|
def tab_correction(self, line: str):
|
89
92
|
return line.replace("\t", " " * Settings.client["tabstop"])
|
90
93
|
|
94
|
+
def color_correction(self, line: str):
|
95
|
+
# 注:发现processer处理并非自上而下逐行处理的,因此不能使用这种颜色校正方式。
|
96
|
+
if self._color_correction:
|
97
|
+
other = self.COLOR_REGX.findall(line)
|
98
|
+
|
99
|
+
line = f"{self._color_start}{line}"
|
100
|
+
logging.debug(f"已校正增加颜色标志 {self._color_start}: {line}")
|
101
|
+
|
102
|
+
if other:
|
103
|
+
self._color_correction = False
|
104
|
+
self._color_start = ""
|
105
|
+
logging.debug(f"颜色校正结束: {line}")
|
106
|
+
else:
|
107
|
+
color = self.START_COLOR_REGX.findall(line)
|
108
|
+
if color:
|
109
|
+
other = self.COLOR_REGX.findall(line)
|
110
|
+
if len(other) == 1:
|
111
|
+
self._color_correction = True
|
112
|
+
self._color_start = color[0]
|
113
|
+
logging.debug(f"获取到一个颜色开头 {color[0]}: {line}")
|
114
|
+
|
115
|
+
return line
|
116
|
+
|
91
117
|
def line_correction(self, line: str):
|
92
118
|
# 处理\r符号(^M)
|
93
119
|
line = self.return_correction(line)
|
94
120
|
# 处理Tab(\r)符号(^I)
|
95
121
|
line = self.tab_correction(line)
|
122
|
+
# 处理颜色跨行问题。发现processer处理并非自上而下逐行处理的,因此不能使用这种颜色校正方式。
|
123
|
+
# line = self.color_correction(line)
|
96
124
|
# 美化(解决中文英文在Console中不对齐的问题)
|
97
125
|
if Settings.client["beautify"]:
|
98
126
|
line = self.width_correction(line)
|
@@ -430,9 +458,9 @@ class SessionBufferControl(BufferControl):
|
|
430
458
|
|
431
459
|
if double_click:
|
432
460
|
start = buffer.document.translate_row_col_to_index(position.y, 0)
|
433
|
-
end = buffer.document.translate_row_col_to_index(position.y,
|
461
|
+
end = buffer.document.translate_row_col_to_index(position.y + 1, 0) - 1
|
434
462
|
buffer.cursor_position = start
|
435
|
-
buffer.start_selection(selection_type=SelectionType.
|
463
|
+
buffer.start_selection(selection_type=SelectionType.LINES)
|
436
464
|
buffer.cursor_position = end
|
437
465
|
|
438
466
|
else:
|
@@ -1008,76 +1036,5 @@ class DotDict(dict):
|
|
1008
1036
|
def __setstate__(self, state):
|
1009
1037
|
self.update(state)
|
1010
1038
|
|
1011
|
-
import importlib
|
1012
|
-
import importlib.util
|
1013
|
-
|
1014
|
-
class Plugin:
|
1015
|
-
"""
|
1016
|
-
插件管理类。对加载的插件文件进行管理。该类型由PyMudApp进行管理,无需人工创建。
|
1017
|
-
|
1018
|
-
有关插件的详细信息,请参见 `插件 <plugins.html>`_
|
1019
|
-
|
1020
|
-
:param name: 插件的文件名, 如'myplugin.py'
|
1021
|
-
:param location: 插件所在的目录。自动加载的插件包括PyMUD包目录下的plugins目录以及当前目录下的plugins目录
|
1022
|
-
|
1023
|
-
"""
|
1024
|
-
def __init__(self, name, location):
|
1025
|
-
self._plugin_file = name
|
1026
|
-
self._plugin_loc = location
|
1027
|
-
|
1028
|
-
self.reload()
|
1029
|
-
|
1030
|
-
def reload(self):
|
1031
|
-
"加载/重新加载插件对象"
|
1032
|
-
#del self.modspec, self.mod
|
1033
|
-
self.modspec = importlib.util.spec_from_file_location(self._plugin_file[:-3], self._plugin_loc)
|
1034
|
-
self.mod = importlib.util.module_from_spec(self.modspec)
|
1035
|
-
self.modspec.loader.exec_module(self.mod)
|
1036
|
-
|
1037
|
-
self._app_init = self.mod.__dict__["PLUGIN_PYMUD_START"]
|
1038
|
-
self._session_create = self.mod.__dict__["PLUGIN_SESSION_CREATE"]
|
1039
|
-
self._session_destroy = self.mod.__dict__["PLUGIN_SESSION_DESTROY"]
|
1040
|
-
|
1041
|
-
@property
|
1042
|
-
def name(self):
|
1043
|
-
"插件名称,由插件文件中的 PLUGIN_NAME 常量定义"
|
1044
|
-
return self.mod.__dict__["PLUGIN_NAME"]
|
1045
|
-
|
1046
|
-
@property
|
1047
|
-
def desc(self):
|
1048
|
-
"插件描述,由插件文件中的 PLUGIN_DESC 常量定义"
|
1049
|
-
return self.mod.__dict__["PLUGIN_DESC"]
|
1050
|
-
|
1051
|
-
@property
|
1052
|
-
def help(self):
|
1053
|
-
"插件帮助,由插件文件中的文档字符串定义"
|
1054
|
-
return self.mod.__doc__
|
1055
|
-
|
1056
|
-
def onAppInit(self, app):
|
1057
|
-
"""
|
1058
|
-
PyMUD应用启动时对插件执行的操作,由插件文件中的 PLUGIN_PYMUD_START 函数定义
|
1059
|
-
|
1060
|
-
:param app: 启动的 PyMudApp 对象实例
|
1061
|
-
"""
|
1062
|
-
self._app_init(app)
|
1063
|
-
|
1064
|
-
def onSessionCreate(self, session):
|
1065
|
-
"""
|
1066
|
-
新会话创建时对插件执行的操作,由插件文件中的 PLUGIN_SESSION_CREATE 函数定义
|
1067
|
-
|
1068
|
-
:param session: 新创建的会话对象实例
|
1069
|
-
"""
|
1070
|
-
self._session_create(session)
|
1071
|
-
|
1072
|
-
def onSessionDestroy(self, session):
|
1073
|
-
"""
|
1074
|
-
会话关闭时(注意不是断开)对插件执行的操作,由插件文件中的 PLUGIN_SESSION_DESTROY 函数定义
|
1075
|
-
|
1076
|
-
:param session: 所关闭的会话对象实例
|
1077
|
-
"""
|
1078
|
-
self._session_destroy(session)
|
1079
1039
|
|
1080
|
-
def __getattr__(self, __name: str) -> Any:
|
1081
|
-
if hasattr(self.mod, __name):
|
1082
|
-
return self.mod.__getattribute__(__name)
|
1083
1040
|
|
pymud/logger.py
CHANGED
@@ -1,9 +1,10 @@
|
|
1
|
-
import os, re, datetime, threading
|
1
|
+
import os, re, datetime, threading, pathlib
|
2
2
|
from queue import SimpleQueue, Empty
|
3
|
+
from pathlib import Path
|
3
4
|
|
4
5
|
class Logger:
|
5
6
|
"""
|
6
|
-
PyMUD
|
7
|
+
PyMUD 的记录器类型,可用于会话中向文件记录数据。记录文件保存在当前目录下的 log 子目录中
|
7
8
|
|
8
9
|
:param name: 记录器名称,各记录器名称应保持唯一。记录器名称会作为记录文件名称的主要参数
|
9
10
|
:param mode: 记录模式。可选模式包括 a, w, n 三种。
|
@@ -56,7 +57,12 @@ class Logger:
|
|
56
57
|
now = datetime.datetime.now().strftime("%Y_%m_%d_%H_%M_%S")
|
57
58
|
filename = f"{self.name}.{now}.log"
|
58
59
|
|
59
|
-
|
60
|
+
logdir = Path.cwd().joinpath('log')
|
61
|
+
if not logdir.exists() or not logdir.is_dir():
|
62
|
+
logdir.mkdir()
|
63
|
+
|
64
|
+
filename = logdir.joinpath(filename)
|
65
|
+
#filename = os.path.abspath(filename)
|
60
66
|
self._stream = open(filename, mode = mode, encoding = self._encoding, errors = self._errors)
|
61
67
|
self._thread = t = threading.Thread(target=self._monitor)
|
62
68
|
t.daemon = True
|
pymud/modules.py
ADDED
@@ -0,0 +1,188 @@
|
|
1
|
+
|
2
|
+
import importlib, importlib.util
|
3
|
+
from abc import ABC, ABCMeta
|
4
|
+
from typing import Any
|
5
|
+
from .objects import BaseObject, Command
|
6
|
+
|
7
|
+
class ModuleInfo:
|
8
|
+
"""
|
9
|
+
模块管理类。对加载的模块文件进行管理。该类型由Session类进行管理,无需人工创建和干预。
|
10
|
+
|
11
|
+
有关模块的分类和使用的详细信息,请参见 `脚本 <scripts.html>`_
|
12
|
+
|
13
|
+
:param module_name: 模块的名称, 应与 import xxx 语法中的 xxx 保持一致
|
14
|
+
:param session: 加载/创建本模块的会话
|
15
|
+
|
16
|
+
"""
|
17
|
+
def __init__(self, module_name: str, session):
|
18
|
+
self.session = session
|
19
|
+
self._name = module_name
|
20
|
+
self._ismainmodule = False
|
21
|
+
self.load()
|
22
|
+
|
23
|
+
def _load(self, reload = False):
|
24
|
+
result = True
|
25
|
+
if reload:
|
26
|
+
self._module = importlib.reload(self._module)
|
27
|
+
else:
|
28
|
+
self._module = importlib.import_module(self.name)
|
29
|
+
self._config = {}
|
30
|
+
for attr_name in dir(self._module):
|
31
|
+
attr = getattr(self._module, attr_name)
|
32
|
+
if isinstance(attr, type) and attr.__module__ == self._module.__name__:
|
33
|
+
if (attr_name == "Configuration") or issubclass(attr, IConfig):
|
34
|
+
try:
|
35
|
+
self._config[f"{self.name}.{attr_name}"] = attr(self.session, reload = reload)
|
36
|
+
self.session.info(f"配置对象 {self.name}.{attr_name} {'重新' if reload else ''}创建成功.")
|
37
|
+
except Exception as e:
|
38
|
+
result = False
|
39
|
+
self.session.error(f"配置对象 {self.name}.{attr_name} 创建失败. 错误信息为: {e}")
|
40
|
+
self._ismainmodule = (self._config != {})
|
41
|
+
return result
|
42
|
+
|
43
|
+
def _unload(self):
|
44
|
+
for key, config in self._config.items():
|
45
|
+
if isinstance(config, Command):
|
46
|
+
# Command 对象在从会话中移除时,自动调用其 unload 系列方法,因此不能产生递归
|
47
|
+
self.session.delObject(config)
|
48
|
+
|
49
|
+
else:
|
50
|
+
|
51
|
+
if hasattr(config, "__unload__"):
|
52
|
+
unload = getattr(config, "__unload__", None)
|
53
|
+
if callable(unload): unload()
|
54
|
+
|
55
|
+
if hasattr(config, "unload"):
|
56
|
+
unload = getattr(config, "unload", None)
|
57
|
+
if callable(unload): unload()
|
58
|
+
|
59
|
+
if isinstance(config, BaseObject):
|
60
|
+
self.session.delObject(config)
|
61
|
+
|
62
|
+
del config
|
63
|
+
self._config.clear()
|
64
|
+
|
65
|
+
def load(self):
|
66
|
+
"加载模块内容"
|
67
|
+
if self._load():
|
68
|
+
self.session.info(f"{'主' if self.ismainmodule else '从'}配置模块 {self.name} 加载完成.")
|
69
|
+
else:
|
70
|
+
self.session.error(f"{'主' if self.ismainmodule else '从'}配置模块 {self.name} 加载失败.")
|
71
|
+
|
72
|
+
def unload(self):
|
73
|
+
"卸载模块内容"
|
74
|
+
self._unload()
|
75
|
+
self._loaded = False
|
76
|
+
self.session.info(f"{'主' if self.ismainmodule else '从'}配置模块 {self.name} 卸载完成.")
|
77
|
+
|
78
|
+
def reload(self):
|
79
|
+
"模块文件更新后调用,重新加载已加载的模块内容"
|
80
|
+
self._unload()
|
81
|
+
self._load(reload = True)
|
82
|
+
self.session.info(f"{'主' if self.ismainmodule else '从'}配置模块 {self.name} 重新加载完成.")
|
83
|
+
|
84
|
+
@property
|
85
|
+
def name(self):
|
86
|
+
"只读属性,模块名称"
|
87
|
+
return self._name
|
88
|
+
|
89
|
+
@property
|
90
|
+
def module(self):
|
91
|
+
"只读属性,模块文件的 ModuleType 对象"
|
92
|
+
return self._module
|
93
|
+
|
94
|
+
@property
|
95
|
+
def config(self):
|
96
|
+
"只读字典属性,根据模块文件 ModuleType 对象创建的其中名为 Configuration 的类型或继承自 IConfig 的子类型实例(若有)"
|
97
|
+
return self._config
|
98
|
+
|
99
|
+
@property
|
100
|
+
def ismainmodule(self):
|
101
|
+
"只读属性,区分是否主模块(即包含具体config的模块)"
|
102
|
+
return self._ismainmodule
|
103
|
+
|
104
|
+
class IConfig(metaclass = ABCMeta):
|
105
|
+
"""
|
106
|
+
用于提示PyMUD应用是否自动创建该配置类型的基础类(模拟接口)。
|
107
|
+
|
108
|
+
继承 IConfig 类型让应用自动管理该类型,唯一需要的是,构造函数中,仅存在一个必须指定的参数 Session。
|
109
|
+
|
110
|
+
在应用自动创建 IConfig 实例时,除 session 参数外,还会传递一个 reload 参数 (bool类型),表示是首次加载还是重新加载特性。
|
111
|
+
可以从kwargs 中获取该参数,并针对性的设计相应代码。例如,重新加载相关联的其他模块等。
|
112
|
+
"""
|
113
|
+
def __init__(self, session, *args, **kwargs):
|
114
|
+
self.session = session
|
115
|
+
|
116
|
+
def __unload__(self):
|
117
|
+
if self.session:
|
118
|
+
self.session.delObject(self)
|
119
|
+
|
120
|
+
class Plugin:
|
121
|
+
"""
|
122
|
+
插件管理类。对加载的插件文件进行管理。该类型由PyMudApp进行管理,无需人工创建。
|
123
|
+
|
124
|
+
有关插件的详细信息,请参见 `插件 <plugins.html>`_
|
125
|
+
|
126
|
+
:param name: 插件的文件名, 如'myplugin.py'
|
127
|
+
:param location: 插件所在的目录。自动加载的插件包括PyMUD包目录下的plugins目录以及当前目录下的plugins目录
|
128
|
+
|
129
|
+
"""
|
130
|
+
def __init__(self, name, location):
|
131
|
+
self._plugin_file = name
|
132
|
+
self._plugin_loc = location
|
133
|
+
|
134
|
+
self.reload()
|
135
|
+
|
136
|
+
def reload(self):
|
137
|
+
"加载/重新加载插件对象"
|
138
|
+
#del self.modspec, self.mod
|
139
|
+
self.modspec = importlib.util.spec_from_file_location(self._plugin_file[:-3], self._plugin_loc)
|
140
|
+
self.mod = importlib.util.module_from_spec(self.modspec)
|
141
|
+
self.modspec.loader.exec_module(self.mod)
|
142
|
+
|
143
|
+
self._app_init = self.mod.__dict__["PLUGIN_PYMUD_START"]
|
144
|
+
self._session_create = self.mod.__dict__["PLUGIN_SESSION_CREATE"]
|
145
|
+
self._session_destroy = self.mod.__dict__["PLUGIN_SESSION_DESTROY"]
|
146
|
+
|
147
|
+
@property
|
148
|
+
def name(self):
|
149
|
+
"插件名称,由插件文件中的 PLUGIN_NAME 常量定义"
|
150
|
+
return self.mod.__dict__["PLUGIN_NAME"]
|
151
|
+
|
152
|
+
@property
|
153
|
+
def desc(self):
|
154
|
+
"插件描述,由插件文件中的 PLUGIN_DESC 常量定义"
|
155
|
+
return self.mod.__dict__["PLUGIN_DESC"]
|
156
|
+
|
157
|
+
@property
|
158
|
+
def help(self):
|
159
|
+
"插件帮助,由插件文件中的文档字符串定义"
|
160
|
+
return self.mod.__doc__
|
161
|
+
|
162
|
+
def onAppInit(self, app):
|
163
|
+
"""
|
164
|
+
PyMUD应用启动时对插件执行的操作,由插件文件中的 PLUGIN_PYMUD_START 函数定义
|
165
|
+
|
166
|
+
:param app: 启动的 PyMudApp 对象实例
|
167
|
+
"""
|
168
|
+
self._app_init(app)
|
169
|
+
|
170
|
+
def onSessionCreate(self, session):
|
171
|
+
"""
|
172
|
+
新会话创建时对插件执行的操作,由插件文件中的 PLUGIN_SESSION_CREATE 函数定义
|
173
|
+
|
174
|
+
:param session: 新创建的会话对象实例
|
175
|
+
"""
|
176
|
+
self._session_create(session)
|
177
|
+
|
178
|
+
def onSessionDestroy(self, session):
|
179
|
+
"""
|
180
|
+
会话关闭时(注意不是断开)对插件执行的操作,由插件文件中的 PLUGIN_SESSION_DESTROY 函数定义
|
181
|
+
|
182
|
+
:param session: 所关闭的会话对象实例
|
183
|
+
"""
|
184
|
+
self._session_destroy(session)
|
185
|
+
|
186
|
+
def __getattr__(self, __name: str) -> Any:
|
187
|
+
if hasattr(self.mod, __name):
|
188
|
+
return self.mod.__getattribute__(__name)
|
pymud/objects.py
CHANGED
@@ -2,7 +2,8 @@
|
|
2
2
|
MUD会话(session)中, 支持的对象列表
|
3
3
|
"""
|
4
4
|
|
5
|
-
import asyncio, logging, re
|
5
|
+
import asyncio, logging, re, importlib
|
6
|
+
from abc import ABC, ABCMeta, abstractmethod
|
6
7
|
from collections.abc import Iterable
|
7
8
|
from collections import namedtuple
|
8
9
|
from typing import Any
|
@@ -321,7 +322,12 @@ class BaseObject:
|
|
321
322
|
"内部缩写代码前缀"
|
322
323
|
|
323
324
|
def __init__(self, session, *args, **kwargs):
|
324
|
-
|
325
|
+
from .session import Session
|
326
|
+
if isinstance(session, Session):
|
327
|
+
self.session = session
|
328
|
+
else:
|
329
|
+
assert("session must be an instance of class Session!")
|
330
|
+
|
325
331
|
self._enabled = True # give a default value
|
326
332
|
self.log = logging.getLogger(f"pymud.{self.__class__.__name__}")
|
327
333
|
self.id = kwargs.get("id", session.getUniqueID(self.__class__.__abbr__))
|
@@ -342,6 +348,8 @@ class BaseObject:
|
|
342
348
|
|
343
349
|
self.log.debug(f"对象实例 {self} 创建成功.")
|
344
350
|
|
351
|
+
self.session.addObject(self)
|
352
|
+
|
345
353
|
@property
|
346
354
|
def enabled(self):
|
347
355
|
"可读写属性,使能或取消使能本对象"
|
@@ -392,7 +400,8 @@ class BaseObject:
|
|
392
400
|
return self.__detailed__()
|
393
401
|
|
394
402
|
def __detailed__(self) -> str:
|
395
|
-
|
403
|
+
group = f'group = "{self.group}" ' if self.group else ''
|
404
|
+
return f'<{self.__class__.__name__}> id = "{self.id}" {group}enabled = {self.enabled}'
|
396
405
|
|
397
406
|
class GMCPTrigger(BaseObject):
|
398
407
|
"""
|
@@ -442,7 +451,8 @@ class GMCPTrigger(BaseObject):
|
|
442
451
|
self._onSuccess(self.id, value, value_exp)
|
443
452
|
|
444
453
|
def __detailed__(self) -> str:
|
445
|
-
|
454
|
+
group = f'group = "{self.group}" ' if self.group else ''
|
455
|
+
return f'<{self.__class__.__name__}> name = "{self.id}" value = "{self.value}" {group}enabled = {self.enabled} '
|
446
456
|
|
447
457
|
class MatchObject(BaseObject):
|
448
458
|
"""
|
@@ -477,7 +487,7 @@ class MatchObject(BaseObject):
|
|
477
487
|
super().__init__(session, patterns = patterns, *args, **kwargs)
|
478
488
|
|
479
489
|
def __del__(self):
|
480
|
-
|
490
|
+
pass
|
481
491
|
|
482
492
|
@property
|
483
493
|
def patterns(self):
|
@@ -516,11 +526,11 @@ class MatchObject(BaseObject):
|
|
516
526
|
self._mline = 0
|
517
527
|
|
518
528
|
def reset(self):
|
519
|
-
"复位事件,用于async
|
529
|
+
"复位事件,用于async执行未等待结果时,对事件的复位。仅异步有效。"
|
520
530
|
self.event.clear()
|
521
531
|
|
522
532
|
def set(self):
|
523
|
-
"
|
533
|
+
"设置事件标记,可以用于人工强制触发,仅在异步触发器下生效。"
|
524
534
|
self.event.set()
|
525
535
|
|
526
536
|
def match(self, line: str, docallback = True) -> BaseObject.State:
|
@@ -587,14 +597,16 @@ class MatchObject(BaseObject):
|
|
587
597
|
state = BaseObject.State(result, self.id, "\n".join(self.lines), tuple(self.wildcards))
|
588
598
|
|
589
599
|
# 采用回调方式执行的时候,执行函数回调(仅当self.sync和docallback均为真时才执行同步
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
|
600
|
+
# 当docallback为真时,是真正的进行匹配和触发,为false时,仅返回匹配结果,不实际触发
|
601
|
+
if docallback:
|
602
|
+
self.event.set()
|
603
|
+
if self.sync:
|
604
|
+
if state.result == self.SUCCESS:
|
605
|
+
self._onSuccess(state.id, state.line, state.wildcards)
|
606
|
+
elif state.result == self.FAILURE:
|
607
|
+
self._onFailure(state.id, state.line, state.wildcards)
|
608
|
+
elif state.result == self.TIMEOUT:
|
609
|
+
self._onTimeout(state.id, state.line, state.wildcards)
|
598
610
|
|
599
611
|
self.state = state
|
600
612
|
return state
|
@@ -616,7 +628,8 @@ class MatchObject(BaseObject):
|
|
616
628
|
return self.state
|
617
629
|
|
618
630
|
def __detailed__(self) -> str:
|
619
|
-
|
631
|
+
group = f'group = "{self.group}" ' if self.group else ''
|
632
|
+
return f'<{self.__class__.__name__}> id = "{self.id}" {group}enabled = {self.enabled} patterns = "{self.patterns}"'
|
620
633
|
|
621
634
|
class Alias(MatchObject):
|
622
635
|
"""
|
@@ -646,7 +659,8 @@ class SimpleAlias(Alias):
|
|
646
659
|
self._codeblock.execute(self.session, id = id, line = line, wildcards = wildcards)
|
647
660
|
|
648
661
|
def __detailed__(self) -> str:
|
649
|
-
|
662
|
+
group = f'group = "{self.group}" ' if self.group else ''
|
663
|
+
return f'<{self.__class__.__name__}> id = "{self.id}" {group}enabled = {self.enabled} patterns = "{self.patterns}" code = "{self._code}"'
|
650
664
|
|
651
665
|
def __repr__(self) -> str:
|
652
666
|
return self.__detailed__()
|
@@ -697,7 +711,8 @@ class SimpleTrigger(Trigger):
|
|
697
711
|
self._codeblock.execute(self.session, id = id, line = line, raw = raw, wildcards = wildcards)
|
698
712
|
|
699
713
|
def __detailed__(self) -> str:
|
700
|
-
|
714
|
+
group = f'group = "{self.group}" ' if self.group else ''
|
715
|
+
return f'<{self.__class__.__name__}> id = "{self.id}" {group}enabled = {self.enabled} patterns = "{self.patterns}" code = "{self._code}"'
|
701
716
|
|
702
717
|
def __repr__(self) -> str:
|
703
718
|
return self.__detailed__()
|
@@ -983,7 +998,8 @@ class Timer(BaseObject):
|
|
983
998
|
self.startTimer()
|
984
999
|
|
985
1000
|
def __detailed__(self) -> str:
|
986
|
-
|
1001
|
+
group = f'group = "{self.group}" ' if self.group else ''
|
1002
|
+
return f'<{self.__class__.__name__}> id = "{self.id}" {group}enabled = {self.enabled} timeout = {self.timeout}'
|
987
1003
|
|
988
1004
|
def __repr__(self) -> str:
|
989
1005
|
return self.__detailed__()
|
@@ -1005,5 +1021,6 @@ class SimpleTimer(Timer):
|
|
1005
1021
|
self._codeblock.execute(self.session, id = id)
|
1006
1022
|
|
1007
1023
|
def __detailed__(self) -> str:
|
1008
|
-
|
1024
|
+
group = f'group = "{self.group}" ' if self.group else ''
|
1025
|
+
return f'<{self.__class__.__name__}> id = "{self.id}" {group}enabled = {self.enabled} timeout = {self.timeout} code = "{self._code}"'
|
1009
1026
|
|
pymud/pymud.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
import asyncio, functools, re,
|
1
|
+
import asyncio, functools, re, os, webbrowser, threading
|
2
2
|
from datetime import datetime
|
3
|
-
import
|
3
|
+
from pathlib import Path
|
4
4
|
from prompt_toolkit.shortcuts import set_title, radiolist_dialog
|
5
5
|
from prompt_toolkit.output import ColorDepth
|
6
6
|
from prompt_toolkit.clipboard.pyperclip import PyperclipClipboard
|
@@ -36,7 +36,8 @@ from prompt_toolkit.layout.processors import (
|
|
36
36
|
from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
|
37
37
|
|
38
38
|
from .objects import CodeBlock
|
39
|
-
from .extras import MudFormatProcessor, SessionBuffer, EasternMenuContainer, VSplitWindow, SessionBufferControl, DotDict
|
39
|
+
from .extras import MudFormatProcessor, SessionBuffer, EasternMenuContainer, VSplitWindow, SessionBufferControl, DotDict
|
40
|
+
from .modules import Plugin
|
40
41
|
from .session import Session
|
41
42
|
from .settings import Settings
|
42
43
|
from .dialogs import MessageDialog, WelcomeDialog, QueryDialog, NewSessionDialog, LogSelectionDialog
|
@@ -92,6 +93,7 @@ class PyMudApp:
|
|
92
93
|
self._mouse_support = True
|
93
94
|
self._plugins = DotDict() # 增加 插件 字典
|
94
95
|
self._globals = DotDict() # 增加所有session使用的全局变量
|
96
|
+
self._onTimerCallbacks = dict()
|
95
97
|
self.sessions = {}
|
96
98
|
self.current_session = None
|
97
99
|
self.status_display = STATUS_DISPLAY(Settings.client["status_display"])
|
@@ -149,6 +151,24 @@ class PyMudApp:
|
|
149
151
|
|
150
152
|
self.load_plugins()
|
151
153
|
|
154
|
+
async def onSystemTimerTick(self):
|
155
|
+
while True:
|
156
|
+
await asyncio.sleep(1)
|
157
|
+
self.app.invalidate()
|
158
|
+
for callback in self._onTimerCallbacks.values():
|
159
|
+
if callable(callback):
|
160
|
+
callback()
|
161
|
+
|
162
|
+
def addTimerTickCallback(self, name, func):
|
163
|
+
'注册一个系统定时器回调,每1s触发一次。指定name为回调函数关键字,func为回调函数。'
|
164
|
+
if callable(func) and (not name in self._onTimerCallbacks.keys()):
|
165
|
+
self._onTimerCallbacks[name] = func
|
166
|
+
|
167
|
+
def removeTimerTickCallback(self, name):
|
168
|
+
'从系统定时器回调中移除一个回调函数。指定name为回调函数关键字。'
|
169
|
+
if name in self._onTimerCallbacks.keys():
|
170
|
+
self._onTimerCallbacks.pop(name)
|
171
|
+
|
152
172
|
def initUI(self):
|
153
173
|
"""初始化UI界面"""
|
154
174
|
self.style = Style.from_dict(Settings.styles)
|
@@ -567,6 +587,7 @@ class PyMudApp:
|
|
567
587
|
log_list = list()
|
568
588
|
files = [f for f in os.listdir('.') if os.path.isfile(f) and f.endswith('.log')]
|
569
589
|
for file in files:
|
590
|
+
file = os.path.abspath(file)
|
570
591
|
filename = os.path.basename(file).ljust(20)
|
571
592
|
filesize = f"{os.path.getsize(file):,} Bytes".rjust(20)
|
572
593
|
# ctime = datetime.fromtimestamp(os.path.getctime(file)).strftime('%Y-%m-%d %H:%M:%S').rjust(23)
|
@@ -574,6 +595,19 @@ class PyMudApp:
|
|
574
595
|
|
575
596
|
file_display_line = "{}{}{}".format(filename, filesize, mtime)
|
576
597
|
log_list.append((file, file_display_line))
|
598
|
+
|
599
|
+
logDir = os.path.abspath(os.path.join(os.curdir, 'log'))
|
600
|
+
if os.path.exists(logDir):
|
601
|
+
files = [f for f in os.listdir(logDir) if f.endswith('.log')]
|
602
|
+
for file in files:
|
603
|
+
file = os.path.join(logDir, file)
|
604
|
+
filename = ('log/' + os.path.basename(file)).ljust(20)
|
605
|
+
filesize = f"{os.path.getsize(file):,} Bytes".rjust(20)
|
606
|
+
# ctime = datetime.fromtimestamp(os.path.getctime(file)).strftime('%Y-%m-%d %H:%M:%S').rjust(23)
|
607
|
+
mtime = datetime.fromtimestamp(os.path.getmtime(file)).strftime('%Y-%m-%d %H:%M:%S').rjust(23)
|
608
|
+
|
609
|
+
file_display_line = "{}{}{}".format(filename, filesize, mtime)
|
610
|
+
log_list.append((file, file_display_line))
|
577
611
|
|
578
612
|
dialog = LogSelectionDialog(
|
579
613
|
text = head_line,
|
@@ -1130,16 +1164,17 @@ class PyMudApp:
|
|
1130
1164
|
|
1131
1165
|
async def run_async(self):
|
1132
1166
|
"以异步方式运行本程序"
|
1133
|
-
|
1167
|
+
asyncio.create_task(self.onSystemTimerTick())
|
1168
|
+
await self.app.run_async(set_exception_handler = False)
|
1134
1169
|
|
1135
1170
|
def run(self):
|
1136
1171
|
"运行本程序"
|
1137
|
-
self.app.run()
|
1138
|
-
|
1172
|
+
#self.app.run(set_exception_handler = False)
|
1173
|
+
asyncio.run(self.run_async())
|
1139
1174
|
|
1140
1175
|
def get_width(self):
|
1141
|
-
"获取ConsoleView
|
1142
|
-
size = self.app.output.get_size().columns
|
1176
|
+
"获取ConsoleView的实际宽度,等于输出宽度,(已经没有左右线条和滚动条了)"
|
1177
|
+
size = self.app.output.get_size().columns
|
1143
1178
|
if Settings.client["status_display"] == 2:
|
1144
1179
|
size = size - Settings.client["status_width"] - 1
|
1145
1180
|
return size
|