omdev 0.0.0.dev294__py3-none-any.whl → 0.0.0.dev295__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/.manifests.json +1 -1
- omdev/__about__.py +1 -1
- omdev/ptk/markdown/LICENSE +22 -0
- omdev/ptk/markdown/__init__.py +2 -0
- omdev/ptk/markdown/__main__.py +4 -0
- omdev/ptk/markdown/border.py +94 -0
- omdev/ptk/markdown/cli.py +30 -0
- omdev/ptk/markdown/markdown.py +384 -0
- omdev/ptk/markdown/parser.py +42 -0
- omdev/ptk/markdown/styles.py +29 -0
- omdev/ptk/markdown/tags.py +298 -0
- omdev/ptk/markdown/utils.py +365 -0
- omdev/tools/doc.py +3 -0
- {omdev-0.0.0.dev294.dist-info → omdev-0.0.0.dev295.dist-info}/METADATA +4 -2
- {omdev-0.0.0.dev294.dist-info → omdev-0.0.0.dev295.dist-info}/RECORD +19 -9
- {omdev-0.0.0.dev294.dist-info → omdev-0.0.0.dev295.dist-info}/WHEEL +0 -0
- {omdev-0.0.0.dev294.dist-info → omdev-0.0.0.dev295.dist-info}/entry_points.txt +0 -0
- {omdev-0.0.0.dev294.dist-info → omdev-0.0.0.dev295.dist-info}/licenses/LICENSE +0 -0
- {omdev-0.0.0.dev294.dist-info → omdev-0.0.0.dev295.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,298 @@
|
|
1
|
+
import typing as ta
|
2
|
+
|
3
|
+
from ... import ptk
|
4
|
+
from .border import DoubleBorder
|
5
|
+
from .border import SquareBorder
|
6
|
+
from .utils import FormattedTextAlign
|
7
|
+
from .utils import add_border
|
8
|
+
from .utils import align
|
9
|
+
from .utils import apply_style
|
10
|
+
from .utils import indent
|
11
|
+
from .utils import lex
|
12
|
+
from .utils import strip
|
13
|
+
from .utils import wrap
|
14
|
+
|
15
|
+
|
16
|
+
if ta.TYPE_CHECKING:
|
17
|
+
from markdown_it.token import Token
|
18
|
+
|
19
|
+
|
20
|
+
TagRule: ta.TypeAlias = ta.Callable[
|
21
|
+
[
|
22
|
+
ptk.StyleAndTextTuples,
|
23
|
+
int,
|
24
|
+
int,
|
25
|
+
'Token',
|
26
|
+
],
|
27
|
+
ptk.StyleAndTextTuples,
|
28
|
+
]
|
29
|
+
|
30
|
+
|
31
|
+
##
|
32
|
+
|
33
|
+
|
34
|
+
def h1(
|
35
|
+
ft: ptk.StyleAndTextTuples,
|
36
|
+
width: int,
|
37
|
+
left: int,
|
38
|
+
token: 'Token',
|
39
|
+
) -> ptk.StyleAndTextTuples:
|
40
|
+
"""Format a top-level heading wrapped and centered with a full width double border."""
|
41
|
+
|
42
|
+
ft = wrap(ft, width - 4)
|
43
|
+
ft = align(FormattedTextAlign.CENTER, ft, width=width - 4)
|
44
|
+
ft = add_border(ft, width, style='class:md.h1.border', border=DoubleBorder)
|
45
|
+
ft.append(('', '\n\n'))
|
46
|
+
return ft
|
47
|
+
|
48
|
+
|
49
|
+
def h2(
|
50
|
+
ft: ptk.StyleAndTextTuples,
|
51
|
+
width: int,
|
52
|
+
left: int,
|
53
|
+
token: 'Token',
|
54
|
+
) -> ptk.StyleAndTextTuples:
|
55
|
+
"""Format a 2nd-level headding wrapped and centered with a double border."""
|
56
|
+
|
57
|
+
ft = wrap(ft, width=width - 4)
|
58
|
+
ft = align(FormattedTextAlign.CENTER, ft)
|
59
|
+
ft = add_border(ft, style='class:md.h2.border', border=SquareBorder)
|
60
|
+
ft = align(FormattedTextAlign.CENTER, ft, width=width)
|
61
|
+
ft.append(('', '\n\n'))
|
62
|
+
return ft
|
63
|
+
|
64
|
+
|
65
|
+
def h(
|
66
|
+
ft: ptk.StyleAndTextTuples,
|
67
|
+
width: int,
|
68
|
+
left: int,
|
69
|
+
token: 'Token',
|
70
|
+
) -> ptk.StyleAndTextTuples:
|
71
|
+
"""Format headings wrapped and centeredr."""
|
72
|
+
|
73
|
+
ft = wrap(ft, width)
|
74
|
+
ft = align(FormattedTextAlign.CENTER, ft, width=width)
|
75
|
+
ft.append(('', '\n\n'))
|
76
|
+
return ft
|
77
|
+
|
78
|
+
|
79
|
+
def p(
|
80
|
+
ft: ptk.StyleAndTextTuples,
|
81
|
+
width: int,
|
82
|
+
left: int,
|
83
|
+
token: 'Token',
|
84
|
+
) -> ptk.StyleAndTextTuples:
|
85
|
+
"""Format paragraphs wrapped."""
|
86
|
+
|
87
|
+
ft = wrap(ft, width)
|
88
|
+
ft.append(('', '\n' if token.hidden else '\n\n'))
|
89
|
+
return ft
|
90
|
+
|
91
|
+
|
92
|
+
def ul(
|
93
|
+
ft: ptk.StyleAndTextTuples,
|
94
|
+
width: int,
|
95
|
+
left: int,
|
96
|
+
token: 'Token',
|
97
|
+
) -> ptk.StyleAndTextTuples:
|
98
|
+
"""Format unordered lists."""
|
99
|
+
|
100
|
+
ft.append(('', '\n'))
|
101
|
+
return ft
|
102
|
+
|
103
|
+
|
104
|
+
def ol(
|
105
|
+
ft: ptk.StyleAndTextTuples,
|
106
|
+
width: int,
|
107
|
+
left: int,
|
108
|
+
token: 'Token',
|
109
|
+
) -> ptk.StyleAndTextTuples:
|
110
|
+
"""Formats ordered lists."""
|
111
|
+
|
112
|
+
ft.append(('', '\n'))
|
113
|
+
return ft
|
114
|
+
|
115
|
+
|
116
|
+
def li(
|
117
|
+
ft: ptk.StyleAndTextTuples,
|
118
|
+
width: int,
|
119
|
+
left: int,
|
120
|
+
token: 'Token',
|
121
|
+
) -> ptk.StyleAndTextTuples:
|
122
|
+
"""Formats list items."""
|
123
|
+
|
124
|
+
ft = strip(ft)
|
125
|
+
|
126
|
+
# Determine if this is an ordered or unordered list
|
127
|
+
if token.attrs.get('data-list-type') == 'ol':
|
128
|
+
margin_style = 'class:md.ol.margin'
|
129
|
+
else:
|
130
|
+
margin_style = 'class:md.ul.margin'
|
131
|
+
|
132
|
+
# Get the margin (potentially contains aligned item numbers)
|
133
|
+
margin = str(token.attrs.get('data-margin', '•'))
|
134
|
+
|
135
|
+
# We put a speace each side of the margin
|
136
|
+
ft = indent(ft, margin=' ' * (len(margin) + 2), style=margin_style)
|
137
|
+
ft[0] = (ft[0][0], f' {margin} ')
|
138
|
+
|
139
|
+
ft.append(('', '\n'))
|
140
|
+
return ft
|
141
|
+
|
142
|
+
|
143
|
+
def hr(
|
144
|
+
ft: ptk.StyleAndTextTuples,
|
145
|
+
width: int,
|
146
|
+
left: int,
|
147
|
+
token: 'Token',
|
148
|
+
) -> ptk.StyleAndTextTuples:
|
149
|
+
"""Format horizontal rules."""
|
150
|
+
|
151
|
+
ft = [
|
152
|
+
('class:md.hr', '─' * width),
|
153
|
+
('', '\n\n'),
|
154
|
+
]
|
155
|
+
return ft
|
156
|
+
|
157
|
+
|
158
|
+
def br(
|
159
|
+
ft: ptk.StyleAndTextTuples,
|
160
|
+
width: int,
|
161
|
+
left: int,
|
162
|
+
token: 'Token',
|
163
|
+
) -> ptk.StyleAndTextTuples:
|
164
|
+
"""Format line breaks."""
|
165
|
+
|
166
|
+
return [('', '\n')]
|
167
|
+
|
168
|
+
|
169
|
+
def blockquote(
|
170
|
+
ft: ptk.StyleAndTextTuples,
|
171
|
+
width: int,
|
172
|
+
left: int,
|
173
|
+
token: 'Token',
|
174
|
+
) -> ptk.StyleAndTextTuples:
|
175
|
+
"""Format blockquotes with a solid left margin."""
|
176
|
+
|
177
|
+
ft = strip(ft)
|
178
|
+
ft = indent(ft, margin='▌ ', style='class:md.blockquote.margin')
|
179
|
+
ft.append(('', '\n\n'))
|
180
|
+
return ft
|
181
|
+
|
182
|
+
|
183
|
+
def code(
|
184
|
+
ft: ptk.StyleAndTextTuples,
|
185
|
+
width: int,
|
186
|
+
left: int,
|
187
|
+
token: 'Token',
|
188
|
+
) -> ptk.StyleAndTextTuples:
|
189
|
+
"""Format inline code, and lexes and formats code blocks with a border."""
|
190
|
+
|
191
|
+
if token.block:
|
192
|
+
ft = strip(ft, left=False, right=True, char='\n')
|
193
|
+
ft = lex(ft, lexer_name=token.info)
|
194
|
+
ft = align(FormattedTextAlign.LEFT, ft, width - 4)
|
195
|
+
ft = add_border(ft, width, style='class:md.code.border', border=SquareBorder)
|
196
|
+
ft.append(('', '\n\n'))
|
197
|
+
else:
|
198
|
+
ft = apply_style(ft, style='class:md.code.inline')
|
199
|
+
|
200
|
+
return ft
|
201
|
+
|
202
|
+
|
203
|
+
def math(
|
204
|
+
ft: ptk.StyleAndTextTuples,
|
205
|
+
width: int,
|
206
|
+
left: int,
|
207
|
+
token: 'Token',
|
208
|
+
) -> ptk.StyleAndTextTuples:
|
209
|
+
"""Format inline maths, and quotes math blocks."""
|
210
|
+
|
211
|
+
if token.block:
|
212
|
+
return blockquote(ft, width - 2, left, token)
|
213
|
+
else:
|
214
|
+
return ft
|
215
|
+
|
216
|
+
|
217
|
+
def a(
|
218
|
+
ft: ptk.StyleAndTextTuples,
|
219
|
+
width: int,
|
220
|
+
left: int,
|
221
|
+
token: 'Token',
|
222
|
+
) -> ptk.StyleAndTextTuples:
|
223
|
+
"""Format hyperlinks and adds link escape sequences."""
|
224
|
+
|
225
|
+
result: ptk.StyleAndTextTuples = []
|
226
|
+
href = token.attrs.get('href')
|
227
|
+
if href:
|
228
|
+
result.append(('[ZeroWidthEscape]', f'\x1b]8;;{href}\x1b\\'))
|
229
|
+
result += ft
|
230
|
+
if href:
|
231
|
+
result.append(('[ZeroWidthEscape]', '\x1b]8;;\x1b\\'))
|
232
|
+
return result
|
233
|
+
|
234
|
+
|
235
|
+
def img(
|
236
|
+
ft: ptk.StyleAndTextTuples,
|
237
|
+
width: int,
|
238
|
+
left: int,
|
239
|
+
token: 'Token',
|
240
|
+
) -> ptk.StyleAndTextTuples:
|
241
|
+
"""Format image titles."""
|
242
|
+
|
243
|
+
bounds = ('', '')
|
244
|
+
if not ptk.to_plain_text(ft):
|
245
|
+
# Add fallback text if there is no image title
|
246
|
+
title = str(token.attrs.get('alt'))
|
247
|
+
|
248
|
+
# Try getting the filename
|
249
|
+
src = str(token.attrs.get('src', ''))
|
250
|
+
if not title and not src.startswith('data:'):
|
251
|
+
title = src.rsplit('/', 1)[-1]
|
252
|
+
if not title:
|
253
|
+
title = 'Image'
|
254
|
+
ft = [('class:md.img', title)]
|
255
|
+
|
256
|
+
# Add the sunrise emoji to represent an image. I would use :framed_picture:, but it requires multiple code-points
|
257
|
+
# and causes breakage in many terminals
|
258
|
+
result = [('class:md.img', '🌄 '), *ft]
|
259
|
+
result = apply_style(result, style='class:md.img')
|
260
|
+
result = [
|
261
|
+
('class:md.img.border', f'{bounds[0]}'),
|
262
|
+
*result,
|
263
|
+
('class:md.img.border', f'{bounds[1]}'),
|
264
|
+
]
|
265
|
+
return result
|
266
|
+
|
267
|
+
|
268
|
+
##
|
269
|
+
|
270
|
+
|
271
|
+
# Maps HTML tag names to formatting functions. Functionality can be extended by modifying this dictionary
|
272
|
+
TAG_RULES: ta.Mapping[str, TagRule] = {
|
273
|
+
'h1': h1,
|
274
|
+
'h2': h2,
|
275
|
+
'h3': h,
|
276
|
+
'h4': h,
|
277
|
+
'h5': h,
|
278
|
+
'h6': h,
|
279
|
+
'p': p,
|
280
|
+
'ul': ul,
|
281
|
+
'ol': ol,
|
282
|
+
'li': li,
|
283
|
+
'hr': hr,
|
284
|
+
'br': br,
|
285
|
+
'blockquote': blockquote,
|
286
|
+
'code': code,
|
287
|
+
'math': math,
|
288
|
+
'a': a,
|
289
|
+
'img': img,
|
290
|
+
}
|
291
|
+
|
292
|
+
|
293
|
+
# Mapping showing how much width the formatting of block elements used. This is used to reduce the available width when
|
294
|
+
# rendering child elements
|
295
|
+
TAG_INSETS = {
|
296
|
+
'li': 3,
|
297
|
+
'blockquote': 2,
|
298
|
+
}
|
@@ -0,0 +1,365 @@
|
|
1
|
+
import enum
|
2
|
+
import typing as ta
|
3
|
+
|
4
|
+
from pygments.lexers import get_lexer_by_name
|
5
|
+
from pygments.util import ClassNotFound
|
6
|
+
|
7
|
+
from ... import ptk
|
8
|
+
from .border import Border
|
9
|
+
from .border import SquareBorder
|
10
|
+
|
11
|
+
|
12
|
+
##
|
13
|
+
|
14
|
+
|
15
|
+
class FormattedTextAlign(enum.Enum):
|
16
|
+
"""Alignment of formatted text."""
|
17
|
+
|
18
|
+
LEFT = 'LEFT'
|
19
|
+
RIGHT = 'RIGHT'
|
20
|
+
CENTER = 'CENTER'
|
21
|
+
|
22
|
+
|
23
|
+
def last_line_length(ft: ptk.StyleAndTextTuples) -> int:
|
24
|
+
"""Calculate the length of the last line in formatted text."""
|
25
|
+
|
26
|
+
line: ptk.StyleAndTextTuples = []
|
27
|
+
for style, text, *_ in ft[::-1]:
|
28
|
+
index = text.find('\n')
|
29
|
+
line.append((style, text[index + 1:]))
|
30
|
+
if index > -1:
|
31
|
+
break
|
32
|
+
|
33
|
+
return ptk.fragment_list_width(line)
|
34
|
+
|
35
|
+
|
36
|
+
def max_line_width(ft: ptk.StyleAndTextTuples) -> int:
|
37
|
+
"""Calculate the length of the longest line in formatted text."""
|
38
|
+
|
39
|
+
return max(ptk.fragment_list_width(line) for line in ptk.split_lines(ft))
|
40
|
+
|
41
|
+
|
42
|
+
def fragment_list_to_words(fragments: ptk.StyleAndTextTuples) -> ta.Iterable[ptk.OneStyleAndTextTuple]:
|
43
|
+
"""Split formatted text into word fragments."""
|
44
|
+
|
45
|
+
for style, string, *mouse_handler in fragments:
|
46
|
+
parts = string.split(' ')
|
47
|
+
for part in parts[:-1]:
|
48
|
+
yield ta.cast('ptk.OneStyleAndTextTuple', (style, part, *mouse_handler))
|
49
|
+
yield ta.cast('ptk.OneStyleAndTextTuple', (style, ' ', *mouse_handler))
|
50
|
+
|
51
|
+
yield ta.cast('ptk.OneStyleAndTextTuple', (style, parts[-1], *mouse_handler))
|
52
|
+
|
53
|
+
|
54
|
+
def apply_style(ft: ptk.StyleAndTextTuples, style: str) -> ptk.StyleAndTextTuples:
|
55
|
+
"""Apply a style to formatted text."""
|
56
|
+
|
57
|
+
return [
|
58
|
+
(
|
59
|
+
f'{fragment_style} {style}'
|
60
|
+
if '[ZeroWidthEscape]' not in fragment_style else fragment_style,
|
61
|
+
text,
|
62
|
+
)
|
63
|
+
for (fragment_style, text, *_) in ft
|
64
|
+
]
|
65
|
+
|
66
|
+
|
67
|
+
def strip(
|
68
|
+
ft: ptk.StyleAndTextTuples,
|
69
|
+
left: bool = True,
|
70
|
+
right: bool = True,
|
71
|
+
char: str | None = None,
|
72
|
+
) -> ptk.StyleAndTextTuples:
|
73
|
+
"""
|
74
|
+
Strip whitespace (or a given character) from the ends of formatted text.
|
75
|
+
|
76
|
+
Args:
|
77
|
+
ft: The formatted text to strip
|
78
|
+
left: If :py:const:`True`, strip from the left side of the input
|
79
|
+
right: If :py:const:`True`, strip from the right side of the input
|
80
|
+
char: The character to strip. If :py:const:`None`, strips whitespace
|
81
|
+
Returns:
|
82
|
+
The stripped formatted text
|
83
|
+
"""
|
84
|
+
|
85
|
+
result = ft[:]
|
86
|
+
for toggle, index, strip_func in [
|
87
|
+
(left, 0, str.lstrip),
|
88
|
+
(right, -1, str.rstrip),
|
89
|
+
]:
|
90
|
+
if result and toggle:
|
91
|
+
text = strip_func(result[index][1], char)
|
92
|
+
|
93
|
+
while result and not text:
|
94
|
+
del result[index]
|
95
|
+
if not result:
|
96
|
+
break
|
97
|
+
|
98
|
+
text = strip_func(result[index][1], char)
|
99
|
+
|
100
|
+
if result and '[ZeroWidthEscape]' not in result[index][0]:
|
101
|
+
result[index] = (result[index][0], text)
|
102
|
+
|
103
|
+
return result
|
104
|
+
|
105
|
+
|
106
|
+
def truncate(
|
107
|
+
ft: ptk.StyleAndTextTuples,
|
108
|
+
width: int,
|
109
|
+
style: str = '',
|
110
|
+
placeholder: str = '…',
|
111
|
+
) -> ptk.StyleAndTextTuples:
|
112
|
+
"""
|
113
|
+
Truncates all lines at a given length.
|
114
|
+
|
115
|
+
Args:
|
116
|
+
ft: The formatted text to truncate
|
117
|
+
width: The width at which to truncate the text
|
118
|
+
style: The style to apply to the truncation placeholder. The style of the truncated text will be used if not
|
119
|
+
provided
|
120
|
+
placeholder: The string that will appear at the end of a truncated line
|
121
|
+
Returns:
|
122
|
+
The truncated formatted text
|
123
|
+
"""
|
124
|
+
|
125
|
+
result: ptk.StyleAndTextTuples = []
|
126
|
+
phw = sum(ptk.get_cwidth(c) for c in placeholder)
|
127
|
+
for line in ptk.split_lines(ft):
|
128
|
+
used_width = 0
|
129
|
+
for item in line:
|
130
|
+
fragment_width = sum(
|
131
|
+
ptk.get_cwidth(c)
|
132
|
+
for c in item[1]
|
133
|
+
if '[ZeroWidthEscape]' not in item[0]
|
134
|
+
)
|
135
|
+
|
136
|
+
if used_width + fragment_width > width - phw:
|
137
|
+
remaining_width = width - used_width - fragment_width - phw
|
138
|
+
result.append((item[0], item[1][:remaining_width]))
|
139
|
+
result.append((style or item[0], placeholder))
|
140
|
+
break
|
141
|
+
|
142
|
+
result.append(item)
|
143
|
+
used_width += fragment_width
|
144
|
+
|
145
|
+
result.append(('', '\n'))
|
146
|
+
|
147
|
+
result.pop()
|
148
|
+
return result
|
149
|
+
|
150
|
+
|
151
|
+
def wrap(
|
152
|
+
ft: ptk.StyleAndTextTuples,
|
153
|
+
width: int,
|
154
|
+
style: str = '',
|
155
|
+
placeholder: str = '…',
|
156
|
+
) -> ptk.StyleAndTextTuples:
|
157
|
+
"""
|
158
|
+
Wraps formatted text at a given width. If words are longer than the given line they will be truncated
|
159
|
+
|
160
|
+
Args:
|
161
|
+
ft: The formatted text to wrap
|
162
|
+
width: The width at which to wrap the text
|
163
|
+
style: The style to apply to the truncation placeholder
|
164
|
+
placeholder: The string that will appear at the end of a truncated line
|
165
|
+
Returns:
|
166
|
+
The wrapped formatted text
|
167
|
+
"""
|
168
|
+
|
169
|
+
result: ptk.StyleAndTextTuples = []
|
170
|
+
lines = list(ptk.split_lines(ft))
|
171
|
+
for i, line in enumerate(lines):
|
172
|
+
if ptk.fragment_list_width(line) <= width:
|
173
|
+
result += line
|
174
|
+
if i < len(lines) - 1:
|
175
|
+
result.append(('', '\n'))
|
176
|
+
continue
|
177
|
+
|
178
|
+
used_width = 0
|
179
|
+
for item in fragment_list_to_words(line):
|
180
|
+
fragment_width = sum(
|
181
|
+
ptk.get_cwidth(c)
|
182
|
+
for c in item[1]
|
183
|
+
if '[ZeroWidthEscape]' not in item[0]
|
184
|
+
)
|
185
|
+
|
186
|
+
# Start a new line we are at the end
|
187
|
+
if used_width + fragment_width > width and used_width > 0:
|
188
|
+
# Remove trailing whitespace
|
189
|
+
result = strip(result, left=False)
|
190
|
+
result.append(('', '\n'))
|
191
|
+
used_width = 0
|
192
|
+
|
193
|
+
# Truncate words longer than a line
|
194
|
+
if fragment_width > width and used_width == 0:
|
195
|
+
result += truncate([item], width, style, placeholder)
|
196
|
+
used_width += fragment_width
|
197
|
+
|
198
|
+
# Left-strip words at the start of a line
|
199
|
+
elif used_width == 0:
|
200
|
+
result += strip([item], right=False)
|
201
|
+
used_width += fragment_width
|
202
|
+
|
203
|
+
# Otherwise just add the word to the line
|
204
|
+
else:
|
205
|
+
result.append(item)
|
206
|
+
used_width += fragment_width
|
207
|
+
|
208
|
+
return result
|
209
|
+
|
210
|
+
|
211
|
+
def align(
|
212
|
+
how: FormattedTextAlign,
|
213
|
+
ft: ptk.StyleAndTextTuples,
|
214
|
+
width: int | None = None,
|
215
|
+
style: str = '',
|
216
|
+
placeholder: str = '…',
|
217
|
+
) -> ptk.StyleAndTextTuples:
|
218
|
+
"""
|
219
|
+
Align formatted text at a given width.
|
220
|
+
|
221
|
+
Args:
|
222
|
+
how: The alignment direction
|
223
|
+
ft: The formatted text to strip
|
224
|
+
width: The width to which the output should be padded. If :py:const:`None`, the length of the longest line is
|
225
|
+
used
|
226
|
+
style: The style to apply to the padding
|
227
|
+
placeholder: The string that will appear at the end of a truncated line
|
228
|
+
Returns:
|
229
|
+
The aligned formatted text
|
230
|
+
"""
|
231
|
+
|
232
|
+
lines = ptk.split_lines(ft)
|
233
|
+
if width is None:
|
234
|
+
lines = [strip(line) for line in ptk.split_lines(ft)]
|
235
|
+
width = max(ptk.fragment_list_width(line) for line in lines)
|
236
|
+
|
237
|
+
result: ptk.StyleAndTextTuples = []
|
238
|
+
for line in lines:
|
239
|
+
line_width = ptk.fragment_list_width(line)
|
240
|
+
|
241
|
+
# Truncate the line if it is too long
|
242
|
+
if line_width > width:
|
243
|
+
result += truncate(line, width, style, placeholder)
|
244
|
+
|
245
|
+
else:
|
246
|
+
pad_left = pad_right = 0
|
247
|
+
|
248
|
+
if how == FormattedTextAlign.CENTER:
|
249
|
+
pad_left = (width - line_width) // 2
|
250
|
+
pad_right = width - line_width - pad_left
|
251
|
+
|
252
|
+
elif how == FormattedTextAlign.LEFT:
|
253
|
+
pad_right = width - line_width
|
254
|
+
|
255
|
+
elif how == FormattedTextAlign.RIGHT:
|
256
|
+
pad_left = width - line_width
|
257
|
+
|
258
|
+
if pad_left:
|
259
|
+
result.append((style, ' ' * pad_left))
|
260
|
+
|
261
|
+
result += line
|
262
|
+
|
263
|
+
if pad_right:
|
264
|
+
result.append((style, ' ' * pad_right))
|
265
|
+
|
266
|
+
result.append((style, '\n'))
|
267
|
+
|
268
|
+
result.pop()
|
269
|
+
return result
|
270
|
+
|
271
|
+
|
272
|
+
def indent(
|
273
|
+
ft: ptk.StyleAndTextTuples,
|
274
|
+
margin: str = ' ',
|
275
|
+
style: str = '',
|
276
|
+
skip_first: bool = False,
|
277
|
+
) -> ptk.StyleAndTextTuples:
|
278
|
+
"""
|
279
|
+
Indents formatted text with a given margin.
|
280
|
+
|
281
|
+
Args:
|
282
|
+
ft: The formatted text to strip
|
283
|
+
margin: The margin string to add
|
284
|
+
style: The style to apply to the margin
|
285
|
+
skip_first: If :py:const:`True`, the first line is skipped
|
286
|
+
Returns:
|
287
|
+
The indented formatted text
|
288
|
+
"""
|
289
|
+
|
290
|
+
result: ptk.StyleAndTextTuples = []
|
291
|
+
for i, line in enumerate(ptk.split_lines(ft)):
|
292
|
+
if not (i == 0 and skip_first):
|
293
|
+
result.append((style, margin))
|
294
|
+
result += line
|
295
|
+
result.append(('', '\n'))
|
296
|
+
|
297
|
+
result.pop()
|
298
|
+
return result
|
299
|
+
|
300
|
+
|
301
|
+
def add_border(
|
302
|
+
ft: ptk.StyleAndTextTuples,
|
303
|
+
width: int | None = None,
|
304
|
+
style: str = '',
|
305
|
+
border: type[Border] = SquareBorder,
|
306
|
+
) -> ptk.StyleAndTextTuples:
|
307
|
+
"""
|
308
|
+
Adds a border around formatted text.
|
309
|
+
|
310
|
+
Args:
|
311
|
+
ft: The formatted text to enclose with a border
|
312
|
+
width: The target width of the output including the border
|
313
|
+
style: The style to apply to the border
|
314
|
+
border: The border to apply
|
315
|
+
Returns:
|
316
|
+
The indented formatted text
|
317
|
+
"""
|
318
|
+
|
319
|
+
# if border is None:
|
320
|
+
# See mypy issue #4236
|
321
|
+
# border = cast("Type[Border]", Border)
|
322
|
+
if width is None:
|
323
|
+
width = max_line_width(ft) + 4
|
324
|
+
|
325
|
+
# ft = align(FormattedTextAlign.LEFT, ft, width - 4)
|
326
|
+
result: ptk.StyleAndTextTuples = []
|
327
|
+
|
328
|
+
result.append((
|
329
|
+
style,
|
330
|
+
border.TOP_LEFT + border.HORIZONTAL * (width - 2) + border.TOP_RIGHT + '\n',
|
331
|
+
))
|
332
|
+
|
333
|
+
for line in ptk.split_lines(ft):
|
334
|
+
result += [
|
335
|
+
(style, border.VERTICAL),
|
336
|
+
('', ' '),
|
337
|
+
*line,
|
338
|
+
('', ' '),
|
339
|
+
(style, border.VERTICAL + '\n'),
|
340
|
+
]
|
341
|
+
|
342
|
+
result.append((
|
343
|
+
style,
|
344
|
+
border.BOTTOM_LEFT + border.HORIZONTAL * (width - 2) + border.BOTTOM_RIGHT,
|
345
|
+
))
|
346
|
+
|
347
|
+
return result
|
348
|
+
|
349
|
+
|
350
|
+
def lex(ft: ptk.StyleAndTextTuples, lexer_name: str) -> ptk.StyleAndTextTuples:
|
351
|
+
"""Format formatted text using a named :py:mod:`pygments` lexer."""
|
352
|
+
|
353
|
+
from prompt_toolkit.lexers.pygments import _token_cache # noqa
|
354
|
+
|
355
|
+
text = ptk.fragment_list_to_text(ft)
|
356
|
+
|
357
|
+
try:
|
358
|
+
lexer = get_lexer_by_name(lexer_name)
|
359
|
+
except ClassNotFound:
|
360
|
+
return ft
|
361
|
+
else:
|
362
|
+
return [
|
363
|
+
(_token_cache[t], v)
|
364
|
+
for _, t, v in lexer.get_tokens_unprocessed(text)
|
365
|
+
]
|
omdev/tools/doc.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: omdev
|
3
|
-
Version: 0.0.0.
|
3
|
+
Version: 0.0.0.dev295
|
4
4
|
Summary: omdev
|
5
5
|
Author: wrmsr
|
6
6
|
License: BSD-3-Clause
|
@@ -12,13 +12,14 @@ Classifier: Operating System :: OS Independent
|
|
12
12
|
Classifier: Operating System :: POSIX
|
13
13
|
Requires-Python: >=3.12
|
14
14
|
License-File: LICENSE
|
15
|
-
Requires-Dist: omlish==0.0.0.
|
15
|
+
Requires-Dist: omlish==0.0.0.dev295
|
16
16
|
Provides-Extra: all
|
17
17
|
Requires-Dist: black~=25.1; extra == "all"
|
18
18
|
Requires-Dist: pycparser~=2.22; extra == "all"
|
19
19
|
Requires-Dist: pcpp~=1.30; extra == "all"
|
20
20
|
Requires-Dist: docutils~=0.21; extra == "all"
|
21
21
|
Requires-Dist: markdown-it-py~=3.0; extra == "all"
|
22
|
+
Requires-Dist: mdit-py-plugins~=0.4; extra == "all"
|
22
23
|
Requires-Dist: mypy~=1.15; extra == "all"
|
23
24
|
Requires-Dist: gprof2dot~=2025.4; extra == "all"
|
24
25
|
Requires-Dist: prompt-toolkit~=3.0; extra == "all"
|
@@ -31,6 +32,7 @@ Requires-Dist: pcpp~=1.30; extra == "c"
|
|
31
32
|
Provides-Extra: doc
|
32
33
|
Requires-Dist: docutils~=0.21; extra == "doc"
|
33
34
|
Requires-Dist: markdown-it-py~=3.0; extra == "doc"
|
35
|
+
Requires-Dist: mdit-py-plugins~=0.4; extra == "doc"
|
34
36
|
Provides-Extra: mypy
|
35
37
|
Requires-Dist: mypy~=1.15; extra == "mypy"
|
36
38
|
Provides-Extra: prof
|