From 517cabe8ec54d0bf5f5f9cc9089d76a1fad7bb6a Mon Sep 17 00:00:00 2001 From: Minijackson Date: Sun, 7 Nov 2021 23:09:34 +0100 Subject: initial commit with PoC --- .gitignore | 3 + Cargo.lock | 729 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 22 ++ flake.lock | 27 +++ flake.nix | 14 ++ src/build.rs | 451 +++++++++++++++++++++++++++++++++++ src/cli.rs | 21 ++ src/config.rs | 59 +++++ src/filters.rs | 115 +++++++++ src/main.rs | 93 ++++++++ src/utils.rs | 116 +++++++++ 11 files changed, 1650 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 src/build.rs create mode 100644 src/cli.rs create mode 100644 src/config.rs create mode 100644 src/filters.rs create mode 100644 src/main.rs create mode 100644 src/utils.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a8ea10b --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +result +result-* +/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 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "backtrace" +version = "0.3.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "321629d8ba6513061f26707241fa9bc89524ff1cd7a915a97ef0c62c666ce1b6" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "cc" +version = "1.0.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79c2681d6594606957bbb8631c4b90a7fcaaa72cdb714743a437b156d6a7eedd" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "3.0.0-beta.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "feff3878564edb93745d58cf63e17b63f24142506e7a20c87a5521ed7bfb1d63" +dependencies = [ + "atty", + "bitflags", + "clap_derive", + "indexmap", + "lazy_static", + "os_str_bytes", + "strsim", + "termcolor", + "textwrap", + "unicase", +] + +[[package]] +name = "clap_derive" +version = "3.0.0-beta.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b15c6b4f786ffb6192ffe65a36855bc1fc2444bcd0945ae16748dcd6ed7d0d3" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "color-eyre" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f1885697ee8a177096d42f158922251a41973117f6d8a234cee94b9509157b7" +dependencies = [ + "backtrace", + "color-spantrace", + "eyre", + "indenter", + "once_cell", + "owo-colors", + "tracing-error", +] + +[[package]] +name = "color-spantrace" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6eee477a4a8a72f4addd4de416eb56d54bc307b284d6601bafdee1f4ea462d1" +dependencies = [ + "once_cell", + "owo-colors", + "tracing-core", + "tracing-error", +] + +[[package]] +name = "config" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1b9d958c2b1368a663f05538fc1b5975adce1e19f435acceae987aceeeb369" +dependencies = [ + "lazy_static", + "nom", + "rust-ini", + "serde 1.0.130", + "serde-hjson", + "serde_json", + "toml", + "yaml-rust", +] + +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + +[[package]] +name = "env_logger" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "eyre" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "221239d1d5ea86bf5d6f91c9d6bc3646ffe471b08ff9b0f91c44f115ac969d2b" +dependencies = [ + "indenter", + "once_cell", +] + +[[package]] +name = "gimli" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" + +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" + +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + +[[package]] +name = "indexmap" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "itertools" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f56a2d0bc861f9165be4eb3442afd3c236d8a98afd426f65d92324ae1091a484" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "lexical-core" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe" +dependencies = [ + "arrayvec", + "bitflags", + "cfg-if", + "ryu", + "static_assertions", +] + +[[package]] +name = "libc" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a60553f9a9e039a333b4e9b20573b9e9b9c0bb3a11e201ccc48ef4283456d673" + +[[package]] +name = "linked-hash-map" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" + +[[package]] +name = "log" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "memchr" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" + +[[package]] +name = "miniz_oxide" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" +dependencies = [ + "adler", + "autocfg", +] + +[[package]] +name = "nom" +version = "5.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" +dependencies = [ + "lexical-core", + "memchr", + "version_check", +] + +[[package]] +name = "num-traits" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31" +dependencies = [ + "num-traits 0.2.14", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + +[[package]] +name = "object" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67ac1d3f9a1d3616fd9a60c8d74296f22406a238b6a72f5cc1e6f314df4ffbf9" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" + +[[package]] +name = "os_str_bytes" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addaa943333a514159c80c97ff4a93306530d965d27e139188283cd13e06a799" +dependencies = [ + "memchr", +] + +[[package]] +name = "owo-colors" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2386b4ebe91c2f7f51082d4cefa145d030e33a1842a96b12e4885cc3c01f7a55" + +[[package]] +name = "pandoc" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c5ab1abdd81ed62e852d412d18964925e42c8f154c61e89c6296b1a06b6daeb" +dependencies = [ + "itertools", +] + +[[package]] +name = "pandoc-docbook" +version = "0.1.0" +dependencies = [ + "clap", + "color-eyre", + "config", + "env_logger", + "eyre", + "lazy_static", + "log", + "pandoc", + "pandoc_ast", + "serde 1.0.130", +] + +[[package]] +name = "pandoc_ast" +version = "0.8.0" +source = "git+https://github.com/oli-obk/pandoc-ast?rev=d73a7d0a065f568d60bcee6cff2340f75065273e#d73a7d0a065f568d60bcee6cff2340f75065273e" +dependencies = [ + "serde 1.0.130", + "serde_derive", + "serde_json", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443" + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" + +[[package]] +name = "rust-ini" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e52c148ef37f8c375d49d5a73aa70713125b7f19095948a923f80afdeb22ec2" + +[[package]] +name = "rustc-demangle" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" + +[[package]] +name = "ryu" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" + +[[package]] +name = "serde" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dad3f759919b92c3068c696c15c3d17238234498bbdcc80f2c469606f948ac8" + +[[package]] +name = "serde" +version = "1.0.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-hjson" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a3a4e0ea8a88553209f6cc6cfe8724ecad22e1acf372793c27d995290fe74f8" +dependencies = [ + "lazy_static", + "num-traits 0.1.43", + "regex", + "serde 0.8.23", +] + +[[package]] +name = "serde_derive" +version = "1.0.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e466864e431129c7e0d3476b92f20458e5879919a0596c6472738d9fa2d342f8" +dependencies = [ + "itoa", + "ryu", + "serde 1.0.130", +] + +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2afee18b8beb5a596ecb4a2dce128c719b4ba399d34126b9e4396e3f9860966" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "termcolor" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "thread_local" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd" +dependencies = [ + "once_cell", +] + +[[package]] +name = "toml" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +dependencies = [ + "serde 1.0.130", +] + +[[package]] +name = "tracing" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "375a639232caf30edfc78e8d89b2d4c375515393e7af7e16f01cd96917fb2105" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f480b8f81512e825f337ad51e94c1eb5d3bbdf2b363dcd01e2b19a9ffe3f8e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f4ed65637b8390770814083d20756f87bfa2c21bf2f110babdc5438351746e4" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "tracing-error" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4d7c0b83d4a500748fa5879461652b361edf5c9d51ede2a2ac03875ca185e24" +dependencies = [ + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "tracing-subscriber" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71" +dependencies = [ + "sharded-slab", + "thread_local", + "tracing-core", +] + +[[package]] +name = "unicase" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicode-segmentation" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" + +[[package]] +name = "unicode-width" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "version_check" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..365b07b --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "pandoc-docbook" +version = "0.1.0" +edition = "2021" +repository = "https://github.com/minijackson/pandoc-docbook" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +clap = "3.0.0-beta.5" +color-eyre = "0.5" +config = { version = "0.11", features = [ "toml" ] } +env_logger = "0.9" +eyre = "0.6" +lazy_static = "1" +log = "0.4" +pandoc = "0.8" +#pandoc_ast = "0.8" +# Needed: https://github.com/oli-obk/pandoc-ast/pull/12 +# TODO: change this when new version is released +pandoc_ast = { git = "https://github.com/oli-obk/pandoc-ast", rev = "d73a7d0a065f568d60bcee6cff2340f75065273e" } +serde = { 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 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1636138231, + "narHash": "sha256-FZQ5wBcB9zI21uQMB/PaEQ/cLYk+Bwnig5cg1+2rPgg=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "5c02380de3951d0237807c16eb19873cb3c5f75d", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-21.05", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..c917cd5 --- /dev/null +++ b/flake.nix @@ -0,0 +1,14 @@ +{ + description = "A very basic flake"; + + inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-21.05"; + + outputs = { self, nixpkgs }: let + pkgs = nixpkgs.legacyPackages.x86_64-linux; + in { + + devShell.x86_64-linux = pkgs.mkShell { + }; + + }; +} 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 @@ +use std::path::{Path, PathBuf}; + +use eyre::{eyre, ContextCompat, Result, WrapErr}; +use log::{debug, error, log_enabled, trace, warn}; +use pandoc_ast::MutVisitor; + +use crate::{ + filters, + utils::{AutoIdentifier, PandocMeta, PandocOutputExt}, +}; + +pub fn do_build(config: &crate::config::Config) -> Result<()> { + let summary = Summary::try_from_file(&config.book.summary)?; + let source_root = Path::new(&config.book.summary) + .parent() + .expect("Summary has no parent"); + let files = summary.collect_source_files(source_root)?; + + let build_dir = Path::new(&config.build.build_dir); + trace!("Creating build directory: '{}'", build_dir.display()); + std::fs::create_dir_all(build_dir).wrap_err_with(|| { + format!( + "Could not create build directory: '{}'", + build_dir.display() + ) + })?; + + // Pre-create files so that we know which links to relativize + for SourceFile { path, .. } in &files { + let output_file = build_dir.join(path.with_extension("html")); + + let product_dir = build_dir.join(path.parent().expect("Source file has no parent")); + trace!("Creating product directory: '{}'", product_dir.display()); + std::fs::create_dir_all(&product_dir).wrap_err_with(|| { + format!( + "Could not create build output directory: '{}'", + product_dir.display() + ) + })?; + + std::fs::OpenOptions::new() + .write(true) + .create(true) + .open(&output_file) + .wrap_err_with(|| { + format!("Failed to create output file: '{}'", output_file.display()) + })?; + } + + for SourceFile { path, source } in &files { + let mut pandoc_command = pandoc::new(); + + let output_file = build_dir.join(path.with_extension("html")); + + debug!("Generating file: '{}'", output_file.display()); + + // To be captured in the filter + let config_clone = config.clone(); + let source_dir = path + .parent() + .expect("Source file has no parent") + .to_path_buf(); + let build_dir_clone = build_dir.to_path_buf(); + let summary_clone = summary.source.clone(); + + pandoc_command + .set_input(pandoc::InputKind::Pipe(source.to_json())) + .set_input_format(pandoc::InputFormat::Json, vec![]) + .set_output(pandoc::OutputKind::File(output_file)) + .set_output_format(pandoc::OutputFormat::Html5, vec![]) + .add_options(&[pandoc::PandocOption::SelfContained]) + .add_filter(move |source| { + let level = source_dir + .components() + .skip_while(|c| matches!(c, std::path::Component::CurDir)) + .count(); + + let mut insert_summary_filter = filters::InsertSummary { + level, + summary: &summary_clone, + }; + + let mut relativize_urls_filter = filters::RelativizeUrls { + config: &config_clone, + // TODO: other output formats + extension: "html", + build_dir: &build_dir_clone, + source_dir: &source_dir, + }; + + let mut source = pandoc_ast::Pandoc::from_json(&source); + insert_summary_filter.walk_pandoc(&mut source); + relativize_urls_filter.walk_pandoc(&mut source); + source.to_json() + }); + + if log_enabled!(log::Level::Trace) { + pandoc_command.set_show_cmdline(true); + } + + pandoc_command + .execute() + .wrap_err_with(|| format!("Failed to generate output of: '{}'", path.display()))?; + } + + Ok(()) +} + +// TODO: move that into generated.rs +fn generate_source( + title: Vec, + children: Vec<(PandocMeta, PathBuf)>, + level: usize, +) -> Result { + // TODO: make that text configurable + let mut content = vec![pandoc_ast::Block::Para(vec![pandoc_ast::Inline::Str( + "Here are the articles in this section:".to_string(), + )])]; + + for (mut child, file) in children { + let title = match child.remove("title") { + None => { + warn!("Missing title for file: '{}'", file.display()); + vec![pandoc_ast::Inline::Str("Untitled page".to_string())] + } + Some(pandoc_ast::MetaValue::MetaInlines(inlines)) => inlines, + Some(pandoc_ast::MetaValue::MetaString(s)) => { + vec![pandoc_ast::Inline::Str(s)] + } + // TODO: check that other values are actually invalid + _ => { + error!("Invalid value for title"); + vec![pandoc_ast::Inline::Str("Untitled page".to_string())] + } + }; + + let link_target = std::iter::repeat(std::path::Component::ParentDir) + .take(level) + .collect::() + .join(file); + + content.push(pandoc_ast::Block::Para(vec![pandoc_ast::Inline::Link( + // TODO: attribute to recognize big links? + (String::new(), vec![], vec![]), + title, + ( + link_target + .to_str() + .expect("Filename contains invalid unicode") + .to_string(), + String::new(), + ), + )])); + } + + let mut meta = PandocMeta::new(); + meta.insert( + "title".to_string(), + pandoc_ast::MetaValue::MetaInlines(title), + ); + + Ok(pandoc_ast::Pandoc { + meta, + blocks: content, + pandoc_api_version: vec![1, 22], + }) +} + +fn list_content(block: &mut pandoc_ast::Block) -> Result<&mut Vec>> { + match block { + pandoc_ast::Block::OrderedList(_, list) => Ok(list), + pandoc_ast::Block::BulletList(list) => Ok(list), + _ => Err(eyre!("Expected list in summary, found something else")), + } +} + +fn try_into_node_vec(vec: &mut Vec>) -> Result> { + vec.iter_mut().map(Node::try_from_vec_block).collect() +} + +// TODO: support separators like these: +// --------- + +#[derive(Debug)] +pub struct Summary { + source: pandoc_ast::Pandoc, + nodes: Vec, +} + +#[derive(Debug)] +struct SourceFile { + path: PathBuf, + source: pandoc_ast::Pandoc, +} + +// TODO: move that into summary.rs +impl Summary { + fn try_from_file(file: &str) -> Result { + debug!("Parsing summary"); + let mut pandoc_command = pandoc::new(); + pandoc_command + .add_input(file) + .set_output_format(pandoc::OutputFormat::Json, vec![]) + .set_output(pandoc::OutputKind::Pipe); + + trace!("Launching pandoc command"); + + if log_enabled!(log::Level::Trace) { + pandoc_command.set_show_cmdline(true); + } + + let output = pandoc_command + .execute() + .wrap_err("Could not execute pandoc")? + .buffer(); + + let document = pandoc_ast::Pandoc::from_json(&output); + + let summary: Self = document.try_into()?; + if summary.has_files_missing( + Path::new(file) + .parent() + .expect("Summary file has no parent"), + ) { + return Err(eyre!("Files from the summary are missing, aborting")); + } + + Ok(summary) + } + + fn has_files_missing(&self, root: &Path) -> bool { + // Do not use `.any()` to prevent short-circuiting, we want to report all missing files + self.nodes.iter().fold(false, |acc, node| { + let missing = node.has_files_missing(root); + acc || missing + }) + } + + /// Get a list of source files. + /// + /// If a file is a generated file, generate it and store it in memory. + fn collect_source_files(&self, root: &Path) -> Result> { + let mut result = Vec::new(); + + for node in &self.nodes { + node.collect_source_files(&mut result, root, Path::new("."), 0)?; + } + + Ok(result) + } +} + +impl TryFrom for Summary { + type Error = eyre::Error; + + fn try_from(mut document: pandoc_ast::Pandoc) -> Result { + if document.blocks.len() != 1 { + return Err(eyre!("Summary does not contain a single list")); + } + + let root = &mut document.blocks[0]; + + let list = list_content(root)?; + + let nodes = list + .iter_mut() + .map(Node::try_from_vec_block) + .collect::>()?; + + Ok(Summary { + source: document, + nodes, + }) + } +} + +#[derive(Debug)] +pub enum Node { + Provided { + file: String, + children: Vec, + }, + Generated { + file: String, + title: Vec, + children: Vec, + }, +} + +impl Node { + fn children(&self) -> &[Node] { + match self { + Node::Provided { children, .. } => children, + Node::Generated { children, .. } => children, + } + } + + fn has_files_missing(&self, root: &Path) -> bool { + if let Node::Provided { file, .. } = self { + if !root.join(file).exists() { + error!("File '{}' specified in summary does not exists", file); + return true; + } + } + + // Do not use `.any()` to prevent short-circuiting, we want to report all missing files + self.children().iter().fold(false, |acc, node| { + let missing = node.has_files_missing(root); + acc || missing + }) + } + + fn collect_source_files( + &self, + result: &mut Vec, + root: &Path, + parent: &Path, + level: usize, + ) -> Result<()> { + let new_parent; + let children_; + let path; + let source: Box _>; + + match self { + Node::Provided { file, children } => { + trace!("Parsing file: '{}'", file); + + // TODO: some filters here? not all filters, since we may want to filter generated + // files too + let mut pandoc_command = pandoc::new(); + pandoc_command + .add_input(&root.join(file)) + .set_output(pandoc::OutputKind::Pipe) + .set_output_format(pandoc::OutputFormat::Json, vec![]); + + if log_enabled!(log::Level::Trace) { + pandoc_command.set_show_cmdline(true); + } + + let raw_source = pandoc_command + .execute() + .wrap_err_with(|| format!("Failed to parse '{}'", file))? + .buffer(); + source = Box::new(move |_| Ok(pandoc_ast::Pandoc::from_json(&raw_source))); + + let file = Path::new(&file); + let stem = file.file_stem().expect("No file name"); + let id = + AutoIdentifier::from(stem.to_str().wrap_err("Invalid unicode in file name")?); + + path = file.into(); + new_parent = file.parent().expect("Source file has no parent").join(&*id); + children_ = children; + } + + Self::Generated { + file, + title, + children, + } => { + trace!("Found file to generate: '{}'", file); + + path = file.into(); + + source = Box::new(move |direct_children| { + generate_source(title.clone(), direct_children, level) + }); + new_parent = Path::new(file).with_extension(""); + children_ = children; + } + }; + + let mut direct_children = Vec::with_capacity(children_.len()); + + for child in children_ { + child.collect_source_files(result, root, &new_parent, level + 1)?; + let direct_child = result.last().unwrap(); + direct_children.push((direct_child.source.meta.clone(), direct_child.path.clone())); + } + + result.push(SourceFile { + path, + source: source(direct_children)?, + }); + + Ok(()) + } + + // Wil also modify the block to linkify generated pages + fn try_from_vec_block(value: &mut Vec) -> Result { + if value.len() != 1 && value.len() != 2 { + // TODO: better error message? + return Err(eyre!("Summary does not contain a single list")); + } + + let mut value = value.iter_mut(); + + let item = match value.next().unwrap() { + pandoc_ast::Block::Plain(inlines) => inlines, + pandoc_ast::Block::Para(inlines) => inlines, + _ => return Err(eyre!("List item is not a link or plain text")), + }; + + if item.is_empty() { + return Err(eyre!("Summary list items cannot be empty")); + } + + let children = if let Some(children) = value.next() { + try_into_node_vec(list_content(children)?)? + } else { + vec![] + }; + + match &item[0] { + pandoc_ast::Inline::Link(_, _, target) => { + if item.len() != 1 { + return Err(eyre!("Summary list item not a single link or plain text")); + } + + let file = target.0.clone(); + + Ok(Node::Provided { file, children }) + } + _ => { + let title = item.clone(); + + let id = AutoIdentifier::from(title.as_slice()); + + // TODO: missing parent + + // Move generate page into this pass + //let mut file = parent.join(&*id); + //file.set_extension("md"); + + // TODO: Attribute to style them differently + *item = vec![pandoc_ast::Inline::Link( + (String::new(), vec!["generated".to_string()], vec![]), + item.clone(), + (id.0.clone(), String::new()), + )]; + + Ok(Node::Generated { + file: id.0, + title, + children, + }) + } + } + } +} 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 @@ +use clap::{AppSettings, Parser}; + +// TODO: document + +#[derive(Debug, Parser)] +#[clap(setting = AppSettings::InferSubcommands)] +pub struct Cli { + #[clap(short, long, default_value = "pdbook.toml")] + pub config: String, + #[clap(short, long)] + pub quiet: bool, + #[clap(short, long, parse(from_occurrences))] + pub verbose: u8, + #[clap(subcommand)] + pub subcommand: SubCommand, +} + +#[derive(Debug, Parser)] +pub enum SubCommand { + Build, +} 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 @@ +use log::debug; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct Config { + #[serde(default)] + pub book: BookConfig, + #[serde(default)] + pub build: BuildConfig, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct BookConfig { + #[serde(default = "default_summary")] + pub summary: String, +} + +impl Default for BookConfig { + fn default() -> Self { + Self { + summary: default_summary(), + } + } +} + +fn default_summary() -> String { + "src/_summary.md".to_string() +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct BuildConfig { + #[serde(default = "default_build_dir")] + pub build_dir: String, +} + +impl Default for BuildConfig { + fn default() -> Self { + Self { + build_dir: default_build_dir(), + } + } +} + +fn default_build_dir() -> String { + "pdbook".to_string() +} + +impl Config { + pub fn new(config_file: &str) -> Result { + let mut s = config::Config::default(); + + debug!("Parsing config file: {}", config_file); + s.merge(config::File::with_name(config_file))?; + debug!("Parsing config from environment"); + s.merge(config::Environment::with_prefix("PANDOC_DOCBOOK").separator("_"))?; + + s.try_into() + } +} 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 @@ +use std::{collections::HashMap, path::Path}; + +use log::trace; +use pandoc_ast::MutVisitor; + +// If a link points to `./a/b/c.ext`, and a file in the output directory `pdbook/./a/b/c.html` +// exists, rewrite that link. +pub struct RelativizeUrls<'a> { + pub config: &'a crate::config::Config, + pub extension: &'a str, + pub build_dir: &'a Path, + pub source_dir: &'a Path, +} + +impl<'a> pandoc_ast::MutVisitor for RelativizeUrls<'a> { + fn walk_inline(&mut self, inline: &mut pandoc_ast::Inline) { + let link = match inline { + pandoc_ast::Inline::Link(_, _, target) => &mut target.0, + _ => return, + }; + + if link.starts_with('#') || link.contains("://") { + return; + } + + let link_path = self.source_dir.join(&link); + + if link_path.is_absolute() { + return; + } + + let mut output_path = self.build_dir.join(&link_path); + if !output_path.set_extension(self.extension) { + return; + } + + trace!("Checking output_path: {:?}", output_path); + + // TODO: warn when referencing a "markdown or other" file not specified in the summary + if output_path.exists() { + // TODO: relativize from URL root + + trace!("Relativizing link '{}'", link_path.display()); + + *link = Path::new(link) + .with_extension(&self.extension) + .to_str() + .expect("Path constructed from UTF-8 valid strings in not UTF-8 valid") + .to_string(); + + trace!("-> into '{}'", link); + } + } +} + +// Applied just to the summary +pub struct RelativizeSummary { + level: usize, +} + +impl pandoc_ast::MutVisitor for RelativizeSummary { + fn walk_inline(&mut self, inline: &mut pandoc_ast::Inline) { + if self.level == 0 { + return; + } + + let link = match inline { + pandoc_ast::Inline::Link(_, _, target) => &mut target.0, + _ => return, + }; + + // Assume link is to a managed file + for _ in 0..self.level { + link.insert_str(0, "../"); + } + } +} + +pub fn relativize_summary(summary: &pandoc_ast::Pandoc, level: usize) -> pandoc_ast::Pandoc { + use std::sync::RwLock; + + lazy_static::lazy_static! { + static ref CACHE: RwLock> = RwLock::new(HashMap::new()); + } + + CACHE + .write() + .expect("Relativized summary cache poison") + .entry(level) + .or_insert_with(|| { + let mut summary = summary.clone(); + RelativizeSummary { level }.walk_pandoc(&mut summary); + summary + }) + .clone() +} + +pub struct InsertSummary<'a> { + pub summary: &'a pandoc_ast::Pandoc, + pub level: usize, +} + +impl<'a> pandoc_ast::MutVisitor for InsertSummary<'a> { + fn walk_pandoc(&mut self, pandoc: &mut pandoc_ast::Pandoc) { + let summary = relativize_summary(self.summary, self.level); + + pandoc.blocks.insert( + 0, + pandoc_ast::Block::Div( + (String::new(), vec!["summary".to_string()], vec![]), + summary.blocks, + ), + ); + } +} 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 @@ +mod build; +mod cli; +mod config; +mod utils; +mod filters; + +use std::path::PathBuf; + +use cli::Cli; + +use clap::Parser; +use eyre::{Result, WrapErr}; +use log::trace; + +fn init_log(quiet: bool, verbosity: u8) { + use log::LevelFilter; + + let verbosity = match verbosity { + 0 => LevelFilter::Info, + 1 => LevelFilter::Debug, + _ => LevelFilter::Trace, + }; + + let env = env_logger::Env::default().default_filter_or(if quiet { + "off".to_string() + } else { + format!("pandoc_docbook={}", verbosity) + }); + + let mut builder = env_logger::Builder::from_env(env); + + // Shamelessly taken from pretty_env_logger + builder.format(move |f, record| { + use std::io::Write; + + let target = record.target(); + + let mut style = f.style(); + let level = colored_level(&mut style, record.level()); + + let mut style = f.style(); + let target = style.set_bold(true).value(target); + + if verbosity >= LevelFilter::Debug { + writeln!(f, " {} {} > {}", level, target, record.args()) + } else { + writeln!(f, " {} > {}", level, record.args()) + } + }); + + builder.init(); +} + +fn colored_level<'a>( + style: &'a mut env_logger::fmt::Style, + level: log::Level, +) -> env_logger::fmt::StyledValue<'a, &'static str> { + use env_logger::fmt::Color; + use log::Level; + + match level { + Level::Trace => style.set_color(Color::Magenta).value("TRACE"), + Level::Debug => style.set_color(Color::Blue).value("DEBUG"), + Level::Info => style.set_color(Color::Green).value("INFO "), + Level::Warn => style.set_color(Color::Yellow).value("WARN "), + Level::Error => style.set_color(Color::Red).value("ERROR"), + } +} + +fn main() -> Result<()> { + color_eyre::install()?; + + let cli = Cli::parse(); + init_log(cli.quiet, cli.verbose); + + let config = config::Config::new(&cli.config)?; + trace!("Parsed configuration: {:?}", config); + + std::env::set_current_dir( + PathBuf::from(cli.config) + .parent() + .expect("Configuration file has no parent"), + ) + .wrap_err("Could not change current directory to the configuration file's directory")?; + + match cli.subcommand { + cli::SubCommand::Build => { + build::do_build(&config)? + } + } + + Ok(()) +} 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 @@ +use std::path::PathBuf; + +pub fn pandoc_stringify(inlines: &[pandoc_ast::Inline]) -> String { + fn pandoc_stringify_(result: &mut String, inlines: &[pandoc_ast::Inline]) { + for inline in inlines { + match inline { + pandoc_ast::Inline::Str(s) + | pandoc_ast::Inline::Code(_, s) + | pandoc_ast::Inline::Math(_, s) => result.push_str(s), + pandoc_ast::Inline::Emph(inner) + | pandoc_ast::Inline::Underline(inner) + | pandoc_ast::Inline::Strong(inner) + | pandoc_ast::Inline::Strikeout(inner) + | pandoc_ast::Inline::Superscript(inner) + | pandoc_ast::Inline::Subscript(inner) + | pandoc_ast::Inline::SmallCaps(inner) + | pandoc_ast::Inline::Quoted(_, inner) + | pandoc_ast::Inline::Cite(_, inner) + | pandoc_ast::Inline::Link(_, inner, _) + | pandoc_ast::Inline::Image(_, inner, _) + | pandoc_ast::Inline::Span(_, inner) => pandoc_stringify_(result, inner), + pandoc_ast::Inline::Space => result.push(' '), + pandoc_ast::Inline::SoftBreak => todo!(), + pandoc_ast::Inline::LineBreak => todo!(), + pandoc_ast::Inline::RawInline(_, _) => todo!(), + pandoc_ast::Inline::Note(_) => todo!(), + } + } + } + + let mut result = String::new(); + pandoc_stringify_(&mut result, inlines); + result +} + +/// Follows the algorithm specified in the Pandoc manual[1] +/// +/// [1]: +#[derive(Debug)] +pub struct AutoIdentifier(pub String); + +impl std::ops::Deref for AutoIdentifier { + type Target = String; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl From for String { + fn from(id: AutoIdentifier) -> Self { + id.0 + } +} + +impl From<&[pandoc_ast::Inline]> for AutoIdentifier { + fn from(inlines: &[pandoc_ast::Inline]) -> Self { + let text = pandoc_stringify(inlines); + AutoIdentifier::from(text.as_str()) + } +} + +impl From<&str> for AutoIdentifier { + fn from(text: &str) -> Self { + let id = text + .chars() + .skip_while(|ch| !ch.is_alphabetic()) + .filter_map(|ch| { + if !ch.is_ascii_alphanumeric() + && !ch.is_whitespace() + && ch != '_' + && ch != '-' + && ch != '.' + { + return None; + } + + if ch.is_whitespace() { + return Some('-'); + } + + Some(ch.to_ascii_lowercase()) + }) + .collect(); + + AutoIdentifier(id) + } +} + +pub trait PandocOutputExt { + fn buffer(self) -> String; + fn file(self) -> PathBuf; +} + +impl PandocOutputExt for pandoc::PandocOutput { + fn buffer(self) -> String { + match self { + pandoc::PandocOutput::ToBuffer(buffer) => buffer, + pandoc::PandocOutput::ToBufferRaw(_) => { + panic!("Expected text pandoc output, found binary format") + } + pandoc::PandocOutput::ToFile(_) => { + panic!("Expected buffered pandoc output, found file") + } + } + } + + fn file(self) -> PathBuf { + match self { + pandoc::PandocOutput::ToFile(file) => file, + _ => panic!("Expected file pandoc output, found buffer"), + } + } +} + +pub type PandocMeta = pandoc_ast::Map; -- cgit v1.2.3