ttyengine 0.1.0__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.
- ttyengine/__init__.py +45 -0
- ttyengine/ansi.py +201 -0
- ttyengine/ascii_styler.py +473 -0
- ttyengine/components.py +4718 -0
- ttyengine/compositor.py +780 -0
- ttyengine/errors.py +181 -0
- ttyengine/figlet-LICENSE.txt +27 -0
- ttyengine/figlet.py +430 -0
- ttyengine/fonts.zip +0 -0
- ttyengine/image_reader.py +813 -0
- ttyengine/input_handler.py +814 -0
- ttyengine/launcher.py +822 -0
- ttyengine/region.py +180 -0
- ttyengine/scene.py +614 -0
- ttyengine/sound.py +1439 -0
- ttyengine/terminal.py +283 -0
- ttyengine-0.1.0.dist-info/METADATA +903 -0
- ttyengine-0.1.0.dist-info/RECORD +21 -0
- ttyengine-0.1.0.dist-info/WHEEL +5 -0
- ttyengine-0.1.0.dist-info/licenses/LICENSE +373 -0
- ttyengine-0.1.0.dist-info/top_level.txt +1 -0
ttyengine/__init__.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
_NAME = "ttyengine"
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def _pyproject_version():
|
|
5
|
+
import re
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
table = re.compile(r"^\s*\[+\s*([^\[\]]+?)\s*\]+\s*(?:#.*)?$")
|
|
9
|
+
field = re.compile(r"""^\s*(name|version)\s*=\s*(?:"([^"]*)"|'([^']*)')\s*(?:#.*)?$""")
|
|
10
|
+
try:
|
|
11
|
+
with open(Path(__file__).resolve().parent.parent / "pyproject.toml", encoding="utf-8") as f:
|
|
12
|
+
lines = f.read().splitlines()
|
|
13
|
+
except OSError:
|
|
14
|
+
return None
|
|
15
|
+
fields = {}
|
|
16
|
+
current = None
|
|
17
|
+
for line in lines:
|
|
18
|
+
header = table.match(line)
|
|
19
|
+
if header:
|
|
20
|
+
current = header.group(1)
|
|
21
|
+
continue
|
|
22
|
+
found = field.match(line) if current == "project" else None
|
|
23
|
+
if found:
|
|
24
|
+
fields[found.group(1)] = found.group(2) if found.group(2) is not None else found.group(3)
|
|
25
|
+
return fields.get("version") if fields.get("name") == _NAME else None
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _installed_version():
|
|
29
|
+
try:
|
|
30
|
+
from importlib.metadata import version
|
|
31
|
+
return version(_NAME)
|
|
32
|
+
except Exception:
|
|
33
|
+
return None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
__version__ = _installed_version() or _pyproject_version() or "0+unknown"
|
|
37
|
+
|
|
38
|
+
__all__ = ["Session", "World", "Simulation", "start", "__version__"]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def __getattr__(name):
|
|
42
|
+
if name in ("Session", "World", "Simulation", "start"):
|
|
43
|
+
from . import launcher
|
|
44
|
+
return getattr(launcher, name)
|
|
45
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
ttyengine/ansi.py
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
|
|
4
|
+
ESC = chr(27)
|
|
5
|
+
BEL = chr(7)
|
|
6
|
+
CSI_INTRO = '['
|
|
7
|
+
OSC_INTRO = ']'
|
|
8
|
+
DCS_INTRO = 'P'
|
|
9
|
+
SS3_INTRO = 'O'
|
|
10
|
+
CSI = ESC + CSI_INTRO
|
|
11
|
+
OSC = ESC + OSC_INTRO
|
|
12
|
+
DCS = ESC + DCS_INTRO
|
|
13
|
+
SS3 = ESC + SS3_INTRO
|
|
14
|
+
ST = ESC + '\\'
|
|
15
|
+
|
|
16
|
+
PREFIX_ENV = 'TTYENGINE_ANSI_PREFIX'
|
|
17
|
+
SUFFIX_ENV = 'TTYENGINE_ANSI_SUFFIX'
|
|
18
|
+
NO_COLOR_ENV = 'NO_COLOR'
|
|
19
|
+
|
|
20
|
+
ESC_RE = re.escape(ESC)
|
|
21
|
+
BEL_RE = re.escape(BEL)
|
|
22
|
+
CSI_RE = re.escape(CSI)
|
|
23
|
+
OSC_RE = re.escape(OSC)
|
|
24
|
+
DCS_RE = re.escape(DCS)
|
|
25
|
+
ST_RE = re.escape(ST)
|
|
26
|
+
|
|
27
|
+
_prefix = ''
|
|
28
|
+
_suffix = ''
|
|
29
|
+
_version = 0
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def csi(params, final):
|
|
33
|
+
return CSI + params + final
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def sgr(*params):
|
|
37
|
+
return CSI + ';'.join([str(int(p)) for p in params]) + 'm'
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def fg(red, green, blue):
|
|
41
|
+
return '%s38;2;%d;%d;%dm' % (CSI, red, green, blue)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def bg(red, green, blue):
|
|
45
|
+
return '%s48;2;%d;%d;%dm' % (CSI, red, green, blue)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def move(row, column):
|
|
49
|
+
return '%s%d;%dH' % (CSI, row, column)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def osc(body):
|
|
53
|
+
return OSC + body + BEL
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def mode(number, on):
|
|
57
|
+
return '%s?%d%s' % (CSI, number, 'h' if on else 'l')
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def modes(numbers, on):
|
|
61
|
+
return CSI + '?' + ';'.join([str(number) for number in numbers]) + ('h' if on else 'l')
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
RESET = sgr(0)
|
|
65
|
+
RESETS = (RESET, sgr())
|
|
66
|
+
BOLD = sgr(1)
|
|
67
|
+
ITALIC = sgr(3)
|
|
68
|
+
UNDERLINE = sgr(4)
|
|
69
|
+
HOME = csi('', 'H')
|
|
70
|
+
CLEAR = csi('2', 'J')
|
|
71
|
+
CLEAR_SCROLLBACK = csi('3', 'J')
|
|
72
|
+
CLEAR_ALL = HOME + CLEAR + CLEAR_SCROLLBACK
|
|
73
|
+
ERASE_LINE = csi('', 'K')
|
|
74
|
+
ERASE_BELOW = csi('', 'J')
|
|
75
|
+
FULL_RESET = ESC + 'c'
|
|
76
|
+
SAVE_CURSOR = ESC + '7'
|
|
77
|
+
RESTORE_CURSOR = ESC + '8'
|
|
78
|
+
SYNC_BEGIN = mode(2026, True)
|
|
79
|
+
SYNC_END = mode(2026, False)
|
|
80
|
+
|
|
81
|
+
_ANY = re.compile(
|
|
82
|
+
ESC_RE + r'(?:' + re.escape(CSI_INTRO) + r'[0-?]*[ -/]*[@-~]|'
|
|
83
|
+
+ re.escape(OSC_INTRO) + r'[^' + BEL_RE + ESC_RE + r']*(?:' + BEL_RE + '|' + ST_RE + r')|[ -/]*[0-~])'
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _decode(text):
|
|
88
|
+
try:
|
|
89
|
+
return text.encode('ascii', 'backslashreplace').decode('unicode_escape')
|
|
90
|
+
except UnicodeDecodeError:
|
|
91
|
+
return text
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def set_wrapper(prefix=None, suffix=None):
|
|
95
|
+
global _prefix, _suffix, _version
|
|
96
|
+
if prefix is None:
|
|
97
|
+
prefix = _decode(os.environ.get(PREFIX_ENV, ''))
|
|
98
|
+
if suffix is None:
|
|
99
|
+
suffix = _decode(os.environ.get(SUFFIX_ENV, ''))
|
|
100
|
+
if not isinstance(prefix, str) or not isinstance(suffix, str):
|
|
101
|
+
raise TypeError('wrapper prefix and suffix must be strings')
|
|
102
|
+
_prefix, _suffix = prefix, suffix
|
|
103
|
+
_version += 1
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def get_wrapper():
|
|
107
|
+
return _prefix, _suffix
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def wrapped():
|
|
111
|
+
return bool(_prefix or _suffix)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def wrap(code):
|
|
115
|
+
return _prefix + code + _suffix
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def wrap_all(text):
|
|
119
|
+
if not (_prefix or _suffix):
|
|
120
|
+
return text
|
|
121
|
+
return _ANY.sub(lambda match: _prefix + match.group(0) + _suffix, text)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _surround(body):
|
|
125
|
+
before = '(?:%s)?' % re.escape(_prefix) if _prefix else ''
|
|
126
|
+
after = '(?:%s)?' % re.escape(_suffix) if _suffix else ''
|
|
127
|
+
return before + '(?:' + body + ')' + after
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class Pattern:
|
|
131
|
+
def __init__(self, body, flags=0):
|
|
132
|
+
self.body = body
|
|
133
|
+
self.flags = flags
|
|
134
|
+
self._seen = -1
|
|
135
|
+
self._regex = None
|
|
136
|
+
|
|
137
|
+
def regex(self):
|
|
138
|
+
if self._seen != _version:
|
|
139
|
+
self._regex = re.compile(_surround(self.body), self.flags)
|
|
140
|
+
self._seen = _version
|
|
141
|
+
return self._regex
|
|
142
|
+
|
|
143
|
+
def search(self, *args):
|
|
144
|
+
return self.regex().search(*args)
|
|
145
|
+
|
|
146
|
+
def match(self, *args):
|
|
147
|
+
return self.regex().match(*args)
|
|
148
|
+
|
|
149
|
+
def split(self, *args):
|
|
150
|
+
return self.regex().split(*args)
|
|
151
|
+
|
|
152
|
+
def sub(self, *args):
|
|
153
|
+
return self.regex().sub(*args)
|
|
154
|
+
|
|
155
|
+
def finditer(self, *args):
|
|
156
|
+
return self.regex().finditer(*args)
|
|
157
|
+
|
|
158
|
+
def findall(self, *args):
|
|
159
|
+
return self.regex().findall(*args)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
SGR = Pattern(CSI_RE + r'([0-9;]*)m')
|
|
163
|
+
SEQUENCE = Pattern(
|
|
164
|
+
'(' + CSI_RE + r'[0-9;]*[A-Za-z]|' + OSC_RE + r'[^' + BEL_RE + r']*' + BEL_RE + '?|' + ESC_RE + r'[()]?[A-Za-z0-9])'
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def color_enabled():
|
|
169
|
+
return not os.environ.get(NO_COLOR_ENV)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def monochrome(no_color=None):
|
|
173
|
+
return (not color_enabled()) if no_color is None else bool(no_color)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _drop_color(match):
|
|
177
|
+
params = match.group(1).split(';')
|
|
178
|
+
kept = []
|
|
179
|
+
i = 0
|
|
180
|
+
while i < len(params):
|
|
181
|
+
token = params[i]
|
|
182
|
+
code = int(token) if token else 0
|
|
183
|
+
if code in (38, 48, 58):
|
|
184
|
+
kind = params[i + 1] if i + 1 < len(params) else ''
|
|
185
|
+
i += 3 if kind == '5' else 5 if kind == '2' else 1
|
|
186
|
+
continue
|
|
187
|
+
if 30 <= code <= 39 or 40 <= code <= 49 or code == 59 or 90 <= code <= 97 or 100 <= code <= 107:
|
|
188
|
+
i += 1
|
|
189
|
+
continue
|
|
190
|
+
kept.append(token)
|
|
191
|
+
i += 1
|
|
192
|
+
if not kept:
|
|
193
|
+
return ''
|
|
194
|
+
return CSI + ';'.join(kept) + 'm'
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def strip_color(text):
|
|
198
|
+
return SGR.sub(_drop_color, text)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
set_wrapper()
|