omdev 0.0.0.dev456__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.
- omdev/.omlish-manifests.json +15 -27
- omdev/__about__.py +5 -4
- omdev/tui/apps/__init__.py +0 -0
- omdev/tui/apps/markdown/__init__.py +0 -0
- omdev/tui/apps/markdown/__main__.py +11 -0
- omdev/{ptk → tui/apps}/markdown/cli.py +6 -7
- {omdev-0.0.0.dev456.dist-info → omdev-0.0.0.dev458.dist-info}/METADATA +7 -5
- {omdev-0.0.0.dev456.dist-info → omdev-0.0.0.dev458.dist-info}/RECORD +13 -22
- omdev/ptk/__init__.py +0 -104
- omdev/ptk/apps/ncdu.py +0 -167
- omdev/ptk/markdown/LICENSE +0 -22
- omdev/ptk/markdown/__init__.py +0 -10
- omdev/ptk/markdown/__main__.py +0 -11
- omdev/ptk/markdown/border.py +0 -94
- omdev/ptk/markdown/markdown.py +0 -390
- omdev/ptk/markdown/parser.py +0 -42
- omdev/ptk/markdown/styles.py +0 -29
- omdev/ptk/markdown/tags.py +0 -299
- omdev/ptk/markdown/utils.py +0 -366
- omdev/ptk/prompts.py +0 -68
- /omdev/{ptk/apps → tui}/__init__.py +0 -0
- {omdev-0.0.0.dev456.dist-info → omdev-0.0.0.dev458.dist-info}/WHEEL +0 -0
- {omdev-0.0.0.dev456.dist-info → omdev-0.0.0.dev458.dist-info}/entry_points.txt +0 -0
- {omdev-0.0.0.dev456.dist-info → omdev-0.0.0.dev458.dist-info}/licenses/LICENSE +0 -0
- {omdev-0.0.0.dev456.dist-info → omdev-0.0.0.dev458.dist-info}/top_level.txt +0 -0
omdev/ptk/markdown/markdown.py
DELETED
@@ -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
|
omdev/ptk/markdown/parser.py
DELETED
@@ -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
|
omdev/ptk/markdown/styles.py
DELETED
@@ -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
|
-
]
|