...
1#!/hint/python3
2"""Generate terminal "control seqences" for ANSI X3.64 terminals.
3Or rather, ECMA-48 terminals, because ANSI withdrew X3.64 in favor of
4ECMA-48.
5(https://www.ecma-international.org/publications/files/ECMA-ST/Ecma-048.pdf)
6"control sequences" are a subset of "escape codes"; which are so named
7because they start with the ASCII ESC character ("\033").
8If you're going to try to read ECMA-48, be aware the notation they use
9for a byte is "AB/CD" where AB and CD are zero-padded decimal numbers
10that represent 4-bit sequences. It's easiest to think of them as
11hexadecimal byte; for example, when ECMA-48 says "11/15", think
12"hexadecimal 0xBF". And then to make sense of that hexadecimal
13number, you'll want to have an ASCII reference table handy.
14This implementation is not complete/exhaustive; it only supports the
15things that we've found it handy to support.
16"""
17
18from typing import List, Union, cast, overload
19
20_number = Union[int, float]
21
22
23@overload
24def cs(params: List[_number], op: str) -> str:
25 ...
26
27
28@overload
29def cs(op: str) -> str:
30 ...
31
32
33def cs(arg1, arg2=None): # type: ignore
34 """cs returns a formatted 'control sequence' (ECMA-48 §5.4).
35 This only supports text/ASCII ("7-bit") control seqences, and does
36 support binary ("8-bit") control seqeneces.
37 This only supports standard parameters (ECMA-48 §5.4.1.a /
38 §5.4.2), and does NOT support "experimental"/"private" parameters
39 (ECMA-48 §5.4.1.b).
40 """
41 csi = '\033['
42 if arg2:
43 params: List[_number] = arg1
44 op: str = arg2
45 else:
46 params = []
47 op = arg1
48 return csi + (';'.join(str(n).replace('.', ':') for n in params)) + op
49
50
51# The "EL" ("Erase in Line") control seqence (ECMA-48 §8.3.41) with no
52# parameters.
53clear_rest_of_line = cs('K')
54
55# The "ED" ("Erase in Display^H^H^H^H^H^H^HPage") control seqence
56# (ECMA-48 §8.3.39) with no parameters.
57clear_rest_of_screen = cs('J')
58
59
60def cursor_up(lines: int = 1) -> str:
61 """Generate the "CUU" ("CUrsor Up") control sequence (ECMA-48 §8.3.22)."""
62 if lines == 1:
63 return cs('A')
64 return cs([lines], 'A')
65
66
67def _sgr_code(code: int) -> '_SGR':
68 def get(self: '_SGR') -> '_SGR':
69 return _SGR(self.params + [code])
70
71 return cast('_SGR', property(get))
72
73
74class _SGR:
75 def __init__(self, params: List[_number] = []) -> None:
76 self.params = params
77
78 def __str__(self) -> str:
79 return cs(self.params, 'm')
80
81 reset = _sgr_code(0)
82 bold = _sgr_code(1)
83 fg_blk = _sgr_code(30)
84 fg_red = _sgr_code(31)
85 fg_grn = _sgr_code(32)
86 fg_yel = _sgr_code(33)
87 fg_blu = _sgr_code(34)
88 fg_prp = _sgr_code(35)
89 fg_cyn = _sgr_code(36)
90 fg_wht = _sgr_code(37)
91 # 38 is 8bit/24bit color
92 fg_def = _sgr_code(39)
93
94
95# sgr builds "Set Graphics Rendition" control sequences (ECMA-48
96# §8.3.117).
97sgr = _SGR()
View as plain text