ferrodoc 0.2.0__tar.gz
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.
- ferrodoc-0.2.0/Cargo.lock +802 -0
- ferrodoc-0.2.0/Cargo.toml +32 -0
- ferrodoc-0.2.0/PKG-INFO +29 -0
- ferrodoc-0.2.0/README.md +14 -0
- ferrodoc-0.2.0/pyproject.toml +31 -0
- ferrodoc-0.2.0/python/ferrodoc/__init__.py +23 -0
- ferrodoc-0.2.0/python/ferrodoc/__init__.pyi +41 -0
- ferrodoc-0.2.0/python/ferrodoc/py.typed +0 -0
- ferrodoc-0.2.0/src/lib.rs +152 -0
- ferrodoc-0.2.0/tests/test_ferrodoc.py +179 -0
|
@@ -0,0 +1,802 @@
|
|
|
1
|
+
# This file is automatically @generated by Cargo.
|
|
2
|
+
# It is not intended for manual editing.
|
|
3
|
+
version = 4
|
|
4
|
+
|
|
5
|
+
[[package]]
|
|
6
|
+
name = "adler2"
|
|
7
|
+
version = "2.0.1"
|
|
8
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
9
|
+
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
|
10
|
+
|
|
11
|
+
[[package]]
|
|
12
|
+
name = "autocfg"
|
|
13
|
+
version = "1.5.1"
|
|
14
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
15
|
+
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
|
|
16
|
+
|
|
17
|
+
[[package]]
|
|
18
|
+
name = "bitflags"
|
|
19
|
+
version = "2.13.1"
|
|
20
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
21
|
+
checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
|
|
22
|
+
|
|
23
|
+
[[package]]
|
|
24
|
+
name = "bumpalo"
|
|
25
|
+
version = "3.20.3"
|
|
26
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
27
|
+
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
|
|
28
|
+
|
|
29
|
+
[[package]]
|
|
30
|
+
name = "caseless"
|
|
31
|
+
version = "0.2.2"
|
|
32
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
33
|
+
checksum = "8b6fd507454086c8edfd769ca6ada439193cdb209c7681712ef6275cccbfe5d8"
|
|
34
|
+
dependencies = [
|
|
35
|
+
"unicode-normalization",
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
[[package]]
|
|
39
|
+
name = "cfg-if"
|
|
40
|
+
version = "1.0.4"
|
|
41
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
42
|
+
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
|
43
|
+
|
|
44
|
+
[[package]]
|
|
45
|
+
name = "comrak"
|
|
46
|
+
version = "0.54.0"
|
|
47
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
48
|
+
checksum = "0d5910408554659ed848ff469e67ec83b30f179e72cec286cfdae64d1616f466"
|
|
49
|
+
dependencies = [
|
|
50
|
+
"caseless",
|
|
51
|
+
"entities",
|
|
52
|
+
"finl_unicode",
|
|
53
|
+
"jetscii",
|
|
54
|
+
"phf",
|
|
55
|
+
"phf_codegen",
|
|
56
|
+
"rustc-hash",
|
|
57
|
+
"smallvec",
|
|
58
|
+
"typed-arena",
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
[[package]]
|
|
62
|
+
name = "crc32fast"
|
|
63
|
+
version = "1.5.0"
|
|
64
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
65
|
+
checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
|
|
66
|
+
dependencies = [
|
|
67
|
+
"cfg-if",
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
[[package]]
|
|
71
|
+
name = "entities"
|
|
72
|
+
version = "1.0.1"
|
|
73
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
74
|
+
checksum = "b5320ae4c3782150d900b79807611a59a99fc9a1d61d686faafc24b93fc8d7ca"
|
|
75
|
+
|
|
76
|
+
[[package]]
|
|
77
|
+
name = "equivalent"
|
|
78
|
+
version = "1.0.2"
|
|
79
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
80
|
+
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
|
81
|
+
|
|
82
|
+
[[package]]
|
|
83
|
+
name = "fastrand"
|
|
84
|
+
version = "2.5.0"
|
|
85
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
86
|
+
checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223"
|
|
87
|
+
|
|
88
|
+
[[package]]
|
|
89
|
+
name = "ferrodoc"
|
|
90
|
+
version = "0.2.0"
|
|
91
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
92
|
+
checksum = "a8b4c24ebd7ca2bdb9ae6609f665cdcc2890b23a1d0aee50628eb0d0fe4b896b"
|
|
93
|
+
dependencies = [
|
|
94
|
+
"ferrodoc-asciidoc",
|
|
95
|
+
"ferrodoc-ast",
|
|
96
|
+
"ferrodoc-docx",
|
|
97
|
+
"ferrodoc-epub",
|
|
98
|
+
"ferrodoc-html",
|
|
99
|
+
"ferrodoc-ipynb",
|
|
100
|
+
"ferrodoc-latex",
|
|
101
|
+
"ferrodoc-markdown",
|
|
102
|
+
"ferrodoc-odt",
|
|
103
|
+
"ferrodoc-rst",
|
|
104
|
+
"ferrodoc-text",
|
|
105
|
+
"serde_json",
|
|
106
|
+
]
|
|
107
|
+
|
|
108
|
+
[[package]]
|
|
109
|
+
name = "ferrodoc-asciidoc"
|
|
110
|
+
version = "0.2.0"
|
|
111
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
112
|
+
checksum = "644f68c13a7f2e83c6e167fe811e981f9379a3484fda60d7779d92926ac76d5b"
|
|
113
|
+
dependencies = [
|
|
114
|
+
"ferrodoc-ast",
|
|
115
|
+
]
|
|
116
|
+
|
|
117
|
+
[[package]]
|
|
118
|
+
name = "ferrodoc-ast"
|
|
119
|
+
version = "0.2.0"
|
|
120
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
121
|
+
checksum = "744dacb26dc0f4ffc8f31afe739a38aea4f8feed0038edfb3846ebbb3c3800a7"
|
|
122
|
+
dependencies = [
|
|
123
|
+
"serde",
|
|
124
|
+
"serde_json",
|
|
125
|
+
]
|
|
126
|
+
|
|
127
|
+
[[package]]
|
|
128
|
+
name = "ferrodoc-docx"
|
|
129
|
+
version = "0.2.0"
|
|
130
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
131
|
+
checksum = "eb8f959eead7cdddc45e980474c749191b77c9678c125bea1a26f8290815c17a"
|
|
132
|
+
dependencies = [
|
|
133
|
+
"ferrodoc-ast",
|
|
134
|
+
"quick-xml",
|
|
135
|
+
"zip",
|
|
136
|
+
]
|
|
137
|
+
|
|
138
|
+
[[package]]
|
|
139
|
+
name = "ferrodoc-epub"
|
|
140
|
+
version = "0.2.0"
|
|
141
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
142
|
+
checksum = "ac0360dd4143d1905602220279e11081b8b29bd91b8e09084e43b34d94ad76a5"
|
|
143
|
+
dependencies = [
|
|
144
|
+
"ferrodoc-ast",
|
|
145
|
+
"ferrodoc-docx",
|
|
146
|
+
"ferrodoc-html",
|
|
147
|
+
"zip",
|
|
148
|
+
]
|
|
149
|
+
|
|
150
|
+
[[package]]
|
|
151
|
+
name = "ferrodoc-html"
|
|
152
|
+
version = "0.2.0"
|
|
153
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
154
|
+
checksum = "5bd1c4aac12d2450b481d5ed5771d7ae14871dafb87ee9f22ce8b6f7eb67bb79"
|
|
155
|
+
dependencies = [
|
|
156
|
+
"ferrodoc-ast",
|
|
157
|
+
"html5ever",
|
|
158
|
+
"markup5ever_rcdom",
|
|
159
|
+
]
|
|
160
|
+
|
|
161
|
+
[[package]]
|
|
162
|
+
name = "ferrodoc-ipynb"
|
|
163
|
+
version = "0.2.0"
|
|
164
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
165
|
+
checksum = "35e6721a40e8e9510c0e136fa11df215b64a3506dd4a5158701296e65cfc5454"
|
|
166
|
+
dependencies = [
|
|
167
|
+
"ferrodoc-ast",
|
|
168
|
+
"ferrodoc-markdown",
|
|
169
|
+
"serde_json",
|
|
170
|
+
]
|
|
171
|
+
|
|
172
|
+
[[package]]
|
|
173
|
+
name = "ferrodoc-latex"
|
|
174
|
+
version = "0.2.0"
|
|
175
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
176
|
+
checksum = "ee5ef23fa0e3018f2d0c7e018b37b28630e256b9becd167449b596b8a2552a56"
|
|
177
|
+
dependencies = [
|
|
178
|
+
"ferrodoc-ast",
|
|
179
|
+
]
|
|
180
|
+
|
|
181
|
+
[[package]]
|
|
182
|
+
name = "ferrodoc-markdown"
|
|
183
|
+
version = "0.2.0"
|
|
184
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
185
|
+
checksum = "f41cd4d0c03197b0fa4a58a79b9a63c1feefa8c1127a6333a93efcfd0176fe7e"
|
|
186
|
+
dependencies = [
|
|
187
|
+
"comrak",
|
|
188
|
+
"ferrodoc-ast",
|
|
189
|
+
]
|
|
190
|
+
|
|
191
|
+
[[package]]
|
|
192
|
+
name = "ferrodoc-odt"
|
|
193
|
+
version = "0.2.0"
|
|
194
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
195
|
+
checksum = "17cc59f298debe4ac007e793ee10e525acba16e7cb568b42c1d05c06cb661a26"
|
|
196
|
+
dependencies = [
|
|
197
|
+
"ferrodoc-ast",
|
|
198
|
+
"ferrodoc-docx",
|
|
199
|
+
"zip",
|
|
200
|
+
]
|
|
201
|
+
|
|
202
|
+
[[package]]
|
|
203
|
+
name = "ferrodoc-python"
|
|
204
|
+
version = "0.2.0"
|
|
205
|
+
dependencies = [
|
|
206
|
+
"ferrodoc",
|
|
207
|
+
"pyo3",
|
|
208
|
+
]
|
|
209
|
+
|
|
210
|
+
[[package]]
|
|
211
|
+
name = "ferrodoc-rst"
|
|
212
|
+
version = "0.2.0"
|
|
213
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
214
|
+
checksum = "61f18a56ea96d5ac80ecfdfe1bc71e4f94db4db7c522a8a3bc8c9299c2acab1e"
|
|
215
|
+
dependencies = [
|
|
216
|
+
"ferrodoc-ast",
|
|
217
|
+
]
|
|
218
|
+
|
|
219
|
+
[[package]]
|
|
220
|
+
name = "ferrodoc-text"
|
|
221
|
+
version = "0.2.0"
|
|
222
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
223
|
+
checksum = "6d9553d60028e83fb1e529e70f248756abf3e9d103ffff11aec41fc0a512ddd6"
|
|
224
|
+
dependencies = [
|
|
225
|
+
"ferrodoc-ast",
|
|
226
|
+
]
|
|
227
|
+
|
|
228
|
+
[[package]]
|
|
229
|
+
name = "finl_unicode"
|
|
230
|
+
version = "1.4.0"
|
|
231
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
232
|
+
checksum = "9844ddc3a6e533d62bba727eb6c28b5d360921d5175e9ff0f1e621a5c590a4d5"
|
|
233
|
+
|
|
234
|
+
[[package]]
|
|
235
|
+
name = "flate2"
|
|
236
|
+
version = "1.1.9"
|
|
237
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
238
|
+
checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c"
|
|
239
|
+
dependencies = [
|
|
240
|
+
"miniz_oxide",
|
|
241
|
+
"zlib-rs",
|
|
242
|
+
]
|
|
243
|
+
|
|
244
|
+
[[package]]
|
|
245
|
+
name = "hashbrown"
|
|
246
|
+
version = "0.17.1"
|
|
247
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
248
|
+
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
|
|
249
|
+
|
|
250
|
+
[[package]]
|
|
251
|
+
name = "heck"
|
|
252
|
+
version = "0.5.0"
|
|
253
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
254
|
+
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
|
255
|
+
|
|
256
|
+
[[package]]
|
|
257
|
+
name = "html5ever"
|
|
258
|
+
version = "0.39.0"
|
|
259
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
260
|
+
checksum = "46a1761807faccc9a19e86944bbf40610014066306f96edcdedc2fb714bcb7b8"
|
|
261
|
+
dependencies = [
|
|
262
|
+
"log",
|
|
263
|
+
"markup5ever",
|
|
264
|
+
]
|
|
265
|
+
|
|
266
|
+
[[package]]
|
|
267
|
+
name = "indexmap"
|
|
268
|
+
version = "2.14.0"
|
|
269
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
270
|
+
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
|
|
271
|
+
dependencies = [
|
|
272
|
+
"equivalent",
|
|
273
|
+
"hashbrown",
|
|
274
|
+
]
|
|
275
|
+
|
|
276
|
+
[[package]]
|
|
277
|
+
name = "indoc"
|
|
278
|
+
version = "2.0.7"
|
|
279
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
280
|
+
checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706"
|
|
281
|
+
dependencies = [
|
|
282
|
+
"rustversion",
|
|
283
|
+
]
|
|
284
|
+
|
|
285
|
+
[[package]]
|
|
286
|
+
name = "itoa"
|
|
287
|
+
version = "1.0.18"
|
|
288
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
289
|
+
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
|
290
|
+
|
|
291
|
+
[[package]]
|
|
292
|
+
name = "jetscii"
|
|
293
|
+
version = "0.5.3"
|
|
294
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
295
|
+
checksum = "47f142fe24a9c9944451e8349de0a56af5f3e7226dc46f3ed4d4ecc0b85af75e"
|
|
296
|
+
|
|
297
|
+
[[package]]
|
|
298
|
+
name = "libc"
|
|
299
|
+
version = "0.2.189"
|
|
300
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
301
|
+
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
|
|
302
|
+
|
|
303
|
+
[[package]]
|
|
304
|
+
name = "lock_api"
|
|
305
|
+
version = "0.4.14"
|
|
306
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
307
|
+
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
|
|
308
|
+
dependencies = [
|
|
309
|
+
"scopeguard",
|
|
310
|
+
]
|
|
311
|
+
|
|
312
|
+
[[package]]
|
|
313
|
+
name = "log"
|
|
314
|
+
version = "0.4.33"
|
|
315
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
316
|
+
checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
|
|
317
|
+
|
|
318
|
+
[[package]]
|
|
319
|
+
name = "markup5ever"
|
|
320
|
+
version = "0.39.0"
|
|
321
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
322
|
+
checksum = "7122d987ec5f704ee56f6e5b41a7d93722e9aae27ae07cafa4036c4d3f9757de"
|
|
323
|
+
dependencies = [
|
|
324
|
+
"log",
|
|
325
|
+
"tendril",
|
|
326
|
+
"web_atoms",
|
|
327
|
+
]
|
|
328
|
+
|
|
329
|
+
[[package]]
|
|
330
|
+
name = "markup5ever_rcdom"
|
|
331
|
+
version = "0.39.0+unofficial"
|
|
332
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
333
|
+
checksum = "3ac010f19d6c4af81eeb4018a39d7a115de9d285af45c126a4ac02e6fc5716b7"
|
|
334
|
+
dependencies = [
|
|
335
|
+
"html5ever",
|
|
336
|
+
"markup5ever",
|
|
337
|
+
"tendril",
|
|
338
|
+
"xml5ever",
|
|
339
|
+
]
|
|
340
|
+
|
|
341
|
+
[[package]]
|
|
342
|
+
name = "memchr"
|
|
343
|
+
version = "2.8.3"
|
|
344
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
345
|
+
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
|
|
346
|
+
|
|
347
|
+
[[package]]
|
|
348
|
+
name = "memoffset"
|
|
349
|
+
version = "0.9.1"
|
|
350
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
351
|
+
checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
|
|
352
|
+
dependencies = [
|
|
353
|
+
"autocfg",
|
|
354
|
+
]
|
|
355
|
+
|
|
356
|
+
[[package]]
|
|
357
|
+
name = "miniz_oxide"
|
|
358
|
+
version = "0.8.9"
|
|
359
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
360
|
+
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
|
|
361
|
+
dependencies = [
|
|
362
|
+
"adler2",
|
|
363
|
+
"simd-adler32",
|
|
364
|
+
]
|
|
365
|
+
|
|
366
|
+
[[package]]
|
|
367
|
+
name = "new_debug_unreachable"
|
|
368
|
+
version = "1.0.6"
|
|
369
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
370
|
+
checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
|
|
371
|
+
|
|
372
|
+
[[package]]
|
|
373
|
+
name = "once_cell"
|
|
374
|
+
version = "1.21.4"
|
|
375
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
376
|
+
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
|
377
|
+
|
|
378
|
+
[[package]]
|
|
379
|
+
name = "parking_lot"
|
|
380
|
+
version = "0.12.5"
|
|
381
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
382
|
+
checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
|
|
383
|
+
dependencies = [
|
|
384
|
+
"lock_api",
|
|
385
|
+
"parking_lot_core",
|
|
386
|
+
]
|
|
387
|
+
|
|
388
|
+
[[package]]
|
|
389
|
+
name = "parking_lot_core"
|
|
390
|
+
version = "0.9.12"
|
|
391
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
392
|
+
checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
|
|
393
|
+
dependencies = [
|
|
394
|
+
"cfg-if",
|
|
395
|
+
"libc",
|
|
396
|
+
"redox_syscall",
|
|
397
|
+
"smallvec",
|
|
398
|
+
"windows-link",
|
|
399
|
+
]
|
|
400
|
+
|
|
401
|
+
[[package]]
|
|
402
|
+
name = "phf"
|
|
403
|
+
version = "0.13.1"
|
|
404
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
405
|
+
checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf"
|
|
406
|
+
dependencies = [
|
|
407
|
+
"phf_shared",
|
|
408
|
+
"serde",
|
|
409
|
+
]
|
|
410
|
+
|
|
411
|
+
[[package]]
|
|
412
|
+
name = "phf_codegen"
|
|
413
|
+
version = "0.13.1"
|
|
414
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
415
|
+
checksum = "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1"
|
|
416
|
+
dependencies = [
|
|
417
|
+
"phf_generator",
|
|
418
|
+
"phf_shared",
|
|
419
|
+
]
|
|
420
|
+
|
|
421
|
+
[[package]]
|
|
422
|
+
name = "phf_generator"
|
|
423
|
+
version = "0.13.1"
|
|
424
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
425
|
+
checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737"
|
|
426
|
+
dependencies = [
|
|
427
|
+
"fastrand",
|
|
428
|
+
"phf_shared",
|
|
429
|
+
]
|
|
430
|
+
|
|
431
|
+
[[package]]
|
|
432
|
+
name = "phf_shared"
|
|
433
|
+
version = "0.13.1"
|
|
434
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
435
|
+
checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266"
|
|
436
|
+
dependencies = [
|
|
437
|
+
"siphasher",
|
|
438
|
+
]
|
|
439
|
+
|
|
440
|
+
[[package]]
|
|
441
|
+
name = "portable-atomic"
|
|
442
|
+
version = "1.15.0"
|
|
443
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
444
|
+
checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85"
|
|
445
|
+
|
|
446
|
+
[[package]]
|
|
447
|
+
name = "precomputed-hash"
|
|
448
|
+
version = "0.1.1"
|
|
449
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
450
|
+
checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
|
|
451
|
+
|
|
452
|
+
[[package]]
|
|
453
|
+
name = "proc-macro2"
|
|
454
|
+
version = "1.0.107"
|
|
455
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
456
|
+
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
|
|
457
|
+
dependencies = [
|
|
458
|
+
"unicode-ident",
|
|
459
|
+
]
|
|
460
|
+
|
|
461
|
+
[[package]]
|
|
462
|
+
name = "pyo3"
|
|
463
|
+
version = "0.26.0"
|
|
464
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
465
|
+
checksum = "7ba0117f4212101ee6544044dae45abe1083d30ce7b29c4b5cbdfa2354e07383"
|
|
466
|
+
dependencies = [
|
|
467
|
+
"indoc",
|
|
468
|
+
"libc",
|
|
469
|
+
"memoffset",
|
|
470
|
+
"once_cell",
|
|
471
|
+
"portable-atomic",
|
|
472
|
+
"pyo3-build-config",
|
|
473
|
+
"pyo3-ffi",
|
|
474
|
+
"pyo3-macros",
|
|
475
|
+
"unindent",
|
|
476
|
+
]
|
|
477
|
+
|
|
478
|
+
[[package]]
|
|
479
|
+
name = "pyo3-build-config"
|
|
480
|
+
version = "0.26.0"
|
|
481
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
482
|
+
checksum = "4fc6ddaf24947d12a9aa31ac65431fb1b851b8f4365426e182901eabfb87df5f"
|
|
483
|
+
dependencies = [
|
|
484
|
+
"target-lexicon",
|
|
485
|
+
]
|
|
486
|
+
|
|
487
|
+
[[package]]
|
|
488
|
+
name = "pyo3-ffi"
|
|
489
|
+
version = "0.26.0"
|
|
490
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
491
|
+
checksum = "025474d3928738efb38ac36d4744a74a400c901c7596199e20e45d98eb194105"
|
|
492
|
+
dependencies = [
|
|
493
|
+
"libc",
|
|
494
|
+
"pyo3-build-config",
|
|
495
|
+
]
|
|
496
|
+
|
|
497
|
+
[[package]]
|
|
498
|
+
name = "pyo3-macros"
|
|
499
|
+
version = "0.26.0"
|
|
500
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
501
|
+
checksum = "2e64eb489f22fe1c95911b77c44cc41e7c19f3082fc81cce90f657cdc42ffded"
|
|
502
|
+
dependencies = [
|
|
503
|
+
"proc-macro2",
|
|
504
|
+
"pyo3-macros-backend",
|
|
505
|
+
"quote",
|
|
506
|
+
"syn 2.0.119",
|
|
507
|
+
]
|
|
508
|
+
|
|
509
|
+
[[package]]
|
|
510
|
+
name = "pyo3-macros-backend"
|
|
511
|
+
version = "0.26.0"
|
|
512
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
513
|
+
checksum = "100246c0ecf400b475341b8455a9213344569af29a3c841d29270e53102e0fcf"
|
|
514
|
+
dependencies = [
|
|
515
|
+
"heck",
|
|
516
|
+
"proc-macro2",
|
|
517
|
+
"pyo3-build-config",
|
|
518
|
+
"quote",
|
|
519
|
+
"syn 2.0.119",
|
|
520
|
+
]
|
|
521
|
+
|
|
522
|
+
[[package]]
|
|
523
|
+
name = "quick-xml"
|
|
524
|
+
version = "0.41.0"
|
|
525
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
526
|
+
checksum = "e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1"
|
|
527
|
+
dependencies = [
|
|
528
|
+
"memchr",
|
|
529
|
+
]
|
|
530
|
+
|
|
531
|
+
[[package]]
|
|
532
|
+
name = "quote"
|
|
533
|
+
version = "1.0.47"
|
|
534
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
535
|
+
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
|
|
536
|
+
dependencies = [
|
|
537
|
+
"proc-macro2",
|
|
538
|
+
]
|
|
539
|
+
|
|
540
|
+
[[package]]
|
|
541
|
+
name = "redox_syscall"
|
|
542
|
+
version = "0.5.18"
|
|
543
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
544
|
+
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
|
|
545
|
+
dependencies = [
|
|
546
|
+
"bitflags",
|
|
547
|
+
]
|
|
548
|
+
|
|
549
|
+
[[package]]
|
|
550
|
+
name = "rustc-hash"
|
|
551
|
+
version = "2.1.3"
|
|
552
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
553
|
+
checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d"
|
|
554
|
+
|
|
555
|
+
[[package]]
|
|
556
|
+
name = "rustversion"
|
|
557
|
+
version = "1.0.23"
|
|
558
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
559
|
+
checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
|
|
560
|
+
|
|
561
|
+
[[package]]
|
|
562
|
+
name = "scopeguard"
|
|
563
|
+
version = "1.2.0"
|
|
564
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
565
|
+
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
|
566
|
+
|
|
567
|
+
[[package]]
|
|
568
|
+
name = "serde"
|
|
569
|
+
version = "1.0.229"
|
|
570
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
571
|
+
checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
|
|
572
|
+
dependencies = [
|
|
573
|
+
"serde_core",
|
|
574
|
+
"serde_derive",
|
|
575
|
+
]
|
|
576
|
+
|
|
577
|
+
[[package]]
|
|
578
|
+
name = "serde_core"
|
|
579
|
+
version = "1.0.229"
|
|
580
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
581
|
+
checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
|
|
582
|
+
dependencies = [
|
|
583
|
+
"serde_derive",
|
|
584
|
+
]
|
|
585
|
+
|
|
586
|
+
[[package]]
|
|
587
|
+
name = "serde_derive"
|
|
588
|
+
version = "1.0.229"
|
|
589
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
590
|
+
checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
|
|
591
|
+
dependencies = [
|
|
592
|
+
"proc-macro2",
|
|
593
|
+
"quote",
|
|
594
|
+
"syn 3.0.3",
|
|
595
|
+
]
|
|
596
|
+
|
|
597
|
+
[[package]]
|
|
598
|
+
name = "serde_json"
|
|
599
|
+
version = "1.0.151"
|
|
600
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
601
|
+
checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
|
|
602
|
+
dependencies = [
|
|
603
|
+
"itoa",
|
|
604
|
+
"memchr",
|
|
605
|
+
"serde",
|
|
606
|
+
"serde_core",
|
|
607
|
+
"zmij",
|
|
608
|
+
]
|
|
609
|
+
|
|
610
|
+
[[package]]
|
|
611
|
+
name = "simd-adler32"
|
|
612
|
+
version = "0.3.10"
|
|
613
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
614
|
+
checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea"
|
|
615
|
+
|
|
616
|
+
[[package]]
|
|
617
|
+
name = "siphasher"
|
|
618
|
+
version = "1.0.3"
|
|
619
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
620
|
+
checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649"
|
|
621
|
+
|
|
622
|
+
[[package]]
|
|
623
|
+
name = "smallvec"
|
|
624
|
+
version = "1.15.2"
|
|
625
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
626
|
+
checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
|
|
627
|
+
|
|
628
|
+
[[package]]
|
|
629
|
+
name = "string_cache"
|
|
630
|
+
version = "0.9.0"
|
|
631
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
632
|
+
checksum = "a18596f8c785a729f2819c0f6a7eae6ebeebdfffbfe4214ae6b087f690e31901"
|
|
633
|
+
dependencies = [
|
|
634
|
+
"new_debug_unreachable",
|
|
635
|
+
"parking_lot",
|
|
636
|
+
"phf_shared",
|
|
637
|
+
"precomputed-hash",
|
|
638
|
+
"serde",
|
|
639
|
+
]
|
|
640
|
+
|
|
641
|
+
[[package]]
|
|
642
|
+
name = "string_cache_codegen"
|
|
643
|
+
version = "0.6.1"
|
|
644
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
645
|
+
checksum = "585635e46db231059f76c5849798146164652513eb9e8ab2685939dd90f29b69"
|
|
646
|
+
dependencies = [
|
|
647
|
+
"phf_generator",
|
|
648
|
+
"phf_shared",
|
|
649
|
+
"proc-macro2",
|
|
650
|
+
"quote",
|
|
651
|
+
]
|
|
652
|
+
|
|
653
|
+
[[package]]
|
|
654
|
+
name = "syn"
|
|
655
|
+
version = "2.0.119"
|
|
656
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
657
|
+
checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
|
|
658
|
+
dependencies = [
|
|
659
|
+
"proc-macro2",
|
|
660
|
+
"quote",
|
|
661
|
+
"unicode-ident",
|
|
662
|
+
]
|
|
663
|
+
|
|
664
|
+
[[package]]
|
|
665
|
+
name = "syn"
|
|
666
|
+
version = "3.0.3"
|
|
667
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
668
|
+
checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3"
|
|
669
|
+
dependencies = [
|
|
670
|
+
"proc-macro2",
|
|
671
|
+
"quote",
|
|
672
|
+
"unicode-ident",
|
|
673
|
+
]
|
|
674
|
+
|
|
675
|
+
[[package]]
|
|
676
|
+
name = "target-lexicon"
|
|
677
|
+
version = "0.13.5"
|
|
678
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
679
|
+
checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
|
|
680
|
+
|
|
681
|
+
[[package]]
|
|
682
|
+
name = "tendril"
|
|
683
|
+
version = "0.5.1"
|
|
684
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
685
|
+
checksum = "5fed54709c5b3a53d09bb1c113ea4f5ceafd1e772ddcb0030a82e1d56c087b08"
|
|
686
|
+
dependencies = [
|
|
687
|
+
"new_debug_unreachable",
|
|
688
|
+
]
|
|
689
|
+
|
|
690
|
+
[[package]]
|
|
691
|
+
name = "tinyvec"
|
|
692
|
+
version = "1.12.0"
|
|
693
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
694
|
+
checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f"
|
|
695
|
+
dependencies = [
|
|
696
|
+
"tinyvec_macros",
|
|
697
|
+
]
|
|
698
|
+
|
|
699
|
+
[[package]]
|
|
700
|
+
name = "tinyvec_macros"
|
|
701
|
+
version = "0.1.1"
|
|
702
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
703
|
+
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
|
704
|
+
|
|
705
|
+
[[package]]
|
|
706
|
+
name = "typed-arena"
|
|
707
|
+
version = "2.0.2"
|
|
708
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
709
|
+
checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a"
|
|
710
|
+
|
|
711
|
+
[[package]]
|
|
712
|
+
name = "typed-path"
|
|
713
|
+
version = "0.12.3"
|
|
714
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
715
|
+
checksum = "8e28f89b80c87b8fb0cf04ab448d5dd0dd0ade2f8891bae878de66a75a28600e"
|
|
716
|
+
|
|
717
|
+
[[package]]
|
|
718
|
+
name = "unicode-ident"
|
|
719
|
+
version = "1.0.24"
|
|
720
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
721
|
+
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
|
722
|
+
|
|
723
|
+
[[package]]
|
|
724
|
+
name = "unicode-normalization"
|
|
725
|
+
version = "0.1.25"
|
|
726
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
727
|
+
checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8"
|
|
728
|
+
dependencies = [
|
|
729
|
+
"tinyvec",
|
|
730
|
+
]
|
|
731
|
+
|
|
732
|
+
[[package]]
|
|
733
|
+
name = "unindent"
|
|
734
|
+
version = "0.2.4"
|
|
735
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
736
|
+
checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3"
|
|
737
|
+
|
|
738
|
+
[[package]]
|
|
739
|
+
name = "web_atoms"
|
|
740
|
+
version = "0.2.6"
|
|
741
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
742
|
+
checksum = "ba8b815c1b593dc0baf78dd0f4fc8fdb2de53198fb1163738093e9a311c33fb3"
|
|
743
|
+
dependencies = [
|
|
744
|
+
"phf",
|
|
745
|
+
"phf_codegen",
|
|
746
|
+
"string_cache",
|
|
747
|
+
"string_cache_codegen",
|
|
748
|
+
]
|
|
749
|
+
|
|
750
|
+
[[package]]
|
|
751
|
+
name = "windows-link"
|
|
752
|
+
version = "0.2.1"
|
|
753
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
754
|
+
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
|
755
|
+
|
|
756
|
+
[[package]]
|
|
757
|
+
name = "xml5ever"
|
|
758
|
+
version = "0.39.0"
|
|
759
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
760
|
+
checksum = "5ab627f34ff61b80d756180d556f9c68801d836d271b3b8c094504ceca69d221"
|
|
761
|
+
dependencies = [
|
|
762
|
+
"log",
|
|
763
|
+
"markup5ever",
|
|
764
|
+
]
|
|
765
|
+
|
|
766
|
+
[[package]]
|
|
767
|
+
name = "zip"
|
|
768
|
+
version = "8.6.0"
|
|
769
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
770
|
+
checksum = "2d04a6b5381502aa6087c94c669499eb1602eb9c5e8198e534de571f7154809b"
|
|
771
|
+
dependencies = [
|
|
772
|
+
"crc32fast",
|
|
773
|
+
"flate2",
|
|
774
|
+
"indexmap",
|
|
775
|
+
"memchr",
|
|
776
|
+
"typed-path",
|
|
777
|
+
"zopfli",
|
|
778
|
+
]
|
|
779
|
+
|
|
780
|
+
[[package]]
|
|
781
|
+
name = "zlib-rs"
|
|
782
|
+
version = "0.6.7"
|
|
783
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
784
|
+
checksum = "34b31d188d9d685a4f9c7b46d6e36631b07058d2cfe190267adce54dc230bf12"
|
|
785
|
+
|
|
786
|
+
[[package]]
|
|
787
|
+
name = "zmij"
|
|
788
|
+
version = "1.0.23"
|
|
789
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
790
|
+
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
|
|
791
|
+
|
|
792
|
+
[[package]]
|
|
793
|
+
name = "zopfli"
|
|
794
|
+
version = "0.8.3"
|
|
795
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
796
|
+
checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249"
|
|
797
|
+
dependencies = [
|
|
798
|
+
"bumpalo",
|
|
799
|
+
"crc32fast",
|
|
800
|
+
"log",
|
|
801
|
+
"simd-adler32",
|
|
802
|
+
]
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "ferrodoc-python"
|
|
3
|
+
description = "Python bindings for ferrodoc, a pandoc-compatible document converter"
|
|
4
|
+
version = "0.2.0"
|
|
5
|
+
edition = "2024"
|
|
6
|
+
rust-version = "1.89"
|
|
7
|
+
license = "MIT OR Apache-2.0"
|
|
8
|
+
repository = "https://github.com/alexgaoth/ferrodoc"
|
|
9
|
+
# Distributed as a wheel on PyPI, never as a crate.
|
|
10
|
+
publish = false
|
|
11
|
+
readme = "README.md"
|
|
12
|
+
|
|
13
|
+
[lib]
|
|
14
|
+
name = "_ferrodoc"
|
|
15
|
+
crate-type = ["cdylib"]
|
|
16
|
+
|
|
17
|
+
[dependencies]
|
|
18
|
+
# From crates.io, not by path: maturin's source distribution contains only
|
|
19
|
+
# this directory, so a path dependency would not resolve for anyone
|
|
20
|
+
# building the sdist.
|
|
21
|
+
ferrodoc = "0.2"
|
|
22
|
+
# abi3 means one wheel per platform serves every Python from 3.9 up,
|
|
23
|
+
# rather than one wheel per interpreter version.
|
|
24
|
+
pyo3 = { version = "0.26", features = ["abi3-py39", "extension-module"] }
|
|
25
|
+
|
|
26
|
+
[lints.rust]
|
|
27
|
+
# The same guarantee the workspace makes, made here too: this crate is
|
|
28
|
+
# outside the workspace, so it does not inherit the lint.
|
|
29
|
+
unsafe_code = "forbid"
|
|
30
|
+
|
|
31
|
+
[profile.release]
|
|
32
|
+
strip = true
|
ferrodoc-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ferrodoc
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Classifier: Programming Language :: Rust
|
|
5
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
6
|
+
Classifier: Topic :: Text Processing :: Markup
|
|
7
|
+
Classifier: Typing :: Typed
|
|
8
|
+
Summary: Convert documents between markdown, HTML and DOCX — in process, without shelling out to pandoc
|
|
9
|
+
Keywords: pandoc,docx,markdown,html,converter
|
|
10
|
+
License: MIT OR Apache-2.0
|
|
11
|
+
Requires-Python: >=3.9
|
|
12
|
+
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
13
|
+
Project-URL: Repository, https://github.com/alexgaoth/ferrodoc
|
|
14
|
+
|
|
15
|
+
# ferrodoc
|
|
16
|
+
|
|
17
|
+
Convert documents between markdown (CommonMark and GFM), HTML and DOCX —
|
|
18
|
+
in your process, without shelling out to pandoc.
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
import ferrodoc
|
|
22
|
+
|
|
23
|
+
with open("report.docx", "rb") as f:
|
|
24
|
+
markdown = ferrodoc.convert(f.read(), "docx", "gfm")
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Built on the Rust [ferrodoc](https://github.com/alexgaoth/ferrodoc) crates,
|
|
28
|
+
whose output is checked against pandoc 3.8.2.1 document by document.
|
|
29
|
+
|
ferrodoc-0.2.0/README.md
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# ferrodoc
|
|
2
|
+
|
|
3
|
+
Convert documents between markdown (CommonMark and GFM), HTML and DOCX —
|
|
4
|
+
in your process, without shelling out to pandoc.
|
|
5
|
+
|
|
6
|
+
```python
|
|
7
|
+
import ferrodoc
|
|
8
|
+
|
|
9
|
+
with open("report.docx", "rb") as f:
|
|
10
|
+
markdown = ferrodoc.convert(f.read(), "docx", "gfm")
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Built on the Rust [ferrodoc](https://github.com/alexgaoth/ferrodoc) crates,
|
|
14
|
+
whose output is checked against pandoc 3.8.2.1 document by document.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["maturin>=1.7,<2.0"]
|
|
3
|
+
build-backend = "maturin"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "ferrodoc"
|
|
7
|
+
description = "Convert documents between markdown, HTML and DOCX — in process, without shelling out to pandoc"
|
|
8
|
+
requires-python = ">=3.9"
|
|
9
|
+
license = { text = "MIT OR Apache-2.0" }
|
|
10
|
+
readme = "README.md"
|
|
11
|
+
keywords = ["pandoc", "docx", "markdown", "html", "converter"]
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Programming Language :: Rust",
|
|
14
|
+
"Programming Language :: Python :: Implementation :: CPython",
|
|
15
|
+
"Topic :: Text Processing :: Markup",
|
|
16
|
+
"Typing :: Typed",
|
|
17
|
+
]
|
|
18
|
+
dynamic = ["version"]
|
|
19
|
+
|
|
20
|
+
[project.urls]
|
|
21
|
+
Repository = "https://github.com/alexgaoth/ferrodoc"
|
|
22
|
+
|
|
23
|
+
[tool.maturin]
|
|
24
|
+
features = ["pyo3/extension-module"]
|
|
25
|
+
# A mixed layout: the compiled module is an implementation detail behind a
|
|
26
|
+
# Python package that carries the type stubs.
|
|
27
|
+
python-source = "python"
|
|
28
|
+
module-name = "ferrodoc._ferrodoc"
|
|
29
|
+
# Or a source distribution built after a wheel carries that wheel inside
|
|
30
|
+
# it — a stale binary of itself, uploaded to PyPI.
|
|
31
|
+
exclude = ["dist/**", "target/**"]
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Convert documents between markdown, HTML and DOCX, in this process.
|
|
2
|
+
|
|
3
|
+
The compiled module does the work; this package exists to carry the type
|
|
4
|
+
stubs and to keep the import name stable if the extension is ever split.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from ._ferrodoc import (
|
|
8
|
+
ConversionError,
|
|
9
|
+
__version__,
|
|
10
|
+
convert,
|
|
11
|
+
formats,
|
|
12
|
+
read_formats,
|
|
13
|
+
write_formats,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"ConversionError",
|
|
18
|
+
"__version__",
|
|
19
|
+
"convert",
|
|
20
|
+
"formats",
|
|
21
|
+
"read_formats",
|
|
22
|
+
"write_formats",
|
|
23
|
+
]
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from typing import Union
|
|
2
|
+
|
|
3
|
+
__version__: str
|
|
4
|
+
|
|
5
|
+
class ConversionError(ValueError):
|
|
6
|
+
"""A document could not be read as the format it was said to be, or
|
|
7
|
+
could not be written as the format asked for."""
|
|
8
|
+
|
|
9
|
+
# Every spelling `convert` accepts, which is what `Format::parse` accepts.
|
|
10
|
+
# Written as plain `str` rather than a `Literal` union: a format name
|
|
11
|
+
# usually arrives from argv, a config file or a MIME lookup, and a stub
|
|
12
|
+
# that rejected `str` would make the common call site fail to type-check
|
|
13
|
+
# for no benefit.
|
|
14
|
+
Format = str
|
|
15
|
+
|
|
16
|
+
def convert(
|
|
17
|
+
data: Union[str, bytes, bytearray, memoryview],
|
|
18
|
+
from_format: Format,
|
|
19
|
+
to_format: Format,
|
|
20
|
+
) -> Union[str, bytes]:
|
|
21
|
+
"""Convert a document from one format to another.
|
|
22
|
+
|
|
23
|
+
Returns `str` for a text format and `bytes` for the packaged ones —
|
|
24
|
+
`docx`, `odt`, `epub` and `ipynb`. Ask `read_formats()` and
|
|
25
|
+
`write_formats()` rather than reading a list here that goes stale:
|
|
26
|
+
this one said `docx, json` for two versions after `odt`, `epub`,
|
|
27
|
+
`ipynb`, `latex`, `rst` and `asciidoc` existed.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def formats() -> list[str]:
|
|
31
|
+
"""Every format name `convert` knows, in either direction.
|
|
32
|
+
|
|
33
|
+
Not all of them both ways: `pandoc_markdown` is read-only and
|
|
34
|
+
`plain`, `latex`, `rst` and `asciidoc` are write-only.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def read_formats() -> list[str]:
|
|
38
|
+
"""The formats a document can be converted from."""
|
|
39
|
+
|
|
40
|
+
def write_formats() -> list[str]:
|
|
41
|
+
"""The formats a document can be converted to."""
|
|
File without changes
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
//! Python bindings for ferrodoc.
|
|
2
|
+
//!
|
|
3
|
+
//! The surface is deliberately one function. Everything the CLI can do is
|
|
4
|
+
//! a pair of format names, and the pandoc AST is reachable through the
|
|
5
|
+
//! `json` format rather than through a second API that would have to be
|
|
6
|
+
//! kept in step with it.
|
|
7
|
+
|
|
8
|
+
use pyo3::exceptions::PyValueError;
|
|
9
|
+
use pyo3::prelude::*;
|
|
10
|
+
use pyo3::types::{PyBytes, PyString};
|
|
11
|
+
|
|
12
|
+
// The exception's own docstring is the fourth argument below; a `///`
|
|
13
|
+
// here would be attached to the macro call and dropped.
|
|
14
|
+
//
|
|
15
|
+
// Named for the `ferrodoc` package rather than the `_ferrodoc` extension
|
|
16
|
+
// inside it, because that name has to be importable: pickle resolves an
|
|
17
|
+
// exception by `__module__` and `__qualname__`, and a batch running over
|
|
18
|
+
// a `ProcessPoolExecutor` can only receive this error if it pickles. One
|
|
19
|
+
// corrupt document would otherwise break the pool rather than raise.
|
|
20
|
+
pyo3::create_exception!(
|
|
21
|
+
ferrodoc,
|
|
22
|
+
ConversionError,
|
|
23
|
+
PyValueError,
|
|
24
|
+
"A document could not be read as the format it was said to be, or \
|
|
25
|
+
could not be written as the format asked for."
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
/// Whether a format's bytes are text a caller would rather have as `str`.
|
|
29
|
+
fn is_text(format: ferrodoc::Format) -> bool {
|
|
30
|
+
// The three zip formats, and only those. This said "everything but
|
|
31
|
+
// `docx`" from the version where `docx` was the only one, so
|
|
32
|
+
// `convert(doc, "gfm", "odt")` raised "invalid utf-8 sequence" and
|
|
33
|
+
// the binding could not produce an ODT or an EPUB **at all** — the
|
|
34
|
+
// wheel could not be built while `ferrodoc 0.2` was unpublished, so
|
|
35
|
+
// no test could reach it.
|
|
36
|
+
//
|
|
37
|
+
// `ipynb` is *not* here on purpose: a notebook is JSON, so it comes
|
|
38
|
+
// back as `str` like every other text format. `Format::embeds_media`
|
|
39
|
+
// is the wrong predicate to reuse — it counts `ipynb` because a
|
|
40
|
+
// notebook carries base64 pictures, which is a different question.
|
|
41
|
+
!matches!(
|
|
42
|
+
format,
|
|
43
|
+
ferrodoc::Format::Docx | ferrodoc::Format::Odt | ferrodoc::Format::Epub
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
fn format(name: &str, role: &str) -> PyResult<ferrodoc::Format> {
|
|
48
|
+
ferrodoc::Format::parse(name).ok_or_else(|| {
|
|
49
|
+
PyValueError::new_err(format!(
|
|
50
|
+
"unknown {role} format {name:?}; known formats: {}",
|
|
51
|
+
ferrodoc::Format::NAMES.join(", ")
|
|
52
|
+
))
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/// Convert a document from one format to another.
|
|
57
|
+
///
|
|
58
|
+
/// `data` may be `str` or `bytes`; DOCX input has to be `bytes`. The
|
|
59
|
+
/// result is `str` for a text format and `bytes` for DOCX, which is what
|
|
60
|
+
/// a caller wants to write to a file or hand to a model.
|
|
61
|
+
#[pyfunction]
|
|
62
|
+
#[pyo3(signature = (data, from_format, to_format), text_signature = "(data, from_format, to_format)")]
|
|
63
|
+
fn convert<'py>(
|
|
64
|
+
python: Python<'py>,
|
|
65
|
+
data: &Bound<'py, PyAny>,
|
|
66
|
+
from_format: &str,
|
|
67
|
+
to_format: &str,
|
|
68
|
+
) -> PyResult<Bound<'py, PyAny>> {
|
|
69
|
+
let from = format(from_format, "input")?;
|
|
70
|
+
let to = format(to_format, "output")?;
|
|
71
|
+
|
|
72
|
+
// `bytearray` and `memoryview` are what a caller holding an mmap or a
|
|
73
|
+
// socket read has, and asking them to copy first would be a rule with
|
|
74
|
+
// no reason behind it.
|
|
75
|
+
let owned_text;
|
|
76
|
+
let owned_bytes;
|
|
77
|
+
let bytes: &[u8] = if let Ok(text) = data.downcast::<PyString>() {
|
|
78
|
+
owned_text = text.to_cow()?.into_owned();
|
|
79
|
+
owned_text.as_bytes()
|
|
80
|
+
} else if let Ok(buffer) = data.downcast::<PyBytes>() {
|
|
81
|
+
buffer.as_bytes()
|
|
82
|
+
} else if let Ok(copied) = data.extract::<Vec<u8>>() {
|
|
83
|
+
owned_bytes = copied;
|
|
84
|
+
&owned_bytes
|
|
85
|
+
} else {
|
|
86
|
+
return Err(PyValueError::new_err(format!(
|
|
87
|
+
"data must be str, bytes, bytearray or a buffer, not {}; \
|
|
88
|
+
a DOCX document has to be bytes",
|
|
89
|
+
data.get_type().name()?,
|
|
90
|
+
)));
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// The conversion is pure computation on bytes we already hold, so the
|
|
94
|
+
// GIL is released for it: a caller converting a directory of
|
|
95
|
+
// documents across a thread pool gets the parallelism they asked for.
|
|
96
|
+
let converted = python
|
|
97
|
+
.detach(|| ferrodoc::convert(bytes, from, to))
|
|
98
|
+
.map_err(|error| ConversionError::new_err(error.to_string()))?;
|
|
99
|
+
|
|
100
|
+
if is_text(to) {
|
|
101
|
+
let text = String::from_utf8(converted)
|
|
102
|
+
.map_err(|error| ConversionError::new_err(error.to_string()))?;
|
|
103
|
+
return Ok(PyString::new(python, &text).into_any());
|
|
104
|
+
}
|
|
105
|
+
Ok(PyBytes::new(python, &converted).into_any())
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/// Every format name `convert` knows, in either direction.
|
|
109
|
+
///
|
|
110
|
+
/// **Not every one of these can be both read and written**, which is why
|
|
111
|
+
/// [`read_formats`] and [`write_formats`] exist beside it: `pandoc_markdown`
|
|
112
|
+
/// is read-only and `plain`, `latex`, `rst` and `asciidoc` are write-only.
|
|
113
|
+
/// Saying "every format `convert` accepts" and meaning the union is what
|
|
114
|
+
/// made the test that iterated this fail the moment a read-only format
|
|
115
|
+
/// existed.
|
|
116
|
+
#[pyfunction]
|
|
117
|
+
fn formats() -> Vec<String> {
|
|
118
|
+
named(|_| true)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/// The formats a document can be converted **from**.
|
|
122
|
+
#[pyfunction]
|
|
123
|
+
fn read_formats() -> Vec<String> {
|
|
124
|
+
named(ferrodoc::Format::readable)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/// The formats a document can be converted **to**.
|
|
128
|
+
#[pyfunction]
|
|
129
|
+
fn write_formats() -> Vec<String> {
|
|
130
|
+
named(ferrodoc::Format::writable)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
fn named(keep: impl Fn(ferrodoc::Format) -> bool) -> Vec<String> {
|
|
134
|
+
ferrodoc::Format::NAMES
|
|
135
|
+
.iter()
|
|
136
|
+
.filter(|name| {
|
|
137
|
+
ferrodoc::Format::parse(name).is_some_and(|format| format.compiled() && keep(format))
|
|
138
|
+
})
|
|
139
|
+
.map(|name| (*name).to_owned())
|
|
140
|
+
.collect()
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
#[pymodule]
|
|
144
|
+
fn _ferrodoc(module: &Bound<'_, PyModule>) -> PyResult<()> {
|
|
145
|
+
module.add_function(wrap_pyfunction!(convert, module)?)?;
|
|
146
|
+
module.add_function(wrap_pyfunction!(formats, module)?)?;
|
|
147
|
+
module.add_function(wrap_pyfunction!(read_formats, module)?)?;
|
|
148
|
+
module.add_function(wrap_pyfunction!(write_formats, module)?)?;
|
|
149
|
+
module.add("ConversionError", module.py().get_type::<ConversionError>())?;
|
|
150
|
+
module.add("__version__", env!("CARGO_PKG_VERSION"))?;
|
|
151
|
+
Ok(())
|
|
152
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"""Tests for the ferrodoc Python bindings.
|
|
2
|
+
|
|
3
|
+
These check the binding, not the converter — the converter is checked
|
|
4
|
+
document by document against pandoc by the Rust harness. What can only go
|
|
5
|
+
wrong here is the boundary: which Python type comes back, what happens to
|
|
6
|
+
bytes that are not the format they claim to be, and whether an error
|
|
7
|
+
arrives as an exception rather than as a crash.
|
|
8
|
+
|
|
9
|
+
pip install dist/*.whl && python -m pytest tests/
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
import pathlib
|
|
14
|
+
|
|
15
|
+
import pytest
|
|
16
|
+
|
|
17
|
+
import ferrodoc
|
|
18
|
+
|
|
19
|
+
MARKDOWN = "# Title\n\nHello *world*.\n\n| a | b |\n|---|---|\n| 1 | 2 |\n"
|
|
20
|
+
CORPUS = pathlib.Path(__file__).resolve().parents[3] / "corpus"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_text_output_is_str_and_binary_output_is_bytes():
|
|
24
|
+
# The whole reason `convert` inspects the target format: a caller
|
|
25
|
+
# writing markdown to a file wants str, and a caller writing a .docx
|
|
26
|
+
# wants bytes. Returning bytes for both would make every text caller
|
|
27
|
+
# decode, and returning str for docx would corrupt it.
|
|
28
|
+
assert isinstance(ferrodoc.convert(MARKDOWN, "gfm", "html"), str)
|
|
29
|
+
assert isinstance(ferrodoc.convert(MARKDOWN, "gfm", "docx"), bytes)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_str_and_bytes_input_agree():
|
|
33
|
+
from_text = ferrodoc.convert(MARKDOWN, "gfm", "html")
|
|
34
|
+
from_bytes = ferrodoc.convert(MARKDOWN.encode(), "gfm", "html")
|
|
35
|
+
assert from_text == from_bytes
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_a_table_survives_markdown_to_docx_and_back():
|
|
39
|
+
# The path the binding exists for. A table is the structure most
|
|
40
|
+
# easily lost, and the one people notice.
|
|
41
|
+
docx = ferrodoc.convert(MARKDOWN, "gfm", "docx")
|
|
42
|
+
back = ferrodoc.convert(docx, "docx", "gfm")
|
|
43
|
+
assert "| a | b |" in back.replace(" ", " ")
|
|
44
|
+
assert "Title" in back
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_the_ast_is_reachable_through_the_json_format():
|
|
48
|
+
# No second API to keep in step: the pandoc AST is a format.
|
|
49
|
+
ast = json.loads(ferrodoc.convert(MARKDOWN, "gfm", "json"))
|
|
50
|
+
assert [block["t"] for block in ast["blocks"]] == ["Header", "Para", "Table"]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@pytest.mark.skipif(not CORPUS.exists(), reason="corpus is not beside the wheel")
|
|
54
|
+
def test_a_document_another_program_wrote():
|
|
55
|
+
# Not one of ours: LibreOffice Writer produced this one.
|
|
56
|
+
document = CORPUS / "docx-libreoffice" / "report.docx"
|
|
57
|
+
markdown = ferrodoc.convert(document.read_bytes(), "docx", "gfm")
|
|
58
|
+
assert "# Quarterly Report" in markdown
|
|
59
|
+
assert "| Region | Growth |" in markdown
|
|
60
|
+
assert "[the appendix](https://example.com/appendix)" in markdown
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def test_an_unknown_format_names_the_ones_that_exist():
|
|
64
|
+
with pytest.raises(ValueError) as caught:
|
|
65
|
+
ferrodoc.convert(MARKDOWN, "markdown", "pdf")
|
|
66
|
+
assert "pdf" in str(caught.value)
|
|
67
|
+
assert "docx" in str(caught.value), "the message should list what is available"
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def test_bad_input_raises_rather_than_crashing():
|
|
71
|
+
with pytest.raises(ferrodoc.ConversionError):
|
|
72
|
+
ferrodoc.convert(b"this is not a zip archive", "docx", "gfm")
|
|
73
|
+
with pytest.raises(ferrodoc.ConversionError):
|
|
74
|
+
ferrodoc.convert(b"{ not json", "json", "html")
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def test_data_has_to_be_str_or_bytes():
|
|
78
|
+
with pytest.raises(ValueError):
|
|
79
|
+
ferrodoc.convert(42, "markdown", "html")
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def test_conversion_error_is_a_value_error():
|
|
83
|
+
# So a caller can catch ValueError without knowing this package.
|
|
84
|
+
assert issubclass(ferrodoc.ConversionError, ValueError)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def test_empty_input_is_an_empty_document_not_an_error():
|
|
88
|
+
# A newline, which is exactly what `pandoc -f commonmark -t html`
|
|
89
|
+
# writes for the same input — not an empty string, and not an error.
|
|
90
|
+
assert ferrodoc.convert("", "markdown", "html") == "\n"
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def test_every_named_format_converts_in_the_direction_it_claims():
|
|
94
|
+
# `formats()` is the union, and iterating it in one direction fails on
|
|
95
|
+
# the first format that only goes the other way. That is exactly what
|
|
96
|
+
# happened: `pandoc_markdown` is read-only, and this test could not
|
|
97
|
+
# run at all for two versions because the wheel would not build, so
|
|
98
|
+
# nothing said so.
|
|
99
|
+
readable = ferrodoc.read_formats()
|
|
100
|
+
writable = ferrodoc.write_formats()
|
|
101
|
+
assert set(readable) | set(writable) == set(ferrodoc.formats())
|
|
102
|
+
assert "pandoc_markdown" in readable and "pandoc_markdown" not in writable
|
|
103
|
+
assert "plain" in writable and "plain" not in readable
|
|
104
|
+
|
|
105
|
+
# Every writable format, in the type it should come back as. The
|
|
106
|
+
# three zip formats are bytes and everything else is str — including
|
|
107
|
+
# `ipynb`, which is JSON.
|
|
108
|
+
for name in writable:
|
|
109
|
+
out = ferrodoc.convert("x", "markdown", name)
|
|
110
|
+
want = bytes if name in ("docx", "odt", "epub") else str
|
|
111
|
+
assert isinstance(out, want), f"{name} came back as {type(out).__name__}"
|
|
112
|
+
for name in readable:
|
|
113
|
+
ferrodoc.convert(ferrodoc.convert("x", "markdown", "json"), "json", "markdown")
|
|
114
|
+
if name in ("markdown", "commonmark", "md", "gfm", "markdown_github",
|
|
115
|
+
"pandoc_markdown", "html", "htm"):
|
|
116
|
+
ferrodoc.convert("x", name, "markdown")
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def test_the_error_survives_a_process_boundary():
|
|
120
|
+
# The audience for this binding runs a pool over a directory. If the
|
|
121
|
+
# exception cannot be pickled, one corrupt document does not raise —
|
|
122
|
+
# it breaks the pool, and the batch dies instead of skipping a file.
|
|
123
|
+
import pickle
|
|
124
|
+
|
|
125
|
+
revived = pickle.loads(pickle.dumps(ferrodoc.ConversionError("boom")))
|
|
126
|
+
assert isinstance(revived, ferrodoc.ConversionError)
|
|
127
|
+
assert ferrodoc.ConversionError.__module__ == "ferrodoc", (
|
|
128
|
+
"the class has to name an importable module or pickle cannot find it"
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def test_a_buffer_is_accepted_and_a_wrong_type_says_what_it_got():
|
|
133
|
+
# An mmap or a socket read is a memoryview, and a caller should not
|
|
134
|
+
# have to copy it first to satisfy a rule with no reason behind it.
|
|
135
|
+
for data in (b"# T\n", bytearray(b"# T\n"), memoryview(b"# T\n")):
|
|
136
|
+
assert ferrodoc.convert(data, "markdown", "html") == "<h1>T</h1>\n"
|
|
137
|
+
with pytest.raises(ValueError, match="not int"):
|
|
138
|
+
ferrodoc.convert(42, "markdown", "html")
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def test_the_gil_is_released_so_threads_actually_overlap():
|
|
142
|
+
# The reason the binding is worth having over a subprocess: a pool of
|
|
143
|
+
# threads converts in parallel. With the GIL held across a conversion,
|
|
144
|
+
# N of them take N times one — that is the shape being tested, and the
|
|
145
|
+
# only shape that can be tested portably.
|
|
146
|
+
#
|
|
147
|
+
# Both bounds here have to survive a shared CI runner. The worker
|
|
148
|
+
# count follows the machine, because eight workers on four cores
|
|
149
|
+
# cannot overlap eightfold however well the binding behaves: an
|
|
150
|
+
# earlier version fixed at eight measured 2.45 of 8.0 here and 4.89 of
|
|
151
|
+
# 8.0 on a smaller runner, and failed a threshold tuned to this
|
|
152
|
+
# machine. And the document is large on purpose: at around 100 KiB the
|
|
153
|
+
# pool's own overhead is a big share of the time.
|
|
154
|
+
import concurrent.futures
|
|
155
|
+
import os
|
|
156
|
+
import time
|
|
157
|
+
|
|
158
|
+
workers = min(4, os.cpu_count() or 1)
|
|
159
|
+
if workers < 2:
|
|
160
|
+
pytest.skip("one core cannot overlap anything")
|
|
161
|
+
big = MARKDOWN * 8000
|
|
162
|
+
|
|
163
|
+
ferrodoc.convert(big, "gfm", "docx") # warm
|
|
164
|
+
start = time.perf_counter()
|
|
165
|
+
ferrodoc.convert(big, "gfm", "docx")
|
|
166
|
+
alone = time.perf_counter() - start
|
|
167
|
+
|
|
168
|
+
start = time.perf_counter()
|
|
169
|
+
with concurrent.futures.ThreadPoolExecutor(max_workers=workers) as pool:
|
|
170
|
+
list(pool.map(lambda _: ferrodoc.convert(big, "gfm", "docx"), range(workers)))
|
|
171
|
+
together = time.perf_counter() - start
|
|
172
|
+
|
|
173
|
+
# Serial is `workers`; anything meaningfully under it is overlap the
|
|
174
|
+
# GIL would have prevented.
|
|
175
|
+
assert together < alone * workers * 0.85, (
|
|
176
|
+
f"{workers} conversions took {together:.3f}s against {alone:.3f}s for one, "
|
|
177
|
+
f"a ratio of {together / alone:.2f} where serial is {workers}.0; "
|
|
178
|
+
"the GIL is being held across the conversion"
|
|
179
|
+
)
|