diff --git a/Cargo.lock b/Cargo.lock index 5a1f808..661a051 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,6 +18,212 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618" +[[package]] +name = "actix-codec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" +dependencies = [ + "bitflags 2.11.0", + "bytes", + "futures-core", + "futures-sink", + "memchr", + "pin-project-lite", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "actix-files" +version = "0.6.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8c4f30e3272d7c345f88ae0aac3848507ef5ba871f9cc2a41c8085a0f0523b" +dependencies = [ + "actix-http", + "actix-service", + "actix-utils", + "actix-web", + "bitflags 2.11.0", + "bytes", + "derive_more", + "futures-core", + "http-range", + "log", + "mime", + "mime_guess", + "percent-encoding", + "pin-project-lite", + "v_htmlescape", +] + +[[package]] +name = "actix-http" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f860ee6746d0c5b682147b2f7f8ef036d4f92fe518251a3a35ffa3650eafdf0e" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-service", + "actix-utils", + "base64", + "bitflags 2.11.0", + "brotli", + "bytes", + "bytestring", + "derive_more", + "encoding_rs", + "flate2", + "foldhash 0.1.5", + "futures-core", + "h2", + "http 0.2.12", + "httparse", + "httpdate", + "itoa", + "language-tags", + "local-channel", + "mime", + "percent-encoding", + "pin-project-lite", + "rand 0.9.2", + "sha1", + "smallvec", + "tokio", + "tokio-util", + "tracing", + "zstd", +] + +[[package]] +name = "actix-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" +dependencies = [ + "quote", + "syn 2.0.117", +] + +[[package]] +name = "actix-router" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14f8c75c51892f18d9c46150c5ac7beb81c95f78c8b83a634d49f4ca32551fe7" +dependencies = [ + "bytestring", + "cfg-if", + "http 0.2.12", + "regex", + "regex-lite", + "serde", + "tracing", +] + +[[package]] +name = "actix-rt" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92589714878ca59a7626ea19734f0e07a6a875197eec751bb5d3f99e64998c63" +dependencies = [ + "futures-core", + "tokio", +] + +[[package]] +name = "actix-server" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a65064ea4a457eaf07f2fba30b4c695bf43b721790e9530d26cb6f9019ff7502" +dependencies = [ + "actix-rt", + "actix-service", + "actix-utils", + "futures-core", + "futures-util", + "mio", + "socket2 0.5.10", + "tokio", + "tracing", +] + +[[package]] +name = "actix-service" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e46f36bf0e5af44bdc4bdb36fbbd421aa98c79a9bce724e1edeb3894e10dc7f" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "actix-utils" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" +dependencies = [ + "local-waker", + "pin-project-lite", +] + +[[package]] +name = "actix-web" +version = "4.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff87453bc3b56e9b2b23c1cc0b1be8797184accf51d2abe0f8a33ec275d316bf" +dependencies = [ + "actix-codec", + "actix-http", + "actix-macros", + "actix-router", + "actix-rt", + "actix-server", + "actix-service", + "actix-utils", + "actix-web-codegen", + "bytes", + "bytestring", + "cfg-if", + "cookie 0.16.2", + "derive_more", + "encoding_rs", + "foldhash 0.1.5", + "futures-core", + "futures-util", + "impl-more", + "itoa", + "language-tags", + "log", + "mime", + "once_cell", + "pin-project-lite", + "regex", + "regex-lite", + "serde", + "serde_json", + "serde_urlencoded", + "smallvec", + "socket2 0.6.3", + "time", + "tracing", + "url", +] + +[[package]] +name = "actix-web-codegen" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f591380e2e68490b5dfaf1dd1aa0ebe78d84ba7067078512b4ea6e4492d622b8" +dependencies = [ + "actix-router", + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "adler2" version = "2.0.1" @@ -75,6 +281,21 @@ dependencies = [ "equator", ] +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + [[package]] name = "allocator-api2" version = "0.2.21" @@ -115,6 +336,56 @@ dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + [[package]] name = "anyhow" version = "1.0.102" @@ -235,6 +506,18 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "async-compression" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0f9ee0f6e02ffd7ad5816e9464499fba7b3effd01123b515c41d1697c43dad1" +dependencies = [ + "compression-codecs", + "compression-core", + "pin-project-lite", + "tokio", +] + [[package]] name = "async-executor" version = "1.14.0" @@ -428,7 +711,7 @@ dependencies = [ "anyhow", "arrayvec", "log", - "nom", + "nom 8.0.0", "num-rational", "v_frame", ] @@ -452,7 +735,7 @@ dependencies = [ "bytes", "form_urlencoded", "futures-util", - "http", + "http 1.4.0", "http-body", "http-body-util", "hyper", @@ -483,7 +766,7 @@ checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" dependencies = [ "bytes", "futures-core", - "http", + "http 1.4.0", "http-body", "http-body-util", "mime", @@ -520,14 +803,16 @@ dependencies = [ "keyring", "liquid", "liquid-core", + "pagefind", "pulldown-cmark", + "rayon", "refinery", "reqwest", "rusqlite", "rust-stemmers", "serde", "serde_json", - "serde_yaml", + "serde_yaml 0.9.34+deprecated", "sha2", "sys-locale", "tempfile", @@ -598,6 +883,15 @@ dependencies = [ "bit-vec 0.8.0", ] +[[package]] +name = "bit-set" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2f926cc3060f09db9ebc5b52823d85268d24bb917e472c0c4bea35780a7d" +dependencies = [ + "bit-vec 0.9.1", +] + [[package]] name = "bit-vec" version = "0.6.3" @@ -610,6 +904,15 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" +[[package]] +name = "bit-vec" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71798fca2c1fe1086445a7258a4bc81e6e49dcd24c8d0dd9a1e57395b603f51" +dependencies = [ + "serde", +] + [[package]] name = "bit_field" version = "0.10.3" @@ -686,6 +989,27 @@ dependencies = [ "piper", ] +[[package]] +name = "brotli" +version = "8.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + [[package]] name = "built" version = "0.8.0" @@ -742,6 +1066,15 @@ version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +[[package]] +name = "bytestring" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "113b4343b5f6617e7ad401ced8de3cc8b012e73a594347c307b90db3e9271289" +dependencies = [ + "bytes", +] + [[package]] name = "cairo-rs" version = "0.18.5" @@ -878,6 +1211,46 @@ dependencies = [ "windows-link 0.2.1", ] +[[package]] +name = "clap" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + [[package]] name = "clipboard-win" version = "5.4.1" @@ -933,6 +1306,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + [[package]] name = "com" version = "0.6.0" @@ -974,6 +1353,23 @@ dependencies = [ "memchr", ] +[[package]] +name = "compression-codecs" +version = "0.4.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb7b51a7d9c967fc26773061ba86150f19c50c0d65c887cb1fbe295fd16619b7" +dependencies = [ + "compression-core", + "flate2", + "memchr", +] + +[[package]] +name = "compression-core" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75984efb6ed102a0d42db99afb6c1948f0380d1d91808d5529916e6c08b49d8d" + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -983,6 +1379,79 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "config-derive" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c547326a30684f853601fb959cc8ecbd0d72abbdd27ba634850a918fa29afc4" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "console" +version = "0.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d64e8af5551369d19cf50138de61f1c42074ab970f74e99be916646777f8fc87" +dependencies = [ + "encode_unicode", + "libc", + "unicode-width 0.2.2", + "windows-sys 0.61.2", +] + +[[package]] +name = "const_format" +version = "0.2.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7faa7469a93a566e9ccc1c73fe783b4a65c274c5ace346038dca9c39fe0030ad" +dependencies = [ + "const_format_proc_macros", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "convert_case" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "affbf0190ed2caf063e3def54ff444b449371d55c58e513a95ab98eca50adb49" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "cookie" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + [[package]] name = "cookie" version = "0.18.1" @@ -1262,10 +1731,12 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" dependencies = [ + "convert_case 0.10.0", "proc-macro2", "quote", "rustc_version", "syn 2.0.117", + "unicode-xid", ] [[package]] @@ -1407,7 +1878,7 @@ dependencies = [ "foldhash 0.2.0", "html5ever", "precomputed-hash", - "selectors", + "selectors 0.36.1", "tendril", ] @@ -1490,6 +1961,30 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +[[package]] +name = "emojis" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50c1c1870b766fc398e5f0526498d09c94b6de15be5fd769a28bbc804fb1b05d" +dependencies = [ + "phf 0.13.1", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + [[package]] name = "endi" version = "1.1.1" @@ -1517,6 +2012,15 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "envy" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f47e0157f2cb54f5ae1bd371b30a2ae4311e1c028f575cd4e81de7353215965" +dependencies = [ + "serde", +] + [[package]] name = "equator" version = "0.4.2" @@ -2316,6 +2820,25 @@ dependencies = [ "svg_fmt", ] +[[package]] +name = "h2" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap 2.13.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "half" version = "2.7.1" @@ -2360,6 +2883,13 @@ name = "hashbrown" version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", + "serde", + "serde_core", +] [[package]] name = "hashlink" @@ -2415,6 +2945,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" +[[package]] +name = "html-escape" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d1ad449764d627e22bfd7cd5e8868264fc9236e07c752972b4080cd351cb476" +dependencies = [ + "utf8-width", +] + [[package]] name = "html5ever" version = "0.38.0" @@ -2425,6 +2964,17 @@ dependencies = [ "markup5ever", ] +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + [[package]] name = "http" version = "1.4.0" @@ -2442,7 +2992,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http", + "http 1.4.0", ] [[package]] @@ -2453,11 +3003,17 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", - "http", + "http 1.4.0", "http-body", "pin-project-lite", ] +[[package]] +name = "http-range" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573" + [[package]] name = "httparse" version = "1.10.1" @@ -2480,7 +3036,7 @@ dependencies = [ "bytes", "futures-channel", "futures-core", - "http", + "http 1.4.0", "http-body", "httparse", "httpdate", @@ -2497,7 +3053,7 @@ version = "0.27.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ - "http", + "http 1.4.0", "hyper", "hyper-util", "rustls", @@ -2518,14 +3074,14 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http", + "http 1.4.0", "http-body", "hyper", "ipnet", "libc", "percent-encoding", "pin-project-lite", - "socket2", + "socket2 0.6.3", "tokio", "tower-service", "tracing", @@ -2912,6 +3468,41 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7c5cedc30da3a610cac6b4ba17597bdf7152cf974e8aab3afb3d54455e371c8" +[[package]] +name = "impl-more" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2" + +[[package]] +name = "include_dir" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "923d117408f1e49d914f1a379a309cffe4f18c05cf4e3d12e613a15fc81bd0dd" +dependencies = [ + "include_dir_macros", +] + +[[package]] +name = "include_dir_macros" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + [[package]] name = "indexmap" version = "2.13.0" @@ -2979,6 +3570,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + [[package]] name = "itertools" version = "0.14.0" @@ -3205,6 +3802,18 @@ dependencies = [ "smallvec", ] +[[package]] +name = "language-tags" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "leb128fmt" version = "0.1.0" @@ -3217,6 +3826,63 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" +[[package]] +name = "lexical-core" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d8d125a277f807e55a77304455eb7b1cb52f2b18c143b60e766c120bd64a594" +dependencies = [ + "lexical-parse-float", + "lexical-parse-integer", + "lexical-util", + "lexical-write-float", + "lexical-write-integer", +] + +[[package]] +name = "lexical-parse-float" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52a9f232fbd6f550bc0137dcb5f99ab674071ac2d690ac69704593cb4abbea56" +dependencies = [ + "lexical-parse-integer", + "lexical-util", +] + +[[package]] +name = "lexical-parse-integer" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a7a039f8fb9c19c996cd7b2fcce303c1b2874fe1aca544edc85c4a5f8489b34" +dependencies = [ + "lexical-util", +] + +[[package]] +name = "lexical-util" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2604dd126bb14f13fb5d1bd6a66155079cb9fa655b37f875b3a742c705dbed17" + +[[package]] +name = "lexical-write-float" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50c438c87c013188d415fbabbb1dceb44249ab81664efbd31b14ae55dabb6361" +dependencies = [ + "lexical-util", + "lexical-write-integer", +] + +[[package]] +name = "lexical-write-integer" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "409851a618475d2d5796377cad353802345cba92c867d9fbcde9cf4eac4e14df" +dependencies = [ + "lexical-util", +] + [[package]] name = "libc" version = "0.2.184" @@ -3394,6 +4060,23 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" +[[package]] +name = "local-channel" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6cbc85e69b8df4b8bb8b89ec634e7189099cea8927a276b7384ce5488e53ec8" +dependencies = [ + "futures-core", + "futures-sink", + "local-waker", +] + +[[package]] +name = "local-waker" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487" + [[package]] name = "lock_api" version = "0.4.14" @@ -3409,6 +4092,25 @@ version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +[[package]] +name = "lol_html" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ff94cb6aef6ee52afd2c69331e9109906d855e82bd241f3110dfdf6185899ab" +dependencies = [ + "bitflags 2.11.0", + "cfg-if", + "cssparser", + "encoding_rs", + "foldhash 0.2.0", + "hashbrown 0.16.1", + "memchr", + "mime", + "precomputed-hash", + "selectors 0.33.0", + "thiserror 2.0.18", +] + [[package]] name = "loop9" version = "0.1.5" @@ -3511,6 +4213,51 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "minicbor" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e70eae6d4f18f7d76877fe7b13f0bc21f7c2b7239d2041c338335f7b388d0dd7" +dependencies = [ + "minicbor-derive", +] + +[[package]] +name = "minicbor-derive" +version = "0.19.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "294f0a0c161c510e9746adf546b8b044fbb0b00677d7dfc9a2452f9fdf63439b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "minifier" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14f1541610994bba178cb36757e102d06a52a2d9612aa6d34c64b3b377c5d943" +dependencies = [ + "clap", +] + +[[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" @@ -3528,6 +4275,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" dependencies = [ "libc", + "log", "wasi", "windows-sys 0.61.2", ] @@ -3578,7 +4326,7 @@ dependencies = [ "bitflags 2.11.0", "codespan-reporting", "hexf-parse", - "indexmap", + "indexmap 2.13.0", "log", "num-traits", "rustc-hash 1.1.0", @@ -3646,6 +4394,16 @@ dependencies = [ "memoffset", ] +[[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 = "nom" version = "8.0.0" @@ -4150,6 +4908,12 @@ version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + [[package]] name = "onig" version = "6.5.1" @@ -4228,6 +4992,57 @@ dependencies = [ "ttf-parser 0.25.1", ] +[[package]] +name = "pagefind" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa7d3c661e0e18b6c9eec1305908baf2bd9564af247727f9e7f4b4d25cd2da39" +dependencies = [ + "actix-files", + "actix-web", + "anyhow", + "async-compression", + "base64", + "bit-set 0.10.0", + "clap", + "console", + "convert_case 0.11.0", + "either", + "emojis", + "flate2", + "futures", + "hashbrown 0.16.1", + "html-escape", + "include_dir", + "lazy_static", + "lexical-core", + "lol_html", + "minicbor", + "minifier", + "pagefind_stem", + "path-slash", + "portpicker", + "rayon", + "regex", + "rust-patch", + "serde", + "serde_json", + "sha-1", + "tikv-jemallocator", + "tokio", + "twelf", + "typed-builder", + "unicode-normalization", + "unicode-segmentation", + "wax", +] + +[[package]] +name = "pagefind_stem" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8dfa810b158f3ac364e5acd43ca4a6020a6e729d40c15ce1bed1d911237a52e5" + [[package]] name = "palette" version = "0.7.6" @@ -4343,6 +5158,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec" +[[package]] +name = "path-slash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e91099d4268b0e11973f036e885d652fb0b21fedcf69738c627f94db6a44f42" + [[package]] name = "pathdiff" version = "0.2.3" @@ -4561,7 +5382,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07" dependencies = [ "base64", - "indexmap", + "indexmap 2.13.0", "quick-xml 0.38.4", "serde", "time", @@ -4613,6 +5434,24 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" +[[package]] +name = "pori" +version = "0.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a63d338dec139f56dacc692ca63ad35a6be6a797442479b55acd611d79e906" +dependencies = [ + "nom 7.1.3", +] + +[[package]] +name = "portpicker" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be97d76faf1bfab666e1375477b23fde79eccf0276e9b63b92a39d676a889ba9" +dependencies = [ + "rand 0.8.5", +] + [[package]] name = "potential_utf" version = "0.1.5" @@ -4811,7 +5650,7 @@ dependencies = [ "quinn-udp", "rustc-hash 2.1.2", "rustls", - "socket2", + "socket2 0.6.3", "thiserror 2.0.18", "tokio", "tracing", @@ -4848,7 +5687,7 @@ dependencies = [ "cfg_aliases 0.2.1", "libc", "once_cell", - "socket2", + "socket2 0.6.3", "tracing", "windows-sys 0.60.2", ] @@ -5154,6 +5993,12 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "regex-lite" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" + [[package]] name = "regex-syntax" version = "0.8.10" @@ -5177,7 +6022,7 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "http", + "http 1.4.0", "http-body", "http-body-util", "hyper", @@ -5309,6 +6154,27 @@ dependencies = [ "ordered-multimap", ] +[[package]] +name = "rust-patch" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4076837f5df7460d37d1e245c966e64f6aaeeb59a76f186f352ca91d6087fb43" +dependencies = [ + "rust-patch-derive", +] + +[[package]] +name = "rust-patch-derive" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9927610a0a7c3e3dece1e89a114c31e435f27db01b1d630e81eb02ecd820f0b" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "rust-stemmers" version = "1.2.0" @@ -5500,6 +6366,25 @@ dependencies = [ "libc", ] +[[package]] +name = "selectors" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "feef350c36147532e1b79ea5c1f3791373e61cbd9a6a2615413b3807bb164fb7" +dependencies = [ + "bitflags 2.11.0", + "cssparser", + "derive_more", + "log", + "new_debug_unreachable", + "phf 0.13.1", + "phf_codegen", + "precomputed-hash", + "rustc-hash 2.1.2", + "servo_arc", + "smallvec", +] + [[package]] name = "selectors" version = "0.36.1" @@ -5617,13 +6502,25 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_yaml" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578a7433b776b56a35785ed5ce9a7e777ac0598aac5a6dd1b4b18a307c7fc71b" +dependencies = [ + "indexmap 1.9.3", + "ryu", + "serde", + "yaml-rust", +] + [[package]] name = "serde_yaml" version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap", + "indexmap 2.13.0", "itoa", "ryu", "serde", @@ -5639,6 +6536,17 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "sha-1" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sha1" version = "0.10.6" @@ -5826,6 +6734,16 @@ dependencies = [ "serde", ] +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "socket2" version = "0.6.3" @@ -5954,6 +6872,12 @@ dependencies = [ "quote", ] +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "subtle" version = "2.6.1" @@ -6068,7 +6992,7 @@ dependencies = [ "cfg-expr", "heck 0.5.0", "pkg-config", - "toml", + "toml 0.8.2", "version-compare", ] @@ -6186,6 +7110,26 @@ dependencies = [ "zune-jpeg", ] +[[package]] +name = "tikv-jemalloc-sys" +version = "0.6.1+5.3.0-1-ge13ca993e8ccb9ba9847cc330696e02839f328f7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd8aa5b2ab86a2cefa406d889139c162cbb230092f7d1d7cbc1716405d852a3b" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "tikv-jemallocator" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0359b4327f954e0567e69fb191cf1436617748813819c94b8cd4a431422d053a" +dependencies = [ + "libc", + "tikv-jemalloc-sys", +] + [[package]] name = "time" version = "0.3.47" @@ -6293,7 +7237,7 @@ dependencies = [ "parking_lot 0.12.5", "pin-project-lite", "signal-hook-registry", - "socket2", + "socket2 0.6.3", "tokio-macros", "windows-sys 0.61.2", ] @@ -6319,6 +7263,28 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + [[package]] name = "toml" version = "0.8.2" @@ -6355,7 +7321,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap", + "indexmap 2.13.0", "toml_datetime 0.6.3", "winnow 0.5.40", ] @@ -6366,7 +7332,7 @@ version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" dependencies = [ - "indexmap", + "indexmap 2.13.0", "serde", "serde_spanned", "toml_datetime 0.6.3", @@ -6379,7 +7345,7 @@ version = "0.25.10+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a82418ca169e235e6c399a84e395ab6debeb3bc90edc959bf0f48647c6a32d1b" dependencies = [ - "indexmap", + "indexmap 2.13.0", "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", "winnow 1.0.1", @@ -6419,7 +7385,7 @@ dependencies = [ "bitflags 2.11.0", "bytes", "futures-util", - "http", + "http 1.4.0", "http-body", "iri-string", "pin-project-lite", @@ -6496,6 +7462,43 @@ version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" +[[package]] +name = "twelf" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16de46d08a9d3a25e0a65bb70090797b970bd1e95d72872567ba8d02c0b03bdf" +dependencies = [ + "clap", + "config-derive", + "envy", + "log", + "serde", + "serde_json", + "serde_yaml 0.8.26", + "thiserror 1.0.69", + "toml 0.5.11", +] + +[[package]] +name = "typed-builder" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31aa81521b70f94402501d848ccc0ecaa8f93c8eb6999eb9747e72287757ffda" +dependencies = [ + "typed-builder-macro", +] + +[[package]] +name = "typed-builder-macro" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "076a02dc54dd46795c2e9c8282ed40bcfb1e22747e955de9389a1de28190fb26" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "typenum" version = "1.19.0" @@ -6555,6 +7558,15 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" +[[package]] +name = "unicode-normalization" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" +dependencies = [ + "tinyvec", +] + [[package]] name = "unicode-properties" version = "0.1.4" @@ -6661,12 +7673,24 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "utf8-width" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1292c0d970b54115d14f2492fe0170adf21d68a1de108eebc51c1df4f346a091" + [[package]] name = "utf8_iter" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "uuid" version = "1.23.0" @@ -6690,6 +7714,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "v_htmlescape" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e8257fbc510f0a46eb602c10215901938b5c2a7d5e70fc11483b1d3c9b5b18c" + [[package]] name = "vcpkg" version = "0.2.15" @@ -6823,7 +7853,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" dependencies = [ "anyhow", - "indexmap", + "indexmap 2.13.0", "wasm-encoder", "wasmparser", ] @@ -6851,10 +7881,25 @@ checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ "bitflags 2.11.0", "hashbrown 0.15.5", - "indexmap", + "indexmap 2.13.0", "semver", ] +[[package]] +name = "wax" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f8cbf8125142b9b30321ac8721f54c52fbcd6659f76cf863d5e2e38c07a3d7b" +dependencies = [ + "const_format", + "itertools", + "nom 7.1.3", + "pori", + "regex", + "thiserror 2.0.18", + "walkdir", +] + [[package]] name = "wayland-backend" version = "0.3.15" @@ -7153,7 +8198,7 @@ dependencies = [ "bitflags 2.11.0", "cfg_aliases 0.1.1", "codespan-reporting", - "indexmap", + "indexmap 2.13.0", "log", "naga", "once_cell", @@ -7864,7 +8909,7 @@ checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" dependencies = [ "anyhow", "heck 0.5.0", - "indexmap", + "indexmap 2.13.0", "prettyplease", "syn 2.0.117", "wasm-metadata", @@ -7895,7 +8940,7 @@ checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", "bitflags 2.11.0", - "indexmap", + "indexmap 2.13.0", "log", "serde", "serde_derive", @@ -7914,7 +8959,7 @@ checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" dependencies = [ "anyhow", "id-arena", - "indexmap", + "indexmap 2.13.0", "log", "semver", "serde", @@ -7938,7 +8983,7 @@ checksum = "3013fd6116aac351dd2e18f349b28b2cfef3a5ff3253a9d0ce2d7193bb1b4429" dependencies = [ "base64", "block2 0.6.2", - "cookie", + "cookie 0.18.1", "crossbeam-channel", "dirs 6.0.0", "dom_query", @@ -7946,7 +8991,7 @@ dependencies = [ "dunce", "gdkx11", "gtk", - "http", + "http 1.4.0", "javascriptcore-rs", "jni 0.21.1", "libc", @@ -8336,6 +9381,34 @@ version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +dependencies = [ + "cc", + "pkg-config", +] + [[package]] name = "zune-core" version = "0.5.1" diff --git a/Cargo.toml b/Cargo.toml index 67c952a..512b8d5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,8 @@ open = "5" pulldown-cmark = "0.13" liquid = "0.26" liquid-core = { version = "0.26", features = ["derive"] } +rayon = "1.10" +pagefind = "1.5.2" reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "rustls-tls"] } keyring = { version = "3", features = ["apple-native", "windows-native", "sync-secret-service"] } diff --git a/assets/starter-templates/not-found.liquid b/assets/starter-templates/not-found.liquid index 1fb3b0a..5715dd4 100644 --- a/assets/starter-templates/not-found.liquid +++ b/assets/starter-templates/not-found.liquid @@ -1,6 +1,6 @@ - {% render 'partials/head', page_title: page_title, pico_stylesheet_href: pico_stylesheet_href %} + {% render 'partials/head', page_title: page_title, pico_stylesheet_href: pico_stylesheet_href, language_prefix: language_prefix, alternate_links: alternate_links %}
@@ -9,7 +9,7 @@ {% assign default_not_found_message = 'render.notFound.message' | i18n: language %} {% assign default_not_found_back = 'render.notFound.back' | i18n: language %}

{{ not_found_message | default: default_not_found_message }}

-

{{ not_found_back_label | default: default_not_found_back }}

+

{{ not_found_back_label | default: default_not_found_back }}

diff --git a/assets/starter-templates/post-list.liquid b/assets/starter-templates/post-list.liquid index 3238405..1e24eb5 100644 --- a/assets/starter-templates/post-list.liquid +++ b/assets/starter-templates/post-list.liquid @@ -1,6 +1,6 @@ - {% render 'partials/head', page_title: page_title, pico_stylesheet_href: pico_stylesheet_href, language_prefix: language_prefix %} + {% render 'partials/head', page_title: page_title, pico_stylesheet_href: pico_stylesheet_href, language_prefix: language_prefix, alternate_links: alternate_links %}
{% render 'partials/language-switcher', blog_languages: blog_languages, language: language %} diff --git a/assets/starter-templates/single-post.liquid b/assets/starter-templates/single-post.liquid index 1187ba6..bd076c9 100644 --- a/assets/starter-templates/single-post.liquid +++ b/assets/starter-templates/single-post.liquid @@ -9,11 +9,12 @@ {% if post_categories.size > 0 or post_tags.size > 0 %}
{% for category in post_categories %} - {{ category | escape }} + {{ category.name | default: category | escape }} {% endfor %} {% for tag in post_tags %} - {% assign tag_color = tag_color_by_name[tag] %} - {{ tag | escape }} + {% assign tag_name = tag.name | default: tag %} + {% assign tag_color = tag.color %} + {{ tag_name | escape }} {% endfor %}
{% endif %} diff --git a/crates/bds-core/Cargo.toml b/crates/bds-core/Cargo.toml index c3a6346..e26a6fe 100644 --- a/crates/bds-core/Cargo.toml +++ b/crates/bds-core/Cargo.toml @@ -22,6 +22,8 @@ sys-locale = { workspace = true } pulldown-cmark = { workspace = true } liquid = { workspace = true } liquid-core = { workspace = true } +rayon = { workspace = true } +pagefind = { workspace = true } reqwest = { workspace = true } keyring = { workspace = true } tokio = { workspace = true } diff --git a/crates/bds-core/src/engine/generation.rs b/crates/bds-core/src/engine/generation.rs index 3d668fc..16795d5 100644 --- a/crates/bds-core/src/engine/generation.rs +++ b/crates/bds-core/src/engine/generation.rs @@ -2,6 +2,8 @@ use std::path::Path; use std::collections::HashMap; use chrono::{DateTime, TimeZone, Utc}; +use pagefind::api::PagefindIndex; +use pagefind::options::PagefindServiceConfig; use rusqlite::Connection; use crate::db::queries; @@ -9,7 +11,7 @@ use crate::engine::{EngineError, EngineResult}; use crate::model::{Post, ProjectMetadata}; use crate::render::{ GeneratedWriteOutcome, build_calendar_json, build_canonical_post_path, - render_markdown_to_html, render_starter_list_page_with_media_map, render_starter_single_post_page_with_media_map, + build_site_render_artifacts, render_markdown_to_html, write_generated_bytes, write_generated_file, }; @@ -34,31 +36,15 @@ pub fn generate_starter_site( language: &str, ) -> EngineResult { let mut report = GenerationReport::default(); - let media_rewrite_map = build_media_rewrite_map(conn, project_id)?; - - let list_input = posts + let input_posts = posts .iter() .map(|source| (source.post.clone(), source.body_markdown.clone())) .collect::>(); - let index_page = render_starter_list_page_with_media_map( - &list_input, - metadata, - language, - media_rewrite_map.clone(), - ) + let artifacts = build_site_render_artifacts(conn, output_dir.parent().unwrap_or(output_dir), project_id, metadata, &input_posts) .map_err(|error| EngineError::Parse(error.to_string()))?; - write_out(conn, output_dir, project_id, &index_page.relative_path, &index_page.html, &mut report)?; - for source in posts { - let rendered = render_starter_single_post_page_with_media_map( - &source.post, - &source.body_markdown, - metadata, - language, - media_rewrite_map.clone(), - ) - .map_err(|error| EngineError::Parse(error.to_string()))?; - write_out(conn, output_dir, project_id, &rendered.relative_path, &rendered.html, &mut report)?; + for page in &artifacts.pages { + write_out(conn, output_dir, project_id, &page.relative_path, &page.html, &mut report)?; } write_out( @@ -70,11 +56,23 @@ pub fn generate_starter_site( &mut report, )?; - let rss = build_rss_xml(metadata, posts, language); - write_out(conn, output_dir, project_id, "rss.xml", &rss, &mut report)?; - write_out(conn, output_dir, project_id, "feed.xml", &rss, &mut report)?; - write_out(conn, output_dir, project_id, "atom.xml", &build_atom_xml(metadata, posts, language), &mut report)?; - write_out(conn, output_dir, project_id, "sitemap.xml", &build_sitemap_xml(metadata, posts, language), &mut report)?; + for render_language in render_languages(metadata) { + let localized_posts = localized_sources(conn, output_dir.parent().unwrap_or(output_dir), posts, &render_language, metadata)?; + let prefix = if render_language == metadata.main_language.clone().unwrap_or_else(|| "en".to_string()) { + String::new() + } else { + format!("{}/", render_language) + }; + let rss = build_rss_xml(metadata, &localized_posts, &render_language); + if prefix.is_empty() { + write_out(conn, output_dir, project_id, "rss.xml", &rss, &mut report)?; + } + write_out(conn, output_dir, project_id, &format!("{prefix}feed.xml"), &rss, &mut report)?; + write_out(conn, output_dir, project_id, &format!("{prefix}atom.xml"), &build_atom_xml(metadata, &localized_posts, &render_language), &mut report)?; + write_out(conn, output_dir, project_id, &format!("{prefix}sitemap.xml"), &build_sitemap_xml(metadata, &localized_posts, &render_language), &mut report)?; + } + + write_pagefind_indexes(conn, output_dir, project_id, &artifacts.pagefind_documents, &mut report)?; Ok(report) } @@ -118,6 +116,99 @@ fn write_out( Ok(()) } +fn write_pagefind_indexes( + conn: &Connection, + output_dir: &Path, + project_id: &str, + documents: &[crate::render::PagefindDocument], + report: &mut GenerationReport, +) -> EngineResult<()> { + let runtime = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .map_err(EngineError::Io)?; + + let grouped = documents.iter().fold(HashMap::>::new(), |mut acc, doc| { + acc.entry(doc.language.clone()).or_default().push(doc); + acc + }); + + for (language, docs) in grouped { + let config = PagefindServiceConfig::builder() + .keep_index_url(true) + .force_language(language.clone()) + .build(); + let mut index = PagefindIndex::new(Some(config)) + .map_err(|error| EngineError::Parse(error.to_string()))?; + runtime.block_on(async { + for doc in docs { + index + .add_html_file(Some(doc.relative_path.clone()), None, doc.html.clone()) + .await + .map_err(|error| EngineError::Parse(error.to_string()))?; + } + let files = index.get_files().await.map_err(|error| EngineError::Parse(error.to_string()))?; + for file in files { + let relative = file.filename.to_string_lossy().trim_start_matches('/').to_string(); + match write_generated_bytes(conn, output_dir, project_id, &relative, &file.contents) + .map_err(|error| EngineError::Parse(error.to_string()))? + { + GeneratedWriteOutcome::Written => report.written_paths.push(relative), + GeneratedWriteOutcome::SkippedUnchanged => report.skipped_paths.push(relative), + } + } + Ok::<(), EngineError>(()) + })?; + } + + Ok(()) +} + +fn render_languages(metadata: &ProjectMetadata) -> Vec { + let main = metadata.main_language.clone().unwrap_or_else(|| "en".to_string()); + let mut languages = vec![main.clone()]; + for language in &metadata.blog_languages { + if !languages.iter().any(|existing| existing.eq_ignore_ascii_case(language)) { + languages.push(language.clone()); + } + } + languages +} + +fn localized_sources( + conn: &Connection, + data_dir: &Path, + posts: &[PublishedPostSource], + language: &str, + metadata: &ProjectMetadata, +) -> EngineResult> { + let main_language = metadata.main_language.as_deref().unwrap_or("en"); + let mut localized = Vec::new(); + for source in posts { + if language.eq_ignore_ascii_case(main_language) { + localized.push(source.clone()); + continue; + } + if let Ok(translation) = queries::post_translation::get_post_translation_by_post_and_language(conn, &source.post.id, language) { + let raw = std::fs::read_to_string(data_dir.join(translation.file_path.trim_start_matches('/'))) + .map_err(EngineError::Io)?; + let (_, body) = crate::util::frontmatter::read_translation_file(&raw) + .map_err(EngineError::Parse)?; + let mut translated_post = source.post.clone(); + translated_post.title = translation.title.clone(); + translated_post.excerpt = translation.excerpt.clone(); + translated_post.language = Some(translation.language.clone()); + translated_post.file_path = translation.file_path.clone(); + translated_post.published_at = translation.published_at.or(source.post.published_at); + localized.push(PublishedPostSource { + post: translated_post, + body_markdown: body, + }); + } + } + Ok(localized) +} + fn build_rss_xml(metadata: &ProjectMetadata, posts: &[PublishedPostSource], language: &str) -> String { let base_url = metadata.public_url.as_deref().unwrap_or("").trim_end_matches('/'); let last_build = posts diff --git a/crates/bds-core/src/engine/mod.rs b/crates/bds-core/src/engine/mod.rs index 6c0019d..ddef658 100644 --- a/crates/bds-core/src/engine/mod.rs +++ b/crates/bds-core/src/engine/mod.rs @@ -18,6 +18,7 @@ pub mod search; pub mod calendar; pub mod generation; pub mod preview; +pub mod validate_site; pub mod validate_translations; pub mod validate_media; pub mod validate_content; diff --git a/crates/bds-core/src/engine/preview.rs b/crates/bds-core/src/engine/preview.rs index 8e60776..76b0907 100644 --- a/crates/bds-core/src/engine/preview.rs +++ b/crates/bds-core/src/engine/preview.rs @@ -1,7 +1,6 @@ use std::fs; use std::path::{Path, PathBuf}; use std::thread; -use std::collections::HashMap; use axum::extract::{Path as AxumPath, Query, State}; use axum::http::{StatusCode, Uri, header}; @@ -15,7 +14,7 @@ use crate::db::{Database, queries}; use crate::engine::generation::PublishedPostSource; use crate::engine::{EngineError, EngineResult}; use crate::model::{Post, PostStatus, ProjectMetadata}; -use crate::render::{build_canonical_post_path, render_starter_list_page_with_media_map, render_starter_single_post_page_with_media_map}; +use crate::render::build_preview_response; use crate::util::frontmatter::{read_post_file, read_translation_file}; pub const PREVIEW_HOST: &str = "127.0.0.1"; @@ -62,6 +61,12 @@ struct DraftPreviewQuery { language: Option, } +#[derive(Debug, Deserialize, Default)] +struct StylePreviewQuery { + theme: Option, + mode: Option, +} + pub fn start_preview_server( db_path: PathBuf, data_dir: PathBuf, @@ -91,6 +96,7 @@ pub fn start_preview_server( let listener = tokio::net::TcpListener::from_std(listener).expect("preview listener"); let app = Router::new() .route("/__draft/{post_id}", get(handle_draft_preview)) + .route("/__style-preview", get(handle_style_preview)) .route("/", get(handle_preview_request)) .route("/{*path}", get(handle_preview_request)) .with_state(state); @@ -107,48 +113,6 @@ pub fn start_preview_server( }) } -pub fn render_preview_path( - path: &str, - metadata: &ProjectMetadata, - posts: &[PublishedPostSource], - canonical_media_path_by_source_path: &HashMap, -) -> EngineResult> { - let normalized = if path.is_empty() { "/" } else { path }; - let main_language = metadata.main_language.as_deref().unwrap_or("en"); - - if normalized == "/" { - let list_posts = posts - .iter() - .map(|source| (source.post.clone(), source.body_markdown.clone())) - .collect::>(); - return render_starter_list_page_with_media_map( - &list_posts, - metadata, - main_language, - canonical_media_path_by_source_path.clone(), - ) - .map(|page| Some(page.html)) - .map_err(|error| EngineError::Parse(error.to_string())); - } - - let (language, route_path) = split_language_prefix(normalized, metadata); - if let Some(source) = posts.iter().find(|source| { - build_canonical_post_path(&source.post, &language, main_language) == route_path - }) { - return render_starter_single_post_page_with_media_map( - &source.post, - &source.body_markdown, - metadata, - &language, - canonical_media_path_by_source_path.clone(), - ) - .map(|page| Some(page.html)) - .map_err(|error| EngineError::Parse(error.to_string())); - } - - Ok(None) -} - async fn handle_preview_request( State(state): State, uri: Uri, @@ -170,6 +134,21 @@ async fn handle_draft_preview( } } +async fn handle_style_preview( + Query(query): Query, +) -> Response { + let theme = query.theme.unwrap_or_else(|| "default".to_string()); + let mode = query.mode.unwrap_or_else(|| "auto".to_string()); + let html = format!( + "Style Preview

Style Preview

Theme: {}

Mode: {}

", + theme, + mode, + theme, + mode, + ); + Html(html).into_response() +} + fn render_preview_response( state: &PreviewServerState, path: &str, @@ -186,12 +165,15 @@ fn render_preview_response( let metadata = crate::engine::meta::read_project_json(&state.data_dir)?; let db = Database::open(&state.db_path)?; - let media_rewrite_map = build_media_rewrite_map(db.conn(), &state.project_id)?; let published_posts = collect_published_posts(state, &metadata)?; - match render_preview_path(path, &metadata, &published_posts, &media_rewrite_map)? { - Some(html) => Ok(Html(html).into_response()), - None => Ok(error_response(StatusCode::NOT_FOUND, "preview not found")), - } + let input_posts = published_posts + .iter() + .map(|source| (source.post.clone(), source.body_markdown.clone())) + .collect::>(); + let response = build_preview_response(db.conn(), &state.data_dir, &state.project_id, &metadata, &input_posts, path) + .map_err(|error| EngineError::Parse(error.to_string()))?; + let status = StatusCode::from_u16(response.status_code).unwrap_or(StatusCode::OK); + Ok((status, Html(response.html)).into_response()) } fn render_draft_preview( @@ -211,7 +193,6 @@ fn render_draft_preview( post_id, target_language, ) { - let media_rewrite_map = build_media_rewrite_map(db.conn(), &post.project_id)?; let mut translated_post = post.clone(); translated_post.title = translation.title.clone(); translated_post.excerpt = translation.excerpt.clone(); @@ -220,56 +201,35 @@ fn render_draft_preview( translated_post.file_path = translation.file_path.clone(); translated_post.published_at = translation.published_at.or(post.published_at); let body = load_translation_body(&state.data_dir, &translation)?; - return render_starter_single_post_page_with_media_map( - &translated_post, - &body, + let response = build_preview_response( + db.conn(), + &state.data_dir, + &state.project_id, &metadata, - target_language, - media_rewrite_map, + &[(translated_post, body)], + &crate::render::build_canonical_post_path(&post, target_language, metadata.main_language.as_deref().unwrap_or("en")), ) - .map(|page| page.html) - .map_err(|error| EngineError::Parse(error.to_string())); + .map_err(|error| EngineError::Parse(error.to_string()))?; + return Ok(response.html); } } - let media_rewrite_map = build_media_rewrite_map(db.conn(), &post.project_id)?; let body = load_post_body(&state.data_dir, &post)?; - render_starter_single_post_page_with_media_map( - &post, - &body, + let response = build_preview_response( + db.conn(), + &state.data_dir, + &state.project_id, &metadata, - canonical_language, - media_rewrite_map, + &[(post.clone(), body)], + &crate::render::build_canonical_post_path(&post, canonical_language, metadata.main_language.as_deref().unwrap_or("en")), ) - .map(|page| page.html) - .map_err(|error| EngineError::Parse(error.to_string())) -} - -fn build_media_rewrite_map( - conn: &rusqlite::Connection, - project_id: &str, -) -> EngineResult> { - let media_items = queries::media::list_media_by_project(conn, project_id)?; - let mut map = HashMap::new(); - - for media in media_items { - let canonical_path = if media.file_path.starts_with('/') { - media.file_path.clone() - } else { - format!("/{}", media.file_path.trim_start_matches('/')) - }; - map.insert(format!("bds-media://{}", media.id), canonical_path.clone()); - - let relative_key = media.file_path.trim_start_matches('/').to_lowercase(); - map.insert(relative_key, canonical_path); - } - - Ok(map) + .map_err(|error| EngineError::Parse(error.to_string()))?; + Ok(response.html) } fn collect_published_posts( state: &PreviewServerState, - metadata: &ProjectMetadata, + _metadata: &ProjectMetadata, ) -> EngineResult> { let db = Database::open(&state.db_path)?; let posts = queries::post::list_posts_by_project(db.conn(), &state.project_id)?; @@ -280,8 +240,7 @@ fn collect_published_posts( post, }); } - let main_language = metadata.main_language.as_deref().unwrap_or("en"); - published.sort_by_key(|source| build_canonical_post_path(&source.post, main_language, main_language)); + published.sort_by_key(|source| source.post.published_at.unwrap_or(source.post.created_at)); Ok(published) } @@ -373,21 +332,6 @@ fn error_response(status: StatusCode, message: &str) -> Response { (status, [(header::CONTENT_TYPE, "text/plain; charset=utf-8")], message.to_string()).into_response() } -fn split_language_prefix(path: &str, metadata: &ProjectMetadata) -> (String, String) { - let trimmed = path.trim_start_matches('/'); - let mut segments = trimmed.split('/'); - let first = segments.next().unwrap_or_default(); - if metadata.blog_languages.iter().any(|language| language == first) { - let remainder = segments.collect::>().join("/"); - return (first.to_string(), format!("/{first}/{}", remainder.trim_start_matches('/'))); - } - - ( - metadata.main_language.as_deref().unwrap_or("en").to_string(), - path.to_string(), - ) -} - #[cfg(test)] mod tests { use super::*; @@ -505,36 +449,52 @@ mod tests { #[test] fn root_preview_renders_index_page() { - let html = render_preview_path("/", &make_metadata(), &[make_post()], &HashMap::new()) + let db = Database::open_in_memory().unwrap(); + let html = build_preview_response(db.conn(), Path::new("."), "project-1", &make_metadata(), &[(make_post().post, make_post().body_markdown)], "/") .unwrap() - .unwrap(); + .html; assert!(html.contains("post-list")); } #[test] fn preview_renders_single_post_for_canonical_path() { - let html = render_preview_path( - "/2024/03/09/hello", + let db = Database::open_in_memory().unwrap(); + let source = make_post(); + let html = build_preview_response( + db.conn(), + Path::new("."), + "project-1", &make_metadata(), - &[make_post()], - &HashMap::new(), + &[(source.post, source.body_markdown)], + "/2024/03/09/hello", ) - .unwrap() - .unwrap(); + .unwrap() + .html; assert!(html.contains("

Hello

")); assert!(html.contains("world")); } #[test] fn preview_renders_language_prefixed_single_post() { - let html = render_preview_path( - "/de/2024/03/09/hello", - &make_metadata(), - &[make_post()], - &HashMap::new(), + let dir = tempfile::tempdir().unwrap(); + std::fs::create_dir_all(dir.path().join("meta")).unwrap(); + std::fs::write( + dir.path().join("posts/2024/03/hello.de.md"), + "---\ntranslationFor: post-1\nlanguage: de\ntitle: Hallo\n---\nHallo **welt**", ) - .unwrap() - .unwrap(); + .ok(); + let db = Database::open_in_memory().unwrap(); + let source = make_post(); + let html = build_preview_response( + db.conn(), + dir.path(), + "project-1", + &make_metadata(), + &[(source.post, source.body_markdown)], + "/de/2024/03/09/hello", + ) + .unwrap() + .html; assert!(html.contains("lang=\"de\"")); } @@ -592,4 +552,31 @@ mod tests { assert_eq!(response.status(), StatusCode::NOT_FOUND); } + + #[test] + fn preview_server_serves_style_preview() { + let _guard = preview_port_guard().lock().unwrap(); + let (dir, _db) = setup_preview_fixture(); + + let server = start_preview_server( + dir.path().join("bds.db"), + dir.path().to_path_buf(), + "project-1".into(), + ) + .unwrap(); + + let client = reqwest::blocking::Client::new(); + let response = client + .get(format!( + "http://{PREVIEW_HOST}:{PREVIEW_PORT}/__style-preview?theme=nightfall&mode=dark" + )) + .send() + .unwrap(); + let body = response.text().unwrap(); + server.stop().unwrap(); + + assert!(body.contains("Style Preview")); + assert!(body.contains("nightfall")); + assert!(body.contains("dark")); + } } \ No newline at end of file diff --git a/crates/bds-core/src/engine/validate_site.rs b/crates/bds-core/src/engine/validate_site.rs new file mode 100644 index 0000000..72b58ba --- /dev/null +++ b/crates/bds-core/src/engine/validate_site.rs @@ -0,0 +1,133 @@ +use std::collections::HashSet; +use std::path::Path; + +use rusqlite::Connection; +use walkdir::WalkDir; + +use crate::db::queries; +use crate::engine::{EngineError, EngineResult}; +use crate::model::{Post, PostStatus}; +use crate::render::build_site_render_artifacts; +use crate::util::file_hash; + +#[derive(Debug, Clone, Default)] +pub struct SiteValidationReport { + pub missing_pages: Vec, + pub extra_pages: Vec, + pub stale_pages: Vec, +} + +pub fn validate_site( + conn: &Connection, + data_dir: &Path, + project_id: &str, +) -> EngineResult { + let metadata = crate::engine::meta::read_project_json(data_dir)?; + let output_dir = data_dir.to_path_buf(); + let published_posts = load_published_posts(data_dir, conn, project_id)?; + let artifacts = build_site_render_artifacts(conn, data_dir, project_id, &metadata, &published_posts) + .map_err(|error| EngineError::Parse(error.to_string()))?; + + let mut expected = artifacts + .pages + .iter() + .map(|page| page.relative_path.clone()) + .collect::>(); + expected.insert("calendar.json".to_string()); + expected.insert("rss.xml".to_string()); + for language in render_languages(&metadata) { + let prefix = if language == metadata.main_language.clone().unwrap_or_else(|| "en".to_string()) { + String::new() + } else { + format!("{language}/") + }; + expected.insert(format!("{prefix}feed.xml")); + expected.insert(format!("{prefix}atom.xml")); + expected.insert(format!("{prefix}sitemap.xml")); + } + + let mut actual = HashSet::new(); + if output_dir.exists() { + for entry in WalkDir::new(&output_dir).into_iter().filter_map(Result::ok) { + if !entry.file_type().is_file() { + continue; + } + let rel = entry + .path() + .strip_prefix(&output_dir) + .unwrap_or(entry.path()) + .to_string_lossy() + .replace('\\', "/"); + if rel.starts_with("meta/") + || rel.starts_with("posts/") + || rel.starts_with("media/") + || rel.starts_with("assets/") + { + continue; + } + if rel.starts_with("pagefind") || rel.contains("/pagefind/") { + continue; + } + if rel.ends_with(".html") || rel.ends_with(".xml") || rel.ends_with(".json") { + actual.insert(rel); + } + } + } + + let mut missing_pages = expected.difference(&actual).cloned().collect::>(); + let mut extra_pages = actual.difference(&expected).cloned().collect::>(); + + let mut stale_pages = Vec::new(); + for rel in expected.intersection(&actual) { + if let Ok(stored) = queries::generated_file_hash::get_generated_file_hash(conn, project_id, rel) { + let actual_hash = file_hash(&output_dir.join(rel))?; + if actual_hash != stored.content_hash { + stale_pages.push(rel.clone()); + } + } + } + + missing_pages.sort(); + extra_pages.sort(); + stale_pages.sort(); + + Ok(SiteValidationReport { + missing_pages, + extra_pages, + stale_pages, + }) +} + +fn load_published_posts( + data_dir: &Path, + conn: &Connection, + project_id: &str, +) -> EngineResult> { + let posts = queries::post::list_posts_by_project(conn, project_id)?; + let mut published = Vec::new(); + for post in posts.into_iter().filter(|post| post.status == PostStatus::Published) { + let body = if let Some(content) = &post.content { + content.clone() + } else if let Some(content) = &post.published_content { + content.clone() + } else { + let raw = std::fs::read_to_string(data_dir.join(post.file_path.trim_start_matches('/')))?; + crate::util::frontmatter::read_post_file(&raw) + .map(|(_, body)| body) + .map_err(EngineError::Parse)? + }; + published.push((post, body)); + } + Ok(published) +} + +fn render_languages(metadata: &crate::model::ProjectMetadata) -> Vec { + let main = metadata.main_language.clone().unwrap_or_else(|| "en".to_string()); + let mut languages = vec![main.clone()]; + for language in &metadata.blog_languages { + if !languages.iter().any(|existing| existing.eq_ignore_ascii_case(language)) { + languages.push(language.clone()); + } + } + languages +} \ No newline at end of file diff --git a/crates/bds-core/src/render/generation.rs b/crates/bds-core/src/render/generation.rs index 2d45ac3..e5d2f2a 100644 --- a/crates/bds-core/src/render/generation.rs +++ b/crates/bds-core/src/render/generation.rs @@ -56,6 +56,39 @@ pub fn write_generated_file( Ok(GeneratedWriteOutcome::Written) } +pub fn write_generated_bytes( + conn: &Connection, + output_dir: &Path, + project_id: &str, + relative_path: &str, + content: &[u8], +) -> Result> { + let hash = content_hash(content); + if let Ok(existing) = qhash::get_generated_file_hash(conn, project_id, relative_path) { + if existing.content_hash == hash { + return Ok(GeneratedWriteOutcome::SkippedUnchanged); + } + } + + let target_path = output_dir.join(relative_path); + if let Some(parent) = target_path.parent() { + fs::create_dir_all(parent)?; + } + crate::util::atomic_write(&target_path, content)?; + + qhash::upsert_generated_file_hash( + conn, + &GeneratedFileHash { + project_id: project_id.to_string(), + relative_path: relative_path.to_string(), + content_hash: hash, + updated_at: now_unix_ms(), + }, + )?; + + Ok(GeneratedWriteOutcome::Written) +} + pub fn build_core_generation_paths(main_language: &str, blog_languages: &[String]) -> Vec { let mut paths = vec![ "index.html".to_string(), diff --git a/crates/bds-core/src/render/mod.rs b/crates/bds-core/src/render/mod.rs index 643cf1f..63be7f3 100644 --- a/crates/bds-core/src/render/mod.rs +++ b/crates/bds-core/src/render/mod.rs @@ -2,11 +2,12 @@ mod markdown; mod generation; mod page_renderer; mod routes; +mod site; mod template_lookup; pub use generation::{ CalendarArchiveData, GeneratedWriteOutcome, build_calendar_json, - build_core_generation_paths, write_generated_file, + build_core_generation_paths, write_generated_bytes, write_generated_file, }; pub use markdown::render_markdown_to_html; pub use page_renderer::{RenderError, render_liquid_template}; @@ -15,6 +16,10 @@ pub use routes::{ render_starter_list_page_with_media_map, render_starter_single_post_page, render_starter_single_post_page_with_media_map, }; +pub use site::{ + PagefindDocument, PreviewRenderResult, SitePage, SiteRenderArtifacts, + build_preview_response, build_site_render_artifacts, +}; pub use template_lookup::{ RenderCategorySettings, RenderTemplateLookup, TemplateLookupError, resolve_post_template, diff --git a/crates/bds-core/src/render/site.rs b/crates/bds-core/src/render/site.rs new file mode 100644 index 0000000..9fed07b --- /dev/null +++ b/crates/bds-core/src/render/site.rs @@ -0,0 +1,1085 @@ +use std::collections::{BTreeMap, HashMap}; +use std::error::Error; +use std::fs; +use std::path::Path; + +use chrono::{Datelike, TimeZone, Utc}; +use rayon::prelude::*; +use rusqlite::Connection; +use serde_json::{Value, json}; + +use crate::db::queries; +use crate::model::{CategorySettings, Media, Post, ProjectMetadata, Tag, Template, TemplateKind, TemplateStatus}; +use crate::render::{RenderCategorySettings, RenderTemplateLookup, build_canonical_post_path, render_liquid_template, resolve_post_template}; +use crate::util::frontmatter::{read_template_file, read_translation_file}; +use crate::util::slugify; + +const STARTER_SINGLE_POST_TEMPLATE: &str = include_str!("../../../../assets/starter-templates/single-post.liquid"); +const STARTER_POST_LIST_TEMPLATE: &str = include_str!("../../../../assets/starter-templates/post-list.liquid"); +const STARTER_NOT_FOUND_TEMPLATE: &str = include_str!("../../../../assets/starter-templates/not-found.liquid"); +const STARTER_HEAD_PARTIAL: &str = include_str!("../../../../assets/starter-templates/partials/head.liquid"); +const STARTER_MENU_PARTIAL: &str = include_str!("../../../../assets/starter-templates/partials/menu.liquid"); +const STARTER_LANGUAGE_SWITCHER_PARTIAL: &str = include_str!("../../../../assets/starter-templates/partials/language-switcher.liquid"); + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct SitePage { + pub language: String, + pub relative_path: String, + pub url_path: String, + pub html: String, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct PagefindDocument { + pub language: String, + pub relative_path: String, + pub url_path: String, + pub html: String, +} + +#[derive(Debug, Clone, Default)] +pub struct SiteRenderArtifacts { + pub pages: Vec, + pub pagefind_documents: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct PreviewRenderResult { + pub status_code: u16, + pub html: String, +} + +#[derive(Debug, Clone)] +struct TemplateBundle { + post_templates: Vec