diff --git a/Cargo.lock b/Cargo.lock
index bb2332e34..2f00d1f25 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -216,7 +216,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.67",
+ "syn 2.0.68",
]
[[package]]
@@ -227,7 +227,7 @@ checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.67",
+ "syn 2.0.68",
]
[[package]]
@@ -778,7 +778,7 @@ dependencies = [
"heck 0.4.1",
"proc-macro2",
"quote",
- "syn 2.0.67",
+ "syn 2.0.68",
]
[[package]]
@@ -830,6 +830,15 @@ version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
+[[package]]
+name = "basic-toml"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "823388e228f614e9558c6804262db37960ec8821856535f5c3f59913140558f8"
+dependencies = [
+ "serde",
+]
+
[[package]]
name = "bincode"
version = "1.3.3"
@@ -847,9 +856,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
-version = "2.5.0"
+version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
+checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
dependencies = [
"serde",
]
@@ -931,9 +940,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
[[package]]
name = "cc"
-version = "1.0.99"
+version = "1.0.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "96c51067fd44124faa7f870b4b1c969379ad32b2ba805aa959430ceaa384f695"
+checksum = "74b6a57f98764a267ff415d50a25e6e166f3831a5071af4995296ea97d210490"
dependencies = [
"jobserver",
"libc",
@@ -965,28 +974,6 @@ dependencies = [
"windows-targets 0.52.5",
]
-[[package]]
-name = "chrono-tz"
-version = "0.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "93698b29de5e97ad0ae26447b344c482a7284c737d9ddc5f9e52b74a336671bb"
-dependencies = [
- "chrono",
- "chrono-tz-build",
- "phf 0.11.2",
-]
-
-[[package]]
-name = "chrono-tz-build"
-version = "0.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0c088aee841df9c3041febbb73934cfc39708749bf96dc827e3359cd39ef11b1"
-dependencies = [
- "parse-zoneinfo",
- "phf 0.11.2",
- "phf_codegen 0.11.2",
-]
-
[[package]]
name = "ciborium"
version = "0.2.2"
@@ -1016,9 +1003,9 @@ dependencies = [
[[package]]
name = "clap"
-version = "4.5.7"
+version = "4.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5db83dced34638ad474f39f250d7fea9598bdd239eaced1bdf45d597da0f433f"
+checksum = "84b3edb18336f4df585bc9aa31dd99c036dfa5dc5e9a2939a722a188f3a8970d"
dependencies = [
"clap_builder",
"clap_derive",
@@ -1026,9 +1013,9 @@ dependencies = [
[[package]]
name = "clap_builder"
-version = "4.5.7"
+version = "4.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f7e204572485eb3fbf28f871612191521df159bc3e15a9f5064c66dba3a8c05f"
+checksum = "c1c09dd5ada6c6c78075d6fd0da3f90d8080651e2d6cc8eb2f1aaa4034ced708"
dependencies = [
"anstream",
"anstyle",
@@ -1038,14 +1025,14 @@ dependencies = [
[[package]]
name = "clap_derive"
-version = "4.5.5"
+version = "4.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c780290ccf4fb26629baa7a1081e68ced113f1d3ec302fa5948f1c381ebf06c6"
+checksum = "2bac35c6dafb060fd4d275d9a4ffae97917c13a6327903a8be2153cd964f7085"
dependencies = [
"heck 0.5.0",
"proc-macro2",
"quote",
- "syn 2.0.67",
+ "syn 2.0.68",
]
[[package]]
@@ -1369,7 +1356,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331"
dependencies = [
"quote",
- "syn 2.0.67",
+ "syn 2.0.68",
]
[[package]]
@@ -1389,9 +1376,9 @@ dependencies = [
[[package]]
name = "curl-sys"
-version = "0.4.72+curl-8.6.0"
+version = "0.4.73+curl-8.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "29cbdc8314c447d11e8fd156dcdd031d9e02a7a976163e396b548c03153bc9ea"
+checksum = "450ab250ecf17227c39afb9a2dd9261dc0035cb80f2612472fc0c4aac2dcb84d"
dependencies = [
"cc",
"libc",
@@ -1432,7 +1419,7 @@ dependencies = [
"proc-macro2",
"quote",
"strsim",
- "syn 2.0.67",
+ "syn 2.0.68",
]
[[package]]
@@ -1443,7 +1430,7 @@ checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178"
dependencies = [
"darling_core",
"quote",
- "syn 2.0.67",
+ "syn 2.0.68",
]
[[package]]
@@ -1508,7 +1495,7 @@ checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.67",
+ "syn 2.0.68",
]
[[package]]
@@ -1529,7 +1516,7 @@ dependencies = [
"darling",
"proc-macro2",
"quote",
- "syn 2.0.67",
+ "syn 2.0.68",
]
[[package]]
@@ -1539,7 +1526,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "206868b8242f27cecce124c19fd88157fbd0dd334df2587f36417bafbc85097b"
dependencies = [
"derive_builder_core",
- "syn 2.0.67",
+ "syn 2.0.68",
]
[[package]]
@@ -1552,7 +1539,7 @@ dependencies = [
"proc-macro2",
"quote",
"rustc_version",
- "syn 2.0.67",
+ "syn 2.0.68",
]
[[package]]
@@ -1587,7 +1574,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.67",
+ "syn 2.0.68",
]
[[package]]
@@ -1651,6 +1638,7 @@ dependencies = [
"rayon",
"regex",
"reqwest",
+ "rinja",
"rusqlite",
"rustwide",
"semver",
@@ -1669,7 +1657,6 @@ dependencies = [
"strum",
"syntect",
"tempfile",
- "tera",
"test-case",
"thiserror",
"thread_local",
@@ -1739,9 +1726,9 @@ dependencies = [
[[package]]
name = "either"
-version = "1.12.0"
+version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b"
+checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
dependencies = [
"serde",
]
@@ -1909,7 +1896,7 @@ checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181"
dependencies = [
"futures-core",
"futures-sink",
- "spin 0.9.8",
+ "spin",
]
[[package]]
@@ -1920,7 +1907,7 @@ checksum = "2cd66269887534af4b0c3e3337404591daa8dc8b9b2b3db71f9523beb4bafb41"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.67",
+ "syn 2.0.68",
]
[[package]]
@@ -2043,7 +2030,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.67",
+ "syn 2.0.68",
]
[[package]]
@@ -2130,7 +2117,7 @@ version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b903b73e45dc0c6c596f2d37eccece7c1c8bb6e4407b001096387c63d0d93724"
dependencies = [
- "bitflags 2.5.0",
+ "bitflags 2.6.0",
"libc",
"libgit2-sys",
"log",
@@ -2246,9 +2233,9 @@ dependencies = [
[[package]]
name = "gix-actor"
-version = "0.31.2"
+version = "0.31.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d69c59d392c7e6c94385b6fd6089d6df0fe945f32b4357687989f3aee253cd7f"
+checksum = "e0f52455500a0fac1fd62a1cf42d9121cfddef8cb3ded2f9e7adb5775deb1fc9"
dependencies = [
"bstr",
"gix-date",
@@ -2367,7 +2354,7 @@ version = "0.14.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbd06203b1a9b33a78c88252a625031b094d9e1b647260070c25b09910c0a804"
dependencies = [
- "bitflags 2.5.0",
+ "bitflags 2.6.0",
"bstr",
"gix-path",
"libc",
@@ -2393,9 +2380,9 @@ dependencies = [
[[package]]
name = "gix-date"
-version = "0.8.6"
+version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "367ee9093b0c2b04fd04c5c7c8b6a1082713534eab537597ae343663a518fa99"
+checksum = "9eed6931f21491ee0aeb922751bd7ec97b4b2fe8fbfedcb678e2a2dce5f3b8c0"
dependencies = [
"bstr",
"itoa 1.0.11",
@@ -2539,7 +2526,7 @@ version = "0.16.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a29ad0990cf02c48a7aac76ed0dbddeb5a0d070034b83675cc3bbf937eace4"
dependencies = [
- "bitflags 2.5.0",
+ "bitflags 2.6.0",
"bstr",
"gix-features",
"gix-path",
@@ -2585,7 +2572,7 @@ version = "0.32.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "881ab3b1fa57f497601a5add8289e72a7ae09471fc0b9bbe483b628ae8e418a1"
dependencies = [
- "bitflags 2.5.0",
+ "bitflags 2.6.0",
"bstr",
"filetime",
"fnv",
@@ -2612,7 +2599,7 @@ version = "0.33.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d8c5a5f1c58edcbc5692b174cda2703aba82ed17d7176ff4c1752eb48b1b167"
dependencies = [
- "bitflags 2.5.0",
+ "bitflags 2.6.0",
"bstr",
"filetime",
"fnv",
@@ -2664,7 +2651,7 @@ checksum = "999ce923619f88194171a67fb3e6d613653b8d4d6078b529b15a765da0edcc17"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.67",
+ "syn 2.0.68",
]
[[package]]
@@ -2673,7 +2660,7 @@ version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d57dec54544d155a495e01de947da024471e1825d7d3f2724301c07a310d6184"
dependencies = [
- "bitflags 2.5.0",
+ "bitflags 2.6.0",
"gix-commitgraph",
"gix-date",
"gix-hash",
@@ -2810,9 +2797,9 @@ dependencies = [
[[package]]
name = "gix-path"
-version = "0.10.7"
+version = "0.10.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "23623cf0f475691a6d943f898c4d0b89f5c1a2a64d0f92bce0e0322ee6528783"
+checksum = "ca987128ffb056d732bd545db5db3d8b103d252fbf083c2567bb0796876619a4"
dependencies = [
"bstr",
"gix-trace",
@@ -2827,7 +2814,7 @@ version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a76cab098dc10ba2d89f634f66bf196dea4d7db4bf10b75c7a9c201c55a2ee19"
dependencies = [
- "bitflags 2.5.0",
+ "bitflags 2.6.0",
"bstr",
"gix-attributes",
"gix-config-value",
@@ -2973,7 +2960,7 @@ version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fddc27984a643b20dd03e97790555804f98cf07404e0e552c0ad8133266a79a1"
dependencies = [
- "bitflags 2.5.0",
+ "bitflags 2.6.0",
"gix-path",
"libc",
"windows-sys 0.52.0",
@@ -3067,7 +3054,7 @@ version = "0.39.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f20cb69b63eb3e4827939f42c05b7756e3488ef49c25c412a876691d568ee2a0"
dependencies = [
- "bitflags 2.5.0",
+ "bitflags 2.6.0",
"gix-commitgraph",
"gix-date",
"gix-hash",
@@ -3149,30 +3136,6 @@ dependencies = [
"gix-validate",
]
-[[package]]
-name = "globset"
-version = "0.4.14"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1"
-dependencies = [
- "aho-corasick",
- "bstr",
- "log",
- "regex-automata 0.4.7",
- "regex-syntax 0.8.4",
-]
-
-[[package]]
-name = "globwalk"
-version = "0.9.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757"
-dependencies = [
- "bitflags 2.5.0",
- "ignore",
- "walkdir",
-]
-
[[package]]
name = "grass"
version = "0.13.3"
@@ -3637,22 +3600,6 @@ dependencies = [
"unicode-normalization",
]
-[[package]]
-name = "ignore"
-version = "0.4.22"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b46810df39e66e925525d6e38ce1e7f6e1d208f72dc39757880fcb66e2c58af1"
-dependencies = [
- "crossbeam-deque",
- "globset",
- "log",
- "memchr",
- "regex-automata 0.4.7",
- "same-file",
- "walkdir",
- "winapi-util",
-]
-
[[package]]
name = "imara-diff"
version = "0.1.5"
@@ -3816,11 +3763,11 @@ dependencies = [
[[package]]
name = "lazy_static"
-version = "1.4.0"
+version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
dependencies = [
- "spin 0.5.2",
+ "spin",
]
[[package]]
@@ -3932,9 +3879,9 @@ dependencies = [
[[package]]
name = "log"
-version = "0.4.21"
+version = "0.4.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
+checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
[[package]]
name = "lol_html"
@@ -3942,7 +3889,7 @@ version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4629ff9c2deeb7aad9b2d0f379fc41937a02f3b739f007732c46af40339dee5"
dependencies = [
- "bitflags 2.5.0",
+ "bitflags 2.6.0",
"cfg-if",
"cssparser",
"encoding_rs",
@@ -4013,7 +3960,7 @@ checksum = "5cf92c10c7e361d6b99666ec1c6f9805b0bea2c3bd8c78dc6fe98ac5bd78db11"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.67",
+ "syn 2.0.68",
]
[[package]]
@@ -4049,9 +3996,9 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "mime_guess"
-version = "2.0.4"
+version = "2.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef"
+checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e"
dependencies = [
"mime",
"unicase",
@@ -4142,7 +4089,7 @@ version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
dependencies = [
- "bitflags 2.5.0",
+ "bitflags 2.6.0",
"cfg-if",
"cfg_aliases",
"libc",
@@ -4257,9 +4204,9 @@ dependencies = [
[[package]]
name = "object"
-version = "0.36.0"
+version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "576dfe1fc8f9df304abb159d767a29d0476f7750fbf8aa7ad07816004a207434"
+checksum = "081b846d1d56ddfc18fdf1a922e4f6e07a11768ea1b92dec44e42b72712ccfce"
dependencies = [
"memchr",
]
@@ -4307,7 +4254,7 @@ version = "0.10.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f"
dependencies = [
- "bitflags 2.5.0",
+ "bitflags 2.6.0",
"cfg-if",
"foreign-types",
"libc",
@@ -4324,7 +4271,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.67",
+ "syn 2.0.68",
]
[[package]]
@@ -4402,15 +4349,6 @@ dependencies = [
"windows-targets 0.52.5",
]
-[[package]]
-name = "parse-zoneinfo"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1f2a05b18d44e2957b88f96ba460715e295bc1d7510468a2f3d3b44535d26c24"
-dependencies = [
- "regex",
-]
-
[[package]]
name = "paste"
version = "1.0.15"
@@ -4438,51 +4376,6 @@ version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
-[[package]]
-name = "pest"
-version = "2.7.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "560131c633294438da9f7c4b08189194b20946c8274c6b9e38881a7874dc8ee8"
-dependencies = [
- "memchr",
- "thiserror",
- "ucd-trie",
-]
-
-[[package]]
-name = "pest_derive"
-version = "2.7.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "26293c9193fbca7b1a3bf9b79dc1e388e927e6cacaa78b4a3ab705a1d3d41459"
-dependencies = [
- "pest",
- "pest_generator",
-]
-
-[[package]]
-name = "pest_generator"
-version = "2.7.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3ec22af7d3fb470a85dd2ca96b7c577a1eb4ef6f1683a9fe9a8c16e136c04687"
-dependencies = [
- "pest",
- "pest_meta",
- "proc-macro2",
- "quote",
- "syn 2.0.67",
-]
-
-[[package]]
-name = "pest_meta"
-version = "2.7.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d7a240022f37c361ec1878d646fc5b7d7c4d28d5946e1a80ad5a7a4f4ca0bdcd"
-dependencies = [
- "once_cell",
- "pest",
- "sha2",
-]
-
[[package]]
name = "phf"
version = "0.8.0"
@@ -4533,16 +4426,6 @@ dependencies = [
"phf_shared 0.10.0",
]
-[[package]]
-name = "phf_codegen"
-version = "0.11.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a"
-dependencies = [
- "phf_generator 0.11.2",
- "phf_shared 0.11.2",
-]
-
[[package]]
name = "phf_generator"
version = "0.8.0"
@@ -4597,7 +4480,7 @@ dependencies = [
"phf_shared 0.11.2",
"proc-macro2",
"quote",
- "syn 2.0.67",
+ "syn 2.0.68",
]
[[package]]
@@ -4644,7 +4527,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.67",
+ "syn 2.0.68",
]
[[package]]
@@ -4747,7 +4630,7 @@ dependencies = [
"heck 0.4.1",
"proc-macro2",
"quote",
- "syn 2.0.67",
+ "syn 2.0.68",
]
[[package]]
@@ -5031,7 +4914,7 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd"
dependencies = [
- "bitflags 2.5.0",
+ "bitflags 2.6.0",
]
[[package]]
@@ -5167,11 +5050,51 @@ dependencies = [
"cfg-if",
"getrandom 0.2.15",
"libc",
- "spin 0.9.8",
+ "spin",
"untrusted",
"windows-sys 0.52.0",
]
+[[package]]
+name = "rinja"
+version = "0.13.0"
+source = "git+https://github.com/rinja-rs/rinja#e3c1fe3548522b654a8734f16167aebf8c847558"
+dependencies = [
+ "humansize",
+ "num-traits",
+ "percent-encoding",
+ "rinja_derive",
+ "rinja_escape",
+]
+
+[[package]]
+name = "rinja_derive"
+version = "0.13.0"
+source = "git+https://github.com/rinja-rs/rinja#e3c1fe3548522b654a8734f16167aebf8c847558"
+dependencies = [
+ "basic-toml",
+ "mime",
+ "mime_guess",
+ "proc-macro2",
+ "quote",
+ "rinja_parser",
+ "serde",
+ "syn 2.0.68",
+]
+
+[[package]]
+name = "rinja_escape"
+version = "0.11.0"
+source = "git+https://github.com/rinja-rs/rinja#e3c1fe3548522b654a8734f16167aebf8c847558"
+
+[[package]]
+name = "rinja_parser"
+version = "0.3.0"
+source = "git+https://github.com/rinja-rs/rinja#e3c1fe3548522b654a8734f16167aebf8c847558"
+dependencies = [
+ "nom",
+]
+
[[package]]
name = "roxmltree"
version = "0.14.1"
@@ -5207,7 +5130,7 @@ version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a78046161564f5e7cd9008aff3b2990b3850dc8e0349119b98e8f251e099f24d"
dependencies = [
- "bitflags 2.5.0",
+ "bitflags 2.6.0",
"fallible-iterator 0.3.0",
"fallible-streaming-iterator",
"hashlink",
@@ -5256,7 +5179,7 @@ version = "0.38.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f"
dependencies = [
- "bitflags 2.5.0",
+ "bitflags 2.6.0",
"errno",
"libc",
"linux-raw-sys 0.4.14",
@@ -5454,7 +5377,7 @@ version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0"
dependencies = [
- "bitflags 2.5.0",
+ "bitflags 2.6.0",
"core-foundation",
"core-foundation-sys",
"libc",
@@ -5650,14 +5573,14 @@ checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.67",
+ "syn 2.0.68",
]
[[package]]
name = "serde_json"
-version = "1.0.117"
+version = "1.0.119"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3"
+checksum = "e8eddb61f0697cc3989c5d64b452f5488e2b8a60fd7d5076a3045076ffef8cb0"
dependencies = [
"indexmap 2.2.6",
"itoa 1.0.11",
@@ -5698,9 +5621,9 @@ dependencies = [
[[package]]
name = "serde_with"
-version = "3.8.1"
+version = "3.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0ad483d2ab0149d5a5ebcd9972a3852711e0153d863bf5a5d0391d28883c4a20"
+checksum = "079f3a42cd87588d924ed95b533f8d30a483388c4e400ab736a7058e34f16169"
dependencies = [
"base64 0.22.1",
"chrono",
@@ -5716,14 +5639,14 @@ dependencies = [
[[package]]
name = "serde_with_macros"
-version = "3.8.1"
+version = "3.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "65569b702f41443e8bc8bbb1c5779bd0450bbe723b56198980e80ec45780bce2"
+checksum = "bc03aad67c1d26b7de277d51c86892e7d9a0110a2fe44bf6b26cc569fba302d6"
dependencies = [
"darling",
"proc-macro2",
"quote",
- "syn 2.0.67",
+ "syn 2.0.68",
]
[[package]]
@@ -5886,12 +5809,6 @@ dependencies = [
"windows-sys 0.52.0",
]
-[[package]]
-name = "spin"
-version = "0.5.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
-
[[package]]
name = "spin"
version = "0.9.8"
@@ -6031,7 +5948,7 @@ checksum = "1ed31390216d20e538e447a7a9b959e06ed9fc51c37b514b46eb758016ecd418"
dependencies = [
"atoi",
"base64 0.21.7",
- "bitflags 2.5.0",
+ "bitflags 2.6.0",
"byteorder",
"bytes",
"chrono",
@@ -6074,7 +5991,7 @@ checksum = "7c824eb80b894f926f89a0b9da0c7f435d27cdd35b8c655b114e58223918577e"
dependencies = [
"atoi",
"base64 0.21.7",
- "bitflags 2.5.0",
+ "bitflags 2.6.0",
"byteorder",
"chrono",
"crc",
@@ -6186,9 +6103,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "strum"
-version = "0.26.2"
+version = "0.26.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29"
+checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06"
dependencies = [
"strum_macros",
]
@@ -6203,14 +6120,14 @@ dependencies = [
"proc-macro2",
"quote",
"rustversion",
- "syn 2.0.67",
+ "syn 2.0.68",
]
[[package]]
name = "subtle"
-version = "2.6.0"
+version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0d0208408ba0c3df17ed26eb06992cb1a1268d41b2c0e12e65203fbe3972cee5"
+checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "syn"
@@ -6225,9 +6142,9 @@ dependencies = [
[[package]]
name = "syn"
-version = "2.0.67"
+version = "2.0.68"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ff8655ed1d86f3af4ee3fd3263786bc14245ad17c4c7e85ba7187fb3ae028c90"
+checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9"
dependencies = [
"proc-macro2",
"quote",
@@ -6322,28 +6239,6 @@ dependencies = [
"utf-8",
]
-[[package]]
-name = "tera"
-version = "1.20.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ab9d851b45e865f178319da0abdbfe6acbc4328759ff18dafc3a41c16b4cd2ee"
-dependencies = [
- "chrono",
- "chrono-tz",
- "globwalk",
- "humansize",
- "lazy_static",
- "percent-encoding",
- "pest",
- "pest_derive",
- "rand 0.8.5",
- "regex",
- "serde",
- "serde_json",
- "slug",
- "unic-segment",
-]
-
[[package]]
name = "test-case"
version = "3.3.1"
@@ -6362,7 +6257,7 @@ dependencies = [
"cfg-if",
"proc-macro2",
"quote",
- "syn 2.0.67",
+ "syn 2.0.68",
]
[[package]]
@@ -6373,7 +6268,7 @@ checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.67",
+ "syn 2.0.68",
"test-case-core",
]
@@ -6400,7 +6295,7 @@ checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.67",
+ "syn 2.0.68",
]
[[package]]
@@ -6458,9 +6353,9 @@ dependencies = [
[[package]]
name = "tinyvec"
-version = "1.6.0"
+version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
+checksum = "c55115c6fbe2d2bef26eb09ad74bde02d8255476fc0c7b515ef09fbb35742d82"
dependencies = [
"tinyvec_macros",
]
@@ -6498,7 +6393,7 @@ checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.67",
+ "syn 2.0.68",
]
[[package]]
@@ -6638,7 +6533,7 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5"
dependencies = [
- "bitflags 2.5.0",
+ "bitflags 2.6.0",
"bytes",
"futures-util",
"http 1.1.0",
@@ -6689,7 +6584,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.67",
+ "syn 2.0.68",
]
[[package]]
@@ -6762,12 +6657,6 @@ version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
-[[package]]
-name = "ucd-trie"
-version = "0.1.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9"
-
[[package]]
name = "uluru"
version = "3.1.0"
@@ -6786,56 +6675,6 @@ dependencies = [
"libc",
]
-[[package]]
-name = "unic-char-property"
-version = "0.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221"
-dependencies = [
- "unic-char-range",
-]
-
-[[package]]
-name = "unic-char-range"
-version = "0.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc"
-
-[[package]]
-name = "unic-common"
-version = "0.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc"
-
-[[package]]
-name = "unic-segment"
-version = "0.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e4ed5d26be57f84f176157270c112ef57b86debac9cd21daaabbe56db0f88f23"
-dependencies = [
- "unic-ucd-segment",
-]
-
-[[package]]
-name = "unic-ucd-segment"
-version = "0.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2079c122a62205b421f499da10f3ee0f7697f012f55b675e002483c73ea34700"
-dependencies = [
- "unic-char-property",
- "unic-char-range",
- "unic-ucd-version",
-]
-
-[[package]]
-name = "unic-ucd-version"
-version = "0.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4"
-dependencies = [
- "unic-common",
-]
-
[[package]]
name = "unicase"
version = "2.7.0"
@@ -6941,9 +6780,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "uuid"
-version = "1.8.0"
+version = "1.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0"
+checksum = "5de17fd2f7da591098415cff336e12965a28061ddace43b59cb3c430179c9439"
dependencies = [
"getrandom 0.2.15",
"serde",
@@ -7031,7 +6870,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
- "syn 2.0.67",
+ "syn 2.0.68",
"wasm-bindgen-shared",
]
@@ -7065,7 +6904,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.67",
+ "syn 2.0.68",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@@ -7420,7 +7259,7 @@ checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.67",
+ "syn 2.0.68",
]
[[package]]
diff --git a/Cargo.toml b/Cargo.toml
index 95d520475..1bd38e6e8 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -100,7 +100,7 @@ tempfile = "3.1.0"
fn-error-context = "0.2.0"
# Templating
-tera = { version = "1.5.0", features = ["builtins"] }
+rinja = { git = "https://github.com/rinja-rs/rinja" }
walkdir = "2"
# Date and Time utilities
diff --git a/src/db/types.rs b/src/db/types.rs
index 17140c595..ea0217dfd 100644
--- a/src/db/types.rs
+++ b/src/db/types.rs
@@ -45,6 +45,16 @@ impl sqlx::postgres::PgHasArrayType for BuildStatus {
}
}
+impl<'a> PartialEq<&'a str> for BuildStatus {
+ fn eq(&self, other: &&str) -> bool {
+ match self {
+ Self::Success => *other == "success",
+ Self::Failure => *other == "failure",
+ Self::InProgress => *other == "in_progress",
+ }
+ }
+}
+
#[cfg(test)]
mod tests {
use super::*;
diff --git a/src/docbuilder/limits.rs b/src/docbuilder/limits.rs
index 1dcdcb797..6b653b4b9 100644
--- a/src/docbuilder/limits.rs
+++ b/src/docbuilder/limits.rs
@@ -6,11 +6,11 @@ const GB: usize = 1024 * 1024 * 1024;
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub(crate) struct Limits {
- memory: usize,
- targets: usize,
- timeout: Duration,
- networking: bool,
- max_log_size: usize,
+ pub memory: usize,
+ pub targets: usize,
+ pub timeout: Duration,
+ pub networking: bool,
+ pub max_log_size: usize,
}
impl Limits {
diff --git a/src/registry_api.rs b/src/registry_api.rs
index 47d403179..5d5ada084 100644
--- a/src/registry_api.rs
+++ b/src/registry_api.rs
@@ -4,6 +4,7 @@ use chrono::{DateTime, Utc};
use reqwest::header::{HeaderValue, ACCEPT, USER_AGENT};
use semver::Version;
use serde::{Deserialize, Serialize};
+use std::fmt;
use tracing::instrument;
use url::Url;
@@ -59,6 +60,15 @@ pub enum OwnerKind {
Team,
}
+impl fmt::Display for OwnerKind {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::User => f.write_str("user"),
+ Self::Team => f.write_str("team"),
+ }
+ }
+}
+
impl RegistryApi {
pub fn new(api_base: Url, max_retries: u32) -> Result {
let headers = vec![
diff --git a/src/utils/html.rs b/src/utils/html.rs
index 2f015ae63..c84867e15 100644
--- a/src/utils/html.rs
+++ b/src/utils/html.rs
@@ -1,7 +1,8 @@
-use crate::web::page::TemplateData;
+use crate::web::page::templates::{Body, Head, Topbar, Vendored};
+use crate::web::rustdoc::RustdocPage;
use lol_html::element;
use lol_html::errors::RewritingError;
-use tera::Context;
+use rinja::Template;
/// Rewrite a rustdoc page to have the docs.rs topbar
///
@@ -12,17 +13,15 @@ use tera::Context;
pub(crate) fn rewrite_lol(
html: &[u8],
max_allowed_memory_usage: usize,
- ctx: Context,
- templates: &TemplateData,
+ data: &RustdocPage,
) -> Result, RewritingError> {
use lol_html::html_content::{ContentType, Element};
use lol_html::{HtmlRewriter, MemorySettings, Settings};
- let templates = &templates.templates;
- let tera_head = templates.render("rustdoc/head.html", &ctx).unwrap();
- let tera_vendored_css = templates.render("rustdoc/vendored.html", &ctx).unwrap();
- let tera_body = templates.render("rustdoc/body.html", &ctx).unwrap();
- let tera_rustdoc_topbar = templates.render("rustdoc/topbar.html", &ctx).unwrap();
+ let head_html = Head::new(data).render().unwrap();
+ let vendored_html = Vendored::new(data).render().unwrap();
+ let body_html = Body::new(data).render().unwrap();
+ let topbar_html = Topbar::new(data).render().unwrap();
// Before: ... rustdoc content ...
// After:
@@ -46,12 +45,12 @@ pub(crate) fn rewrite_lol(
rustdoc_body_class.set_attribute("tabindex", "-1")?;
// Change the `body` to a `div`
rustdoc_body_class.set_tag_name("div")?;
- // Prepend the tera content
- rustdoc_body_class.prepend(&tera_body, ContentType::Html);
+ // Prepend the rinja content
+ rustdoc_body_class.prepend(&body_html, ContentType::Html);
// Wrap the transformed body and topbar into a element
rustdoc_body_class.before(r#""#, ContentType::Html);
// Insert the topbar outside of the rustdoc div
- rustdoc_body_class.before(&tera_rustdoc_topbar, ContentType::Html);
+ rustdoc_body_class.before(&topbar_html, ContentType::Html);
// Finalize body with
rustdoc_body_class.after("", ContentType::Html);
@@ -62,7 +61,7 @@ pub(crate) fn rewrite_lol(
element_content_handlers: vec![
// Append `style.css` stylesheet after all head elements.
element!("head", |head: &mut Element| {
- head.append(&tera_head, ContentType::Html);
+ head.append(&head_html, ContentType::Html);
Ok(())
}),
element!("body", body_handler),
@@ -81,7 +80,7 @@ pub(crate) fn rewrite_lol(
element!(
"link[rel='stylesheet'][href*='rustdoc-']",
|rustdoc_css: &mut Element| {
- rustdoc_css.before(&tera_vendored_css, ContentType::Html);
+ rustdoc_css.before(&vendored_html, ContentType::Html);
Ok(())
}
),
diff --git a/src/web/build_details.rs b/src/web/build_details.rs
index 6e5711c8c..f8bc5a7e9 100644
--- a/src/web/build_details.rs
+++ b/src/web/build_details.rs
@@ -2,10 +2,11 @@ use crate::{
db::types::BuildStatus,
impl_axum_webpage,
web::{
+ crate_details::CrateDetails,
error::{AxumNope, AxumResult},
extractors::{DbConnection, Path},
file::File,
- MetaData,
+ filters, MetaData,
},
AsyncStorage, Config,
};
@@ -13,6 +14,7 @@ use anyhow::Context as _;
use axum::{extract::Extension, response::IntoResponse};
use chrono::{DateTime, Utc};
use futures_util::TryStreamExt;
+use rinja::Template;
use semver::Version;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
@@ -28,6 +30,8 @@ pub(crate) struct BuildDetails {
errors: Option,
}
+#[derive(Template)]
+#[template(path = "crate/build_details.html")]
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
struct BuildDetailsPage {
metadata: MetaData,
@@ -35,10 +39,24 @@ struct BuildDetailsPage {
use_direct_platform_links: bool,
all_log_filenames: Vec,
current_filename: Option,
+ csp_nonce: String,
}
-impl_axum_webpage! {
- BuildDetailsPage = "crate/build_details.html",
+impl_axum_webpage! { BuildDetailsPage }
+
+// Used for template rendering.
+impl BuildDetailsPage {
+ pub(crate) fn krate(&self) -> Option<&CrateDetails> {
+ None
+ }
+
+ pub(crate) fn permalink_path(&self) -> &str {
+ ""
+ }
+
+ pub(crate) fn get_metadata(&self) -> Option<&MetaData> {
+ Some(&self.metadata)
+ }
}
#[derive(Clone, Deserialize, Debug)]
@@ -126,6 +144,7 @@ pub(crate) async fn build_details_handler(
use_direct_platform_links: true,
all_log_filenames,
current_filename,
+ csp_nonce: String::new(),
}
.into_response())
}
diff --git a/src/web/builds.rs b/src/web/builds.rs
index a9e7be475..278d26557 100644
--- a/src/web/builds.rs
+++ b/src/web/builds.rs
@@ -4,9 +4,10 @@ use crate::{
docbuilder::Limits,
impl_axum_webpage,
web::{
+ crate_details::CrateDetails,
error::AxumResult,
extractors::{DbConnection, Path},
- match_version, MetaData, ReqVersion,
+ filters, match_version, MetaData, ReqVersion,
},
Config,
};
@@ -15,6 +16,7 @@ use axum::{
extract::Extension, http::header::ACCESS_CONTROL_ALLOW_ORIGIN, response::IntoResponse, Json,
};
use chrono::{DateTime, Utc};
+use rinja::Template;
use semver::Version;
use serde::Serialize;
use std::sync::Arc;
@@ -29,6 +31,8 @@ pub(crate) struct Build {
errors: Option,
}
+#[derive(Template)]
+#[template(path = "crate/builds.html")]
#[derive(Debug, Clone, Serialize)]
struct BuildsPage {
metadata: MetaData,
@@ -36,10 +40,23 @@ struct BuildsPage {
limits: Limits,
canonical_url: CanonicalUrl,
use_direct_platform_links: bool,
+ csp_nonce: String,
}
-impl_axum_webpage! {
- BuildsPage = "crate/builds.html",
+impl_axum_webpage! { BuildsPage }
+
+impl BuildsPage {
+ pub(crate) fn krate(&self) -> Option<&CrateDetails> {
+ None
+ }
+
+ pub(crate) fn permalink_path(&self) -> &str {
+ ""
+ }
+
+ pub(crate) fn get_metadata(&self) -> Option<&MetaData> {
+ Some(&self.metadata)
+ }
}
pub(crate) async fn build_list_handler(
@@ -64,6 +81,7 @@ pub(crate) async fn build_list_handler(
limits: Limits::for_crate(&config, &mut conn, &name).await?,
canonical_url: CanonicalUrl::from_path(format!("/crate/{name}/latest/builds")),
use_direct_platform_links: true,
+ csp_nonce: String::new(),
}
.into_response())
}
diff --git a/src/web/crate_details.rs b/src/web/crate_details.rs
index 30327645b..7a89d5dbd 100644
--- a/src/web/crate_details.rs
+++ b/src/web/crate_details.rs
@@ -11,6 +11,7 @@ use crate::{
encode_url_path,
error::{AxumNope, AxumResult},
extractors::{DbConnection, Path},
+ page::templates::filters,
MatchedRelease, ReqVersion,
},
AsyncStorage,
@@ -23,6 +24,7 @@ use axum::{
use chrono::{DateTime, Utc};
use futures_util::stream::TryStreamExt;
use log::warn;
+use rinja::Template;
use semver::Version;
use serde::Deserialize;
use serde::{ser::Serializer, Serialize};
@@ -33,11 +35,11 @@ use std::sync::Arc;
#[derive(Debug, Clone, PartialEq, Serialize)]
pub(crate) struct CrateDetails {
- name: String,
- pub version: Version,
- description: Option,
- owners: Vec<(String, String, OwnerKind)>,
- dependencies: Option,
+ pub(crate) name: String,
+ pub(crate) version: Version,
+ pub(crate) description: Option,
+ pub(crate) owners: Vec<(String, String, OwnerKind)>,
+ pub(crate) dependencies: Option,
#[serde(serialize_with = "optional_markdown")]
readme: Option,
#[serde(serialize_with = "optional_markdown")]
@@ -48,8 +50,8 @@ pub(crate) struct CrateDetails {
last_successful_build: Option,
pub rustdoc_status: Option,
pub archive_storage: bool,
- repository_url: Option,
- homepage_url: Option,
+ pub repository_url: Option,
+ pub homepage_url: Option,
keywords: Option,
have_examples: Option, // need to check this manually
pub target_name: Option,
@@ -57,12 +59,12 @@ pub(crate) struct CrateDetails {
repository_metadata: Option,
pub(crate) metadata: MetaData,
is_library: Option,
- license: Option,
+ pub(crate) license: Option,
pub(crate) documentation_url: Option,
- total_items: Option,
- documented_items: Option,
- total_items_needing_examples: Option,
- items_with_examples: Option,
+ pub(crate) total_items: Option,
+ pub(crate) documented_items: Option,
+ pub(crate) total_items_needing_examples: Option,
+ pub(crate) items_with_examples: Option,
/// Database id for this crate
pub(crate) crate_id: i32,
/// Database id for this release
@@ -421,16 +423,33 @@ pub(crate) async fn releases_for_crate(
Ok(releases)
}
+#[derive(Template)]
+#[template(path = "crate/details.html")]
#[derive(Debug, Clone, PartialEq, Serialize)]
struct CrateDetailsPage {
details: CrateDetails,
+ csp_nonce: String,
}
impl_axum_webpage! {
- CrateDetailsPage = "crate/details.html",
+ CrateDetailsPage,
cpu_intensive_rendering = true,
}
+impl CrateDetailsPage {
+ pub(crate) fn krate(&self) -> Option<&CrateDetails> {
+ Some(&self.details)
+ }
+
+ pub(crate) fn permalink_path(&self) -> &str {
+ ""
+ }
+
+ pub(crate) fn get_metadata(&self) -> Option<&MetaData> {
+ Some(&self.details.metadata)
+ }
+}
+
#[derive(Deserialize, Clone, Debug)]
pub(crate) struct CrateDetailHandlerParams {
name: String,
@@ -467,7 +486,11 @@ pub(crate) async fn crate_details_handler(
Err(e) => warn!("error fetching readme: {:?}", &e),
}
- let mut res = CrateDetailsPage { details }.into_response();
+ let mut res = CrateDetailsPage {
+ details,
+ csp_nonce: String::new(),
+ }
+ .into_response();
res.extensions_mut()
.insert::(if req_version.is_latest() {
CachePolicy::ForeverInCdn
@@ -477,16 +500,19 @@ pub(crate) async fn crate_details_handler(
Ok(res.into_response())
}
+#[derive(Template)]
+#[template(path = "rustdoc/releases.html")]
#[derive(Debug, Clone, PartialEq, Serialize)]
struct ReleaseList {
releases: Vec,
crate_name: String,
inner_path: String,
target: String,
+ csp_nonce: String,
}
impl_axum_webpage! {
- ReleaseList = "rustdoc/releases.html",
+ ReleaseList,
cache_policy = |_| CachePolicy::ForeverInCdn,
cpu_intensive_rendering = true,
}
@@ -564,6 +590,7 @@ pub(crate) async fn get_all_releases(
target,
inner_path,
crate_name: params.name,
+ csp_nonce: String::new(),
};
Ok(res.into_response())
}
@@ -576,16 +603,26 @@ struct ShortMetadata {
doc_targets: Vec,
}
+impl ShortMetadata {
+ // Used in templates.
+ pub(crate) fn doc_targets(&self) -> Option<&[String]> {
+ Some(&self.doc_targets)
+ }
+}
+
+#[derive(Template)]
+#[template(path = "rustdoc/platforms.html")]
#[derive(Debug, Clone, PartialEq, Serialize)]
struct PlatformList {
metadata: ShortMetadata,
inner_path: String,
use_direct_platform_links: bool,
current_target: String,
+ csp_nonce: String,
}
impl_axum_webpage! {
- PlatformList = "rustdoc/platforms.html",
+ PlatformList,
cache_policy = |_| CachePolicy::ForeverInCdn,
cpu_intensive_rendering = true,
}
@@ -652,6 +689,7 @@ pub(crate) async fn get_all_platforms_inner(
inner_path: "".into(),
use_direct_platform_links: is_crate_root,
current_target: "".into(),
+ csp_nonce: String::new(),
}
.into_response());
}
@@ -706,6 +744,7 @@ pub(crate) async fn get_all_platforms_inner(
inner_path,
use_direct_platform_links: is_crate_root,
current_target,
+ csp_nonce: String::new(),
}
.into_response())
}
diff --git a/src/web/error.rs b/src/web/error.rs
index 63d5ebdc4..1f2ec322b 100644
--- a/src/web/error.rs
+++ b/src/web/error.rs
@@ -41,6 +41,7 @@ impl IntoResponse for AxumNope {
title: "The requested resource does not exist",
message: "no such resource".into(),
status: StatusCode::NOT_FOUND,
+ csp_nonce: String::new(),
}
.into_response()
}
@@ -49,6 +50,7 @@ impl IntoResponse for AxumNope {
title: "The requested build does not exist",
message: "no such build".into(),
status: StatusCode::NOT_FOUND,
+ csp_nonce: String::new(),
}
.into_response(),
@@ -59,6 +61,7 @@ impl IntoResponse for AxumNope {
title: "The requested crate does not exist",
message: "no such crate".into(),
status: StatusCode::NOT_FOUND,
+ csp_nonce: String::new(),
}
.into_response()
}
@@ -67,6 +70,7 @@ impl IntoResponse for AxumNope {
title: "The requested owner does not exist",
message: "no such owner".into(),
status: StatusCode::NOT_FOUND,
+ csp_nonce: String::new(),
}
.into_response(),
@@ -77,6 +81,7 @@ impl IntoResponse for AxumNope {
title: "The requested version does not exist",
message: "no such version for this crate".into(),
status: StatusCode::NOT_FOUND,
+ csp_nonce: String::new(),
}
.into_response()
}
@@ -93,6 +98,7 @@ impl IntoResponse for AxumNope {
title: "Bad request",
message: Cow::Owned(source.to_string()),
status: StatusCode::BAD_REQUEST,
+ csp_nonce: String::new(),
}
.into_response(),
AxumNope::InternalError(source) => {
@@ -100,6 +106,7 @@ impl IntoResponse for AxumNope {
title: "Internal Server Error",
message: Cow::Owned(source.to_string()),
status: StatusCode::INTERNAL_SERVER_ERROR,
+ csp_nonce: String::new(),
};
crate::utils::report_error(&source);
diff --git a/src/web/features.rs b/src/web/features.rs
index 047efcc9a..e900323d1 100644
--- a/src/web/features.rs
+++ b/src/web/features.rs
@@ -3,19 +3,24 @@ use crate::{
impl_axum_webpage,
web::{
cache::CachePolicy,
+ crate_details::CrateDetails,
error::{AxumNope, AxumResult},
extractors::{DbConnection, Path},
+ filters,
headers::CanonicalUrl,
match_version, MetaData, ReqVersion,
},
};
use anyhow::anyhow;
use axum::response::IntoResponse;
+use rinja::Template;
use serde::Serialize;
use std::collections::{HashMap, VecDeque};
const DEFAULT_NAME: &str = "default";
+#[derive(Template)]
+#[template(path = "crate/features.html")]
#[derive(Debug, Clone, Serialize)]
struct FeaturesPage {
metadata: MetaData,
@@ -24,10 +29,11 @@ struct FeaturesPage {
canonical_url: CanonicalUrl,
is_latest_url: bool,
use_direct_platform_links: bool,
+ csp_nonce: String,
}
impl_axum_webpage! {
- FeaturesPage = "crate/features.html",
+ FeaturesPage,
cache_policy = |page| if page.is_latest_url {
CachePolicy::ForeverInCdn
} else {
@@ -35,6 +41,20 @@ impl_axum_webpage! {
},
}
+impl FeaturesPage {
+ pub(crate) fn krate(&self) -> Option<&CrateDetails> {
+ None
+ }
+
+ pub(crate) fn permalink_path(&self) -> &str {
+ ""
+ }
+
+ pub(crate) fn get_metadata(&self) -> Option<&MetaData> {
+ Some(&self.metadata)
+ }
+}
+
pub(crate) async fn build_features_handler(
Path((name, req_version)): Path<(String, ReqVersion)>,
mut conn: DbConnection,
@@ -82,6 +102,7 @@ pub(crate) async fn build_features_handler(
is_latest_url: req_version.is_latest(),
canonical_url: CanonicalUrl::from_path(format!("/crate/{}/latest/features", &name)),
use_direct_platform_links: true,
+ csp_nonce: String::new(),
}
.into_response())
}
diff --git a/src/web/headers.rs b/src/web/headers.rs
index b93a92257..867a4dc27 100644
--- a/src/web/headers.rs
+++ b/src/web/headers.rs
@@ -3,6 +3,7 @@ use anyhow::Result;
use axum::http::uri::{PathAndQuery, Uri};
use axum_extra::headers::{Header, HeaderName, HeaderValue};
use serde::Serialize;
+use std::fmt;
/// simplified typed header for a `Link rel=canonical` header in the response.
/// Only takes the path to be used, url-encodes it and attaches domain & schema to it.
@@ -52,6 +53,12 @@ impl Header for CanonicalUrl {
}
}
+impl fmt::Display for CanonicalUrl {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{}", self.build_full_uri())
+ }
+}
+
impl Serialize for CanonicalUrl {
fn serialize(&self, serializer: S) -> Result
where
diff --git a/src/web/highlight.rs b/src/web/highlight.rs
index 81d659b38..7b91a1234 100644
--- a/src/web/highlight.rs
+++ b/src/web/highlight.rs
@@ -73,7 +73,9 @@ pub fn with_lang(lang: Option<&str>, code: &str) -> String {
} else {
log::error!("failed while highlighting code: {err:?}");
}
- tera::escape_html(code)
+ crate::web::page::templates::filters::escape_html(code)
+ .map(|s| s.to_string())
+ .unwrap_or_default()
}
}
}
diff --git a/src/web/mod.rs b/src/web/mod.rs
index 923f7f331..8944684b4 100644
--- a/src/web/mod.rs
+++ b/src/web/mod.rs
@@ -1,12 +1,15 @@
//! Web interface of docs.rs
pub mod page;
+// mod tmp;
use crate::db::types::BuildStatus;
use crate::utils::get_correct_docsrs_style_file;
use crate::utils::report_error;
+use crate::web::page::templates::filters;
use anyhow::{anyhow, bail, Context as _, Result};
use axum_extra::middleware::option_layer;
+use rinja::Template;
use serde_json::Value;
use tracing::{info, instrument};
@@ -25,7 +28,7 @@ mod markdown;
pub(crate) mod metrics;
mod releases;
mod routes;
-mod rustdoc;
+pub(crate) mod rustdoc;
mod sitemap;
mod source;
mod statics;
@@ -702,8 +705,22 @@ impl MetaData {
targets.sort_unstable();
targets
}
+
+ fn target_name_url(&self) -> String {
+ if let Some(ref target_name) = self.target_name {
+ format!("{target_name}/index.html")
+ } else {
+ String::new()
+ }
+ }
+
+ pub(crate) fn doc_targets(&self) -> Option<&[String]> {
+ self.doc_targets.as_deref()
+ }
}
+#[derive(Template)]
+#[template(path = "error.html")]
#[derive(Debug, Clone, PartialEq, Serialize)]
pub(crate) struct AxumErrorPage {
/// The title of the page
@@ -712,10 +729,17 @@ pub(crate) struct AxumErrorPage {
pub message: Cow<'static, str>,
#[serde(skip)]
pub status: StatusCode,
+ pub csp_nonce: String,
+}
+
+impl AxumErrorPage {
+ pub(crate) fn get_metadata(&self) -> Option<&MetaData> {
+ None
+ }
}
impl_axum_webpage! {
- AxumErrorPage = "error.html",
+ AxumErrorPage,
status = |err| err.status,
}
diff --git a/src/web/page/mod.rs b/src/web/page/mod.rs
index 3f8a9d2bf..200b9f6a0 100644
--- a/src/web/page/mod.rs
+++ b/src/web/page/mod.rs
@@ -1,4 +1,4 @@
-mod templates;
+pub(crate) mod templates;
pub(crate) mod web_page;
pub(crate) use templates::TemplateData;
diff --git a/src/web/page/templates.rs b/src/web/page/templates.rs
index aa4aeebd4..f828e4e46 100644
--- a/src/web/page/templates.rs
+++ b/src/web/page/templates.rs
@@ -1,19 +1,48 @@
use crate::error::Result;
+use crate::web::rustdoc::RustdocPage;
+use crate::web::MetaData;
use anyhow::Context;
-use chrono::{DateTime, Utc};
-use path_slash::PathExt;
-use serde_json::Value;
-use std::{collections::HashMap, fmt, path::PathBuf, sync::Arc};
-use tera::{Result as TeraResult, Tera};
+use rinja::Template;
+use std::{fmt, ops::Deref, sync::Arc};
use tracing::trace;
-use walkdir::WalkDir;
-const TEMPLATES_DIRECTORY: &str = "templates";
+macro_rules! rustdoc_page {
+ ($name:ident, $path:literal $($extra:ident = $kind:literal)?) => {
+ #[derive(Template)]
+ #[template(path = $path, $($extra = $kind)?)]
+ pub struct $name<'a> {
+ inner: &'a RustdocPage,
+ }
+
+ impl<'a> $name<'a> {
+ pub fn new(inner: &'a RustdocPage) -> Self {
+ Self { inner }
+ }
+
+ pub(crate) fn get_metadata(&self) -> Option<&MetaData> {
+ Some(&self.inner.metadata)
+ }
+ }
+
+ impl<'a> Deref for $name<'a> {
+ type Target = RustdocPage;
+
+ fn deref(&self) -> &Self::Target {
+ self.inner
+ }
+ }
+ };
+}
+
+rustdoc_page!(Head, "rustdoc/head.html");
+rustdoc_page!(Vendored, "rustdoc/vendored.html");
+rustdoc_page!(Body, "rustdoc/body.html");
+rustdoc_page!(Topbar, "rustdoc/topbar.html");
+// rustdoc_page!(Topbar, "rustdoc/topbar.html" print = "code");
/// Holds all data relevant to templating
#[derive(Debug)]
pub(crate) struct TemplateData {
- pub templates: Tera,
/// rendering threadpool for CPU intensive rendering.
/// When the app is shut down, the pool won't wait
/// for pending tasks in this pool.
@@ -31,7 +60,6 @@ impl TemplateData {
trace!("Loading templates");
let data = Self {
- templates: load_templates()?,
rendering_threadpool: rayon::ThreadPoolBuilder::new()
.num_threads(num_threads)
.thread_name(move |idx| format!("docsrs-render {idx}"))
@@ -51,12 +79,11 @@ impl TemplateData {
/// Use this instead of `spawn_blocking` so we don't block tokio.
pub(crate) async fn render_in_threadpool(self: &Arc, render_fn: F) -> Result
where
- F: FnOnce(&TemplateData) -> Result + Send + 'static,
+ F: FnOnce() -> Result + Send + 'static,
R: Send + 'static,
{
let (send, recv) = tokio::sync::oneshot::channel();
self.rendering_threadpool.spawn({
- let templates = self.clone();
move || {
// the job may have been queued on the thread-pool for a while,
// if the request was closed in the meantime the receiver should have
@@ -64,7 +91,7 @@ impl TemplateData {
if !send.is_closed() {
// `.send` only fails when the receiver is dropped while we were rendering,
// at which point we don't need the result any more.
- let _ = send.send(render_fn(&templates));
+ let _ = send.send(render_fn());
}
}
});
@@ -73,106 +100,63 @@ impl TemplateData {
}
}
-fn load_templates() -> Result {
- // This uses a custom function to find the templates in the filesystem instead of Tera's
- // builtin way (passing a glob expression to Tera::new), speeding up the startup of the
- // application and running the tests.
- //
- // The problem with Tera's template loading code is, it walks all the files in the current
- // directory and matches them against the provided glob expression. Unfortunately this means
- // Tera will walk all the rustwide workspaces, the git repository and a bunch of other
- // unrelated data, slowing down the search a lot.
- //
- // TODO: remove this when https://github.com/Gilnaa/globwalk/issues/29 is fixed
- let mut tera = Tera::default();
- let template_files = find_templates_in_filesystem(TEMPLATES_DIRECTORY)
- .with_context(|| format!("failed to search {TEMPLATES_DIRECTORY:?} for tera templates"))?;
- tera.add_template_files(template_files).with_context(|| {
- format!("failed while loading tera templates in {TEMPLATES_DIRECTORY:?}")
- })?;
-
- // This function will return any global alert, if present.
- ReturnValue::add_function_to(
- &mut tera,
- "global_alert",
- serde_json::to_value(crate::GLOBAL_ALERT)?,
- );
- // This function will return the current version of docs.rs.
- ReturnValue::add_function_to(
- &mut tera,
- "docsrs_version",
- Value::String(crate::BUILD_VERSION.into()),
- );
-
- // Custom filters
- tera.register_filter("timeformat", timeformat);
- tera.register_filter("dbg", dbg);
- tera.register_filter("dedent", dedent);
- tera.register_filter("fas", IconType::Strong);
- tera.register_filter("far", IconType::Regular);
- tera.register_filter("fab", IconType::Brand);
- tera.register_filter("highlight", Highlight);
-
- Ok(tera)
-}
-
-fn find_templates_in_filesystem(base: &str) -> Result)>> {
- let root = std::fs::canonicalize(base)?;
-
- let mut files = Vec::new();
- for entry in WalkDir::new(&root) {
- let entry = entry?;
- let path = entry.path();
-
- if !entry.metadata()?.is_file() {
- continue;
+pub mod filters {
+ use super::IconType;
+ use chrono::{DateTime, Utc};
+ use std::borrow::Cow;
+ use std::fmt;
+ use std::str::FromStr;
+
+ // Copied from `tera`.
+ pub fn escape_html(input: &str) -> rinja::Result> {
+ if !input.chars().any(|c| "&<>\"'/".contains(c)) {
+ return Ok(Cow::Borrowed(input));
+ }
+ let mut output = String::with_capacity(input.len() * 2);
+ for c in input.chars() {
+ match c {
+ '&' => output.push_str("&"),
+ '<' => output.push_str("<"),
+ '>' => output.push_str(">"),
+ '"' => output.push_str("""),
+ '\'' => output.push_str("'"),
+ '/' => output.push_str("/"),
+ _ => output.push(c),
+ }
}
- // Strip the root directory from the path and use it as the template name.
- let name = path
- .strip_prefix(&root)
- .with_context(|| format!("{} is not a child of {}", path.display(), root.display()))?
- .to_slash()
- .with_context(|| anyhow::anyhow!("failed to normalize {}", path.display()))?;
- files.push((path.to_path_buf(), Some(name.to_string())));
+ // Not using shrink_to_fit() on purpose
+ Ok(Cow::Owned(output))
}
- Ok(files)
-}
-
-/// Simple function that returns the pre-defined value.
-struct ReturnValue {
- name: &'static str,
- value: Value,
-}
-
-impl ReturnValue {
- fn add_function_to(tera: &mut Tera, name: &'static str, value: Value) {
- tera.register_function(name, Self { name, value })
+ // Copied from `tera`.
+ pub fn escape_xml(input: &str) -> rinja::Result> {
+ if !input.chars().any(|c| "&<>\"'".contains(c)) {
+ return Ok(Cow::Borrowed(input));
+ }
+ let mut output = String::with_capacity(input.len() * 2);
+ for c in input.chars() {
+ match c {
+ '&' => output.push_str("&"),
+ '<' => output.push_str("<"),
+ '>' => output.push_str(">"),
+ '"' => output.push_str("""),
+ '\'' => output.push_str("'"),
+ _ => output.push(c),
+ }
+ }
+ Ok(Cow::Owned(output))
}
-}
-impl tera::Function for ReturnValue {
- fn call(&self, args: &HashMap) -> TeraResult {
- debug_assert!(args.is_empty(), "{} takes no args", self.name);
- Ok(self.value.clone())
+ /// Prettily format a timestamp
+ // TODO: This can be replaced by chrono
+ pub fn timeformat(value: &DateTime) -> rinja::Result {
+ Ok(crate::web::duration_to_str(*value))
}
-}
-/// Prettily format a timestamp
-// TODO: This can be replaced by chrono
-#[allow(clippy::unnecessary_wraps)]
-fn timeformat(value: &Value, args: &HashMap) -> TeraResult {
- let fmt = if let Some(Value::Bool(true)) = args.get("relative") {
- let value = DateTime::parse_from_rfc3339(value.as_str().unwrap())
- .unwrap()
- .with_timezone(&Utc);
-
- super::super::duration_to_str(value)
- } else {
+ pub fn format_secs(mut value: f32) -> rinja::Result {
const TIMES: &[&str] = &["seconds", "minutes", "hours"];
- let mut value = value.as_f64().unwrap();
let mut chosen_time = &TIMES[0];
for time in &TIMES[1..] {
@@ -190,56 +174,142 @@ fn timeformat(value: &Value, args: &HashMap) -> TeraResult
value.truncate(value.len() - 2);
}
- format!("{value} {chosen_time}")
- };
+ Ok(format!("{value} {chosen_time}"))
+ }
- Ok(Value::String(fmt))
-}
+ /// Print a value to stdout
+ #[allow(clippy::unnecessary_wraps)]
+ pub fn dbg(value: T) -> rinja::Result {
+ let value = value.to_string();
+ println!("{value}");
-/// Print a tera value to stdout
-#[allow(clippy::unnecessary_wraps)]
-fn dbg(value: &Value, _args: &HashMap) -> TeraResult {
- println!("{value:?}");
+ Ok(value)
+ }
- Ok(value.clone())
-}
+ /// Dedent a string by removing all leading whitespace
+ #[allow(clippy::unnecessary_wraps)]
+ pub fn dedent>>(
+ value: T,
+ levels: I,
+ ) -> rinja::Result {
+ let string = value.to_string();
+
+ let unindented = if let Some(levels) = levels.into() {
+ string
+ .lines()
+ .map(|mut line| {
+ for _ in 0..levels {
+ // `.strip_prefix` returns `Some(suffix without prefix)` if it's successful. If it fails
+ // to strip the prefix (meaning there's less than `levels` levels of indentation),
+ // we can just quit early
+ if let Some(suffix) = line.strip_prefix(" ") {
+ line = suffix;
+ } else {
+ break;
+ }
+ }
-/// Dedent a string by removing all leading whitespace
-#[allow(clippy::unnecessary_wraps)]
-fn dedent(value: &Value, args: &HashMap) -> TeraResult {
- let string = value.as_str().expect("dedent takes a string");
+ line
+ })
+ .collect::>()
+ .join("\n")
+ } else {
+ string
+ .lines()
+ .map(|l| l.trim_start())
+ .collect::>()
+ .join("\n")
+ };
- let unindented = if let Some(levels) = args
- .get("levels")
- .map(|l| l.as_i64().expect("`levels` must be an integer"))
- {
- string
- .lines()
- .map(|mut line| {
- for _ in 0..levels {
- // `.strip_prefix` returns `Some(suffix without prefix)` if it's successful. If it fails
- // to strip the prefix (meaning there's less than `levels` levels of indentation),
- // we can just quit early
- if let Some(suffix) = line.strip_prefix(" ") {
- line = suffix;
- } else {
- break;
- }
- }
+ Ok(unindented)
+ }
- line
- })
- .collect::>()
- .join("\n")
- } else {
- string
- .lines()
- .map(|l| l.trim_start())
- .collect::>()
- .join("\n")
- };
+ pub fn fas(value: &str, fw: bool, spin: bool, extra: &str) -> rinja::Result {
+ IconType::Strong.render(value, fw, spin, extra)
+ }
+
+ pub fn far(value: &str, fw: bool, spin: bool, extra: &str) -> rinja::Result {
+ IconType::Regular.render(value, fw, spin, extra)
+ }
+
+ pub fn fab(value: &str, fw: bool, spin: bool, extra: &str) -> rinja::Result {
+ IconType::Brand.render(value, fw, spin, extra)
+ }
- Ok(Value::String(unindented))
+ pub fn highlight(code: &str, lang: &str) -> rinja::Result {
+ let highlighted_code = crate::web::highlight::with_lang(Some(lang), code);
+ Ok(format!("{}
", highlighted_code))
+ }
+
+ pub fn slugify>(code: T) -> rinja::Result {
+ Ok(slug::slugify(code.as_ref()))
+ }
+
+ pub fn round(value: &f32, precision: u32) -> rinja::Result {
+ struct Rounder(f32, u32);
+
+ impl fmt::Display for Rounder {
+ fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(formatter, "{1:.*}", self.1 as usize, self.0)
+ }
+ }
+ Ok(format!("{}", Rounder(*value, precision)))
+ }
+
+ pub fn date(value: &DateTime, format: &str) -> rinja::Result {
+ Ok(format!("{}", value.format(format)))
+ }
+
+ pub fn opt_date(value: &Option>, format: &str) -> rinja::Result {
+ if let Some(value) = value {
+ date(value, format)
+ } else {
+ Ok(String::new())
+ }
+ }
+
+ pub fn filesizeformat(num: usize) -> rinja::Result {
+ if num > 1_000_000_000 {
+ Ok(format!(
+ "{}.{} GB",
+ num / 1_000_000_000,
+ num / 100_000_000 % 10
+ ))
+ } else if num > 1_000_000 {
+ Ok(format!("{}.{} MB", num / 1_000_000, num / 100_000 % 10))
+ } else if num > 1_000 {
+ Ok(format!("{}.{} KB", num / 1_000, num / 100 % 10))
+ } else {
+ Ok(format!("{num} B"))
+ }
+ }
+
+ pub fn unwrap(value: &Option) -> rinja::Result<&T> {
+ Ok(value.as_ref().expect("`unwrap` filter failed"))
+ }
+
+ pub fn split_first<'a>(value: &'a str, pat: &str) -> rinja::Result> {
+ Ok(value.split(pat).next())
+ }
+
+ pub fn to_string(value: &T) -> rinja::Result {
+ Ok(value.to_string())
+ }
+
+ pub fn json_encode(value: &T) -> rinja::Result {
+ Ok(serde_json::to_string(value).expect("`encode_json` failed"))
+ }
+
+ pub fn as_f32(value: &i32) -> rinja::Result {
+ Ok(*value as f32)
+ }
+
+ pub fn rest_menu_url(current_target: &str, inner_path: &str) -> rinja::Result {
+ if current_target.is_empty() {
+ return Ok(String::new());
+ }
+ Ok(format!("/{current_target}/{inner_path}"))
+ }
}
enum IconType {
@@ -260,9 +330,9 @@ impl fmt::Display for IconType {
}
}
-impl tera::Filter for IconType {
- fn filter(&self, value: &Value, args: &HashMap) -> TeraResult {
- let icon_name = tera::escape_html(value.as_str().expect("Icons only take strings"));
+impl IconType {
+ fn render(self, icon_name: &str, fw: bool, spin: bool, extra: &str) -> rinja::Result {
+ let class = if fw { "fa-svg fa-svg-fw" } else { "fa-svg" };
let type_ = match self {
IconType::Strong => font_awesome_as_a_crate::Type::Solid,
@@ -273,14 +343,10 @@ impl tera::Filter for IconType {
let icon_file_string = font_awesome_as_a_crate::svg(type_, &icon_name[..]).unwrap_or("");
let mut classes = vec!["fa-svg", "fa-svg-fw"];
- if args
- .get("spin")
- .and_then(|spin| spin.as_bool())
- .unwrap_or(false)
- {
+ if spin {
classes.push("fa-svg-spin");
- };
- if let Some(extra) = args.get("extra").and_then(|s| s.as_str()) {
+ }
+ if !extra.is_empty() {
classes.push(extra);
}
let icon = format!(
@@ -289,55 +355,6 @@ impl tera::Filter for IconType {
class = classes.join(" "),
);
- Ok(Value::String(icon))
- }
-
- fn is_safe(&self) -> bool {
- true
- }
-}
-
-struct Highlight;
-
-impl tera::Filter for Highlight {
- fn filter(&self, value: &Value, args: &HashMap) -> TeraResult {
- let code = value.as_str().ok_or_else(|| {
- let msg = format!( "Filter `highlight` was called on an incorrect value: got `{value}` but expected a string");
- tera::Error::msg(msg)
- })?;
- let lang = args
- .get("lang")
- .and_then(|lang| {
- if lang.is_null() {
- None
- } else {
- Some(lang.as_str().ok_or_else(|| {
- let msg = format!("Filter `highlight` received an incorrect type for arg `{lang}`: got `{lang}` but expected a string");
- tera::Error::msg(msg)
- }))
- }
- })
- .transpose()?;
- let highlighted = crate::web::highlight::with_lang(lang, code);
- Ok(format!("{highlighted}
").into())
- }
-
- fn is_safe(&self) -> bool {
- true
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- fn test_templates_are_valid() {
- crate::test::wrapper(|_| {
- let tera = load_templates().unwrap();
- tera.check_macro_files().unwrap();
-
- Ok(())
- });
+ Ok(icon)
}
}
diff --git a/src/web/page/web_page.rs b/src/web/page/web_page.rs
index 7ead62b42..7a3734a74 100644
--- a/src/web/page/web_page.rs
+++ b/src/web/page/web_page.rs
@@ -1,6 +1,5 @@
use super::TemplateData;
use crate::web::{csp::Csp, error::AxumNope};
-use anyhow::Error;
use axum::{
body::Body,
extract::Request as AxumRequest,
@@ -10,12 +9,15 @@ use axum::{
use futures_util::future::{BoxFuture, FutureExt};
use http::header::CONTENT_LENGTH;
use std::sync::Arc;
-use tera::Context;
+
+pub(crate) trait AddCspNonce: IntoResponse {
+ fn render_with_csp_nonce(&mut self, csp_nonce: String) -> rinja::Result;
+}
#[macro_export]
macro_rules! impl_axum_webpage {
(
- $page:ty = $template:literal
+ $page:ty
$(, status = $status:expr)?
$(, content_type = $content_type:expr)?
$(, canonical_url = $canonical_url:expr)?
@@ -23,25 +25,13 @@ macro_rules! impl_axum_webpage {
$(, cpu_intensive_rendering = $cpu_intensive_rendering:expr)?
$(,)?
) => {
- $crate::impl_axum_webpage!(
- $page = |_| ::std::borrow::Cow::Borrowed($template)
- $(, status = $status)?
- $(, content_type = $content_type)?
- $(, canonical_url = $canonical_url)?
- $(, cache_policy = $cache_policy)?
- $(, cpu_intensive_rendering = $cpu_intensive_rendering )?
- );
- };
+ impl crate::web::page::web_page::AddCspNonce for $page {
+ fn render_with_csp_nonce(&mut self, csp_nonce: String) -> rinja::Result {
+ self.csp_nonce = csp_nonce;
+ self.render()
+ }
+ }
- (
- $page:ty = $template:expr
- $(, status = $status:expr)?
- $(, content_type = $content_type:expr)?
- $(, canonical_url = $canonical_url:expr)?
- $(, cache_policy = $cache_policy:expr)?
- $(, cpu_intensive_rendering = $cpu_intensive_rendering:expr)?
- $(,)?
- ) => {
impl axum::response::IntoResponse for $page
{
fn into_response(self) -> ::axum::response::Response {
@@ -92,16 +82,7 @@ macro_rules! impl_axum_webpage {
response.extensions_mut().insert($crate::web::page::web_page::DelayedTemplateRender {
- context: {
- let mut c = ::tera::Context::from_serialize(&self)
- .expect("could not create tera context from web-page");
- c.insert("DEFAULT_MAX_TARGETS", &$crate::DEFAULT_MAX_TARGETS);
- c
- },
- template: {
- let template: fn(&Self) -> ::std::borrow::Cow<'static, str> = $template;
- template(&self).to_string()
- },
+ template: std::sync::Arc::new(Box::new(self)),
cpu_intensive_rendering,
});
response
@@ -115,42 +96,42 @@ macro_rules! impl_axum_webpage {
/// the context.
#[derive(Clone)]
pub(crate) struct DelayedTemplateRender {
- pub template: String,
- pub context: Context,
+ pub template: Arc>,
pub cpu_intensive_rendering: bool,
}
fn render_response(
mut response: AxumResponse,
- templates: Arc,
+ bla: Arc,
csp_nonce: String,
) -> BoxFuture<'static, AxumResponse> {
async move {
if let Some(render) = response.extensions_mut().remove::() {
let DelayedTemplateRender {
template,
- mut context,
cpu_intensive_rendering,
} = render;
- context.insert("csp_nonce", &csp_nonce);
-
- let rendered = if cpu_intensive_rendering {
- templates
- .render_in_threadpool(move |templates| {
- templates
- .templates
- .render(&template, &context)
- .map_err(Into::into)
- })
- .await
- } else {
- templates
- .templates
- .render(&template, &context)
- .map_err(Error::new)
- };
- let rendered = match rendered {
+ // let rendered = if cpu_intensive_rendering {
+ // templates
+ // .render_in_threadpool(move |templates| {
+ // templates
+ // .templates
+ // .render(&template, &context)
+ // .map_err(Into::into)
+ // })
+ // .await
+ // } else {
+ // templates
+ // .templates
+ // .render(&template, &context)
+ // .map_err(Error::new)
+ // };
+
+ let rendered = match Arc::into_inner(template)
+ .unwrap()
+ .render_with_csp_nonce(csp_nonce.clone())
+ {
Ok(content) => content,
Err(err) => {
if response.status().is_server_error() {
@@ -158,8 +139,8 @@ fn render_response(
panic!("error while serving error page: {err:?}");
} else {
return render_response(
- AxumNope::InternalError(err).into_response(),
- templates,
+ AxumNope::InternalError(err.into()).into_response(),
+ bla,
csp_nonce,
)
.await;
diff --git a/src/web/releases.rs b/src/web/releases.rs
index 5b5c43a70..dad7c8664 100644
--- a/src/web/releases.rs
+++ b/src/web/releases.rs
@@ -10,7 +10,9 @@ use crate::{
axum_parse_uri_with_params, axum_redirect, encode_url_path,
error::{AxumNope, AxumResult},
extractors::{DbConnection, Path},
- match_version, ReqVersion,
+ match_version,
+ page::templates::filters,
+ MetaData, ReqVersion,
},
BuildQueue, Config, InstanceMetrics,
};
@@ -23,6 +25,7 @@ use base64::{engine::general_purpose::STANDARD as b64, Engine};
use chrono::{DateTime, Utc};
use futures_util::stream::TryStreamExt;
use once_cell::sync::Lazy;
+use rinja::Template;
use serde::{Deserialize, Serialize};
use sqlx::Row;
use std::collections::{BTreeMap, HashMap, HashSet};
@@ -44,12 +47,12 @@ const RELEASES_IN_FEED: i64 = 150;
pub struct Release {
pub(crate) name: String,
pub(crate) version: String,
- description: Option,
- target_name: Option,
- rustdoc_status: bool,
+ pub(crate) description: Option,
+ pub(crate) target_name: Option,
+ pub(crate) rustdoc_status: bool,
pub(crate) build_time: Option>,
- stars: i32,
- has_unyanked_releases: Option,
+ pub(crate) stars: i32,
+ pub(crate) has_unyanked_releases: Option,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
@@ -296,39 +299,59 @@ async fn get_search_results(
})
}
+#[derive(Template)]
+#[template(path = "core/home.html")]
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
struct HomePage {
recent_releases: Vec,
+ csp_nonce: String,
}
impl_axum_webpage! {
- HomePage = "core/home.html",
+ HomePage,
cache_policy = |_| CachePolicy::ShortInCdnAndBrowser,
}
+impl HomePage {
+ pub(crate) fn get_metadata(&self) -> Option<&MetaData> {
+ None
+ }
+}
+
pub(crate) async fn home_page(mut conn: DbConnection) -> AxumResult {
let recent_releases =
get_releases(&mut conn, 1, RELEASES_IN_HOME, Order::ReleaseTime, true).await?;
- Ok(HomePage { recent_releases })
+ Ok(HomePage {
+ recent_releases,
+ csp_nonce: String::new(),
+ })
}
+#[derive(Template)]
+#[template(path = "releases/feed.xml")]
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
struct ReleaseFeed {
recent_releases: Vec,
+ csp_nonce: String,
}
impl_axum_webpage! {
- ReleaseFeed = "releases/feed.xml",
+ ReleaseFeed,
content_type = "application/xml",
}
pub(crate) async fn releases_feed_handler(mut conn: DbConnection) -> AxumResult {
let recent_releases =
get_releases(&mut conn, 1, RELEASES_IN_FEED, Order::ReleaseTime, true).await?;
- Ok(ReleaseFeed { recent_releases })
+ Ok(ReleaseFeed {
+ recent_releases,
+ csp_nonce: String::new(),
+ })
}
+#[derive(Template)]
+#[template(path = "releases/releases.html")]
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
struct ViewReleases {
releases: Vec,
@@ -338,10 +361,15 @@ struct ViewReleases {
show_previous_page: bool,
page_number: i64,
owner: Option,
+ csp_nonce: String,
}
-impl_axum_webpage! {
- ViewReleases = "releases/releases.html",
+impl_axum_webpage! { ViewReleases }
+
+impl ViewReleases {
+ pub(crate) fn get_metadata(&self) -> Option<&MetaData> {
+ None
+ }
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize)]
@@ -354,6 +382,29 @@ pub(crate) enum ReleaseType {
Search,
}
+impl<'a> PartialEq<&'a str> for ReleaseType {
+ fn eq(&self, other: &&str) -> bool {
+ self.as_str() == *other
+ }
+}
+impl PartialEq for ReleaseType {
+ fn eq(&self, other: &str) -> bool {
+ self.as_str() == other
+ }
+}
+
+impl ReleaseType {
+ fn as_str(&self) -> &str {
+ match self {
+ Self::Recent => "recent",
+ Self::Stars => "stars",
+ Self::RecentFailures => "recent_failures",
+ Self::Failures => "failures",
+ Self::Search => "search",
+ }
+ }
+}
+
pub(crate) async fn releases_handler(
conn: &mut sqlx::PgConnection,
page: Option,
@@ -403,6 +454,7 @@ pub(crate) async fn releases_handler(
show_previous_page,
page_number,
owner: None,
+ csp_nonce: String::new(),
})
}
@@ -442,11 +494,12 @@ pub(crate) async fn owner_handler(Path(owner): Path) -> AxumResult,
+ pub(super) releases: Vec,
pub(super) search_query: Option,
pub(super) search_sort_by: Option,
pub(super) previous_page_link: Option,
@@ -455,23 +508,31 @@ pub(super) struct Search {
pub(super) release_type: ReleaseType,
#[serde(skip)]
pub(super) status: http::StatusCode,
+ pub(super) csp_nonce: String,
}
impl Default for Search {
fn default() -> Self {
Self {
title: String::default(),
- results: Vec::default(),
+ releases: Vec::default(),
search_query: None,
previous_page_link: None,
next_page_link: None,
search_sort_by: None,
release_type: ReleaseType::Search,
status: http::StatusCode::OK,
+ csp_nonce: String::new(),
}
}
}
+impl Search {
+ pub(crate) fn get_metadata(&self) -> Option<&MetaData> {
+ None
+ }
+}
+
async fn redirect_to_random_crate(
config: Arc,
metrics: Arc,
@@ -527,7 +588,7 @@ async fn redirect_to_random_crate(
}
impl_axum_webpage! {
- Search = "releases/search_results.html",
+ Search,
status = |search| search.status,
}
@@ -655,7 +716,7 @@ pub(crate) async fn search_handler(
Ok(Search {
title,
- results: search_result.results,
+ releases: search_result.results,
search_query: Some(executed_query),
search_sort_by: Some(sort_by),
next_page_link: search_result
@@ -669,18 +730,25 @@ pub(crate) async fn search_handler(
.into_response())
}
+#[derive(Template)]
+#[template(path = "releases/activity.html")]
#[derive(Debug, Clone, PartialEq, Serialize)]
struct ReleaseActivity {
description: &'static str,
dates: Vec,
counts: Vec,
failures: Vec,
+ csp_nonce: String,
}
-impl_axum_webpage! {
- ReleaseActivity = "releases/activity.html",
+impl ReleaseActivity {
+ pub(crate) fn get_metadata(&self) -> Option<&MetaData> {
+ None
+ }
}
+impl_axum_webpage! { ReleaseActivity }
+
pub(crate) async fn activity_handler(mut conn: DbConnection) -> AxumResult {
let rows: Vec<_> = sqlx::query!(
r#"WITH dates AS (
@@ -732,18 +800,26 @@ pub(crate) async fn activity_handler(mut conn: DbConnection) -> AxumResult,
active_deployments: Vec,
+ csp_nonce: String,
}
-impl_axum_webpage! {
- BuildQueuePage = "releases/build_queue.html",
+impl_axum_webpage! { BuildQueuePage }
+
+impl BuildQueuePage {
+ pub(crate) fn get_metadata(&self) -> Option<&MetaData> {
+ None
+ }
}
pub(crate) async fn build_queue_handler(
@@ -780,6 +856,7 @@ pub(crate) async fn build_queue_handler(
description: "crate documentation scheduled to build & deploy",
queue,
active_deployments,
+ csp_nonce: String::new(),
})
}
diff --git a/src/web/routes.rs b/src/web/routes.rs
index ced014a42..40da08372 100644
--- a/src/web/routes.rs
+++ b/src/web/routes.rs
@@ -10,6 +10,7 @@ use axum::{
Router as AxumRouter,
};
use axum_extra::routing::RouterExt;
+use rinja::Template;
use std::convert::Infallible;
use tracing::{debug, instrument};
@@ -271,13 +272,19 @@ pub(super) fn build_axum_routes() -> AxumRouter {
.route(
"/-/storage-change-detection.html",
get_internal(|| async {
+ #[derive(Template)]
+ #[template(path = "storage-change-detection.html")]
#[derive(Debug, Clone, serde::Serialize)]
- struct StorageChangeDetection {}
+ struct StorageChangeDetection {
+ csp_nonce: String,
+ }
crate::impl_axum_webpage!(
- StorageChangeDetection = "storage-change-detection.html",
+ StorageChangeDetection,
cache_policy = |_| CachePolicy::ForeverInCdnAndBrowser,
);
- StorageChangeDetection {}
+ StorageChangeDetection {
+ csp_nonce: String::new(),
+ }
}),
)
.route_with_tsr(
diff --git a/src/web/rustdoc.rs b/src/web/rustdoc.rs
index 177991e44..56875c516 100644
--- a/src/web/rustdoc.rs
+++ b/src/web/rustdoc.rs
@@ -258,21 +258,21 @@ pub(crate) async fn rustdoc_redirector_handler(
}
#[derive(Debug, Clone, Serialize)]
-struct RustdocPage {
- latest_path: String,
- permalink_path: String,
- latest_version: String,
- target: String,
- inner_path: String,
+pub struct RustdocPage {
+ pub latest_path: String,
+ pub permalink_path: String,
+ pub latest_version: String,
+ pub target: String,
+ pub inner_path: String,
// true if we are displaying the latest version of the crate, regardless
// of whether the URL specifies a version number or the string "latest."
- is_latest_version: bool,
+ pub is_latest_version: bool,
// true if the URL specifies a version using the string "latest."
- is_latest_url: bool,
- is_prerelease: bool,
- krate: CrateDetails,
- metadata: MetaData,
- current_target: String,
+ pub is_latest_url: bool,
+ pub is_prerelease: bool,
+ pub krate: CrateDetails,
+ pub metadata: MetaData,
+ pub current_target: String,
}
impl RustdocPage {
@@ -280,20 +280,15 @@ impl RustdocPage {
self,
rustdoc_html: &[u8],
max_parse_memory: usize,
- templates: &TemplateData,
metrics: &InstanceMetrics,
config: &Config,
file_path: &str,
) -> AxumResult {
let is_latest_url = self.is_latest_url;
- // Build the page of documentation
- let mut ctx = tera::Context::from_serialize(self).context("error creating tera context")?;
- ctx.insert("DEFAULT_MAX_TARGETS", &crate::DEFAULT_MAX_TARGETS);
-
// Extract the head and body of the rustdoc file so that we can insert it into our own html
// while logging OOM errors from html rewriting
- let html = match utils::rewrite_lol(rustdoc_html, max_parse_memory, ctx, templates) {
+ let html = match utils::rewrite_lol(rustdoc_html, max_parse_memory, &self) {
Err(RewritingError::MemoryLimitExceeded(..)) => {
metrics.html_rewrite_ooms.inc();
@@ -319,6 +314,16 @@ impl RustdocPage {
)
.into_response())
}
+
+ // Used for template rendering.
+ pub(crate) fn krate(&self) -> Option<&CrateDetails> {
+ Some(&self.krate)
+ }
+
+ // Used for template rendering.
+ pub(crate) fn permalink_path(&self) -> &str {
+ &self.permalink_path
+ }
}
#[derive(Clone, Deserialize, Debug)]
@@ -605,7 +610,7 @@ pub(crate) async fn rustdoc_html_server_handler(
templates
.render_in_threadpool({
let metrics = metrics.clone();
- move |templates| {
+ move || {
let metadata = krate.metadata.clone();
Ok(RustdocPage {
latest_path,
@@ -623,7 +628,6 @@ pub(crate) async fn rustdoc_html_server_handler(
.into_response(
&blob.content,
config.max_parse_memory,
- templates,
&metrics,
&config,
&storage_path,
diff --git a/src/web/sitemap.rs b/src/web/sitemap.rs
index b388a7c64..fdebe212c 100644
--- a/src/web/sitemap.rs
+++ b/src/web/sitemap.rs
@@ -6,31 +6,43 @@ use crate::{
web::{
error::{AxumNope, AxumResult},
extractors::{DbConnection, Path},
- AxumErrorPage,
+ page::templates::filters,
+ AxumErrorPage, MetaData,
},
Config,
};
-use axum::{extract::Extension, http::StatusCode, response::IntoResponse};
+use axum::{
+ extract::Extension,
+ http::StatusCode,
+ response::{IntoResponse, Response},
+};
use chrono::{TimeZone, Utc};
use futures_util::stream::TryStreamExt;
+use rinja::Template;
use serde::Serialize;
use std::sync::Arc;
/// sitemap index
+#[derive(Template)]
+#[template(path = "core/sitemapindex.xml")]
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
struct SitemapIndexXml {
sitemaps: Vec,
+ csp_nonce: String,
}
impl_axum_webpage! {
- SitemapIndexXml = "core/sitemapindex.xml",
+ SitemapIndexXml,
content_type = "application/xml",
}
pub(crate) async fn sitemapindex_handler() -> impl IntoResponse {
let sitemaps: Vec = ('a'..='z').collect();
- SitemapIndexXml { sitemaps }
+ SitemapIndexXml {
+ sitemaps,
+ csp_nonce: String::new(),
+ }
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
@@ -41,13 +53,16 @@ struct SitemapRow {
}
/// The sitemap
+#[derive(Template)]
+#[template(path = "core/sitemap.xml")]
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
struct SitemapXml {
releases: Vec,
+ csp_nonce: String,
}
impl_axum_webpage! {
- SitemapXml = "core/sitemap.xml",
+ SitemapXml,
content_type = "application/xml",
}
@@ -93,9 +108,14 @@ pub(crate) async fn sitemap_handler(
.try_collect()
.await?;
- Ok(SitemapXml { releases })
+ Ok(SitemapXml {
+ releases,
+ csp_nonce: String::new(),
+ })
}
+#[derive(Template)]
+#[template(path = "core/about/builds.html")]
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
struct AboutBuilds {
/// The current version of rustc that docs.rs is using to build crates
@@ -104,9 +124,16 @@ struct AboutBuilds {
limits: Limits,
/// Just for the template, since this isn't shared with AboutPage
active_tab: &'static str,
+ csp_nonce: String,
+}
+
+impl AboutBuilds {
+ pub(crate) fn get_metadata(&self) -> Option<&MetaData> {
+ None
+ }
}
-impl_axum_webpage!(AboutBuilds = "core/about/builds.html");
+impl_axum_webpage!(AboutBuilds);
pub(crate) async fn about_builds_handler(
Extension(pool): Extension,
@@ -122,17 +149,47 @@ pub(crate) async fn about_builds_handler(
rustc_version,
limits: Limits::new(&config),
active_tab: "builds",
+ csp_nonce: String::new(),
})
}
-#[derive(Serialize)]
-struct AboutPage<'a> {
- #[serde(skip)]
- template: String,
- active_tab: &'a str,
+macro_rules! about_page {
+ ($ty:ident, $template:literal) => {
+ #[derive(Template)]
+ #[template(path = $template)]
+ struct $ty {
+ active_tab: &'static str,
+ csp_nonce: String,
+ }
+
+ impl $ty {
+ fn into_response(&self) -> Response {
+ // match self.render() {
+ // Ok(body) => {
+ // let headers = [(
+ // http::header::CONTENT_TYPE,
+ // http::HeaderValue::from_static(::MIME_TYPE),
+ // )];
+
+ // (headers, body).into_response()
+ // }
+ // Err(_) => StatusCode::INTERNAL_SERVER_ERROR.into_response(),
+ // }
+ StatusCode::INTERNAL_SERVER_ERROR.into_response()
+ }
+
+ pub(crate) fn get_metadata(&self) -> Option<&MetaData> {
+ None
+ }
+ }
+ };
}
-impl_axum_webpage!(AboutPage<'_> = |this: &AboutPage| this.template.clone().into());
+about_page!(AboutPage, "core/about/index.html");
+about_page!(AboutPageBadges, "core/about/badges.html");
+about_page!(AboutPageMetadata, "core/about/metadata.html");
+about_page!(AboutPageRedirection, "core/about/redirections.html");
+about_page!(AboutPageDownload, "core/about/download.html");
pub(crate) async fn about_handler(subpage: Option>) -> AxumResult {
let subpage = match subpage {
@@ -140,9 +197,32 @@ pub(crate) async fn about_handler(subpage: Option>) -> AxumResult "index".to_string(),
};
- let name = match &subpage[..] {
- "about" | "index" => "index",
- x @ "badges" | x @ "metadata" | x @ "redirections" | x @ "download" => x,
+ let response = match &subpage[..] {
+ "about" | "index" => AboutPage {
+ active_tab: "index",
+ csp_nonce: String::new(),
+ }
+ .into_response(),
+ "badges" => AboutPageBadges {
+ active_tab: "badges",
+ csp_nonce: String::new(),
+ }
+ .into_response(),
+ "metadata" => AboutPageMetadata {
+ active_tab: "metadata",
+ csp_nonce: String::new(),
+ }
+ .into_response(),
+ "redirections" => AboutPageRedirection {
+ active_tab: "redirections",
+ csp_nonce: String::new(),
+ }
+ .into_response(),
+ "download" => AboutPageDownload {
+ active_tab: "download",
+ csp_nonce: String::new(),
+ }
+ .into_response(),
_ => {
let msg = "This /about page does not exist. \
Perhaps you are interested in creating it?";
@@ -150,16 +230,12 @@ pub(crate) async fn about_handler(subpage: Option>) -> AxumResult Option<&MetaData> {
+ Some(&self.metadata)
+ }
+ pub(crate) fn permalink_path(&self) -> &str {
+ ""
+ }
+ pub(crate) fn krate(&self) -> Option<&CrateDetails> {
+ None
+ }
+}
+
#[derive(Deserialize, Clone, Debug)]
pub(crate) struct SourceBrowserHandlerParams {
name: String,
@@ -321,6 +338,7 @@ pub(crate) async fn source_browser_handler(
is_file_too_large,
is_latest_url: params.version.is_latest(),
use_direct_platform_links: true,
+ csp_nonce: String::new(),
}
.into_response())
}
diff --git a/src/web/tmp.rs b/src/web/tmp.rs
new file mode 100644
index 000000000..44fd6147e
--- /dev/null
+++ b/src/web/tmp.rs
@@ -0,0 +1,19 @@
+use crate::db::types::{BuildStatus, Feature};
+use crate::docbuilder::Limits;
+use crate::error::Result;
+use crate::web::crate_details::CrateDetails;
+use crate::web::headers::CanonicalUrl;
+use crate::web::page::templates::filters;
+use crate::web::releases::ReleaseType;
+use crate::web::rustdoc::RustdocPage;
+use crate::web::{releases::Release, MetaData, ReqVersion};
+use anyhow::Context;
+use axum::http::StatusCode;
+use chrono::{DateTime, Utc};
+use rinja::Template;
+use semver::Version;
+use serde::{Deserialize, Serialize};
+use std::borrow::Cow;
+use std::{fmt, ops::Deref, sync::Arc};
+use tracing::trace;
+
diff --git a/templates/about-base.html b/templates/about-base.html
index 92222f177..6283f516c 100644
--- a/templates/about-base.html
+++ b/templates/about-base.html
@@ -11,32 +11,38 @@
Docs.rs documentation
{% endblock %}
+
+{%- block topbar -%}
+ {% set is_latest_version = true %}
+ {% let search_query = Some(String::new()) %}
+ {%- include "header/topbar.html" -%}
+{%- endblock topbar -%}
diff --git a/templates/base.html b/templates/base.html
index 6f3fb9ba8..02a30faaf 100644
--- a/templates/base.html
+++ b/templates/base.html
@@ -1,17 +1,15 @@
{%- import "macros.html" as macros -%}
-
-
-
+
{%- block meta -%}{%- endblock meta -%}
{# Docs.rs styles #}
-
-
+
+
@@ -20,8 +18,8 @@
{%- block css -%}{%- endblock css -%}
-
-
+
+
diff --git a/templates/core/about/badges.html b/templates/core/about/badges.html
index 762fc807d..b61a3c1f2 100644
--- a/templates/core/about/badges.html
+++ b/templates/core/about/badges.html
@@ -1,4 +1,4 @@
-{% extends "about-base.html" -%}
+{% extends "about-base.html" %}
{%- block title -%} Badges {%- endblock title -%}
diff --git a/templates/core/about/builds.html b/templates/core/about/builds.html
index 4c6c6a50a..08c1585fc 100644
--- a/templates/core/about/builds.html
+++ b/templates/core/about/builds.html
@@ -1,4 +1,4 @@
-{% extends "about-base.html" -%}
+{% extends "about-base.html" %}
{%- block title -%} Builds {%- endblock title -%}
@@ -15,7 +15,7 @@ Builds
All crates are built in a sandbox using the nightly release of the Rust compiler.
- {%- if rustc_version %}
+ {%- if let Some(rustc_version) = rustc_version %}
The current version in use is {{ rustc_version }}
.
{%- endif -%}
@@ -31,20 +31,20 @@ Detecting Docs.rs
To recognize Docs.rs from your Rust code, you can test for the docsrs
cfg, e.g.:
- {% filter highlight(lang="rust") %}{% filter dedent(levels=3) -%}
+ {% filter highlight("rust")|dedent(3) %}
#[cfg(docsrs)]
mod documentation;
- {%- endfilter %}{% endfilter %}
+ {%- endfilter %}
The `docsrs` cfg only applies to the final rustdoc invocation (i.e. the crate currently
being documented). It does not apply to dependencies (including workspace ones).
To recognize Docs.rs from build.rs
files, you can test for the environment variable DOCS_RS
, e.g.:
- {% filter highlight(lang="rust") %}{% filter dedent(levels=3) -%}
+ {% filter highlight("rust")|dedent(3) -%}
if std::env::var("DOCS_RS").is_ok() {
// ... your code here ...
}
- {%- endfilter %}{% endfilter %}
+ {%- endfilter %}
This approach can be helpful if you need dependencies for building the library, but not for building the documentation.
@@ -55,18 +55,18 @@
You can configure how your crate is built by adding package metadata to your Cargo.toml
, e.g.:
- {% filter highlight(lang="toml") %}{% filter dedent -%}
+ {% filter highlight("toml")|dedent(None) -%}
[package.metadata.docs.rs]
rustc-args = ["--cfg", "my_cfg"]
- {%- endfilter %}{% endfilter %}
+ {%- endfilter %}
Here, the compiler arguments are set so that #[cfg(my_cfg)]
(not to be confused with #[cfg(doc)]
) can be used for conditional compilation.
This approach is also useful for setting cargo features .
- {%- set build_subcommand = docsrs_repo ~ "/blob/master/README.md#build-subcommand" -%}
+ {%- set build_subcommand = "{}/blob/master/README.md#build-subcommand"|format(docsrs_repo) -%}
- The Docs.rs README describes how to build
+ The Docs.rs README describes how to build
unpublished crate documentation locally using the same build environment as the Docs.rs build agent.
@@ -93,11 +93,11 @@
- {{ macros::crate_limits(limits=limits) }}
+ {% call macros::crate_limits(limits=limits) %}
If your build fails because it hit one of these limits, please
- open an issue
+ open an issue
to get them increased for your crate.
Since our build agent has finite resources, we have to consider each case individually. However, there are a few general policies: