summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rw-r--r--Cargo.lock729
-rw-r--r--Cargo.toml22
-rw-r--r--flake.lock27
-rw-r--r--flake.nix14
-rw-r--r--src/build.rs451
-rw-r--r--src/cli.rs21
-rw-r--r--src/config.rs59
-rw-r--r--src/filters.rs115
-rw-r--r--src/main.rs93
-rw-r--r--src/utils.rs116
11 files changed, 1650 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..a8ea10b
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
1result
2result-*
3/target
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..5a17968
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,729 @@
1# This file is automatically @generated by Cargo.
2# It is not intended for manual editing.
3version = 3
4
5[[package]]
6name = "addr2line"
7version = "0.17.0"
8source = "registry+https://github.com/rust-lang/crates.io-index"
9checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b"
10dependencies = [
11 "gimli",
12]
13
14[[package]]
15name = "adler"
16version = "1.0.2"
17source = "registry+https://github.com/rust-lang/crates.io-index"
18checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
19
20[[package]]
21name = "aho-corasick"
22version = "0.7.18"
23source = "registry+https://github.com/rust-lang/crates.io-index"
24checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
25dependencies = [
26 "memchr",
27]
28
29[[package]]
30name = "arrayvec"
31version = "0.5.2"
32source = "registry+https://github.com/rust-lang/crates.io-index"
33checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
34
35[[package]]
36name = "atty"
37version = "0.2.14"
38source = "registry+https://github.com/rust-lang/crates.io-index"
39checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
40dependencies = [
41 "hermit-abi",
42 "libc",
43 "winapi",
44]
45
46[[package]]
47name = "autocfg"
48version = "1.0.1"
49source = "registry+https://github.com/rust-lang/crates.io-index"
50checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
51
52[[package]]
53name = "backtrace"
54version = "0.3.63"
55source = "registry+https://github.com/rust-lang/crates.io-index"
56checksum = "321629d8ba6513061f26707241fa9bc89524ff1cd7a915a97ef0c62c666ce1b6"
57dependencies = [
58 "addr2line",
59 "cc",
60 "cfg-if",
61 "libc",
62 "miniz_oxide",
63 "object",
64 "rustc-demangle",
65]
66
67[[package]]
68name = "bitflags"
69version = "1.3.2"
70source = "registry+https://github.com/rust-lang/crates.io-index"
71checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
72
73[[package]]
74name = "cc"
75version = "1.0.71"
76source = "registry+https://github.com/rust-lang/crates.io-index"
77checksum = "79c2681d6594606957bbb8631c4b90a7fcaaa72cdb714743a437b156d6a7eedd"
78
79[[package]]
80name = "cfg-if"
81version = "1.0.0"
82source = "registry+https://github.com/rust-lang/crates.io-index"
83checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
84
85[[package]]
86name = "clap"
87version = "3.0.0-beta.5"
88source = "registry+https://github.com/rust-lang/crates.io-index"
89checksum = "feff3878564edb93745d58cf63e17b63f24142506e7a20c87a5521ed7bfb1d63"
90dependencies = [
91 "atty",
92 "bitflags",
93 "clap_derive",
94 "indexmap",
95 "lazy_static",
96 "os_str_bytes",
97 "strsim",
98 "termcolor",
99 "textwrap",
100 "unicase",
101]
102
103[[package]]
104name = "clap_derive"
105version = "3.0.0-beta.5"
106source = "registry+https://github.com/rust-lang/crates.io-index"
107checksum = "8b15c6b4f786ffb6192ffe65a36855bc1fc2444bcd0945ae16748dcd6ed7d0d3"
108dependencies = [
109 "heck",
110 "proc-macro-error",
111 "proc-macro2",
112 "quote",
113 "syn",
114]
115
116[[package]]
117name = "color-eyre"
118version = "0.5.11"
119source = "registry+https://github.com/rust-lang/crates.io-index"
120checksum = "1f1885697ee8a177096d42f158922251a41973117f6d8a234cee94b9509157b7"
121dependencies = [
122 "backtrace",
123 "color-spantrace",
124 "eyre",
125 "indenter",
126 "once_cell",
127 "owo-colors",
128 "tracing-error",
129]
130
131[[package]]
132name = "color-spantrace"
133version = "0.1.6"
134source = "registry+https://github.com/rust-lang/crates.io-index"
135checksum = "b6eee477a4a8a72f4addd4de416eb56d54bc307b284d6601bafdee1f4ea462d1"
136dependencies = [
137 "once_cell",
138 "owo-colors",
139 "tracing-core",
140 "tracing-error",
141]
142
143[[package]]
144name = "config"
145version = "0.11.0"
146source = "registry+https://github.com/rust-lang/crates.io-index"
147checksum = "1b1b9d958c2b1368a663f05538fc1b5975adce1e19f435acceae987aceeeb369"
148dependencies = [
149 "lazy_static",
150 "nom",
151 "rust-ini",
152 "serde 1.0.130",
153 "serde-hjson",
154 "serde_json",
155 "toml",
156 "yaml-rust",
157]
158
159[[package]]
160name = "either"
161version = "1.6.1"
162source = "registry+https://github.com/rust-lang/crates.io-index"
163checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
164
165[[package]]
166name = "env_logger"
167version = "0.9.0"
168source = "registry+https://github.com/rust-lang/crates.io-index"
169checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3"
170dependencies = [
171 "atty",
172 "humantime",
173 "log",
174 "regex",
175 "termcolor",
176]
177
178[[package]]
179name = "eyre"
180version = "0.6.5"
181source = "registry+https://github.com/rust-lang/crates.io-index"
182checksum = "221239d1d5ea86bf5d6f91c9d6bc3646ffe471b08ff9b0f91c44f115ac969d2b"
183dependencies = [
184 "indenter",
185 "once_cell",
186]
187
188[[package]]
189name = "gimli"
190version = "0.26.1"
191source = "registry+https://github.com/rust-lang/crates.io-index"
192checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4"
193
194[[package]]
195name = "hashbrown"
196version = "0.11.2"
197source = "registry+https://github.com/rust-lang/crates.io-index"
198checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
199
200[[package]]
201name = "heck"
202version = "0.3.3"
203source = "registry+https://github.com/rust-lang/crates.io-index"
204checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
205dependencies = [
206 "unicode-segmentation",
207]
208
209[[package]]
210name = "hermit-abi"
211version = "0.1.19"
212source = "registry+https://github.com/rust-lang/crates.io-index"
213checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
214dependencies = [
215 "libc",
216]
217
218[[package]]
219name = "humantime"
220version = "2.1.0"
221source = "registry+https://github.com/rust-lang/crates.io-index"
222checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
223
224[[package]]
225name = "indenter"
226version = "0.3.3"
227source = "registry+https://github.com/rust-lang/crates.io-index"
228checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
229
230[[package]]
231name = "indexmap"
232version = "1.7.0"
233source = "registry+https://github.com/rust-lang/crates.io-index"
234checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5"
235dependencies = [
236 "autocfg",
237 "hashbrown",
238]
239
240[[package]]
241name = "itertools"
242version = "0.8.2"
243source = "registry+https://github.com/rust-lang/crates.io-index"
244checksum = "f56a2d0bc861f9165be4eb3442afd3c236d8a98afd426f65d92324ae1091a484"
245dependencies = [
246 "either",
247]
248
249[[package]]
250name = "itoa"
251version = "0.4.8"
252source = "registry+https://github.com/rust-lang/crates.io-index"
253checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
254
255[[package]]
256name = "lazy_static"
257version = "1.4.0"
258source = "registry+https://github.com/rust-lang/crates.io-index"
259checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
260
261[[package]]
262name = "lexical-core"
263version = "0.7.6"
264source = "registry+https://github.com/rust-lang/crates.io-index"
265checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe"
266dependencies = [
267 "arrayvec",
268 "bitflags",
269 "cfg-if",
270 "ryu",
271 "static_assertions",
272]
273
274[[package]]
275name = "libc"
276version = "0.2.106"
277source = "registry+https://github.com/rust-lang/crates.io-index"
278checksum = "a60553f9a9e039a333b4e9b20573b9e9b9c0bb3a11e201ccc48ef4283456d673"
279
280[[package]]
281name = "linked-hash-map"
282version = "0.5.4"
283source = "registry+https://github.com/rust-lang/crates.io-index"
284checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3"
285
286[[package]]
287name = "log"
288version = "0.4.14"
289source = "registry+https://github.com/rust-lang/crates.io-index"
290checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
291dependencies = [
292 "cfg-if",
293]
294
295[[package]]
296name = "memchr"
297version = "2.4.1"
298source = "registry+https://github.com/rust-lang/crates.io-index"
299checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
300
301[[package]]
302name = "miniz_oxide"
303version = "0.4.4"
304source = "registry+https://github.com/rust-lang/crates.io-index"
305checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b"
306dependencies = [
307 "adler",
308 "autocfg",
309]
310
311[[package]]
312name = "nom"
313version = "5.1.2"
314source = "registry+https://github.com/rust-lang/crates.io-index"
315checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af"
316dependencies = [
317 "lexical-core",
318 "memchr",
319 "version_check",
320]
321
322[[package]]
323name = "num-traits"
324version = "0.1.43"
325source = "registry+https://github.com/rust-lang/crates.io-index"
326checksum = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31"
327dependencies = [
328 "num-traits 0.2.14",
329]
330
331[[package]]
332name = "num-traits"
333version = "0.2.14"
334source = "registry+https://github.com/rust-lang/crates.io-index"
335checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
336dependencies = [
337 "autocfg",
338]
339
340[[package]]
341name = "object"
342version = "0.27.1"
343source = "registry+https://github.com/rust-lang/crates.io-index"
344checksum = "67ac1d3f9a1d3616fd9a60c8d74296f22406a238b6a72f5cc1e6f314df4ffbf9"
345dependencies = [
346 "memchr",
347]
348
349[[package]]
350name = "once_cell"
351version = "1.8.0"
352source = "registry+https://github.com/rust-lang/crates.io-index"
353checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
354
355[[package]]
356name = "os_str_bytes"
357version = "4.2.0"
358source = "registry+https://github.com/rust-lang/crates.io-index"
359checksum = "addaa943333a514159c80c97ff4a93306530d965d27e139188283cd13e06a799"
360dependencies = [
361 "memchr",
362]
363
364[[package]]
365name = "owo-colors"
366version = "1.3.0"
367source = "registry+https://github.com/rust-lang/crates.io-index"
368checksum = "2386b4ebe91c2f7f51082d4cefa145d030e33a1842a96b12e4885cc3c01f7a55"
369
370[[package]]
371name = "pandoc"
372version = "0.8.6"
373source = "registry+https://github.com/rust-lang/crates.io-index"
374checksum = "8c5ab1abdd81ed62e852d412d18964925e42c8f154c61e89c6296b1a06b6daeb"
375dependencies = [
376 "itertools",
377]
378
379[[package]]
380name = "pandoc-docbook"
381version = "0.1.0"
382dependencies = [
383 "clap",
384 "color-eyre",
385 "config",
386 "env_logger",
387 "eyre",
388 "lazy_static",
389 "log",
390 "pandoc",
391 "pandoc_ast",
392 "serde 1.0.130",
393]
394
395[[package]]
396name = "pandoc_ast"
397version = "0.8.0"
398source = "git+https://github.com/oli-obk/pandoc-ast?rev=d73a7d0a065f568d60bcee6cff2340f75065273e#d73a7d0a065f568d60bcee6cff2340f75065273e"
399dependencies = [
400 "serde 1.0.130",
401 "serde_derive",
402 "serde_json",
403]
404
405[[package]]
406name = "pin-project-lite"
407version = "0.2.7"
408source = "registry+https://github.com/rust-lang/crates.io-index"
409checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443"
410
411[[package]]
412name = "proc-macro-error"
413version = "1.0.4"
414source = "registry+https://github.com/rust-lang/crates.io-index"
415checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
416dependencies = [
417 "proc-macro-error-attr",
418 "proc-macro2",
419 "quote",
420 "syn",
421 "version_check",
422]
423
424[[package]]
425name = "proc-macro-error-attr"
426version = "1.0.4"
427source = "registry+https://github.com/rust-lang/crates.io-index"
428checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
429dependencies = [
430 "proc-macro2",
431 "quote",
432 "version_check",
433]
434
435[[package]]
436name = "proc-macro2"
437version = "1.0.32"
438source = "registry+https://github.com/rust-lang/crates.io-index"
439checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43"
440dependencies = [
441 "unicode-xid",
442]
443
444[[package]]
445name = "quote"
446version = "1.0.10"
447source = "registry+https://github.com/rust-lang/crates.io-index"
448checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05"
449dependencies = [
450 "proc-macro2",
451]
452
453[[package]]
454name = "regex"
455version = "1.5.4"
456source = "registry+https://github.com/rust-lang/crates.io-index"
457checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
458dependencies = [
459 "aho-corasick",
460 "memchr",
461 "regex-syntax",
462]
463
464[[package]]
465name = "regex-syntax"
466version = "0.6.25"
467source = "registry+https://github.com/rust-lang/crates.io-index"
468checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
469
470[[package]]
471name = "rust-ini"
472version = "0.13.0"
473source = "registry+https://github.com/rust-lang/crates.io-index"
474checksum = "3e52c148ef37f8c375d49d5a73aa70713125b7f19095948a923f80afdeb22ec2"
475
476[[package]]
477name = "rustc-demangle"
478version = "0.1.21"
479source = "registry+https://github.com/rust-lang/crates.io-index"
480checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"
481
482[[package]]
483name = "ryu"
484version = "1.0.5"
485source = "registry+https://github.com/rust-lang/crates.io-index"
486checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
487
488[[package]]
489name = "serde"
490version = "0.8.23"
491source = "registry+https://github.com/rust-lang/crates.io-index"
492checksum = "9dad3f759919b92c3068c696c15c3d17238234498bbdcc80f2c469606f948ac8"
493
494[[package]]
495name = "serde"
496version = "1.0.130"
497source = "registry+https://github.com/rust-lang/crates.io-index"
498checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913"
499dependencies = [
500 "serde_derive",
501]
502
503[[package]]
504name = "serde-hjson"
505version = "0.9.1"
506source = "registry+https://github.com/rust-lang/crates.io-index"
507checksum = "6a3a4e0ea8a88553209f6cc6cfe8724ecad22e1acf372793c27d995290fe74f8"
508dependencies = [
509 "lazy_static",
510 "num-traits 0.1.43",
511 "regex",
512 "serde 0.8.23",
513]
514
515[[package]]
516name = "serde_derive"
517version = "1.0.130"
518source = "registry+https://github.com/rust-lang/crates.io-index"
519checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b"
520dependencies = [
521 "proc-macro2",
522 "quote",
523 "syn",
524]
525
526[[package]]
527name = "serde_json"
528version = "1.0.69"
529source = "registry+https://github.com/rust-lang/crates.io-index"
530checksum = "e466864e431129c7e0d3476b92f20458e5879919a0596c6472738d9fa2d342f8"
531dependencies = [
532 "itoa",
533 "ryu",
534 "serde 1.0.130",
535]
536
537[[package]]
538name = "sharded-slab"
539version = "0.1.4"
540source = "registry+https://github.com/rust-lang/crates.io-index"
541checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31"
542dependencies = [
543 "lazy_static",
544]
545
546[[package]]
547name = "static_assertions"
548version = "1.1.0"
549source = "registry+https://github.com/rust-lang/crates.io-index"
550checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
551
552[[package]]
553name = "strsim"
554version = "0.10.0"
555source = "registry+https://github.com/rust-lang/crates.io-index"
556checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
557
558[[package]]
559name = "syn"
560version = "1.0.81"
561source = "registry+https://github.com/rust-lang/crates.io-index"
562checksum = "f2afee18b8beb5a596ecb4a2dce128c719b4ba399d34126b9e4396e3f9860966"
563dependencies = [
564 "proc-macro2",
565 "quote",
566 "unicode-xid",
567]
568
569[[package]]
570name = "termcolor"
571version = "1.1.2"
572source = "registry+https://github.com/rust-lang/crates.io-index"
573checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
574dependencies = [
575 "winapi-util",
576]
577
578[[package]]
579name = "textwrap"
580version = "0.14.2"
581source = "registry+https://github.com/rust-lang/crates.io-index"
582checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80"
583dependencies = [
584 "unicode-width",
585]
586
587[[package]]
588name = "thread_local"
589version = "1.1.3"
590source = "registry+https://github.com/rust-lang/crates.io-index"
591checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd"
592dependencies = [
593 "once_cell",
594]
595
596[[package]]
597name = "toml"
598version = "0.5.8"
599source = "registry+https://github.com/rust-lang/crates.io-index"
600checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa"
601dependencies = [
602 "serde 1.0.130",
603]
604
605[[package]]
606name = "tracing"
607version = "0.1.29"
608source = "registry+https://github.com/rust-lang/crates.io-index"
609checksum = "375a639232caf30edfc78e8d89b2d4c375515393e7af7e16f01cd96917fb2105"
610dependencies = [
611 "cfg-if",
612 "pin-project-lite",
613 "tracing-attributes",
614 "tracing-core",
615]
616
617[[package]]
618name = "tracing-attributes"
619version = "0.1.18"
620source = "registry+https://github.com/rust-lang/crates.io-index"
621checksum = "f4f480b8f81512e825f337ad51e94c1eb5d3bbdf2b363dcd01e2b19a9ffe3f8e"
622dependencies = [
623 "proc-macro2",
624 "quote",
625 "syn",
626]
627
628[[package]]
629name = "tracing-core"
630version = "0.1.21"
631source = "registry+https://github.com/rust-lang/crates.io-index"
632checksum = "1f4ed65637b8390770814083d20756f87bfa2c21bf2f110babdc5438351746e4"
633dependencies = [
634 "lazy_static",
635]
636
637[[package]]
638name = "tracing-error"
639version = "0.1.2"
640source = "registry+https://github.com/rust-lang/crates.io-index"
641checksum = "b4d7c0b83d4a500748fa5879461652b361edf5c9d51ede2a2ac03875ca185e24"
642dependencies = [
643 "tracing",
644 "tracing-subscriber",
645]
646
647[[package]]
648name = "tracing-subscriber"
649version = "0.2.25"
650source = "registry+https://github.com/rust-lang/crates.io-index"
651checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71"
652dependencies = [
653 "sharded-slab",
654 "thread_local",
655 "tracing-core",
656]
657
658[[package]]
659name = "unicase"
660version = "2.6.0"
661source = "registry+https://github.com/rust-lang/crates.io-index"
662checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"
663dependencies = [
664 "version_check",
665]
666
667[[package]]
668name = "unicode-segmentation"
669version = "1.8.0"
670source = "registry+https://github.com/rust-lang/crates.io-index"
671checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b"
672
673[[package]]
674name = "unicode-width"
675version = "0.1.9"
676source = "registry+https://github.com/rust-lang/crates.io-index"
677checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
678
679[[package]]
680name = "unicode-xid"
681version = "0.2.2"
682source = "registry+https://github.com/rust-lang/crates.io-index"
683checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
684
685[[package]]
686name = "version_check"
687version = "0.9.3"
688source = "registry+https://github.com/rust-lang/crates.io-index"
689checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
690
691[[package]]
692name = "winapi"
693version = "0.3.9"
694source = "registry+https://github.com/rust-lang/crates.io-index"
695checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
696dependencies = [
697 "winapi-i686-pc-windows-gnu",
698 "winapi-x86_64-pc-windows-gnu",
699]
700
701[[package]]
702name = "winapi-i686-pc-windows-gnu"
703version = "0.4.0"
704source = "registry+https://github.com/rust-lang/crates.io-index"
705checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
706
707[[package]]
708name = "winapi-util"
709version = "0.1.5"
710source = "registry+https://github.com/rust-lang/crates.io-index"
711checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
712dependencies = [
713 "winapi",
714]
715
716[[package]]
717name = "winapi-x86_64-pc-windows-gnu"
718version = "0.4.0"
719source = "registry+https://github.com/rust-lang/crates.io-index"
720checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
721
722[[package]]
723name = "yaml-rust"
724version = "0.4.5"
725source = "registry+https://github.com/rust-lang/crates.io-index"
726checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
727dependencies = [
728 "linked-hash-map",
729]
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..365b07b
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,22 @@
1[package]
2name = "pandoc-docbook"
3version = "0.1.0"
4edition = "2021"
5repository = "https://github.com/minijackson/pandoc-docbook"
6
7# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
8
9[dependencies]
10clap = "3.0.0-beta.5"
11color-eyre = "0.5"
12config = { version = "0.11", features = [ "toml" ] }
13env_logger = "0.9"
14eyre = "0.6"
15lazy_static = "1"
16log = "0.4"
17pandoc = "0.8"
18#pandoc_ast = "0.8"
19# Needed: https://github.com/oli-obk/pandoc-ast/pull/12
20# TODO: change this when new version is released
21pandoc_ast = { git = "https://github.com/oli-obk/pandoc-ast", rev = "d73a7d0a065f568d60bcee6cff2340f75065273e" }
22serde = { version = "1", features = [ "derive" ] }
diff --git a/flake.lock b/flake.lock
new file mode 100644
index 0000000..39b7417
--- /dev/null
+++ b/flake.lock
@@ -0,0 +1,27 @@
1{
2 "nodes": {
3 "nixpkgs": {
4 "locked": {
5 "lastModified": 1636138231,
6 "narHash": "sha256-FZQ5wBcB9zI21uQMB/PaEQ/cLYk+Bwnig5cg1+2rPgg=",
7 "owner": "NixOS",
8 "repo": "nixpkgs",
9 "rev": "5c02380de3951d0237807c16eb19873cb3c5f75d",
10 "type": "github"
11 },
12 "original": {
13 "owner": "NixOS",
14 "ref": "nixos-21.05",
15 "repo": "nixpkgs",
16 "type": "github"
17 }
18 },
19 "root": {
20 "inputs": {
21 "nixpkgs": "nixpkgs"
22 }
23 }
24 },
25 "root": "root",
26 "version": 7
27}
diff --git a/flake.nix b/flake.nix
new file mode 100644
index 0000000..c917cd5
--- /dev/null
+++ b/flake.nix
@@ -0,0 +1,14 @@
1{
2 description = "A very basic flake";
3
4 inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-21.05";
5
6 outputs = { self, nixpkgs }: let
7 pkgs = nixpkgs.legacyPackages.x86_64-linux;
8 in {
9
10 devShell.x86_64-linux = pkgs.mkShell {
11 };
12
13 };
14}
diff --git a/src/build.rs b/src/build.rs
new file mode 100644
index 0000000..0b0c646
--- /dev/null
+++ b/src/build.rs
@@ -0,0 +1,451 @@
1use std::path::{Path, PathBuf};
2
3use eyre::{eyre, ContextCompat, Result, WrapErr};
4use log::{debug, error, log_enabled, trace, warn};
5use pandoc_ast::MutVisitor;
6
7use crate::{
8 filters,
9 utils::{AutoIdentifier, PandocMeta, PandocOutputExt},
10};
11
12pub fn do_build(config: &crate::config::Config) -> Result<()> {
13 let summary = Summary::try_from_file(&config.book.summary)?;
14 let source_root = Path::new(&config.book.summary)
15 .parent()
16 .expect("Summary has no parent");
17 let files = summary.collect_source_files(source_root)?;
18
19 let build_dir = Path::new(&config.build.build_dir);
20 trace!("Creating build directory: '{}'", build_dir.display());
21 std::fs::create_dir_all(build_dir).wrap_err_with(|| {
22 format!(
23 "Could not create build directory: '{}'",
24 build_dir.display()
25 )
26 })?;
27
28 // Pre-create files so that we know which links to relativize
29 for SourceFile { path, .. } in &files {
30 let output_file = build_dir.join(path.with_extension("html"));
31
32 let product_dir = build_dir.join(path.parent().expect("Source file has no parent"));
33 trace!("Creating product directory: '{}'", product_dir.display());
34 std::fs::create_dir_all(&product_dir).wrap_err_with(|| {
35 format!(
36 "Could not create build output directory: '{}'",
37 product_dir.display()
38 )
39 })?;
40
41 std::fs::OpenOptions::new()
42 .write(true)
43 .create(true)
44 .open(&output_file)
45 .wrap_err_with(|| {
46 format!("Failed to create output file: '{}'", output_file.display())
47 })?;
48 }
49
50 for SourceFile { path, source } in &files {
51 let mut pandoc_command = pandoc::new();
52
53 let output_file = build_dir.join(path.with_extension("html"));
54
55 debug!("Generating file: '{}'", output_file.display());
56
57 // To be captured in the filter
58 let config_clone = config.clone();
59 let source_dir = path
60 .parent()
61 .expect("Source file has no parent")
62 .to_path_buf();
63 let build_dir_clone = build_dir.to_path_buf();
64 let summary_clone = summary.source.clone();
65
66 pandoc_command
67 .set_input(pandoc::InputKind::Pipe(source.to_json()))
68 .set_input_format(pandoc::InputFormat::Json, vec![])
69 .set_output(pandoc::OutputKind::File(output_file))
70 .set_output_format(pandoc::OutputFormat::Html5, vec![])
71 .add_options(&[pandoc::PandocOption::SelfContained])
72 .add_filter(move |source| {
73 let level = source_dir
74 .components()
75 .skip_while(|c| matches!(c, std::path::Component::CurDir))
76 .count();
77
78 let mut insert_summary_filter = filters::InsertSummary {
79 level,
80 summary: &summary_clone,
81 };
82
83 let mut relativize_urls_filter = filters::RelativizeUrls {
84 config: &config_clone,
85 // TODO: other output formats
86 extension: "html",
87 build_dir: &build_dir_clone,
88 source_dir: &source_dir,
89 };
90
91 let mut source = pandoc_ast::Pandoc::from_json(&source);
92 insert_summary_filter.walk_pandoc(&mut source);
93 relativize_urls_filter.walk_pandoc(&mut source);
94 source.to_json()
95 });
96
97 if log_enabled!(log::Level::Trace) {
98 pandoc_command.set_show_cmdline(true);
99 }
100
101 pandoc_command
102 .execute()
103 .wrap_err_with(|| format!("Failed to generate output of: '{}'", path.display()))?;
104 }
105
106 Ok(())
107}
108
109// TODO: move that into generated.rs
110fn generate_source(
111 title: Vec<pandoc_ast::Inline>,
112 children: Vec<(PandocMeta, PathBuf)>,
113 level: usize,
114) -> Result<pandoc_ast::Pandoc> {
115 // TODO: make that text configurable
116 let mut content = vec![pandoc_ast::Block::Para(vec![pandoc_ast::Inline::Str(
117 "Here are the articles in this section:".to_string(),
118 )])];
119
120 for (mut child, file) in children {
121 let title = match child.remove("title") {
122 None => {
123 warn!("Missing title for file: '{}'", file.display());
124 vec![pandoc_ast::Inline::Str("Untitled page".to_string())]
125 }
126 Some(pandoc_ast::MetaValue::MetaInlines(inlines)) => inlines,
127 Some(pandoc_ast::MetaValue::MetaString(s)) => {
128 vec![pandoc_ast::Inline::Str(s)]
129 }
130 // TODO: check that other values are actually invalid
131 _ => {
132 error!("Invalid value for title");
133 vec![pandoc_ast::Inline::Str("Untitled page".to_string())]
134 }
135 };
136
137 let link_target = std::iter::repeat(std::path::Component::ParentDir)
138 .take(level)
139 .collect::<PathBuf>()
140 .join(file);
141
142 content.push(pandoc_ast::Block::Para(vec![pandoc_ast::Inline::Link(
143 // TODO: attribute to recognize big links?
144 (String::new(), vec![], vec![]),
145 title,
146 (
147 link_target
148 .to_str()
149 .expect("Filename contains invalid unicode")
150 .to_string(),
151 String::new(),
152 ),
153 )]));
154 }
155
156 let mut meta = PandocMeta::new();
157 meta.insert(
158 "title".to_string(),
159 pandoc_ast::MetaValue::MetaInlines(title),
160 );
161
162 Ok(pandoc_ast::Pandoc {
163 meta,
164 blocks: content,
165 pandoc_api_version: vec![1, 22],
166 })
167}
168
169fn list_content(block: &mut pandoc_ast::Block) -> Result<&mut Vec<Vec<pandoc_ast::Block>>> {
170 match block {
171 pandoc_ast::Block::OrderedList(_, list) => Ok(list),
172 pandoc_ast::Block::BulletList(list) => Ok(list),
173 _ => Err(eyre!("Expected list in summary, found something else")),
174 }
175}
176
177fn try_into_node_vec(vec: &mut Vec<Vec<pandoc_ast::Block>>) -> Result<Vec<Node>> {
178 vec.iter_mut().map(Node::try_from_vec_block).collect()
179}
180
181// TODO: support separators like these:
182// ---------
183
184#[derive(Debug)]
185pub struct Summary {
186 source: pandoc_ast::Pandoc,
187 nodes: Vec<Node>,
188}
189
190#[derive(Debug)]
191struct SourceFile {
192 path: PathBuf,
193 source: pandoc_ast::Pandoc,
194}
195
196// TODO: move that into summary.rs
197impl Summary {
198 fn try_from_file(file: &str) -> Result<Self> {
199 debug!("Parsing summary");
200 let mut pandoc_command = pandoc::new();
201 pandoc_command
202 .add_input(file)
203 .set_output_format(pandoc::OutputFormat::Json, vec![])
204 .set_output(pandoc::OutputKind::Pipe);
205
206 trace!("Launching pandoc command");
207
208 if log_enabled!(log::Level::Trace) {
209 pandoc_command.set_show_cmdline(true);
210 }
211
212 let output = pandoc_command
213 .execute()
214 .wrap_err("Could not execute pandoc")?
215 .buffer();
216
217 let document = pandoc_ast::Pandoc::from_json(&output);
218
219 let summary: Self = document.try_into()?;
220 if summary.has_files_missing(
221 Path::new(file)
222 .parent()
223 .expect("Summary file has no parent"),
224 ) {
225 return Err(eyre!("Files from the summary are missing, aborting"));
226 }
227
228 Ok(summary)
229 }
230
231 fn has_files_missing(&self, root: &Path) -> bool {
232 // Do not use `.any()` to prevent short-circuiting, we want to report all missing files
233 self.nodes.iter().fold(false, |acc, node| {
234 let missing = node.has_files_missing(root);
235 acc || missing
236 })
237 }
238
239 /// Get a list of source files.
240 ///
241 /// If a file is a generated file, generate it and store it in memory.
242 fn collect_source_files(&self, root: &Path) -> Result<Vec<SourceFile>> {
243 let mut result = Vec::new();
244
245 for node in &self.nodes {
246 node.collect_source_files(&mut result, root, Path::new("."), 0)?;
247 }
248
249 Ok(result)
250 }
251}
252
253impl TryFrom<pandoc_ast::Pandoc> for Summary {
254 type Error = eyre::Error;
255
256 fn try_from(mut document: pandoc_ast::Pandoc) -> Result<Self, Self::Error> {
257 if document.blocks.len() != 1 {
258 return Err(eyre!("Summary does not contain a single list"));
259 }
260
261 let root = &mut document.blocks[0];
262
263 let list = list_content(root)?;
264
265 let nodes = list
266 .iter_mut()
267 .map(Node::try_from_vec_block)
268 .collect::<Result<_>>()?;
269
270 Ok(Summary {
271 source: document,
272 nodes,
273 })
274 }
275}
276
277#[derive(Debug)]
278pub enum Node {
279 Provided {
280 file: String,
281 children: Vec<Node>,
282 },
283 Generated {
284 file: String,
285 title: Vec<pandoc_ast::Inline>,
286 children: Vec<Node>,
287 },
288}
289
290impl Node {
291 fn children(&self) -> &[Node] {
292 match self {
293 Node::Provided { children, .. } => children,
294 Node::Generated { children, .. } => children,
295 }
296 }
297
298 fn has_files_missing(&self, root: &Path) -> bool {
299 if let Node::Provided { file, .. } = self {
300 if !root.join(file).exists() {
301 error!("File '{}' specified in summary does not exists", file);
302 return true;
303 }
304 }
305
306 // Do not use `.any()` to prevent short-circuiting, we want to report all missing files
307 self.children().iter().fold(false, |acc, node| {
308 let missing = node.has_files_missing(root);
309 acc || missing
310 })
311 }
312
313 fn collect_source_files(
314 &self,
315 result: &mut Vec<SourceFile>,
316 root: &Path,
317 parent: &Path,
318 level: usize,
319 ) -> Result<()> {
320 let new_parent;
321 let children_;
322 let path;
323 let source: Box<dyn FnOnce(_) -> _>;
324
325 match self {
326 Node::Provided { file, children } => {
327 trace!("Parsing file: '{}'", file);
328
329 // TODO: some filters here? not all filters, since we may want to filter generated
330 // files too
331 let mut pandoc_command = pandoc::new();
332 pandoc_command
333 .add_input(&root.join(file))
334 .set_output(pandoc::OutputKind::Pipe)
335 .set_output_format(pandoc::OutputFormat::Json, vec![]);
336
337 if log_enabled!(log::Level::Trace) {
338 pandoc_command.set_show_cmdline(true);
339 }
340
341 let raw_source = pandoc_command
342 .execute()
343 .wrap_err_with(|| format!("Failed to parse '{}'", file))?
344 .buffer();
345 source = Box::new(move |_| Ok(pandoc_ast::Pandoc::from_json(&raw_source)));
346
347 let file = Path::new(&file);
348 let stem = file.file_stem().expect("No file name");
349 let id =
350 AutoIdentifier::from(stem.to_str().wrap_err("Invalid unicode in file name")?);
351
352 path = file.into();
353 new_parent = file.parent().expect("Source file has no parent").join(&*id);
354 children_ = children;
355 }
356
357 Self::Generated {
358 file,
359 title,
360 children,
361 } => {
362 trace!("Found file to generate: '{}'", file);
363
364 path = file.into();
365
366 source = Box::new(move |direct_children| {
367 generate_source(title.clone(), direct_children, level)
368 });
369 new_parent = Path::new(file).with_extension("");
370 children_ = children;
371 }
372 };
373
374 let mut direct_children = Vec::with_capacity(children_.len());
375
376 for child in children_ {
377 child.collect_source_files(result, root, &new_parent, level + 1)?;
378 let direct_child = result.last().unwrap();
379 direct_children.push((direct_child.source.meta.clone(), direct_child.path.clone()));
380 }
381
382 result.push(SourceFile {
383 path,
384 source: source(direct_children)?,
385 });
386
387 Ok(())
388 }
389
390 // Wil also modify the block to linkify generated pages
391 fn try_from_vec_block(value: &mut Vec<pandoc_ast::Block>) -> Result<Self> {
392 if value.len() != 1 && value.len() != 2 {
393 // TODO: better error message?
394 return Err(eyre!("Summary does not contain a single list"));
395 }
396
397 let mut value = value.iter_mut();
398
399 let item = match value.next().unwrap() {
400 pandoc_ast::Block::Plain(inlines) => inlines,
401 pandoc_ast::Block::Para(inlines) => inlines,
402 _ => return Err(eyre!("List item is not a link or plain text")),
403 };
404
405 if item.is_empty() {
406 return Err(eyre!("Summary list items cannot be empty"));
407 }
408
409 let children = if let Some(children) = value.next() {
410 try_into_node_vec(list_content(children)?)?
411 } else {
412 vec![]
413 };
414
415 match &item[0] {
416 pandoc_ast::Inline::Link(_, _, target) => {
417 if item.len() != 1 {
418 return Err(eyre!("Summary list item not a single link or plain text"));
419 }
420
421 let file = target.0.clone();
422
423 Ok(Node::Provided { file, children })
424 }
425 _ => {
426 let title = item.clone();
427
428 let id = AutoIdentifier::from(title.as_slice());
429
430 // TODO: missing parent
431
432 // Move generate page into this pass
433 //let mut file = parent.join(&*id);
434 //file.set_extension("md");
435
436 // TODO: Attribute to style them differently
437 *item = vec![pandoc_ast::Inline::Link(
438 (String::new(), vec!["generated".to_string()], vec![]),
439 item.clone(),
440 (id.0.clone(), String::new()),
441 )];
442
443 Ok(Node::Generated {
444 file: id.0,
445 title,
446 children,
447 })
448 }
449 }
450 }
451}
diff --git a/src/cli.rs b/src/cli.rs
new file mode 100644
index 0000000..30e771a
--- /dev/null
+++ b/src/cli.rs
@@ -0,0 +1,21 @@
1use clap::{AppSettings, Parser};
2
3// TODO: document
4
5#[derive(Debug, Parser)]
6#[clap(setting = AppSettings::InferSubcommands)]
7pub struct Cli {
8 #[clap(short, long, default_value = "pdbook.toml")]
9 pub config: String,
10 #[clap(short, long)]
11 pub quiet: bool,
12 #[clap(short, long, parse(from_occurrences))]
13 pub verbose: u8,
14 #[clap(subcommand)]
15 pub subcommand: SubCommand,
16}
17
18#[derive(Debug, Parser)]
19pub enum SubCommand {
20 Build,
21}
diff --git a/src/config.rs b/src/config.rs
new file mode 100644
index 0000000..53922b0
--- /dev/null
+++ b/src/config.rs
@@ -0,0 +1,59 @@
1use log::debug;
2use serde::{Deserialize, Serialize};
3
4#[derive(Debug, Clone, Deserialize, Serialize)]
5pub struct Config {
6 #[serde(default)]
7 pub book: BookConfig,
8 #[serde(default)]
9 pub build: BuildConfig,
10}
11
12#[derive(Debug, Clone, Deserialize, Serialize)]
13pub struct BookConfig {
14 #[serde(default = "default_summary")]
15 pub summary: String,
16}
17
18impl Default for BookConfig {
19 fn default() -> Self {
20 Self {
21 summary: default_summary(),
22 }
23 }
24}
25
26fn default_summary() -> String {
27 "src/_summary.md".to_string()
28}
29
30#[derive(Debug, Clone, Deserialize, Serialize)]
31pub struct BuildConfig {
32 #[serde(default = "default_build_dir")]
33 pub build_dir: String,
34}
35
36impl Default for BuildConfig {
37 fn default() -> Self {
38 Self {
39 build_dir: default_build_dir(),
40 }
41 }
42}
43
44fn default_build_dir() -> String {
45 "pdbook".to_string()
46}
47
48impl Config {
49 pub fn new(config_file: &str) -> Result<Self, config::ConfigError> {
50 let mut s = config::Config::default();
51
52 debug!("Parsing config file: {}", config_file);
53 s.merge(config::File::with_name(config_file))?;
54 debug!("Parsing config from environment");
55 s.merge(config::Environment::with_prefix("PANDOC_DOCBOOK").separator("_"))?;
56
57 s.try_into()
58 }
59}
diff --git a/src/filters.rs b/src/filters.rs
new file mode 100644
index 0000000..1b06920
--- /dev/null
+++ b/src/filters.rs
@@ -0,0 +1,115 @@
1use std::{collections::HashMap, path::Path};
2
3use log::trace;
4use pandoc_ast::MutVisitor;
5
6// If a link points to `./a/b/c.ext`, and a file in the output directory `pdbook/./a/b/c.html`
7// exists, rewrite that link.
8pub struct RelativizeUrls<'a> {
9 pub config: &'a crate::config::Config,
10 pub extension: &'a str,
11 pub build_dir: &'a Path,
12 pub source_dir: &'a Path,
13}
14
15impl<'a> pandoc_ast::MutVisitor for RelativizeUrls<'a> {
16 fn walk_inline(&mut self, inline: &mut pandoc_ast::Inline) {
17 let link = match inline {
18 pandoc_ast::Inline::Link(_, _, target) => &mut target.0,
19 _ => return,
20 };
21
22 if link.starts_with('#') || link.contains("://") {
23 return;
24 }
25
26 let link_path = self.source_dir.join(&link);
27
28 if link_path.is_absolute() {
29 return;
30 }
31
32 let mut output_path = self.build_dir.join(&link_path);
33 if !output_path.set_extension(self.extension) {
34 return;
35 }
36
37 trace!("Checking output_path: {:?}", output_path);
38
39 // TODO: warn when referencing a "markdown or other" file not specified in the summary
40 if output_path.exists() {
41 // TODO: relativize from URL root
42
43 trace!("Relativizing link '{}'", link_path.display());
44
45 *link = Path::new(link)
46 .with_extension(&self.extension)
47 .to_str()
48 .expect("Path constructed from UTF-8 valid strings in not UTF-8 valid")
49 .to_string();
50
51 trace!("-> into '{}'", link);
52 }
53 }
54}
55
56// Applied just to the summary
57pub struct RelativizeSummary {
58 level: usize,
59}
60
61impl pandoc_ast::MutVisitor for RelativizeSummary {
62 fn walk_inline(&mut self, inline: &mut pandoc_ast::Inline) {
63 if self.level == 0 {
64 return;
65 }
66
67 let link = match inline {
68 pandoc_ast::Inline::Link(_, _, target) => &mut target.0,
69 _ => return,
70 };
71
72 // Assume link is to a managed file
73 for _ in 0..self.level {
74 link.insert_str(0, "../");
75 }
76 }
77}
78
79pub fn relativize_summary(summary: &pandoc_ast::Pandoc, level: usize) -> pandoc_ast::Pandoc {
80 use std::sync::RwLock;
81
82 lazy_static::lazy_static! {
83 static ref CACHE: RwLock<HashMap<usize, pandoc_ast::Pandoc>> = RwLock::new(HashMap::new());
84 }
85
86 CACHE
87 .write()
88 .expect("Relativized summary cache poison")
89 .entry(level)
90 .or_insert_with(|| {
91 let mut summary = summary.clone();
92 RelativizeSummary { level }.walk_pandoc(&mut summary);
93 summary
94 })
95 .clone()
96}
97
98pub struct InsertSummary<'a> {
99 pub summary: &'a pandoc_ast::Pandoc,
100 pub level: usize,
101}
102
103impl<'a> pandoc_ast::MutVisitor for InsertSummary<'a> {
104 fn walk_pandoc(&mut self, pandoc: &mut pandoc_ast::Pandoc) {
105 let summary = relativize_summary(self.summary, self.level);
106
107 pandoc.blocks.insert(
108 0,
109 pandoc_ast::Block::Div(
110 (String::new(), vec!["summary".to_string()], vec![]),
111 summary.blocks,
112 ),
113 );
114 }
115}
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..b5de3bf
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,93 @@
1mod build;
2mod cli;
3mod config;
4mod utils;
5mod filters;
6
7use std::path::PathBuf;
8
9use cli::Cli;
10
11use clap::Parser;
12use eyre::{Result, WrapErr};
13use log::trace;
14
15fn init_log(quiet: bool, verbosity: u8) {
16 use log::LevelFilter;
17
18 let verbosity = match verbosity {
19 0 => LevelFilter::Info,
20 1 => LevelFilter::Debug,
21 _ => LevelFilter::Trace,
22 };
23
24 let env = env_logger::Env::default().default_filter_or(if quiet {
25 "off".to_string()
26 } else {
27 format!("pandoc_docbook={}", verbosity)
28 });
29
30 let mut builder = env_logger::Builder::from_env(env);
31
32 // Shamelessly taken from pretty_env_logger
33 builder.format(move |f, record| {
34 use std::io::Write;
35
36 let target = record.target();
37
38 let mut style = f.style();
39 let level = colored_level(&mut style, record.level());
40
41 let mut style = f.style();
42 let target = style.set_bold(true).value(target);
43
44 if verbosity >= LevelFilter::Debug {
45 writeln!(f, " {} {} > {}", level, target, record.args())
46 } else {
47 writeln!(f, " {} > {}", level, record.args())
48 }
49 });
50
51 builder.init();
52}
53
54fn colored_level<'a>(
55 style: &'a mut env_logger::fmt::Style,
56 level: log::Level,
57) -> env_logger::fmt::StyledValue<'a, &'static str> {
58 use env_logger::fmt::Color;
59 use log::Level;
60
61 match level {
62 Level::Trace => style.set_color(Color::Magenta).value("TRACE"),
63 Level::Debug => style.set_color(Color::Blue).value("DEBUG"),
64 Level::Info => style.set_color(Color::Green).value("INFO "),
65 Level::Warn => style.set_color(Color::Yellow).value("WARN "),
66 Level::Error => style.set_color(Color::Red).value("ERROR"),
67 }
68}
69
70fn main() -> Result<()> {
71 color_eyre::install()?;
72
73 let cli = Cli::parse();
74 init_log(cli.quiet, cli.verbose);
75
76 let config = config::Config::new(&cli.config)?;
77 trace!("Parsed configuration: {:?}", config);
78
79 std::env::set_current_dir(
80 PathBuf::from(cli.config)
81 .parent()
82 .expect("Configuration file has no parent"),
83 )
84 .wrap_err("Could not change current directory to the configuration file's directory")?;
85
86 match cli.subcommand {
87 cli::SubCommand::Build => {
88 build::do_build(&config)?
89 }
90 }
91
92 Ok(())
93}
diff --git a/src/utils.rs b/src/utils.rs
new file mode 100644
index 0000000..8928cfb
--- /dev/null
+++ b/src/utils.rs
@@ -0,0 +1,116 @@
1use std::path::PathBuf;
2
3pub fn pandoc_stringify(inlines: &[pandoc_ast::Inline]) -> String {
4 fn pandoc_stringify_(result: &mut String, inlines: &[pandoc_ast::Inline]) {
5 for inline in inlines {
6 match inline {
7 pandoc_ast::Inline::Str(s)
8 | pandoc_ast::Inline::Code(_, s)
9 | pandoc_ast::Inline::Math(_, s) => result.push_str(s),
10 pandoc_ast::Inline::Emph(inner)
11 | pandoc_ast::Inline::Underline(inner)
12 | pandoc_ast::Inline::Strong(inner)
13 | pandoc_ast::Inline::Strikeout(inner)
14 | pandoc_ast::Inline::Superscript(inner)
15 | pandoc_ast::Inline::Subscript(inner)
16 | pandoc_ast::Inline::SmallCaps(inner)
17 | pandoc_ast::Inline::Quoted(_, inner)
18 | pandoc_ast::Inline::Cite(_, inner)
19 | pandoc_ast::Inline::Link(_, inner, _)
20 | pandoc_ast::Inline::Image(_, inner, _)
21 | pandoc_ast::Inline::Span(_, inner) => pandoc_stringify_(result, inner),
22 pandoc_ast::Inline::Space => result.push(' '),
23 pandoc_ast::Inline::SoftBreak => todo!(),
24 pandoc_ast::Inline::LineBreak => todo!(),
25 pandoc_ast::Inline::RawInline(_, _) => todo!(),
26 pandoc_ast::Inline::Note(_) => todo!(),
27 }
28 }
29 }
30
31 let mut result = String::new();
32 pandoc_stringify_(&mut result, inlines);
33 result
34}
35
36/// Follows the algorithm specified in the Pandoc manual[1]
37///
38/// [1]: <https://pandoc.org/MANUAL.html#extension-auto_identifiers>
39#[derive(Debug)]
40pub struct AutoIdentifier(pub String);
41
42impl std::ops::Deref for AutoIdentifier {
43 type Target = String;
44
45 fn deref(&self) -> &Self::Target {
46 &self.0
47 }
48}
49
50impl From<AutoIdentifier> for String {
51 fn from(id: AutoIdentifier) -> Self {
52 id.0
53 }
54}
55
56impl From<&[pandoc_ast::Inline]> for AutoIdentifier {
57 fn from(inlines: &[pandoc_ast::Inline]) -> Self {
58 let text = pandoc_stringify(inlines);
59 AutoIdentifier::from(text.as_str())
60 }
61}
62
63impl From<&str> for AutoIdentifier {
64 fn from(text: &str) -> Self {
65 let id = text
66 .chars()
67 .skip_while(|ch| !ch.is_alphabetic())
68 .filter_map(|ch| {
69 if !ch.is_ascii_alphanumeric()
70 && !ch.is_whitespace()
71 && ch != '_'
72 && ch != '-'
73 && ch != '.'
74 {
75 return None;
76 }
77
78 if ch.is_whitespace() {
79 return Some('-');
80 }
81
82 Some(ch.to_ascii_lowercase())
83 })
84 .collect();
85
86 AutoIdentifier(id)
87 }
88}
89
90pub trait PandocOutputExt {
91 fn buffer(self) -> String;
92 fn file(self) -> PathBuf;
93}
94
95impl PandocOutputExt for pandoc::PandocOutput {
96 fn buffer(self) -> String {
97 match self {
98 pandoc::PandocOutput::ToBuffer(buffer) => buffer,
99 pandoc::PandocOutput::ToBufferRaw(_) => {
100 panic!("Expected text pandoc output, found binary format")
101 }
102 pandoc::PandocOutput::ToFile(_) => {
103 panic!("Expected buffered pandoc output, found file")
104 }
105 }
106 }
107
108 fn file(self) -> PathBuf {
109 match self {
110 pandoc::PandocOutput::ToFile(file) => file,
111 _ => panic!("Expected file pandoc output, found buffer"),
112 }
113 }
114}
115
116pub type PandocMeta = pandoc_ast::Map<String, pandoc_ast::MetaValue>;