1
|
|
|
#!/usr/bin/env python |
2
|
|
|
|
3
|
|
|
""" |
4
|
|
|
Pandoc filter for adding glfm features to pandoc. |
5
|
|
|
""" |
6
|
|
|
|
7
|
|
|
from panflute import ( |
8
|
|
|
Div, |
9
|
|
|
Doc, |
10
|
|
|
Element, |
11
|
|
|
Para, |
12
|
|
|
Plain, |
13
|
|
|
Str, |
14
|
|
|
Strikeout, |
15
|
|
|
convert_text, |
16
|
|
|
run_filters, |
17
|
|
|
) |
18
|
|
|
|
19
|
|
|
|
20
|
|
|
# pylint: disable=inconsistent-return-statements,unused-argument |
21
|
|
|
def alert(elem: Element, doc: Doc) -> Element | None: |
22
|
|
|
""" |
23
|
|
|
Transform some blockquote elements to alerts. |
24
|
|
|
|
25
|
|
|
Arguments |
26
|
|
|
--------- |
27
|
|
|
elem |
28
|
|
|
The current element |
29
|
|
|
doc |
30
|
|
|
The pandoc document |
31
|
|
|
|
32
|
|
|
Returns |
33
|
|
|
------- |
34
|
|
|
Element | None |
35
|
|
|
The modified element or None |
36
|
|
|
""" |
37
|
|
|
if ( |
38
|
|
|
elem.tag == "BlockQuote" |
39
|
|
|
and elem.content |
40
|
|
|
and elem.content[0].tag == "Para" |
41
|
|
|
and elem.content[0].content |
42
|
|
|
and elem.content[0].content[0].tag == "Str" |
43
|
|
|
): |
44
|
|
|
|
45
|
|
|
def extract_first_lines( |
46
|
|
|
first: list[Element], |
47
|
|
|
rest: list[Element], |
48
|
|
|
) -> list[Element]: |
49
|
|
|
result = [] |
50
|
|
|
for item in first: |
51
|
|
|
if item.tag == "Str": |
52
|
|
|
result.append(item.text) |
53
|
|
|
elif item.tag == "SoftBreak": |
54
|
|
|
result.append("\n") |
55
|
|
|
elif item.tag == "Space": |
56
|
|
|
result.append(" ") |
57
|
|
|
else: |
58
|
|
|
result.append( |
59
|
|
|
convert_text( |
60
|
|
|
Plain(item), |
61
|
|
|
input_format="panflute", |
62
|
|
|
output_format="markdown", |
63
|
|
|
), |
64
|
|
|
) |
65
|
|
|
for item in rest: |
66
|
|
|
result.extend( |
67
|
|
|
[ |
68
|
|
|
"\n", |
69
|
|
|
convert_text( |
70
|
|
|
item, |
71
|
|
|
input_format="panflute", |
72
|
|
|
output_format="markdown", |
73
|
|
|
), |
74
|
|
|
] |
75
|
|
|
) |
76
|
|
|
|
77
|
|
|
return convert_text("".join(result)) |
78
|
|
|
|
79
|
|
|
text = elem.content[0].content[0].text.lower() |
80
|
|
|
if text in ("[!note]", "[!tip]", "[!important]", "[!caution]", "[!warning]"): |
81
|
|
|
# Case |
82
|
|
|
# |
83
|
|
|
# > [!tip] |
84
|
|
|
# |
85
|
|
|
# and |
86
|
|
|
# |
87
|
|
|
# > [!tip] |
88
|
|
|
# > |
89
|
|
|
# > Rest of text |
90
|
|
|
if len(elem.content[0].content) == 1: |
91
|
|
|
title = Div(Para(Str(text[2:-1].capitalize())), classes=["title"]) |
92
|
|
|
content = [*elem.content[1:]] |
93
|
|
|
# Case |
94
|
|
|
# |
95
|
|
|
# > [!tip] |
96
|
|
|
# > Rest of text |
97
|
|
|
elif elem.content[0].content[1].tag == "SoftBreak": |
98
|
|
|
title = Div(Para(Str(text[2:-1].capitalize())), classes=["title"]) |
99
|
|
|
content = extract_first_lines( |
100
|
|
|
elem.content[0].content[2:], |
101
|
|
|
elem.content[1:], |
102
|
|
|
) |
103
|
|
|
# Case |
104
|
|
|
# |
105
|
|
|
# > [!tip] title |
106
|
|
|
# > Rest of text |
107
|
|
|
# |
108
|
|
|
# and |
109
|
|
|
# |
110
|
|
|
# > [!tip] title |
111
|
|
|
# > |
112
|
|
|
# > Rest of text |
113
|
|
|
else: |
114
|
|
|
alternate = [] |
115
|
|
|
for index in range(2, len(elem.content[0].content)): |
116
|
|
|
if elem.content[0].content[index].tag == "SoftBreak": |
117
|
|
|
title = Div(Para(*alternate), classes=["title"]) |
118
|
|
|
content = extract_first_lines( |
119
|
|
|
elem.content[0].content[index:], |
120
|
|
|
elem.content[1:], |
121
|
|
|
) |
122
|
|
|
break |
123
|
|
|
alternate.append(elem.content[0].content[index]) |
124
|
|
|
else: |
125
|
|
|
title = Div(Para(*alternate), classes=["title"]) |
126
|
|
|
content = [*elem.content[1:]] |
127
|
|
|
|
128
|
|
|
return convert_text( |
129
|
|
|
convert_text( |
130
|
|
|
Div(title, *content, classes=[text[2:-1]]), |
131
|
|
|
input_format="panflute", |
132
|
|
|
output_format="markdown", |
133
|
|
|
) |
134
|
|
|
) |
135
|
|
|
return None |
136
|
|
|
|
137
|
|
|
|
138
|
|
|
def task(elem: Element, doc: Doc) -> None: |
139
|
|
|
""" |
140
|
|
|
Deal with glfm task lists. |
141
|
|
|
|
142
|
|
|
Arguments |
143
|
|
|
--------- |
144
|
|
|
elem |
145
|
|
|
The current element |
146
|
|
|
doc |
147
|
|
|
The pandoc document |
148
|
|
|
""" |
149
|
|
|
if elem.tag in ("BulletList", "OrderedList"): |
150
|
|
|
for item in elem.content: |
151
|
|
|
if ( |
152
|
|
|
item.content[0].tag in ("Plain", "Para") |
153
|
|
|
and item.content[0].content |
154
|
|
|
and item.content[0].content[0].tag == "Str" |
155
|
|
|
and item.content[0].content[0].text == "[~]" |
156
|
|
|
and len(item.content[0].content) >= 3 |
157
|
|
|
): |
158
|
|
|
item.content[0].content[0].text = "☐" |
159
|
|
|
item.content[0].content[2] = Strikeout( |
160
|
|
|
*remove_strikeout(item.content[0].content[2:]), |
161
|
|
|
) |
162
|
|
|
item.content[0].content[3:] = [] |
163
|
|
|
for block in item.content[1:]: |
164
|
|
|
if block.tag in ("Plain", "Para"): |
165
|
|
|
block.content[0] = Strikeout(*remove_strikeout(block.content)) |
166
|
|
|
block.content[1:] = [] |
167
|
|
|
|
168
|
|
|
|
169
|
|
|
def remove_strikeout(elems: list[Element]) -> list[Element]: |
170
|
|
|
""" |
171
|
|
|
Remove Strikeout from elements. |
172
|
|
|
|
173
|
|
|
Parameters |
174
|
|
|
---------- |
175
|
|
|
elems |
176
|
|
|
Elements from which Strikeout must be removed |
177
|
|
|
|
178
|
|
|
Returns |
179
|
|
|
------- |
180
|
|
|
list[Element] |
181
|
|
|
The elements without the Strikeout. |
182
|
|
|
""" |
183
|
|
|
result = [] |
184
|
|
|
for elem in elems: |
185
|
|
|
if elem.tag == "Strikeout": |
186
|
|
|
result.extend(elem.content) |
187
|
|
|
else: |
188
|
|
|
result.append(elem) |
189
|
|
|
return result |
190
|
|
|
|
191
|
|
|
|
192
|
|
|
def cell(elem: Element, doc: Doc) -> None: |
193
|
|
|
""" |
194
|
|
|
Transfom cell elements that contain <br />. |
195
|
|
|
|
196
|
|
|
Parameters |
197
|
|
|
---------- |
198
|
|
|
The current element |
199
|
|
|
doc |
200
|
|
|
The pandoc document |
201
|
|
|
|
202
|
|
|
""" |
203
|
|
|
if elem.tag == "TableCell": |
204
|
|
|
convert = False |
205
|
|
|
for index, item in enumerate(elem.content[0].content): |
206
|
|
|
if ( |
207
|
|
|
item.tag == "RawInline" |
208
|
|
|
and item.format == "html" |
209
|
|
|
and item.text in ("<br>", "<br/>", "<br />") |
210
|
|
|
): |
211
|
|
|
convert = True |
212
|
|
|
elem.content[0].content[index] = Str("\n") |
213
|
|
|
|
214
|
|
|
if convert: |
215
|
|
|
text = convert_text( |
216
|
|
|
elem.content[0], |
217
|
|
|
input_format="panflute", |
218
|
|
|
output_format="markdown", |
219
|
|
|
) |
220
|
|
|
elem.content = convert_text(text) |
221
|
|
|
|
222
|
|
|
|
223
|
|
|
def main(doc: Doc | None = None) -> Doc: |
224
|
|
|
""" |
225
|
|
|
Convert the pandoc document. |
226
|
|
|
|
227
|
|
|
Arguments |
228
|
|
|
--------- |
229
|
|
|
doc |
230
|
|
|
The pandoc document |
231
|
|
|
|
232
|
|
|
Returns |
233
|
|
|
------- |
234
|
|
|
Doc |
235
|
|
|
The modified pandoc document |
236
|
|
|
""" |
237
|
|
|
return run_filters([alert, task, cell], doc=doc) |
238
|
|
|
|
239
|
|
|
|
240
|
|
|
if __name__ == "__main__": |
241
|
|
|
main() |
242
|
|
|
|