autosh 0.0.3__py3-none-any.whl → 0.0.5__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.
autosh/md/printer.py DELETED
@@ -1,301 +0,0 @@
1
- import os
2
- from typing import AsyncGenerator, Literal
3
- import unicodedata
4
-
5
- from autosh.md.state import Color, State
6
- from autosh.md.stream import TextStream
7
-
8
-
9
- class StreamedMarkdownPrinter:
10
- def __init__(self, gen: AsyncGenerator[str, None]):
11
- self.stream = TextStream(gen)
12
- self.state = State()
13
-
14
- def emit(self, s: str):
15
- self.state.emit(s)
16
-
17
- def peek(self):
18
- return self.stream.peek()
19
-
20
- async def consume(self, n: int = 1):
21
- return await self.stream.consume(n)
22
-
23
- async def check(self, s: str, eof: bool | None = None) -> bool:
24
- return await self.stream.check(s, eof)
25
-
26
- async def parse_inline(self, consume_trailing_newline: bool = True):
27
- from autosh.md.inline_text import InlineTextPrinter
28
-
29
- itp = InlineTextPrinter(self)
30
- await itp.parse_inline(consume_trailing_newline)
31
-
32
- async def parse_heading(self):
33
- hashes = 0
34
- while True:
35
- c = await self.consume()
36
- if c == "#":
37
- hashes += 1
38
- else:
39
- break
40
- match hashes:
41
- case 1:
42
- with self.state.style(bold=True, bg=Color.MAGENTA):
43
- with self.state.style(dim=True):
44
- self.emit("#" * hashes + " ")
45
- await self.parse_inline(consume_trailing_newline=False)
46
- case 2:
47
- with self.state.style(bold=True, underline=True, color=Color.MAGENTA):
48
- with self.state.style(dim=True):
49
- self.emit("#" * hashes + " ")
50
- await self.parse_inline(consume_trailing_newline=False)
51
- case 3:
52
- with self.state.style(bold=True, color=Color.MAGENTA):
53
- with self.state.style(dim=True):
54
- self.emit("#" * hashes + " ")
55
- await self.parse_inline(consume_trailing_newline=False)
56
- case 4:
57
- with self.state.style(bold=True, italic=True, color=Color.MAGENTA):
58
- with self.state.style(dim=True):
59
- self.emit("#" * hashes + " ")
60
- await self.parse_inline(consume_trailing_newline=False)
61
- case _:
62
- with self.state.style(bold=True):
63
- with self.state.style(dim=True):
64
- self.emit("#" * hashes + " ")
65
- await self.parse_inline(consume_trailing_newline=False)
66
- await self.consume() # consume the newline
67
- self.emit("\n")
68
-
69
- async def parse_paragraph(self):
70
- while True:
71
- await self.parse_inline()
72
- if (
73
- self.peek() != "\n"
74
- and not await self.stream.non_paragraph_block_start()
75
- ):
76
- if self.peek() == "\n":
77
- await self.consume()
78
- break
79
- else:
80
- break
81
-
82
- async def parse_multiline_code(self):
83
- with self.state.style(dim=True):
84
- self.emit("```")
85
- await self.consume(3)
86
- while not await self.check("\n```"):
87
- c = await self.consume()
88
- if c is None:
89
- self.emit("\n")
90
- return
91
- self.emit(c)
92
- self.emit("\n```\n")
93
- await self.consume(4)
94
-
95
- async def parse_list(self, ordered: bool):
96
- indents = [0]
97
- counter = [1]
98
- # first item
99
- if ordered:
100
- self.emit("1. ")
101
- await self.consume()
102
- else:
103
- self.emit("• ")
104
- await self.consume()
105
- await self.parse_inline()
106
- while True:
107
- indent = 0
108
- while self.peek() in [" ", "\t", "\n"]:
109
- if self.peek() in [" ", "\t"]:
110
- indent += 1
111
- if self.peek() == "\n":
112
- indent = 0
113
- await self.consume()
114
- if self.peek() is None:
115
- return
116
- if ordered and not await self.stream.ordered_list_label():
117
- return
118
- if not ordered and not await self.stream.unordered_list_label():
119
- return
120
- if not ordered:
121
- await self.consume()
122
- else:
123
- while self.peek() is not None and self.peek() != ".":
124
- await self.consume()
125
- await self.consume()
126
-
127
- depth = None
128
- for i in range(len(indents) - 1):
129
- if indents[i] <= indent and indents[i + 1] > indent:
130
- depth = i
131
- break
132
- if depth is None and indents[-1] + 2 <= indent:
133
- # indent one more level
134
- indents.append(indent)
135
- depth = len(indents) - 1
136
- counter.append(1)
137
- elif depth is None:
138
- # same as last level
139
- depth = len(indents) - 1
140
- counter[depth] += 1
141
- else:
142
- # dedent
143
- indents = indents[: depth + 1]
144
- counter = counter[: depth + 1]
145
- counter[depth] += 1
146
- if not ordered:
147
- self.emit(" " * depth + "• ")
148
- else:
149
- self.emit(" " * depth + str(counter[depth]) + ". ")
150
- await self.parse_inline()
151
-
152
- async def parse_blockquote(self):
153
- from autosh.md.inline_text import InlineTextPrinter
154
-
155
- while True:
156
- while self.peek() in [" ", "\t"]:
157
- await self.consume()
158
- if self.peek() != ">":
159
- break
160
- await self.consume()
161
- with self.state.style(bold=True, dim=True):
162
- self.emit(">")
163
- with self.state.style(dim=True):
164
- itp = InlineTextPrinter(self)
165
- await itp.parse_inline_unformatted()
166
- if self.peek() == "\n":
167
- self.emit("\n")
168
- await self.consume()
169
-
170
- async def parse_table(self):
171
- def str_size(s: str) -> int:
172
- size = 0
173
- for c in s:
174
- match unicodedata.east_asian_width(c):
175
- case "F" | "W":
176
- size += 2
177
- case _:
178
- size += 1
179
- return size
180
-
181
- rows: list[list[str]] = []
182
- while True:
183
- if self.peek() != "|":
184
- break
185
- s = "|"
186
- await self.consume()
187
- while self.peek() not in ["\n", None]:
188
- s += self.peek() or ""
189
- await self.consume()
190
- if self.peek() == "\n":
191
- await self.consume()
192
- s = s.strip("|")
193
- rows.append([c.strip() for c in s.split("|")])
194
- # not enough rows
195
- if len(rows) < 2:
196
- self.emit("| " + " | ".join(rows[0]) + " |\n")
197
- # do some simple formatting
198
- cols = len(rows[0])
199
- col_widths = [0] * cols
200
- aligns: list[Literal["left", "right", "center"]] = ["left"] * cols
201
- for i, row in enumerate(rows):
202
- if i == 1:
203
- # check for alignment
204
- for j, c in enumerate(row):
205
- if c.startswith(":") and c.endswith(":"):
206
- aligns[j] = "center"
207
- elif c.startswith(":"):
208
- aligns[j] = "left"
209
- elif c.endswith(":"):
210
- aligns[j] = "right"
211
- continue
212
- for j, c in enumerate(row):
213
- col_widths[j] = max(col_widths[j], str_size(c))
214
- # print top border
215
- with self.state.style(dim=True):
216
- for j, c in enumerate(rows[0]):
217
- self.emit("┌" if j == 0 else "┬")
218
- self.emit("─" * (col_widths[j] + 2))
219
- self.emit("┐\n")
220
- # print the table
221
- for i, row in enumerate(rows):
222
- for j, c in enumerate(row):
223
- with self.state.style(dim=True):
224
- self.emit("│" if i != 1 else ("├" if j == 0 else "┼"))
225
- if i == 1:
226
- text = "─" * (col_widths[j] + 2)
227
- else:
228
- s = str_size(c)
229
- align = aligns[j] if i != 0 else "center"
230
- match align:
231
- case "left":
232
- text = c + " " * (col_widths[j] - s)
233
- case "right":
234
- text = " " * (col_widths[j] - s) + c
235
- case "center":
236
- padding = col_widths[j] - s
237
- text = " " * (padding // 2) + c
238
- text += " " * (padding - padding // 2)
239
- text = f" {text} "
240
- with self.state.style(dim=i == 1):
241
- self.emit(text)
242
- with self.state.style(dim=True):
243
- self.emit("│\n" if i != 1 else "┤\n")
244
- # print bottom border
245
- with self.state.style(dim=True):
246
- for j, c in enumerate(rows[0]):
247
- self.emit("└" if j == 0 else "┴")
248
- self.emit("─" * (col_widths[j] + 2))
249
- self.emit("┘\n")
250
-
251
- async def parse_doc(self):
252
- await self.stream.init()
253
- start = True
254
- while True:
255
- # Remove leading spaces and empty lines
256
- indent = 0
257
- while self.peek() in [" ", "\t", "\n"]:
258
- if self.peek() in [" ", "\t"]:
259
- indent += 1
260
- if self.peek() == "\n":
261
- indent = 0
262
- await self.consume()
263
- if self.peek() is None:
264
- break
265
- if not start:
266
- self.emit("\n")
267
- start = False
268
- match c := self.peek():
269
- case None:
270
- break
271
- # Heading
272
- case "#":
273
- await self.parse_heading()
274
- # Code
275
- case "`" if await self.check("```"):
276
- await self.parse_multiline_code()
277
- # Separator
278
- case _ if await self.check("---"):
279
- await self.consume(3)
280
- width = min(os.get_terminal_size().columns, 80)
281
- with self.state.style(dim=True):
282
- self.emit("─" * width)
283
- # Unordered list
284
- case _ if await self.stream.unordered_list_label():
285
- await self.parse_list(False)
286
- # Ordered list
287
- case _ if await self.stream.ordered_list_label():
288
- await self.parse_list(True)
289
- # Blockquote
290
- case ">":
291
- await self.parse_blockquote()
292
- # Table
293
- case "|":
294
- await self.parse_table()
295
- # Normal paragraph
296
- case _:
297
- await self.parse_paragraph()
298
- self.emit("\x1b[0m") # Reset all
299
-
300
- def __await__(self):
301
- return self.parse_doc().__await__()
autosh/md/state.py DELETED
@@ -1,136 +0,0 @@
1
- from dataclasses import dataclass
2
- from contextlib import contextmanager
3
- from enum import Enum
4
-
5
- HIGHLIGHT_COLOR_START = "35"
6
- HIGHLIGHT_COLOR_END = "0"
7
- ESC = "\x1b"
8
-
9
-
10
- class Color(Enum):
11
- BLACK = 30, 40
12
- RED = 31, 41
13
- GREEN = 32, 42
14
- YELLOW = 33, 43
15
- BLUE = 34, 44
16
- MAGENTA = 35, 45
17
- CYAN = 36, 46
18
- WHITE = 37, 47
19
-
20
- BRIGHT_BLACK = 90, 100
21
- BRIGHT_RED = 91, 101
22
- BRIGHT_GREEN = 92, 102
23
- BRIGHT_YELLOW = 93, 103
24
- BRIGHT_BLUE = 94, 104
25
- BRIGHT_MAGENTA = 95, 105
26
- BRIGHT_CYAN = 96, 106
27
- BRIGHT_WHITE = 97, 107
28
-
29
- def __init__(self, foreground: int, background: int):
30
- self.foreground = foreground
31
- self.background = background
32
-
33
-
34
- @dataclass
35
- class State:
36
- is_bold: bool = False
37
- is_dim: bool = False
38
- is_italic: bool = False
39
- is_underline: bool = False
40
- is_strike: bool = False
41
- foreground_color: Color | None = None
42
- background_color: Color | None = None
43
-
44
- def emit(self, text: str):
45
- print(text, end="", flush=True)
46
-
47
- def _enter_scope(
48
- self,
49
- bold: bool | None = None,
50
- dim: bool | None = None,
51
- italic: bool | None = None,
52
- underline: bool | None = None,
53
- strike: bool | None = None,
54
- color: Color | None = None,
55
- bg: Color | None = None,
56
- ) -> "State":
57
- old_state = State(
58
- is_bold=self.is_bold,
59
- is_dim=self.is_dim,
60
- is_italic=self.is_italic,
61
- is_underline=self.is_underline,
62
- is_strike=self.is_strike,
63
- foreground_color=self.foreground_color,
64
- background_color=self.background_color,
65
- )
66
- if bold is not None:
67
- self.is_bold = bold
68
- if dim is not None:
69
- self.is_dim = dim
70
- if italic is not None:
71
- self.is_italic = italic
72
- if underline is not None:
73
- self.is_underline = underline
74
- if strike is not None:
75
- self.is_strike = strike
76
- if color is not None:
77
- self.foreground_color = color
78
- if bg is not None:
79
- self.background_color = bg
80
- self.__apply_all()
81
- return old_state
82
-
83
- def _exit_scope(self, old_state: "State"):
84
- self.is_bold = old_state.is_bold
85
- self.is_dim = old_state.is_dim
86
- self.is_italic = old_state.is_italic
87
- self.is_underline = old_state.is_underline
88
- self.is_strike = old_state.is_strike
89
- self.foreground_color = old_state.foreground_color
90
- self.background_color = old_state.background_color
91
- self.__apply_all()
92
-
93
- @contextmanager
94
- def style(
95
- self,
96
- bold: bool | None = None,
97
- dim: bool | None = None,
98
- italic: bool | None = None,
99
- underline: bool | None = None,
100
- strike: bool | None = None,
101
- color: Color | None = None,
102
- bg: Color | None = None,
103
- ):
104
- old_state = self._enter_scope(
105
- bold=bold,
106
- dim=dim,
107
- italic=italic,
108
- underline=underline,
109
- strike=strike,
110
- color=color,
111
- bg=bg,
112
- )
113
- yield
114
- self._exit_scope(old_state)
115
-
116
- def __apply_all(self):
117
- self.emit(f"{ESC}[0m")
118
- codes = []
119
- if self.is_bold:
120
- codes.append(1)
121
- if self.is_dim:
122
- codes.append(2)
123
- if self.is_italic:
124
- codes.append(3)
125
- if self.is_underline:
126
- codes.append(4)
127
- if self.is_strike:
128
- codes.append(9)
129
- if self.foreground_color:
130
- codes.append(self.foreground_color.foreground)
131
- if self.background_color:
132
- codes.append(self.background_color.background)
133
-
134
- codes = ";".join(map(str, codes))
135
- if len(codes) > 0:
136
- self.emit(f"{ESC}[{codes}m")
autosh/md/stream.py DELETED
@@ -1,107 +0,0 @@
1
- from typing import AsyncGenerator
2
-
3
-
4
- class TextStream:
5
- def __init__(self, gen: AsyncGenerator[str, None]):
6
- self.__stream = gen
7
- self.__buf: str = ""
8
- self.__eof = False
9
-
10
- async def init(self):
11
- try:
12
- self.__buf = await anext(self.__stream)
13
- except StopAsyncIteration:
14
- self.__eof = True
15
-
16
- async def __ensure_length(self, n: int):
17
- while (not self.__eof) and len(self.__buf) < n:
18
- try:
19
- self.__buf += await anext(self.__stream)
20
- except StopAsyncIteration:
21
- self.__eof = True
22
-
23
- def peek(self):
24
- c = self.__buf[0] if len(self.__buf) > 0 else None
25
- return c
26
-
27
- async def check(self, s: str, eof: bool | None = None) -> bool:
28
- if len(s) == 0:
29
- return True
30
- await self.__ensure_length(len(s) + 1)
31
- if len(self.__buf) < len(s):
32
- return False
33
- matched = self.__buf[0 : len(s)] == s
34
- if matched:
35
- if eof is not None:
36
- if eof:
37
- # return false if there is more data
38
- if len(self.__buf) > len(s):
39
- return False
40
- else:
41
- # return false if there is no more data
42
- if len(self.__buf) == len(s):
43
- return False
44
- return matched
45
-
46
- async def consume(self, n: int = 1):
47
- await self.__ensure_length(n)
48
- if len(self.__buf) < n:
49
- raise ValueError("Not enough data to consume")
50
- s = self.__buf[:n]
51
- self.__buf = self.__buf[n:]
52
- await self.__ensure_length(1)
53
- return s
54
-
55
- async def unordered_list_label(self) -> bool:
56
- if self.__eof:
57
- return False
58
- await self.__ensure_length(2)
59
- buf = self.__buf
60
- if len(buf) < 2:
61
- return False
62
- if buf[0] in ["-", "+", "*"] and buf[1] == " ":
63
- return True
64
- return False
65
-
66
- async def ordered_list_label(self) -> bool:
67
- if self.__eof:
68
- return False
69
- await self.__ensure_length(5)
70
- buf = self.__buf
71
- # \d+\.
72
- if len(buf) == 0:
73
- return False
74
- if not buf[0].isnumeric():
75
- return False
76
- has_dot = False
77
- for i in range(1, 5):
78
- if i >= len(buf):
79
- return False
80
- c = buf[i]
81
- if c == ".":
82
- if has_dot:
83
- return False
84
- has_dot = True
85
- continue
86
- if c == " ":
87
- if has_dot:
88
- return True
89
- return False
90
- if c.isnumeric():
91
- continue
92
- return False
93
-
94
- async def non_paragraph_block_start(self):
95
- await self.__ensure_length(3)
96
- buf = self.__buf[:3] if len(self.__buf) >= 3 else self.__buf
97
- if buf.startswith("```"):
98
- return True
99
- if buf.startswith("---"):
100
- return True
101
- if buf.startswith("> "):
102
- return True
103
- if await self.ordered_list_label():
104
- return True
105
- if await self.unordered_list_label():
106
- return True
107
- return False
@@ -1,22 +0,0 @@
1
- autosh/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- autosh/config-template.toml,sha256=iLdCBHIK0czWgNHtwAgvuhV670aiNc-IOmaPHP12i0Y,269
3
- autosh/config.py,sha256=7DXUSsqHm8dPsXln-NTu8D0_Uwcs2h4sT8pFtQM8tso,2654
4
- autosh/main.py,sha256=ov8lBOUb7GOa3-WBW00gqKaYrTG3NFYQgIXP3-6Eki4,4975
5
- autosh/session.py,sha256=pU3AAqI3vEEFdtsXK_1ZV6uXzjkzIG8q3Yo01K-Q6dw,9615
6
- autosh/md/__init__.py,sha256=0SK4rkynUwR77E8IU-ORJmWou_aTxC6xzQQo-IfAsaM,213
7
- autosh/md/inline_text.py,sha256=I8CaxksvOpTr1mlUtnk50TVVgF9CLnLsth2ux0cdTM4,6422
8
- autosh/md/printer.py,sha256=qD3AcGosnPMRAf-KiRw1PUixtP-kS5xFIPFjzGLLT0g,11099
9
- autosh/md/state.py,sha256=OEp0kUb63HrAN5BPcPx9mxWogofayumH9GZGqLdOoEA,3792
10
- autosh/md/stream.py,sha256=zaVJR6Kog6EZcuVaGszwgywpysHcn8fRZ0KItmKRhiA,3224
11
- autosh/plugins/__init__.py,sha256=NXq27wvS97NEH6hTtWRyp-ut0BE9fq5QyKBjXOj39zY,2487
12
- autosh/plugins/calc.py,sha256=qo0EajIpNPv9PtLNLygyEjVaxo1F6_S62kmoJZq5oLM,581
13
- autosh/plugins/cli.py,sha256=D6S_QHPmjBBB9gwgXeJrwxUs3u0TNty_tHVICbEPGbs,8522
14
- autosh/plugins/clock.py,sha256=GGi0HAG6f6-FP1qqGoyCcUj11q_VnkaGArumsMk0CkY,542
15
- autosh/plugins/code.py,sha256=0JwFzq6ejgbisCqBm_RG1r1WEVNou64ue-siVIpvZqs,2291
16
- autosh/plugins/search.py,sha256=1d3Gqq6uXu0ntTBpw44Ab_haAySvZLMj3e2MQd3DHO0,2736
17
- autosh/plugins/web.py,sha256=lmD2JnsqVI1qKgSFrk39851jCZoPyPRaVvHeEFYXylA,2597
18
- autosh-0.0.3.dist-info/METADATA,sha256=M2wVPjOWoiCXWbYzs216oQ4eUSE_s5-b67-qTufmnRE,1897
19
- autosh-0.0.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
20
- autosh-0.0.3.dist-info/entry_points.txt,sha256=BV7bzUnxG6Z5InEkrfajGCxjooYORC5tZDDZctOPenQ,67
21
- autosh-0.0.3.dist-info/licenses/LICENSE,sha256=BnLDJsIJe-Dm18unR9DOoSv7QOfAz6LeIQc1yHAjxp0,1066
22
- autosh-0.0.3.dist-info/RECORD,,
File without changes