git-ents.gitmain
⌘K
foforge
commit dce5094
roots: render Markdown and AsciiDoc blobs

Ports pre-redo:crates/git-ents-server/src/markdown.rs and asciidoc.rs into ents-web, adapted to this crate own Result/Error and maud::Markup shapes. acdc-converters-core and acdc-converters-html are not on crates.io, so they are git dependencies on github.com/nlopes/acdc pinned to the same rev pre-redo used (6ae19bc2e6f0fa4254a3e9ebd1c3d2f6c7caafde); acdc-converters-terminal is not taken, since this crate has no terminal-recording rendering. pulldown-cmark stays on the 0.13 line pre-redo pinned.

Carries over the one non-obvious gotcha from the pre-redo port: acdc embedded render mode drops the visible doctitle (Title heading) and subtitle, so to_html reconstructs an h1/doc-subtitle from the parsed doc.header and prepends it to the embedded body.

Neither renderer output is sanitized beyond what the renderer itself guarantees (pulldown-cmark well-formed HTML from the parsed tree, acdc own HTML converter) — matching the pre-redo version stance, which emitted both unescaped via PreEscaped.

Both modules are unused until the next commit wires them into the file browser blob view.

Assisted-by: Claude:claude-sonnet-5

Joseph D. Carpinelli · 1 month ago

Reviews

No reviews of this commit yet — record a verdict below.

Start a review

verdict

Cargo.lock @@ -2,6 +2,77 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "acdc-converters-core" +version = "0.1.0" +source = "git+https://github.com/nlopes/acdc?rev=6ae19bc2e6f0fa4254a3e9ebd1c3d2f6c7caafde#6ae19bc2e6f0fa4254a3e9ebd1c3d2f6c7caafde" +dependencies = [ + "acdc-parser", + "bitflags 2.13.0", + "thiserror 2.0.18", + "tracing", +] + +[[package]] +name = "acdc-converters-html" +version = "0.1.0" +source = "git+https://github.com/nlopes/acdc?rev=6ae19bc2e6f0fa4254a3e9ebd1c3d2f6c7caafde#6ae19bc2e6f0fa4254a3e9ebd1c3d2f6c7caafde" +dependencies = [ + "acdc-converters-core", + "acdc-converters-terminal", + "acdc-parser", + "base64", + "chrono", + "sha2 0.11.0", + "thiserror 2.0.18", + "tracing", +] + +[[package]] +name = "acdc-converters-terminal" +version = "0.1.0" +source = "git+https://github.com/nlopes/acdc?rev=6ae19bc2e6f0fa4254a3e9ebd1c3d2f6c7caafde#6ae19bc2e6f0fa4254a3e9ebd1c3d2f6c7caafde" +dependencies = [ + "acdc-converters-core", + "acdc-parser", + "asciicast-rs", + "comfy-table", + "crossterm", + "libghostty-vt", + "serde", + "serde_json", + "syntect", + "thiserror 2.0.18", + "tracing", + "unicode-width", +] + +[[package]] +name = "acdc-parser" +version = "0.9.0" +source = "git+https://github.com/nlopes/acdc?rev=6ae19bc2e6f0fa4254a3e9ebd1c3d2f6c7caafde#6ae19bc2e6f0fa4254a3e9ebd1c3d2f6c7caafde" +dependencies = [ + "bitflags 2.13.0", + "bumpalo", + "csv", + "encoding_rs", + "evalexpr", + "peg", + "rustc-hash", + "self_cell", + "serde", + "serde_json", + "thiserror 2.0.18", + "tracing", + "url", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + [[package]] name = "aho-corasick" version = "1.1.4" @@ -17,6 +88,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "ansi-str" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "060de1453b69f46304b28274f382132f4e72c55637cf362920926a70d090890d" +dependencies = [ + "ansitok", +] + +[[package]] +name = "ansitok" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0a8acea8c2f1c60f0a92a8cd26bf96ca97db56f10bbcab238bbe0cceba659ee" +dependencies = [ + "nom", + "vte", +] + [[package]] name = "arc-swap" version = "1.9.2" @@ -42,6 +141,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" +[[package]] +name = "asciicast-rs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0ba000776cf478263576bca61217ca5557749c55a8a2d2daf973047188be03" +dependencies = [ + "rgb", + "ruzstd", + "serde", + "serde_json", + "thiserror 2.0.18", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -112,12 +224,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "base64ct" version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "bit-set" version = "0.8.0" @@ -154,6 +281,15 @@ "generic-array", ] +[[package]] +name = "block-buffer" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" +dependencies = [ + "hybrid-array", +] + [[package]] name = "bstr" version = "1.12.3" @@ -222,6 +358,16 @@ "thiserror 2.0.18", ] +[[package]] +name = "cc" +version = "1.2.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17dd265a7d0f31ef544e1b20e03add05d3b45b491b633b10d67145d2acc1a38" +dependencies = [ + "find-msvc-tools", + "shlex", +] + [[package]] name = "cfg-if" version = "1.0.4" @@ -234,13 +380,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-link", +] + [[package]] name = "cipher" version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ - "crypto-common", + "crypto-common 0.1.7", "inout", ] @@ -253,6 +412,31 @@ "hashbrown 0.16.1", ] +[[package]] +name = "comfy-table" +version = "7.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "958c5d6ecf1f214b4c2bbbbf6ab9523a864bd136dcf71a7e8904799acfe1ad47" +dependencies = [ + "ansi-str", + "console", + "crossterm", + "unicode-segmentation", + "unicode-width", +] + +[[package]] +name = "console" +version = "0.16.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fe5f465a4f6fee88fad41b85d990f84c835335e85b5d9e6e63e0d06d28cba7c" +dependencies = [ + "encode_unicode", + "libc", + "unicode-width", + "windows-sys 0.61.2", +] + [[package]] name = "const-fnv1a-hash" version = "1.1.0" @@ -265,6 +449,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + [[package]] name = "convert_case" version = "0.10.0" @@ -280,6 +470,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "980c2afde4af43d6a05c5be738f9eae595cff86dce1f38f88b95058a98c027f3" +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -289,6 +485,15 @@ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + [[package]] name = "crc32fast" version = "1.5.0" @@ -362,6 +567,36 @@ "typenum", ] +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "csv" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52cd9d68cf7efc6ddfaaee42e7288d3a99d613d4b50f76ce9827ae0c6e14f938" +dependencies = [ + "csv-core", + "itoa", + "ryu", + "serde_core", +] + +[[package]] +name = "csv-core" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704a3c26996a80471189265814dbc2c257598b96b8a7feae2d31ace646bb9782" +dependencies = [ + "memchr", +] + [[package]] name = "curve25519-dalek" version = "4.1.3" @@ -369,9 +604,9 @@ checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "curve25519-dalek-derive", - "digest", + "digest 0.10.7", "fiat-crypto", "rustc_version", "subtle", @@ -439,10 +674,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ - "const-oid", + "const-oid 0.9.6", "zeroize", ] +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" + [[package]] name = "derive_more" version = "2.1.1" @@ -471,12 +712,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", - "const-oid", - "crypto-common", + "block-buffer 0.10.4", + "const-oid 0.9.6", + "crypto-common 0.1.7", "subtle", ] +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer 0.12.1", + "const-oid 0.10.2", + "crypto-common 0.2.2", +] + +[[package]] +name = "displaydoc" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "document-features" version = "0.2.12" @@ -499,7 +762,7 @@ checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" dependencies = [ "der", - "digest", + "digest 0.10.7", "elliptic-curve", "rfc6979", "signature", @@ -523,7 +786,7 @@ dependencies = [ "curve25519-dalek", "ed25519", - "sha2", + "sha2 0.10.9", "subtle", ] @@ -535,7 +798,7 @@ dependencies = [ "base16ct", "crypto-bigint", - "digest", + "digest 0.10.7", "ff", "generic-array", "group", @@ -546,6 +809,12 @@ "zeroize", ] +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + [[package]] name = "encoding_rs" version = "0.8.35" @@ -730,6 +999,9 @@ name = "ents-web" version = "0.0.0" dependencies = [ + "acdc-converters-core", + "acdc-converters-html", + "acdc-parser", "axum", "ents-anchor", "ents-effect", @@ -750,6 +1022,7 @@ "gix-ref-store", "http-body-util", "maud", + "pulldown-cmark", "rstest", "serde", "tempfile", @@ -774,6 +1047,12 @@ "windows-sys 0.61.2", ] +[[package]] +name = "evalexpr" +version = "13.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25929004897f2bbab309121a60400d36992f6d911d09baa6c172f6cc55706601" + [[package]] name = "facet" version = "0.50.0-rc.5" @@ -1023,6 +1302,22 @@ "libc", ] +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "fnv" version = "1.0.7" @@ -2163,7 +2458,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest", + "digest 0.10.7", ] [[package]] @@ -2217,6 +2512,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eaec953f16e5bcf6b8a3cb3aa959b17e5577dbd2693e94554c462c08be22624b" +[[package]] +name = "hybrid-array" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "818356c5132c1fede50f837ca96afbe78ff42413047f4abb886217845e1b6c8c" +dependencies = [ + "typenum", +] + [[package]] name = "hyper" version = "1.10.1" @@ -2252,6 +2556,112 @@ "tower-service", ] +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec 1.15.2", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" + +[[package]] +name = "icu_properties" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" + +[[package]] +name = "icu_provider" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + [[package]] name = "iddqd" version = "0.4.5" @@ -2264,6 +2674,27 @@ "hashbrown 0.16.1", ] +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec 1.15.2", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + [[package]] name = "impls" version = "1.0.3" @@ -2289,6 +2720,18 @@ "generic-array", ] +[[package]] +name = "int-enum" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e366a1634cccc76b4cfd3e7580de9b605e4d93f1edac48d786c1f867c0def495" +dependencies = [ + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "syn", +] + [[package]] name = "io-close" version = "0.3.7" @@ -2399,18 +2842,47 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" +[[package]] +name = "libghostty-vt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04c77176dd05b2b5718046d807e786473f463ea6d478d56f8c0105e2fe6f71f5" +dependencies = [ + "bitflags 2.13.0", + "int-enum", + "libghostty-vt-sys", +] + +[[package]] +name = "libghostty-vt-sys" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "404671567c0ac43389a69ae424eefc89390a25d3a5386a54249cdaf13e01b411" + [[package]] name = "libm" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + [[package]] name = "linux-raw-sys" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + [[package]] name = "litrs" version = "1.0.0" @@ -2494,6 +2966,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + [[package]] name = "mio" version = "1.2.1" @@ -2524,6 +3012,16 @@ "libc", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "nonempty" version = "0.12.0" @@ -2546,6 +3044,12 @@ "zeroize", ] +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + [[package]] name = "num-integer" version = "0.1.46" @@ -2581,6 +3085,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +[[package]] +name = "onig" +version = "6.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc3cbf698f9438986c11a880c90a6d04b9de27575afd28bbf45b154b6c709e2" +dependencies = [ + "bitflags 2.13.0", + "libc", + "once_cell", + "onig_sys", +] + +[[package]] +name = "onig_sys" +version = "69.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e68317604e77e53b85896388e1a803c1d21b74c899ec9e5e1112db90735edd7" +dependencies = [ + "cc", + "pkg-config", +] + [[package]] name = "owo-colors" version = "4.3.0" @@ -2600,7 +3126,7 @@ "ecdsa", "elliptic-curve", "primeorder", - "sha2", + "sha2 0.10.9", ] [[package]] @@ -2612,7 +3138,7 @@ "ecdsa", "elliptic-curve", "primeorder", - "sha2", + "sha2 0.10.9", ] [[package]] @@ -2626,7 +3152,7 @@ "elliptic-curve", "primeorder", "rand_core 0.6.4", - "sha2", + "sha2 0.10.9", ] [[package]] @@ -2652,6 +3178,33 @@ "windows-link", ] +[[package]] +name = "peg" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aad070be5b63aa72103f2fcdd70a83adbd5e90112ce5b574171ff1c65501773" +dependencies = [ + "peg-macros", + "peg-runtime", +] + +[[package]] +name = "peg-macros" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd8ef6825cae95355031ae26a99b616a2a21f22ba2de0197c43dfb05acbe7ee" +dependencies = [ + "peg-runtime", + "proc-macro2", + "quote", +] + +[[package]] +name = "peg-runtime" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7011d97b484a5ebdc4b1fdb3b12d5e4bbbea56e9d22b688f2e79e04b65a7d8a6" + [[package]] name = "pem-rfc7468" version = "0.7.0" @@ -2694,6 +3247,25 @@ "spki", ] +[[package]] +name = "pkg-config" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + +[[package]] +name = "plist" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da1d65da6dd5d1e44199ac0f58712d241c0f439f80adea8924d832384087f85" +dependencies = [ + "base64", + "indexmap", + "quick-xml", + "serde", + "time", +] + [[package]] name = "portable-atomic" version = "1.13.1" @@ -2709,6 +3281,21 @@ "portable-atomic", ] +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -2787,12 +3374,39 @@ "unarray", ] +[[package]] +name = "pulldown-cmark" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9f068eba8e7071c5f9511831b44f32c740d5adf574e990f946ddb53db2f314e" +dependencies = [ + "bitflags 2.13.0", + "memchr", + "pulldown-cmark-escape", + "unicase", +] + +[[package]] +name = "pulldown-cmark-escape" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "007d8adb5ddab6f8e3f491ac63566a7d5002cc7ed73901f72057943fa71ae1ae" + [[package]] name = "quick-error" version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" +[[package]] +name = "quick-xml" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1" +dependencies = [ + "memchr", +] + [[package]] name = "quote" version = "1.0.46" @@ -2935,21 +3549,27 @@ "subtle", ] +[[package]] +name = "rgb" +version = "0.8.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4" + [[package]] name = "rsa" version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" dependencies = [ - "const-oid", - "digest", + "const-oid 0.9.6", + "digest 0.10.7", "num-bigint-dig", "num-integer", "num-traits", "pkcs1", "pkcs8", "rand_core 0.6.4", - "sha2", + "sha2 0.10.9", "signature", "spki", "subtle", @@ -3031,6 +3651,12 @@ "wait-timeout", ] +[[package]] +name = "ruzstd" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7c1c839d570d835527c9a5e4db7cb2198683a988cb9d7293fc8674e6bd58fc8" + [[package]] name = "ryu" version = "1.0.23" @@ -3066,6 +3692,12 @@ "zeroize", ] +[[package]] +name = "self_cell" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b12e76d157a900eb52e81bc6e9f3069344290341720e9178cde2407113ac8d89" + [[package]] name = "semver" version = "1.0.28" @@ -3149,8 +3781,8 @@ checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", - "cpufeatures", - "digest", + "cpufeatures 0.2.17", + "digest 0.10.7", ] [[package]] @@ -3159,7 +3791,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89f599ac0c323ebb1c6082821a54962b839832b03984598375bff3975b804423" dependencies = [ - "digest", + "digest 0.10.7", "sha1", ] @@ -3170,8 +3802,19 @@ checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures", - "digest", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "digest 0.11.3", ] [[package]] @@ -3180,6 +3823,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77" +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + [[package]] name = "signal-hook" version = "0.3.18" @@ -3227,10 +3876,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ - "digest", + "digest 0.10.7", "rand_core 0.6.4", ] +[[package]] +name = "simd-adler32" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" + [[package]] name = "slab" version = "0.4.12" @@ -3293,7 +3948,7 @@ dependencies = [ "base64ct", "pem-rfc7468", - "sha2", + "sha2 0.10.9", ] [[package]] @@ -3309,7 +3964,7 @@ "rand_core 0.6.4", "rsa", "sec1", - "sha2", + "sha2 0.10.9", "signature", "ssh-cipher", "ssh-encoding", @@ -3386,6 +4041,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "syntect" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "656b45c05d95a5704399aeef6bd0ddec7b2b3531b7c9e900abbf7c4d2190c925" +dependencies = [ + "bincode", + "flate2", + "fnv", + "once_cell", + "onig", + "plist", + "regex-syntax", + "serde", + "serde_derive", + "serde_json", + "thiserror 2.0.18", + "walkdir", + "yaml-rust", +] + [[package]] name = "tempfile" version = "3.27.0" @@ -3451,6 +4138,46 @@ "syn", ] +[[package]] +name = "time" +version = "0.3.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18dfaaeddcb932337b5e7866ee7d0ce9b76d2fd092997146f187ec09b4558a50" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" + +[[package]] +name = "time-macros" +version = "0.2.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c431b87111666e491a90baa837f914fb45cd5dc3c268591b0220ff5057f2085f" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "tinyvec" version = "1.11.0" @@ -3604,6 +4331,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" +[[package]] +name = "unicase" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" + [[package]] name = "unicode-bom" version = "2.0.3" @@ -3648,6 +4381,24 @@ "rustc-hash", ] +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "uuid" version = "1.23.4" @@ -3671,6 +4422,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "231fdcd7ef3037e8330d8e17e61011a2c244126acc0a982f4040ac3f9f0bc077" dependencies = [ + "arrayvec", "memchr", ] @@ -3790,12 +4542,65 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.59.0" @@ -3893,6 +4698,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + [[package]] name = "xterm-query" version = "0.6.0" @@ -3904,12 +4715,44 @@ "windows-sys 0.59.0", ] +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] + [[package]] name = "yansi" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" +[[package]] +name = "yoke" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + [[package]] name = "zerocopy" version = "0.8.54" @@ -3930,12 +4773,66 @@ "syn", ] +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + [[package]] name = "zeroize" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "zlib-rs" version = "0.6.6"
crates/cli/ents-web/Cargo.toml @@ -6,6 +6,9 @@ license.workspace = true [dependencies] +acdc-converters-core = { workspace = true } +acdc-converters-html = { workspace = true } +acdc-parser = { workspace = true } ents-anchor = { workspace = true } ents-effect = { workspace = true } ents-forge = { workspace = true } @@ -24,6 +27,7 @@ gix-ref-store = { workspace = true } axum = { workspace = true } maud = { workspace = true } +pulldown-cmark = { workspace = true } serde = { version = "1", features = ["derive"] } thiserror = { workspace = true } tokio = { workspace = true }
crates/cli/ents-web/src/error.rs @@ -83,6 +83,12 @@ /// outcome. Boxed; see [`Error::Forge`]'s own doc. #[error(transparent)] Receive(Box<ents_receive::Error>), + + /// `crate::asciidoc::to_html` could not parse or convert an AsciiDoc + /// blob (`acdc` reported no more specific error than "could not + /// convert"). + #[error("could not render asciidoc: {0}")] + Asciidoc(String), } impl From<ents_forge::Error> for Error {
crates/cli/ents-web/src/lib.rs @@ -106,9 +106,11 @@ //! # }); //! ``` +pub(crate) mod asciidoc; pub(crate) mod assets; pub mod error; pub mod identity; +pub(crate) mod markdown; pub mod pages; pub mod render; pub mod router;
crates/cli/ents-web/src/asciidoc.rs @@ -1,0 +1,127 @@ +//! AsciiDoc rendering via the [`acdc`](https://github.com/nlopes/acdc) library. +//! +//! AsciiDoc gets the same treatment Markdown does (`crate::markdown`): an +//! `.adoc`/`.asciidoc` blob in [`crate::pages::files`] renders as a +//! formatted document rather than a plain-text listing. Output is the +//! *embedded* fragment (no `<!DOCTYPE>`/`<html>` frame) so it can drop +//! straight into a `.doc-body`-styled card +//! (`crate::assets::OVERRIDES`). +//! +//! `acdc-converters-core` and `acdc-converters-html` are not on crates.io +//! yet, so they are pinned as git dependencies on the same revision +//! `pre-redo:Cargo.toml` pinned (see this crate's own `Cargo.toml`). +#![cfg_attr( + not(test), + expect( + dead_code, + reason = "wired into crate::pages::files's blob view in the next commit" + ) +)] + +use acdc_converters_core::{Converter, Options as ConvertOptions, inlines_to_string}; +use acdc_converters_html::{Processor, RenderOptions}; +use acdc_parser::Options as ParseOptions; +use maud::{Markup, PreEscaped, html}; + +use crate::error::{Error, Result}; + +/// File extensions that name an AsciiDoc document. +const EXTENSIONS: [&str; 4] = ["adoc", "asciidoc", "asc", "adc"]; + +/// Whether `name` looks like an AsciiDoc file by its extension. +#[must_use] +pub(crate) fn is_asciidoc(name: &str) -> bool { + name.rsplit_once('.') + .is_some_and(|(_, ext)| EXTENSIONS.iter().any(|e| ext.eq_ignore_ascii_case(e))) +} + +/// Render AsciiDoc `source` to an embedded HTML fragment. The fragment +/// carries no document frame, so callers place it inside their own +/// container (`.doc-body`). +/// +/// `acdc`'s embedded render mode omits both the document frame *and* the +/// visible doctitle/subtitle, so this reconstructs them from the parsed +/// header and prepends them to the embedded body -- carried over from +/// `pre-redo:crates/git-ents-server/src/asciidoc.rs`'s own `to_html`, +/// which hit the same gap: without this, a README's own `= Title` line +/// would silently vanish from the rendered page. +/// +/// No sanitization is applied beyond what `acdc`'s HTML converter itself +/// guarantees -- the pre-redo version did the same, emitting the +/// converter's output unescaped via `maud::PreEscaped`. +/// +/// # Errors +/// +/// [`Error::Asciidoc`] if `source` cannot be parsed or converted. +pub(crate) fn to_html(source: &str) -> Result<Markup> { + let parsed = acdc_parser::parse(source, &ParseOptions::default()) + .map_err(|err| Error::Asciidoc(err.to_string()))?; + let doc = parsed.document(); + + let heading = doc + .header + .as_ref() + .filter(|h| !h.title.is_empty()) + .map(|h| { + let title = inlines_to_string(&h.title); + let subtitle = h.subtitle.as_ref().map(|s| inlines_to_string(s)); + html! { + h1 { (title) } + @if let Some(subtitle) = subtitle { + p.doc-subtitle { (subtitle) } + } + } + }); + + let processor = Processor::new(ConvertOptions::default(), doc.attributes.clone()); + let options = RenderOptions { + embedded: true, + ..RenderOptions::default() + }; + let body = processor + .convert_to_string(doc, &options) + .map_err(|err| Error::Asciidoc(err.to_string()))?; + Ok(match heading { + Some(heading) => html! { (heading) (PreEscaped(body)) }, + None => PreEscaped(body), + }) +} + +#[cfg(test)] +mod tests { + #![allow(clippy::expect_used, reason = "unit test")] + + use rstest::rstest; + + use super::*; + + #[rstest] + #[case::adoc("readme.adoc", true)] + #[case::asciidoc("readme.asciidoc", true)] + #[case::asc("notes.asc", true)] + #[case::upper("README.ADOC", true)] + #[case::md("readme.md", false)] + #[case::no_ext("readme", false)] + fn is_asciidoc_matches_by_extension(#[case] name: &str, #[case] expected: bool) { + assert_eq!(is_asciidoc(name), expected); + } + + #[test] + fn to_html_reconstructs_the_doctitle_and_renders_a_paragraph() { + let rendered = to_html("= Title\n\nA paragraph.\n") + .expect("valid asciidoc") + .into_string(); + assert!(rendered.contains("<h1>Title</h1>")); + assert!(rendered.contains("A paragraph.")); + } + + #[test] + fn to_html_reconstructs_a_subtitle() { + let rendered = to_html("= Title: Subtitle\n\nBody.\n") + .expect("valid asciidoc") + .into_string(); + assert!(rendered.contains("<h1>Title</h1>")); + assert!(rendered.contains(r#"class="doc-subtitle""#)); + assert!(rendered.contains("Subtitle")); + } +}
crates/cli/ents-web/src/markdown.rs @@ -1,0 +1,72 @@ +//! Markdown rendering via [`pulldown_cmark`]. +//! +//! Markdown gets the same treatment AsciiDoc does (`crate::asciidoc`): a +//! `.md` blob in [`crate::pages::files`] renders as a formatted document +//! rather than a plain-text listing. Output is an embedded fragment (no +//! document frame) styled by [`crate::assets::OVERRIDES`]'s own +//! `.doc-body` rules. +#![cfg_attr( + not(test), + expect( + dead_code, + reason = "wired into crate::pages::files's blob view in the next commit" + ) +)] + +use maud::{Markup, PreEscaped}; +use pulldown_cmark::{Options, Parser, html}; + +/// File extensions that name a Markdown document. +const EXTENSIONS: [&str; 4] = ["md", "markdown", "mdown", "mkd"]; + +/// Whether `name` looks like a Markdown file by its extension. +#[must_use] +pub(crate) fn is_markdown(name: &str) -> bool { + name.rsplit_once('.') + .is_some_and(|(_, ext)| EXTENSIONS.iter().any(|e| ext.eq_ignore_ascii_case(e))) +} + +/// Render Markdown `source` to an embedded HTML fragment, with the tables, +/// footnotes, strikethrough, and task-list extensions people expect from +/// forge-flavored Markdown. +/// +/// No additional sanitization is applied beyond what `pulldown_cmark` +/// itself guarantees (well-formed HTML output from the parsed Markdown +/// tree, not sanitized against embedded raw HTML in the source) -- +/// `pre-redo:crates/git-ents-server/src/markdown.rs`'s own `to_html` did +/// the same: it emitted `pulldown_cmark::html::push_html`'s output +/// unescaped, straight into the page. +#[must_use] +pub(crate) fn to_html(source: &str) -> Markup { + let options = Options::ENABLE_TABLES + | Options::ENABLE_FOOTNOTES + | Options::ENABLE_STRIKETHROUGH + | Options::ENABLE_TASKLISTS; + let mut out = String::new(); + html::push_html(&mut out, Parser::new_ext(source, options)); + PreEscaped(out) +} + +#[cfg(test)] +mod tests { + use rstest::rstest; + + use super::*; + + #[rstest] + #[case::md("readme.md", true)] + #[case::markdown("readme.markdown", true)] + #[case::upper("README.MD", true)] + #[case::adoc("readme.adoc", false)] + #[case::no_ext("readme", false)] + fn is_markdown_matches_by_extension(#[case] name: &str, #[case] expected: bool) { + assert_eq!(is_markdown(name), expected); + } + + #[test] + fn to_html_renders_a_heading_and_a_table() { + let rendered = to_html("# Title\n\n| a | b |\n|---|---|\n| 1 | 2 |\n").into_string(); + assert!(rendered.contains("<h1>Title</h1>")); + assert!(rendered.contains("<table>")); + } +}