libTerm 0.0.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.
libTerm/__init__.py ADDED
@@ -0,0 +1,2 @@
1
+ from libTerm.term import Term
2
+
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env python
2
+ import os
3
+ if os.name == 'nt':
4
+ from libTerm.term.winnt import Term
5
+ else:
6
+ from libTerm.term.posix import Term
libTerm/term/cursor.py ADDED
@@ -0,0 +1,123 @@
1
+
2
+ from dataclasses import dataclass
3
+ from enum import Enum
4
+ from collections import namedtuple
5
+ import re
6
+ from libTerm.term.types import Coord
7
+ from time import time_ns
8
+ import sys
9
+ @dataclass()
10
+ class ANSI_Cursor(str, Enum):
11
+ esc = '\x1b'
12
+ q = '6n'
13
+ save = 's'
14
+ load = 'u'
15
+ show = '?25h'
16
+ hide = '?25l'
17
+
18
+ def __str__(self):
19
+ return '{ESC}[{CODE}'.format(ESC=self.ESC,CODE=self.value)
20
+ def __repr__(self):
21
+ return repr(self.value)
22
+
23
+
24
+ class Cursor():
25
+ def __init__(__s, term):
26
+ super().__init__()
27
+ __s.term = term
28
+ __s.ansi = ANSI_Cursor
29
+
30
+ __s.re = re.compile(r"^.?\x1b\[(?P<Y>\d*);(?P<X>\d*)R", re.VERBOSE)
31
+ __s.position = __s.__update__
32
+ __s._xy=Coord(0,0)
33
+ __s.XY=Coord(0,0)
34
+ __s.history = [*(None,) * 64]
35
+ __s.init = __s.__update__()
36
+ @property
37
+ def xy(__s):
38
+ __s._xy=__s.__update__()
39
+ return __s._xy
40
+
41
+ def __update__(__s, get='XY'):
42
+ def Parser():
43
+ buf = ' '
44
+ while buf[-1] != "R":
45
+ buf += sys.stdin.read(1)
46
+ # reading the actual values, but what if a keystroke appears while reading
47
+ # from stdin? As dirty work around, getpos() returns if this fails: None
48
+ try:
49
+ groups = __s.re.search(buf).groupdict()
50
+ result = Coord(int(groups['X']), int(groups['Y']))
51
+ except AttributeError:
52
+ result = None
53
+ return result
54
+
55
+ result = None
56
+ timeout = {}
57
+ timeout['limit'] = 500
58
+ timeout['start'] = time_ns() // 1e6
59
+ timeout['running'] = 0
60
+ while not result:
61
+ result = __s.term.ansi(''.join([__s.ansi.esc,'[', __s.ansi.q]), Parser)
62
+ __s.XY =result
63
+ return result
64
+
65
+ def show(__s, state=True):
66
+ if state:
67
+ print('\x1b[?25h', end='', flush=True)
68
+ else:
69
+ __s.hide()
70
+
71
+ def hide(__s, state=True):
72
+ if state:
73
+ print('\x1b[?25l', end='', flush=True)
74
+ atexit.register(__s.show)
75
+ else:
76
+ __s.show()
77
+
78
+ @property
79
+ def x(__s):
80
+ __s.X=__s.__update__('X')
81
+ return __s.X
82
+
83
+ @property
84
+ def y(__s):
85
+ __s.Y=__s.__update__('Y')
86
+ return __s.Y
87
+
88
+
89
+ class vCursor(Cursor):
90
+ def __init__(__s, term,cursor):
91
+ __s.term = term
92
+ __s.realcursor=cursor
93
+ __s.position = Coord(__s.realcursor.x,__s.realcursor.y)
94
+ __s.history = [*(None,) * 64]
95
+ __s.controled = False
96
+ __s.bound = True
97
+ __s.frozen = False
98
+ __s.init = __s.__update__()
99
+
100
+ def freeze(__s, state=True):
101
+ if state:
102
+ __s.frozen = True
103
+ __s.bind(False)
104
+ __s.control(False)
105
+ else:
106
+ __s.frozen = False
107
+
108
+ def __update__(__s, get='XY'):
109
+ pass
110
+
111
+ def show(__s, state=True):
112
+ if state:
113
+ print('\x1b[?25h', end='', flush=True)
114
+ else:
115
+ __s.hide()
116
+
117
+ def hide(__s, state=True):
118
+ if state:
119
+ print('\x1b[?25l', end='', flush=True)
120
+ atexit.register(__s.show)
121
+ else:
122
+ __s.show()
123
+
libTerm/term/posix.py ADDED
@@ -0,0 +1,183 @@
1
+ #!/usr/bin/env python
2
+ import os
3
+ import termios
4
+ import tty
5
+ import atexit
6
+ import re
7
+ import sys
8
+ from time import time_ns
9
+ from shutil import get_terminal_size
10
+ from dataclasses import dataclass, field
11
+ from enum import Enum
12
+ from collections import namedtuple
13
+ from libTerm.term.types import Coord,color, Size,Colors
14
+ from libTerm.term.cursor import Cursor, vCursor
15
+
16
+
17
+
18
+ # Indices for termios list.
19
+ IFLAG = 0
20
+ OFLAG = 1
21
+ CFLAG = 2
22
+ LFLAG = 3
23
+ ISPEED = 4
24
+ OSPEED = 5
25
+ CC = 6
26
+ TCSAFLUSH = termios.TCSAFLUSH
27
+ ECHO = termios.ECHO
28
+ ICANON = termios.ICANON
29
+
30
+ VMIN = 6
31
+ VTIME = 5
32
+
33
+
34
+ class TermAttrs():
35
+ def __init__(s):
36
+ s.stack=[]
37
+ s.init=None
38
+ s.staged=None
39
+ s.active=None
40
+ def stage(s):
41
+ s.staged=list(s.active)
42
+ def update(s,new=None):
43
+ if new is None:
44
+ new=s.staged
45
+ s.stack+=[list(s.active)]
46
+ s.active=new
47
+ s.staged=None
48
+ def restore(s):
49
+ if s.stack:
50
+ s.staged=s.stack.pop()
51
+ return s.staged
52
+
53
+
54
+
55
+ class Term():
56
+ def __init__(s,*a,**k):
57
+ # super().__init__()
58
+ s.pid = os.getpid()
59
+ s.ppid = os.getpid()
60
+
61
+ s.fd = sys.stdin.fileno()
62
+ s.tty = os.ttyname(s.fd)
63
+
64
+ s.TCSAFLUSH = termios.TCSAFLUSH
65
+ s.ECHO = termios.ECHO
66
+ s.ICANON = termios.ICANON
67
+ s.TCSANOW = termios.TCSANOW
68
+
69
+ s.attrs = TermAttrs()
70
+
71
+
72
+ s.attrs.active = s.tcgetattr()
73
+ s.attrs.init = list([*s.attrs.active])
74
+ s.attrs.stack += [list(s.attrs.active)]
75
+
76
+ s._mode = 0
77
+ s.mode = s.__mode__
78
+ atexit.register(s.mode,'normal')
79
+ s.cursor = Cursor(s)
80
+ s.vcursors = {0:vCursor(s,s.cursor)}
81
+ s.size = Size(parent=s)
82
+ s.color = Colors(parent=s)
83
+
84
+
85
+ def tcgetattr(s):
86
+ return termios.tcgetattr(s.fd)
87
+
88
+ def tcsetattr(s,attr,when=TCSAFLUSH):
89
+ termios.tcsetattr(s.fd,when,attr)
90
+
91
+ def setraw(s, when=TCSAFLUSH):
92
+ """Put terminal into raw mode."""
93
+ from termios import IGNBRK,BRKINT,IGNPAR,PARMRK,INPCK,ISTRIP,INLCR,IGNCR,ICRNL,IXON,IXANY,IXOFF,OPOST,PARENB,CSIZE,CS8,ECHO,ECHOE,ECHOK,ECHONL,ICANON,IEXTEN,ISIG,NOFLSH,TOSTOP
94
+ s.attrs.stage()
95
+ # Clear all POSIX.1-2017 input mode flags.
96
+ # See chapter 11 "General Terminal Interface"
97
+ # of POSIX.1-2017 Base Definitions.
98
+ s.attrs.staged[IFLAG] &= ~( IGNBRK | BRKINT | IGNPAR | PARMRK | INPCK | ISTRIP | INLCR | IGNCR | ICRNL | IXON
99
+ | IXANY | IXOFF)
100
+ # Do not post-process output.
101
+ s.attrs.staged[OFLAG] &= ~OPOST
102
+ # Disable parity generation and detection; clear character size mask;
103
+ # let character size be 8 bits.
104
+ s.attrs.staged[CFLAG] &= ~(PARENB | CSIZE)
105
+ s.attrs.staged[CFLAG] |= CS8
106
+ # Clear all POSIX.1-2017 local mode flags.
107
+ s.attrs.staged[LFLAG] &= ~(ECHO | ECHOE | ECHOK | ECHONL | ICANON | IEXTEN | ISIG | NOFLSH | TOSTOP)
108
+ # POSIX.1-2017, 11.1.7 Non-Canonical Mode Input Processing,
109
+ # Case B: MIN>0, TIME=0
110
+ # A pending read shall block until MIN (here 1) bytes are received,
111
+ # or a signal is received.
112
+ s.attrs.staged[CC] = list(s.attr.staged[CC])
113
+ s.attrs.staged[CC][VMIN] = 1
114
+ s.attrs.staged[CC][VTIME] = 0
115
+ s.update(when)
116
+
117
+ def setcbreak(s,when=TCSAFLUSH):
118
+ """Put terminal into cbreak mode."""
119
+ # this code was lifted from the tty module and adapted for being a method
120
+ s.attrs.stage()
121
+ # Do not echo characters; disable canonical input.
122
+ s.attrs.staged[LFLAG] &= ~(ECHO | ICANON)
123
+ # POSIX.1-2017, 11.1.7 Non-Canonical Mode Input Processing,
124
+ # Case B: MIN>0, TIME=0
125
+ # A pending read shall block until MIN (here 1) bytes are received,
126
+ # or a signal is received.
127
+ s.attrs.staged[CC] = list(s.attrs.staged[CC])
128
+ s.attrs.staged[CC][VMIN] = 1
129
+ s.attrs.staged[CC][VTIME] = 0
130
+ s.update(when)
131
+
132
+ def echo(s,enable=False):
133
+ s.attrs.stage()
134
+ s.attrs.staged[3] &= ~s.ECHO
135
+ if enable:
136
+ s.attrs.staged[3] |= s.ECHO
137
+ s.update()
138
+
139
+ def canonical(s,enable):
140
+ s.attrs.stage()
141
+ s.attrs.staged[3] &= ~s.ICANON
142
+ if enable:
143
+ s.attrs.staged[3] |= s.ICANON
144
+ s.update()
145
+
146
+ def __mode__(s,mode=None):
147
+ def Normal():
148
+ # s.cursor.show(True)
149
+ s.echo(True)
150
+ s.canonical(True)
151
+ s.tcsetattr(s.attrs.init)
152
+ s._mode = nmodi.get('normal')
153
+
154
+ def Ctl():
155
+ # s.cursor.show(False)
156
+ s.echo(False)
157
+ s.canonical(False)
158
+ s._mode = nmodi.get('ctl')
159
+
160
+ nmodi={'normal' : 1,'ctl': 2 }
161
+ fmodi = {
162
+ 1 : Normal,
163
+ 2 : Ctl,
164
+ }
165
+ if mode is not None and mode != s._mode:
166
+ nmode=nmodi.get(mode)
167
+ fmodi.get(nmode)()
168
+ return s._mode
169
+
170
+ def update(s,when=TCSAFLUSH):
171
+ s.tcsetattr( s.attrs.staged,when)
172
+ s.attrs.update(s.tcgetattr())
173
+
174
+ def ansi(s, ansi, parser):
175
+ s.setcbreak()
176
+ try:
177
+ sys.stdout.write(ansi)
178
+ sys.stdout.flush()
179
+ result = parser()
180
+ finally:
181
+ s.tcsetattr(s.attrs.restore())
182
+ return result
183
+ #
libTerm/term/types.py ADDED
@@ -0,0 +1,193 @@
1
+ from dataclasses import dataclass,field
2
+ from collections import namedtuple
3
+ from os import get_terminal_size
4
+ from time import sleep, time_ns
5
+ import sys
6
+
7
+
8
+ @dataclass()
9
+ class Coord(namedtuple('Coord', ['x', 'y'])):
10
+ __module__ = None
11
+ __qualname__='Coord'
12
+ _x: int = field(default=0)
13
+ _y: int = field(default=0)
14
+
15
+ def __str__(__s):
16
+ return f'\x1b[{__s.y + 1};{__s.x + 1}H'
17
+
18
+ def __repr__(s):
19
+ return f"{s.__class__.__name__}({s.x}, {s.y})"
20
+
21
+ def __len__(self):
22
+ return 2
23
+ def __iter__(self):
24
+ yield self.x
25
+ yield self.y
26
+ def __getitem__(s, index):
27
+ if 0 > index > 2:
28
+ raise IndexError("numberpair index out of range")
29
+ return (
30
+ ((index == 0)*s.x)+
31
+ ((index == 1)*s.y))
32
+ def __add__(s, other):
33
+ if isinstance(other,Coord2D):
34
+ x=s.x+other.x
35
+ y=s.y+other.y
36
+ return Coord2D(x,y)
37
+ elif isinstance(other,complex):
38
+ x=s.x+other.real
39
+ y=s.y+other.imag
40
+ return Coord2D(x,y)
41
+ elif isinstance(other,str):
42
+ return f'{s.__str__()}{other}'
43
+ else:
44
+ raise TypeError(f"cannot add {type(s)} to {type(other)}")
45
+
46
+
47
+
48
+ @property
49
+ def xy(s) -> tuple[int, int]:
50
+ return (s.x, s.y)
51
+
52
+ @property
53
+ def y(s):
54
+ return s._y
55
+
56
+ @property
57
+ def x(s):
58
+ return s._x
59
+
60
+
61
+ @dataclass(frozen=True)
62
+ class color:
63
+ R: int = field(default=0, metadata={"range": (0, 65535)})
64
+ G: int = field(default=0, metadata={"range": (0, 65535)})
65
+ B: int = field(default=0, metadata={"range": (0, 65535)})
66
+ BIT: int = field(default=8, metadata={"set": (4, 8, 16, 32)})
67
+
68
+ def __post_init__(self):
69
+ for attr_name in ("R", "G", "B"):
70
+ value = getattr(self, attr_name)
71
+ if not isinstance(value, int):
72
+ raise ValueError(f"{attr_name.upper()} must be an integer between 0 and 65535. Got {value}.")
73
+ if not isinstance(getattr(self, "BIT"), int):
74
+ raise ValueError(f"{attr_name.upper()} must be one of 4,8,16,32. Got {value}.")
75
+
76
+ @property
77
+ def RGB(self) -> tuple[int, int, int]:
78
+ return (self.R, self.G, self.B)
79
+
80
+ # @dataclass()
81
+ # class TermCo(namedtuple('Co',['x','y'])):
82
+ # x:int=field(default=0)
83
+ # y:int=field(default=0)
84
+ # class Line(namedtuple('Line',['a','b'])):
85
+ # a:Co=field(default_factory=Co)
86
+ # b:Co=field(default_factory=Co)
87
+ # @classmethod
88
+ # def __add__(s, o):
89
+ # if isinstance(o,Line):
90
+ # if len(s.a)!=0 and len(s.b)!=0:
91
+ # lenx=abs(s.b.x-s.a.x)+abs(o.b.x-o.a.x)
92
+ # leny=abs(s.b.y-s.a.y)+abs(o.b.y-o.a.y)
93
+ # L=Line(s.a,Co(s.a.x+lenx,s.a.y+leny))
94
+ # else:
95
+ # return 0
96
+ #
97
+ # def __len__(s):
98
+ # if len(s.a) != 0 and len(s.b) != 0:
99
+ # lenx = abs(s.b.x - s.a.x) + abs(o.b.x - o.a.x)
100
+ # leny = abs(s.b.y - s.a.y) + abs(o.b.y - o.a.y)
101
+ # return ((lenx**2+leny**2)**(1/2))
102
+
103
+ class Size():
104
+ def __init__(__s, **k):
105
+ __s.parent = k.get('parent')
106
+ __s.getsize = get_terminal_size
107
+ __s.time = None
108
+ __s.last = None
109
+ __s.xy = Coord(1, 1)
110
+ __s._tmp = Coord(1, 1)
111
+ __s.rows = 1
112
+ __s.cols = 1
113
+
114
+ __s.history = []
115
+ __s.changed = False
116
+ __s.changing = False
117
+
118
+ __s.__kwargs__(**k)
119
+ __s.__update__()
120
+
121
+ @property
122
+ def width(__s):
123
+ __s.__update__()
124
+ return __s.cols
125
+ @property
126
+ def height(__s):
127
+ __s.__update__()
128
+ return __s.rows
129
+ @property
130
+ def rc(__s):
131
+ __s.__update__()
132
+ return (__s.cols, __s.rows)
133
+
134
+ def __kwargs__(__s, **k):
135
+ __s.term = k.get('parent')
136
+
137
+ def __update__(__s):
138
+ if __s.time is None:
139
+ __s.last = time_ns()
140
+ size = Coord(*__s.getsize())
141
+ if size != __s.xy:
142
+ if size != __s._tmp:
143
+ __s.changing = True
144
+ __s._tmp = size
145
+ __s._tmptime = time_ns()
146
+ if size == __s._tmp:
147
+ if (time_ns() - __s._tmptime) * 1e6 > 500:
148
+ __s.changing = False
149
+ __s.changed = True
150
+ __s.history += [__s.xy]
151
+ __s.xy = size
152
+ __s.rows = __s.xy.y
153
+ __s.cols = __s.xy.x
154
+ else:
155
+ __s._tmp = size
156
+ if size == __s.xy:
157
+ __s.changed = False
158
+
159
+ class Colors():
160
+ def __init__(__s, **k):
161
+ __s.parent = None
162
+ __s.specs = {'fg': 10, 'bg': 11}
163
+ __s._ansi = '\x1b]{spec};?\a'
164
+ __s.__kwargs__(**k)
165
+ __s.fg = color(255, 255, 255)
166
+ __s.bg = color(0, 0, 0)
167
+ __s.init = __s.__update__()
168
+
169
+ def __kwargs__(__s, **k):
170
+ __s.term = k.get('parent')
171
+
172
+ @staticmethod
173
+ def _ansiparser_():
174
+ buf = ''
175
+ try:
176
+ for i in range(23):
177
+ buf += sys.stdin.read(1)
178
+ rgb = buf.split(':')[1].split('/')
179
+ rgb = [int(i, base=16) for i in rgb]
180
+ rgb = color(*rgb, 16)
181
+ except Exception as E:
182
+ # print(E)
183
+ rgb = None
184
+ return rgb
185
+
186
+ def __update__(__s):
187
+ for ground in __s.specs:
188
+ result = None
189
+ while not result:
190
+ result = __s.term.ansi(__s._ansi.format(spec=__s.specs[ground]), __s._ansiparser_)
191
+ __s.__setattr__(ground, result)
192
+
193
+ return {'fg': __s.fg, 'bg': __s.bg}
libTerm/term/winnt.py ADDED
@@ -0,0 +1,90 @@
1
+ import shutil
2
+ import ctypes
3
+ import sys
4
+ import msvcrt
5
+ import atexit
6
+ import struct
7
+ from libTerm.term.types import color
8
+ from libTerm.term.types import Size
9
+
10
+ class Colors:
11
+ def __init__(s,**k):
12
+ s.parent = k.get('parent')
13
+ s.fg = color(255, 255, 255)
14
+ s.bg = color(0, 0, 0)
15
+ s.refresh()
16
+ def refresh(s):
17
+ # Windows API constants
18
+ STD_OUTPUT_HANDLE = -11
19
+ handle = ctypes.windll.kernel32.GetStdHandle(STD_OUTPUT_HANDLE)
20
+ csbi = ctypes.create_string_buffer(22)
21
+ res = ctypes.windll.kernel32.GetConsoleScreenBufferInfo(handle, csbi)
22
+ if res:
23
+ # Unpack the color attribute as a WORD (2 bytes) at offset 4
24
+ attr = struct.unpack('<H', csbi.raw[4:6])[0]
25
+ s.fg = attr & 0x0F
26
+ s.bg = (attr & 0xF0) >> 4
27
+ s.foreground = s.fg
28
+ s.background = s.bg
29
+ else:
30
+ s.fg = None
31
+ s.bg = None
32
+ s.foreground = None
33
+ s.background = None
34
+
35
+ class Term:
36
+ def __init__(s, *a, **k):
37
+ s.pid = None # Not relevant for Windows terminal
38
+ s.ppid = None
39
+ s.fd = None
40
+ s.tty = None
41
+ s.attrs = None
42
+ s._mode = 0
43
+ s.mode = s.__mode__
44
+ atexit.register(s.mode, 'normal')
45
+ s.cursor = None
46
+ s.vcursors = None
47
+ s.size = Size(parent=s) # Reflects current terminal size
48
+ s.color = Colors(parent=s) # Reflects current terminal colors
49
+
50
+
51
+ def getch(s):
52
+ """Read a single character from the terminal."""
53
+ return msvcrt.getch().decode('utf-8', errors='ignore')
54
+
55
+ def kbhit(s):
56
+ """Check if a keypress is available."""
57
+ return msvcrt.kbhit()
58
+
59
+ def __mode__(s, mode=None):
60
+ nmodi = {'normal': 1, 'ctl': 2}
61
+ if mode is not None and mode != s._mode:
62
+ s._mode = nmodi.get(mode)
63
+ return s._mode
64
+
65
+ def ansi(s, ansi, parser):
66
+ # Parse ANSI color code for foreground (e.g., \x1b[32m)
67
+ import re
68
+ match = re.search(r'\x1b\[(3[0-7])m', ansi)
69
+ if match:
70
+ s.last_ansi_fg = int(match.group(1)[1:]) # 30-37 -> 0-7
71
+ sys.stdout.write(ansi)
72
+ sys.stdout.flush()
73
+ return parser()
74
+
75
+ def refresh(s):
76
+ """Refresh the size and color settings."""
77
+ s.size.refresh()
78
+ s.color.refresh()
79
+
80
+ # Stub methods for compatibility
81
+ def setraw(s, when=None):
82
+ pass
83
+ def setcbreak(s, when=None):
84
+ pass
85
+ def echo(s, enable=False):
86
+ pass
87
+ def canonical(s, enable):
88
+ pass
89
+ def update(s, when=None):
90
+ pass
@@ -0,0 +1,3 @@
1
+ Metadata-Version: 2.4
2
+ Name: libTerm
3
+ Version: 0.0.1
@@ -0,0 +1,10 @@
1
+ libTerm/__init__.py,sha256=XykOeLAxMgxFieSsHqzMB0Qa4izGjCqXPZYubTXPvtc,33
2
+ libTerm/term/__init__.py,sha256=bstJSmazmFJ5ZGDzx35UyeWfLwcEN9pmHtN42c4MeNI,131
3
+ libTerm/term/cursor.py,sha256=qIkS2Nk4Ky6Ab-RInZ9Rqd0h22VJH_brKQscCVwhq_g,2691
4
+ libTerm/term/posix.py,sha256=c4k3FycHxbKCK-PtGzylUDX8qTtffx3qHE9_Zd_UTFc,4544
5
+ libTerm/term/types.py,sha256=-IfgTCCRQxchS6IN7iBmFAoNKHny48YWxDhmyVvVBJk,4694
6
+ libTerm/term/winnt.py,sha256=pMAUKr1BAqFWm8Dct_mp1cvbxISQQi3JBlRfbjEzF48,2528
7
+ libterm-0.0.1.dist-info/METADATA,sha256=7NuroGFhL3AWgyBlj7ojoMG5u4R-1LITMmNnOuVUolY,51
8
+ libterm-0.0.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
9
+ libterm-0.0.1.dist-info/top_level.txt,sha256=pKN_iVl3hsXjz-Dqvni3ur7y7mdgHZrJ3OI7fBbH-lE,8
10
+ libterm-0.0.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ libTerm