omdev 0.0.0.dev457__py3-none-any.whl → 0.0.0.dev458__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.
@@ -1,390 +0,0 @@
1
- """
2
- TODO:
3
- - accept pre-parsed tokens
4
- - provide live view in this pkg
5
- """
6
- import itertools
7
- import typing as ta
8
-
9
- from ... import ptk
10
- from .border import Border
11
- from .border import SquareBorder
12
- from .parser import markdown_parser
13
- from .tags import TAG_INSETS
14
- from .tags import TAG_RULES
15
- from .utils import FormattedTextAlign
16
- from .utils import align
17
- from .utils import apply_style
18
- from .utils import last_line_length
19
- from .utils import lex
20
- from .utils import strip
21
- from .utils import wrap
22
-
23
-
24
- if ta.TYPE_CHECKING:
25
- from markdown_it.token import Token
26
-
27
-
28
- ##
29
-
30
-
31
- _SIDES = {
32
- 'left': FormattedTextAlign.LEFT,
33
- 'right': FormattedTextAlign.RIGHT,
34
- 'center': FormattedTextAlign.CENTER,
35
- }
36
-
37
-
38
- class Markdown:
39
- """A markdown formatted text renderer. Accepts a markdown string and renders it at a given width."""
40
-
41
- def __init__(
42
- self,
43
- markup: str,
44
- *,
45
- width: int | None = None,
46
- strip_trailing_lines: bool = True,
47
- ) -> None:
48
- """
49
- Initialize the markdown formatter.
50
-
51
- Args:
52
- markup: The markdown text to render
53
- width: The width in characters available for rendering. If :py:const:`None` the terminal width will be used
54
- strip_trailing_lines: If :py:const:`True`, empty lines at the end of the rendered output will be removed
55
- """
56
-
57
- super().__init__()
58
-
59
- self.markup = markup
60
- self.width = width or ptk.get_app_session().output.get_size().columns
61
- self.strip_trailing_lines = strip_trailing_lines
62
-
63
- if (parser := markdown_parser()) is not None:
64
- self.formatted_text = self.render(
65
- tokens=parser.parse(self.markup),
66
- width=self.width,
67
- )
68
- else:
69
- self.formatted_text = lex(
70
- ptk.to_formatted_text(self.markup),
71
- lexer_name='markdown',
72
- )
73
-
74
- if strip_trailing_lines:
75
- self.formatted_text = strip(
76
- self.formatted_text,
77
- left=False,
78
- char='\n',
79
- )
80
-
81
- def render(
82
- self,
83
- tokens: ta.Sequence['Token'],
84
- width: int = 80,
85
- left: int = 0,
86
- ) -> ptk.StyleAndTextTuples:
87
- """
88
- Render a list of parsed markdown tokens.
89
-
90
- Args:
91
- tokens: The list of parsed tokens to render
92
- width: The width at which to render the tokens
93
- left: The position on the current line at which to render the output - used to indent subsequent lines when
94
- rendering inline blocks like images
95
- Returns:
96
- Formatted text
97
- """
98
-
99
- ft = []
100
-
101
- i = 0
102
- while i < len(tokens):
103
- token = tokens[i]
104
-
105
- # If this is an inline block, render it's children
106
- if token.type == 'inline' and token.children:
107
- ft += self.render(token.children, width)
108
- i += 1
109
-
110
- # Otherwise gather the tokens in the current block
111
- else:
112
- nest = 0
113
- tokens_in_block = 0
114
- for j, token in enumerate(tokens[i:]):
115
- nest += token.nesting
116
- if nest == 0:
117
- tokens_in_block = j
118
- break
119
-
120
- # If there is a special method for rendering the block, use it
121
-
122
- # Table require a lot of care
123
- if token.tag == 'table':
124
- ft += self.render_table(
125
- tokens[i:i + tokens_in_block + 1],
126
- width=width,
127
- left=last_line_length(ft),
128
- )
129
-
130
- # We need to keep track of item numbers in ordered lists
131
- elif token.tag == 'ol':
132
- ft += self.render_ordered_list(
133
- tokens[i:i + tokens_in_block + 1],
134
- width=width,
135
- left=last_line_length(ft),
136
- )
137
-
138
- # Otherwise all other blocks are rendered in the same way
139
- else:
140
- ft += self.render_block(
141
- tokens[i:i + tokens_in_block + 1],
142
- width=width,
143
- left=last_line_length(ft),
144
- )
145
-
146
- i += j + 1
147
-
148
- return ft
149
-
150
- def render_block(
151
- self,
152
- tokens: ta.Sequence['Token'],
153
- width: int,
154
- left: int = 0,
155
- ) -> ptk.StyleAndTextTuples:
156
- """
157
- Render a list of parsed markdown tokens representing a block element.
158
-
159
- Args:
160
- tokens: The list of parsed tokens to render
161
- width: The width at which to render the tokens
162
- left: The position on the current line at which to render the output - used to indent subsequent lines when
163
- rendering inline blocks like images
164
- Returns:
165
- Formatted text
166
- """
167
-
168
- ft = []
169
- token = tokens[0]
170
-
171
- # Restrict width if necessary
172
- inset = TAG_INSETS.get(token.tag)
173
- if inset:
174
- width -= inset
175
-
176
- style = 'class:md'
177
- if token.tag:
178
- style = f'{style}.{token.tag}'
179
-
180
- # Render innards
181
- if len(tokens) > 1:
182
- ft += self.render(tokens[1:-1], width)
183
- ft = apply_style(ft, style)
184
- else:
185
- ft.append((style, token.content))
186
-
187
- # Apply tag rule
188
- rule = TAG_RULES.get(token.tag)
189
- if rule:
190
- ft = rule(
191
- ft,
192
- width,
193
- left,
194
- token,
195
- )
196
-
197
- return ft
198
-
199
- def render_ordered_list(
200
- self,
201
- tokens: ta.Sequence['Token'],
202
- width: int,
203
- left: int = 0,
204
- ) -> ptk.StyleAndTextTuples:
205
- """Render an ordered list by adding indices to the child list items."""
206
-
207
- # Find the list item tokens
208
- list_level_tokens = []
209
- nest = 0
210
- for token in tokens:
211
- if nest == 1 and token.tag == 'li':
212
- list_level_tokens.append(token)
213
- nest += token.nesting
214
-
215
- # Assign them a marking
216
- margin_width = len(str(len(list_level_tokens)))
217
- for i, token in enumerate(list_level_tokens, start=1):
218
- token.attrs['data-margin'] = str(i).rjust(margin_width) + '.'
219
- token.attrs['data-list-type'] = 'ol'
220
-
221
- # Now render the tokens as normal
222
- return self.render_block(
223
- tokens,
224
- width=width,
225
- left=left,
226
- )
227
-
228
- def render_table(
229
- self,
230
- tokens: ta.Sequence['Token'],
231
- width: int,
232
- left: int = 0,
233
- border: type[Border] = SquareBorder,
234
- ) -> ptk.StyleAndTextTuples:
235
- """
236
- Render a list of parsed markdown tokens representing a table element.
237
-
238
- Args:
239
- tokens: The list of parsed tokens to render
240
- width: The width at which to render the tokens
241
- left: The position on the current line at which to render the output - used to indent subsequent lines when
242
- rendering inline blocks like images
243
- border: The border style to use to render the table
244
- Returns:
245
- Formatted text
246
- """
247
-
248
- ft: ptk.StyleAndTextTuples = []
249
-
250
- # Stack the tokens in the shape of the table
251
- cell_tokens: list[list[list[Token]]] = []
252
- i = 0
253
- while i < len(tokens):
254
- token = tokens[i]
255
- if token.type == 'tr_open':
256
- cell_tokens.append([])
257
- elif token.type in ('th_open', 'td_open'):
258
- for j, token in enumerate(tokens[i:]):
259
- if token.type in ('th_close', 'td_close'):
260
- cell_tokens[-1].append(list(tokens[i:i + j + 1]))
261
- break
262
- i += j
263
- i += 1
264
-
265
- def _render_token(
266
- tokens: ta.Sequence['Token'],
267
- width: int | None = None,
268
- ) -> ptk.StyleAndTextTuples:
269
- """Render a token with correct alignment."""
270
-
271
- side = 'left'
272
-
273
- # Check CSS for text alignment
274
- for style_str in str(tokens[0].attrs.get('style', '')).split(';'):
275
- if ':' in style_str:
276
- key, value = style_str.strip().split(':', 1)
277
- if key.strip() == 'text-align':
278
- side = value
279
-
280
- # Render with a very long line length if we do not have a width
281
- ft = self.render(tokens, width=width or 999999)
282
-
283
- # If we do have a width, wrap and apply the alignment
284
- if width:
285
- ft = wrap(ft, width)
286
- ft = align(_SIDES[side], ft, width)
287
-
288
- return ft
289
-
290
- # Find the naive widths of each cell
291
- cell_renders: list[list[ptk.StyleAndTextTuples]] = []
292
- cell_widths: list[list[int]] = []
293
- for row in cell_tokens:
294
- cell_widths.append([])
295
- cell_renders.append([])
296
- for each_tokens in row:
297
- rendered = _render_token(each_tokens)
298
- cell_renders[-1].append(rendered)
299
- cell_widths[-1].append(ptk.fragment_list_width(rendered))
300
-
301
- # Calculate row and column widths, accounting for broders
302
- col_widths = [
303
- max([row[i] for row in cell_widths])
304
- for i in range(len(cell_widths[0]))
305
- ]
306
-
307
- # Adjust widths and potentially re-render cells. Reduce biggest cells until we fit in width.
308
- while sum(col_widths) + 3 * (len(col_widths) - 1) + 4 > width:
309
- idxmax = max(enumerate(col_widths), key=lambda x: x[1])[0]
310
- col_widths[idxmax] -= 1
311
-
312
- # Re-render changed cells
313
- for i, row_widths in enumerate(cell_widths):
314
- for j, new_width in enumerate(col_widths):
315
- if row_widths[j] != new_width:
316
- cell_renders[i][j] = _render_token(
317
- cell_tokens[i][j],
318
- width=new_width,
319
- )
320
-
321
- # Justify cell contents
322
- for i, renders_row in enumerate(cell_renders):
323
- for j, cell in enumerate(renders_row):
324
- cell_renders[i][j] = align(
325
- FormattedTextAlign.LEFT,
326
- cell,
327
- width=col_widths[j],
328
- )
329
-
330
- # Render table
331
- style = 'class:md.table.border'
332
-
333
- def _draw_add_border(
334
- left: str,
335
- split: str,
336
- right: str,
337
- ) -> None:
338
- ft.append((style, left + border.HORIZONTAL))
339
-
340
- for col_width in col_widths:
341
- ft.append((style, border.HORIZONTAL * col_width))
342
- ft.append((style, border.HORIZONTAL + split + border.HORIZONTAL))
343
-
344
- ft.pop()
345
- ft.append((style, border.HORIZONTAL + right + '\n'))
346
-
347
- # Draw top border
348
- _draw_add_border(
349
- border.TOP_LEFT,
350
- border.TOP_SPLIT,
351
- border.TOP_RIGHT,
352
- )
353
-
354
- # Draw each row
355
- for i, renders_row in enumerate(cell_renders):
356
- for row_lines in itertools.zip_longest(*map(ptk.split_lines, renders_row)):
357
- # Draw each line in each row
358
- ft.append((style, border.VERTICAL + ' '))
359
-
360
- for j, line in enumerate(row_lines):
361
- if line is None:
362
- line = [('', ' ' * col_widths[j])]
363
- ft += line
364
- ft.append((style, ' ' + border.VERTICAL + ' '))
365
-
366
- ft.pop()
367
- ft.append((style, ' ' + border.VERTICAL + '\n'))
368
-
369
- # Draw border between rows
370
- if i < len(cell_renders) - 1:
371
- _draw_add_border(
372
- border.LEFT_SPLIT,
373
- border.CROSS,
374
- border.RIGHT_SPLIT,
375
- )
376
-
377
- # Draw bottom border
378
- _draw_add_border(
379
- border.BOTTOM_LEFT,
380
- border.BOTTOM_SPLIT,
381
- border.BOTTOM_RIGHT,
382
- )
383
-
384
- ft.append(('', '\n'))
385
- return ft
386
-
387
- def __pt_formatted_text__(self) -> ptk.StyleAndTextTuples:
388
- """Formatted text magic method."""
389
-
390
- return self.formatted_text
@@ -1,42 +0,0 @@
1
- import typing as ta
2
- import warnings
3
-
4
- from omlish import lang
5
-
6
-
7
- if ta.TYPE_CHECKING:
8
- from markdown_it import MarkdownIt
9
-
10
-
11
- ##
12
-
13
-
14
- @lang.cached_function(lock=True)
15
- def markdown_parser() -> ta.Optional['MarkdownIt']:
16
- try:
17
- from markdown_it import MarkdownIt
18
- except ModuleNotFoundError:
19
- warnings.warn('The markdown parser requires `markdown-it-py` to be installed')
20
- return None
21
-
22
- parser = (
23
- MarkdownIt()
24
- .enable('linkify')
25
- .enable('table')
26
- .enable('strikethrough')
27
- )
28
-
29
- try:
30
- import mdit_py_plugins # noqa F401
31
- except ModuleNotFoundError:
32
- pass
33
- else:
34
- from mdit_py_plugins.amsmath import amsmath_plugin # noqa
35
- from mdit_py_plugins.dollarmath.index import dollarmath_plugin # noqa
36
- from mdit_py_plugins.texmath.index import texmath_plugin # noqa
37
-
38
- parser.use(texmath_plugin)
39
- parser.use(dollarmath_plugin)
40
- parser.use(amsmath_plugin)
41
-
42
- return parser
@@ -1,29 +0,0 @@
1
- import typing as ta
2
-
3
-
4
- ##
5
-
6
-
7
- MARKDOWN_STYLE: ta.Sequence[tuple[str, str]] = [
8
- ('md.h1', 'bold underline'),
9
- ('md.h1.border', 'fg:ansiyellow nounderline'),
10
- ('md.h2', 'bold'),
11
- ('md.h2.border', 'fg:grey nobold'),
12
- ('md.h3', 'bold'),
13
- ('md.h4', 'bold italic'),
14
- ('md.h5', 'underline'),
15
- ('md.h6', 'italic'),
16
- ('md.code.inline', 'bg:#333'),
17
- ('md.strong', 'bold'),
18
- ('md.em', 'italic'),
19
- ('md.hr', 'fg:ansired'),
20
- ('md.ul.margin', 'fg:ansiyellow'),
21
- ('md.ol.margin', 'fg:ansicyan'),
22
- ('md.blockquote', 'fg:ansipurple'),
23
- ('md.blockquote.margin', 'fg:grey'),
24
- ('md.th', 'bold'),
25
- ('md.a', 'underline fg:ansibrightblue'),
26
- ('md.s', 'strike'),
27
- ('md.img', 'bg:cyan fg:black'),
28
- ('md.img.border', 'fg:cyan bg:default'),
29
- ]