diff --git a/Cargo.lock b/Cargo.lock index bcb141a53..5ceb1c704 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,14 +18,6 @@ name = "adler32" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "aho-corasick" -version = "0.6.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "aho-corasick" version = "0.7.10" @@ -373,7 +365,6 @@ dependencies = [ "failure 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", "git2 0.13.6 (registry+https://github.com/rust-lang/crates.io-index)", - "handlebars-iron 0.25.2 (registry+https://github.com/rust-lang/crates.io-index)", "iron 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "kuchiki 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", @@ -991,34 +982,6 @@ name = "half" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "handlebars" -version = "0.29.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", - "pest 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "quick-error 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.53 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "handlebars-iron" -version = "0.25.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "handlebars 0.29.1 (registry+https://github.com/rust-lang/crates.io-index)", - "iron 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", - "plugin 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.53 (registry+https://github.com/rust-lang/crates.io-index)", - "walkdir 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "heck" version = "0.3.1" @@ -1960,11 +1923,6 @@ dependencies = [ "plugin 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "pest" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "pest" version = "1.0.6" @@ -2523,18 +2481,6 @@ dependencies = [ "rust-argon2 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "regex" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "aho-corasick 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", - "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "regex-syntax 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", - "thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", - "utf8-ranges 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "regex" version = "1.3.7" @@ -2554,14 +2500,6 @@ dependencies = [ "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "regex-syntax" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "ucd-util 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "regex-syntax" version = "0.6.17" @@ -2768,15 +2706,6 @@ name = "safemem" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "same-file" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "same-file" version = "1.0.6" @@ -3280,14 +3209,6 @@ name = "thin-slice" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "thread_local" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "thread_local" version = "1.0.1" @@ -3635,11 +3556,6 @@ name = "ucd-trie" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "ucd-util" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "unic-char-property" version = "0.9.0" @@ -3791,11 +3707,6 @@ name = "utf-8" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "utf8-ranges" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "vcpkg" version = "0.2.8" @@ -3821,16 +3732,6 @@ name = "void" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "walkdir" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "same-file 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "walkdir" version = "2.3.1" @@ -4038,7 +3939,6 @@ dependencies = [ "checksum ab_glyph_rasterizer 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2b7e4e8cf778db814365e46839949ca74df4efb10e87ba4913e6ec5967ef0285" "checksum addr2line 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a49806b9dadc843c61e7c97e72490ad7f7220ae249012fbda9ad0609457c0543" "checksum adler32 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5d2e7343e7fc9de883d1b0341e0b13970f764c14101234857d2ddafa1cb1cac2" -"checksum aho-corasick 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "81ce3d38065e618af2d7b77e10c5ad9a069859b4be3c2250f674af3840d9c8a5" "checksum aho-corasick 0.7.10 (registry+https://github.com/rust-lang/crates.io-index)" = "8716408b8bc624ed7f65d223ddb9ac2d044c0547b6fa4b0d554f3a9540496ada" "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" "checksum arc-swap 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "b585a98a234c46fc563103e9278c9391fde1f4e6850334da895d27edb9580f62" @@ -4144,8 +4044,6 @@ dependencies = [ "checksum h2 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)" = "a5b34c246847f938a410a03c5458c7fee2274436675e76d8b903c08efc29c462" "checksum h2 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "79b7246d7e4b979c03fa093da39cfb3617a96bbeee6310af63991668d7e843ff" "checksum half 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d36fab90f82edc3c747f9d438e06cf0a491055896f2a279638bb5beed6c40177" -"checksum handlebars 0.29.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fb04af2006ea09d985fef82b81e0eb25337e51b691c76403332378a53d521edc" -"checksum handlebars-iron 0.25.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7b8ad7259b5bfcc65da1f1f3525eb5e4d5c4c6c7ce2d3b9c9945165e7e083c9c" "checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" "checksum hermit-abi 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "91780f809e750b0a89f5544be56617ff6b1227ee485bcb06ebe10cdf89bd3b71" "checksum hex 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d6a22814455d41612f41161581c2883c0c6a1c41852729b17d5ed88f01e153aa" @@ -4247,7 +4145,6 @@ dependencies = [ "checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" "checksum percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" "checksum persistent 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8e8fa0009c4f3d350281309909c618abddf10bb7e3145f28410782f6a5ec74c5" -"checksum pest 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0a6dda33d67c26f0aac90d324ab2eb7239c819fc7b2552fe9faa4fe88441edc8" "checksum pest 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0fce5d8b5cc33983fc74f78ad552b5522ab41442c4ca91606e4236eb4b5ceefc" "checksum pest 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" "checksum pest_derive 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3294f437119209b084c797604295f40227cffa35c57220b1e99a6ff3bf8ee4" @@ -4309,10 +4206,8 @@ dependencies = [ "checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" "checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" "checksum redox_users 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "09b23093265f8d200fa7b4c2c76297f47e681c655f6f1285a8780d6a022f7431" -"checksum regex 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9329abc99e39129fcceabd24cf5d85b4671ef7c29c50e972bc5afe32438ec384" "checksum regex 1.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "a6020f034922e3194c711b82a627453881bc4682166cabb07134a10c26ba7692" "checksum regex-automata 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "ae1ded71d66a4a97f5e961fd0cb25a5f366a42a41570d16a763a69c092c26ae4" -"checksum regex-syntax 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7d707a4fa2637f2dca2ef9fd02225ec7661fe01a53623c1e6515b6916511f7a7" "checksum regex-syntax 0.6.17 (registry+https://github.com/rust-lang/crates.io-index)" = "7fe5bd57d1d7414c6b5ed48563a2c855d995ff777729dcd91c369ec7fea395ae" "checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e" "checksum reqwest 0.10.6 (registry+https://github.com/rust-lang/crates.io-index)" = "3b82c9238b305f26f53443e3a4bc8528d64b8d0bee408ec949eb7bf5635ec680" @@ -4331,7 +4226,6 @@ dependencies = [ "checksum ryu 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "ed3d612bc64430efeb3f7ee6ef26d590dce0c43249217bddc62112540c7941e1" "checksum safemem 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e27a8b19b835f7aea908818e871f5cc3a5a186550c30773be987e155e8163d8f" "checksum safemem 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" -"checksum same-file 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d931a44fdaa43b8637009e7632a02adc4f2b2e0733c08caa4cf00e8da4a117a7" "checksum same-file 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" "checksum sass-rs 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "cabcf7c6e55053f359911187ac401409aad2dc14338cae972dec266fee486abd" "checksum sass-sys 0.4.19 (registry+https://github.com/rust-lang/crates.io-index)" = "dd454d3c8fa19fe6c66df5d6ced4933f3a40b29d5875114eacc469451136226d" @@ -4387,7 +4281,6 @@ dependencies = [ "checksum termcolor 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f" "checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" "checksum thin-slice 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c" -"checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" "checksum thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" "checksum time 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)" = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" "checksum tinytemplate 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6d3dc76004a03cec1c5932bca4cdc2e39aaa798e3f82363dd94f9adf6098c12f" @@ -4421,7 +4314,6 @@ dependencies = [ "checksum typemap 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "653be63c80a3296da5551e1bfd2cca35227e13cdd08c6668903ae2f4f77aa1f6" "checksum typenum 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" "checksum ucd-trie 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" -"checksum ucd-util 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "c85f514e095d348c279b1e5cd76795082cf15bd59b93207832abe0b1d8fed236" "checksum unic-char-property 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" "checksum unic-char-range 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" "checksum unic-common 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" @@ -4443,13 +4335,11 @@ dependencies = [ "checksum url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb" "checksum urlencoded 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0a52f50139118b60ae91af08bf15ed158817d34b91b9d24c11ffbe21195d33e3" "checksum utf-8 0.7.5 (registry+https://github.com/rust-lang/crates.io-index)" = "05e42f7c18b8f902290b009cde6d651262f956c98bc51bca4cd1d511c9cd85c7" -"checksum utf8-ranges 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b4ae116fef2b7fea257ed6440d3cfcff7f190865f170cdad00bb6465bf18ecba" "checksum vcpkg 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3fc439f2794e98976c88a2a2dafce96b930fe8010b0a256b3c2199a773933168" "checksum vec_map 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" "checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" "checksum version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce" "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" -"checksum walkdir 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "bb08f9e670fab86099470b97cd2b252d6527f0b3cc1401acdb595ffc9dd288ff" "checksum walkdir 2.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d" "checksum want 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b6395efa4784b027708f7451087e647ec73cc74f5d9bc2e418404248d679a230" "checksum want 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" diff --git a/Cargo.toml b/Cargo.toml index 27d31fe65..6be8b129c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,7 +52,6 @@ serde_json = "1.0" # iron dependencies iron = "0.5" router = "0.5" -handlebars-iron = "0.25" params = "0.8" staticfile = { version = "0.4", features = [ "cache" ] } tempfile = "3.1.0" diff --git a/dockerfiles/Dockerfile b/dockerfiles/Dockerfile index 6e3c54f7b..0992a5140 100644 --- a/dockerfiles/Dockerfile +++ b/dockerfiles/Dockerfile @@ -74,7 +74,6 @@ RUN mkdir -p /opt/docsrs/prefix COPY --from=build /build/target/release/cratesfyi /usr/local/bin COPY static /opt/docsrs/prefix/public_html COPY templates /opt/docsrs/templates -COPY tera-templates /opt/docsrs/tera-templates COPY dockerfiles/entrypoint.sh /opt/docsrs/ WORKDIR /opt/docsrs diff --git a/src/build_queue.rs b/src/build_queue.rs index e3c36b92c..b8cbe6116 100644 --- a/src/build_queue.rs +++ b/src/build_queue.rs @@ -3,7 +3,7 @@ use crate::db::Pool; use crate::error::Result; use log::error; -#[derive(Debug, Eq, PartialEq, serde::Serialize)] +#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize)] pub(crate) struct QueuedCrate { #[serde(skip)] id: i32, diff --git a/src/docbuilder/limits.rs b/src/docbuilder/limits.rs index 39d3cba95..e641afdfc 100644 --- a/src/docbuilder/limits.rs +++ b/src/docbuilder/limits.rs @@ -1,7 +1,7 @@ use crate::error::Result; use postgres::Connection; use serde::Serialize; -use std::{collections::BTreeMap, time::Duration}; +use std::time::Duration; #[derive(Debug, Clone, PartialEq, Eq, Serialize)] pub(crate) struct Limits { @@ -67,52 +67,6 @@ impl Limits { pub(crate) fn targets(&self) -> usize { self.targets } - - pub(crate) fn for_website(&self) -> BTreeMap { - let mut res = BTreeMap::new(); - res.insert("Available RAM".into(), SIZE_SCALE(self.memory)); - res.insert( - "Maximum rustdoc execution time".into(), - TIME_SCALE(self.timeout.as_secs() as usize), - ); - res.insert( - "Maximum size of a build log".into(), - SIZE_SCALE(self.max_log_size), - ); - if self.networking { - res.insert("Network access".into(), "allowed".into()); - } else { - res.insert("Network access".into(), "blocked".into()); - } - res.insert( - "Maximum number of build targets".into(), - self.targets.to_string(), - ); - res - } -} - -const TIME_SCALE: fn(usize) -> String = |v| scale(v, 60, &["seconds", "minutes", "hours"]); -const SIZE_SCALE: fn(usize) -> String = |v| scale(v, 1024, &["bytes", "KB", "MB", "GB"]); - -fn scale(value: usize, interval: usize, labels: &[&str]) -> String { - let (mut value, interval) = (value as f64, interval as f64); - let mut chosen_label = &labels[0]; - for label in &labels[1..] { - if value / interval >= 1.0 { - chosen_label = label; - value /= interval; - } else { - break; - } - } - // 2.x - let mut value = format!("{:.1}", value); - // 2.0 -> 2 - if value.ends_with(".0") { - value.truncate(value.len() - 2); - } - format!("{} {}", value, chosen_label) } #[cfg(test)] @@ -161,56 +115,4 @@ mod test { Ok(()) }); } - - #[test] - fn display_limits() { - let limits = Limits { - memory: 102_400, - timeout: Duration::from_secs(300), - targets: 1, - ..Limits::default() - }; - let display = limits.for_website(); - assert_eq!(display.get("Network access"), Some(&"blocked".into())); - assert_eq!( - display.get("Maximum size of a build log"), - Some(&"100 KB".into()) - ); - assert_eq!( - display.get("Maximum number of build targets"), - Some(&limits.targets.to_string()) - ); - assert_eq!( - display.get("Maximum rustdoc execution time"), - Some(&"5 minutes".into()) - ); - assert_eq!(display.get("Available RAM"), Some(&"100 KB".into())); - } - - #[test] - fn scale_limits() { - // time - assert_eq!(TIME_SCALE(300), "5 minutes"); - assert_eq!(TIME_SCALE(1), "1 seconds"); - assert_eq!(TIME_SCALE(7200), "2 hours"); - - // size - assert_eq!(SIZE_SCALE(1), "1 bytes"); - assert_eq!(SIZE_SCALE(100), "100 bytes"); - assert_eq!(SIZE_SCALE(1024), "1 KB"); - assert_eq!(SIZE_SCALE(10240), "10 KB"); - assert_eq!(SIZE_SCALE(1_048_576), "1 MB"); - assert_eq!(SIZE_SCALE(10_485_760), "10 MB"); - assert_eq!(SIZE_SCALE(1_073_741_824), "1 GB"); - assert_eq!(SIZE_SCALE(10_737_418_240), "10 GB"); - assert_eq!(SIZE_SCALE(std::u32::MAX as usize), "4 GB"); - - // fractional sizes - assert_eq!(TIME_SCALE(90), "1.5 minutes"); - assert_eq!(TIME_SCALE(5400), "1.5 hours"); - - assert_eq!(SIZE_SCALE(1_288_490_189), "1.2 GB"); - assert_eq!(SIZE_SCALE(3_758_096_384), "3.5 GB"); - assert_eq!(SIZE_SCALE(1_048_051_712), "999.5 MB"); - } } diff --git a/src/test/fakes.rs b/src/test/fakes.rs index fa899d75e..1ca35dbbd 100644 --- a/src/test/fakes.rs +++ b/src/test/fakes.rs @@ -19,6 +19,8 @@ pub(crate) struct FakeRelease<'a> { registry_crate_data: RegistryCrateData, has_docs: bool, has_examples: bool, + /// This stores the content, while `package.readme` stores the filename + readme: Option<&'a str>, } impl<'a> FakeRelease<'a> { @@ -62,6 +64,7 @@ impl<'a> FakeRelease<'a> { }, has_docs: true, has_examples: false, + readme: None, } } @@ -92,7 +95,7 @@ impl<'a> FakeRelease<'a> { self } - pub fn author(mut self, author: &str) -> Self { + pub(crate) fn author(mut self, author: &str) -> Self { self.package.authors = vec![author.into()]; self } @@ -153,6 +156,13 @@ impl<'a> FakeRelease<'a> { self } + /// NOTE: this should be markdown. It will be rendered as HTML when served. + pub(crate) fn readme(mut self, content: &'a str) -> Self { + self.readme = Some(content); + self.source_file("README.md", content.as_bytes()) + } + + /// Returns the release_id pub(crate) fn create(self) -> Result { use std::collections::HashSet; use std::fs; @@ -222,10 +232,14 @@ impl<'a> FakeRelease<'a> { } } + let crate_dir = tempdir.path(); + if let Some(markdown) = self.readme { + fs::write(crate_dir.join("README.md"), markdown)?; + } let release_id = crate::db::add_package_into_database( &db.conn(), &package, - tempdir.path(), + crate_dir, &self.build_result, self.default_target.unwrap_or("x86_64-unknown-linux-gnu"), source_meta, diff --git a/src/web/builds.rs b/src/web/builds.rs index 1e0fe9934..f4bc80e6e 100644 --- a/src/web/builds.rs +++ b/src/web/builds.rs @@ -1,42 +1,30 @@ -use super::duration_to_str; -use super::page::Page; -use super::MetaData; -use crate::db::Pool; -use crate::docbuilder::Limits; +use crate::{ + db::Pool, + docbuilder::Limits, + impl_webpage, + web::{page::WebPage, MetaData}, +}; use chrono::{DateTime, NaiveDateTime, Utc}; -use iron::prelude::*; +use iron::{ + headers::{ + AccessControlAllowOrigin, CacheControl, CacheDirective, ContentType, Expires, HttpDate, + }, + status, IronResult, Request, Response, +}; use router::Router; -use serde::ser::{Serialize, SerializeStruct, Serializer}; +use serde::Serialize; -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] pub(crate) struct Build { id: i32, rustc_version: String, - cratesfyi_version: String, + docsrs_version: String, build_status: bool, build_time: DateTime, output: Option, } -impl Serialize for Build { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let mut state = serializer.serialize_struct("Build", 7)?; - state.serialize_field("id", &self.id)?; - state.serialize_field("rustc_version", &self.rustc_version)?; - state.serialize_field("cratesfyi_version", &self.cratesfyi_version)?; - state.serialize_field("build_status", &self.build_status)?; - state.serialize_field("build_time", &self.build_time.format("%+").to_string())?; // RFC 3339 - state.serialize_field("build_time_relative", &duration_to_str(self.build_time))?; - state.serialize_field("output", &self.output)?; - - state.end() - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] struct BuildsPage { metadata: Option, builds: Vec, @@ -44,32 +32,23 @@ struct BuildsPage { limits: Limits, } -impl Serialize for BuildsPage { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let mut state = serializer.serialize_struct("Buildspage", 4)?; - state.serialize_field("metadata", &self.metadata)?; - state.serialize_field("builds", &self.builds)?; - state.serialize_field("build_details", &self.build_details)?; - state.serialize_field("limits", &self.limits.for_website())?; - - state.end() - } +impl_webpage! { + BuildsPage = "crate/builds.html", } pub fn build_list_handler(req: &mut Request) -> IronResult { let router = extension!(req, Router); - let name = cexpect!(router.find("name")); - let version = cexpect!(router.find("version")); + let name = cexpect!(req, router.find("name")); + let version = cexpect!(req, router.find("version")); let req_build_id: i32 = router.find("id").unwrap_or("0").parse().unwrap_or(0); let conn = extension!(req, Pool).get()?; - let limits = ctry!(Limits::for_crate(&conn, name)); + let limits = ctry!(req, Limits::for_crate(&conn, name)); - let query = ctry!(conn.query( - "SELECT crates.name, + let query = ctry!( + req, + conn.query( + "SELECT crates.name, releases.version, releases.description, releases.rustdoc_status, @@ -85,23 +64,24 @@ pub fn build_list_handler(req: &mut Request) -> IronResult { INNER JOIN crates ON releases.crate_id = crates.id WHERE crates.name = $1 AND releases.version = $2 ORDER BY id DESC", - &[&name, &version] - )); + &[&name, &version] + ) + ); let mut build_details = None; // FIXME: getting builds.output may cause performance issues when release have tons of builds - let mut build_list = query + let mut builds = query .into_iter() .map(|row| { - let id: i32 = row.get(5); + let id: i32 = row.get("id"); let build = Build { id, - rustc_version: row.get(6), - cratesfyi_version: row.get(7), - build_status: row.get(8), - build_time: DateTime::from_utc(row.get::<_, NaiveDateTime>(9), Utc), - output: row.get(10), + rustc_version: row.get("rustc_version"), + docsrs_version: row.get("cratesfyi_version"), + build_status: row.get("build_status"), + build_time: DateTime::from_utc(row.get::<_, NaiveDateTime>("build_time"), Utc), + output: row.get("output"), }; if id == req_build_id { @@ -113,19 +93,13 @@ pub fn build_list_handler(req: &mut Request) -> IronResult { .collect::>(); if req.url.path().join("/").ends_with(".json") { - use iron::headers::{ - AccessControlAllowOrigin, CacheControl, CacheDirective, ContentType, Expires, HttpDate, - }; - use iron::status; - // Remove build output from build list for json output - for build in build_list.as_mut_slice() { + for build in builds.iter_mut() { build.output = None; } - let mut resp = Response::with((status::Ok, serde_json::to_string(&build_list).unwrap())); - resp.headers - .set(ContentType("application/json".parse().unwrap())); + let mut resp = Response::with((status::Ok, serde_json::to_string(&builds).unwrap())); + resp.headers.set(ContentType::json()); resp.headers.set(Expires(HttpDate(time::now()))); resp.headers.set(CacheControl(vec![ CacheDirective::NoCache, @@ -133,175 +107,15 @@ pub fn build_list_handler(req: &mut Request) -> IronResult { CacheDirective::MustRevalidate, ])); resp.headers.set(AccessControlAllowOrigin::Any); + Ok(resp) } else { - let builds_page = BuildsPage { + BuildsPage { metadata: MetaData::from_crate(&conn, &name, &version), - builds: build_list, + builds, build_details, limits, - }; - Page::new(builds_page) - .set_true("show_package_navigation") - .set_true("package_navigation_builds_tab") - .to_resp("builds") - } -} - -#[cfg(test)] -mod tests { - use super::*; - use chrono::Utc; - use serde_json::json; - - #[test] - fn serialize_build() { - let time = Utc::now(); - let mut build = Build { - id: 22, - rustc_version: "rustc 1.43.0 (4fb7144ed 2020-04-20)".to_string(), - cratesfyi_version: "docsrs 0.6.0 (3dd32ec 2020-05-01)".to_string(), - build_status: true, - build_time: time, - output: None, - }; - - let correct_json = json!({ - "id": 22, - "rustc_version": "rustc 1.43.0 (4fb7144ed 2020-04-20)", - "cratesfyi_version": "docsrs 0.6.0 (3dd32ec 2020-05-01)", - "build_time": time.format("%+").to_string(), - "build_time_relative": duration_to_str(time), - "output": null, - "build_status": true - }); - - assert_eq!(correct_json, serde_json::to_value(&build).unwrap()); - - build.output = Some("some random stuff".to_string()); - let correct_json = json!({ - "id": 22, - "rustc_version": "rustc 1.43.0 (4fb7144ed 2020-04-20)", - "cratesfyi_version": "docsrs 0.6.0 (3dd32ec 2020-05-01)", - "build_time": time.format("%+").to_string(), - "build_time_relative": duration_to_str(time), - "output": "some random stuff", - "build_status": true - }); - - assert_eq!(correct_json, serde_json::to_value(&build).unwrap()); - } - - #[test] - fn serialize_build_page() { - let time = Utc::now(); - let build = Build { - id: 22, - rustc_version: "rustc 1.43.0 (4fb7144ed 2020-04-20)".to_string(), - cratesfyi_version: "docsrs 0.6.0 (3dd32ec 2020-05-01)".to_string(), - build_status: true, - build_time: time, - output: None, - }; - let limits = Limits::default(); - let mut builds = BuildsPage { - metadata: Some(MetaData { - name: "serde".to_string(), - version: "1.0.0".to_string(), - description: Some("serde does stuff".to_string()), - target_name: None, - rustdoc_status: true, - default_target: "x86_64-unknown-linux-gnu".to_string(), - }), - builds: vec![build.clone()], - build_details: Some(build.clone()), - limits: limits.clone(), - }; - - let correct_json = json!({ - "metadata": { - "name": "serde", - "version": "1.0.0", - "description": "serde does stuff", - "target_name": null, - "rustdoc_status": true, - "default_target": "x86_64-unknown-linux-gnu" - }, - "builds": [{ - "id": 22, - "rustc_version": "rustc 1.43.0 (4fb7144ed 2020-04-20)", - "cratesfyi_version": "docsrs 0.6.0 (3dd32ec 2020-05-01)", - "build_time": time.format("%+").to_string(), - "build_time_relative": duration_to_str(time), - "output": null, - "build_status": true - }], - "build_details": { - "id": 22, - "rustc_version": "rustc 1.43.0 (4fb7144ed 2020-04-20)", - "cratesfyi_version": "docsrs 0.6.0 (3dd32ec 2020-05-01)", - "build_time": time.format("%+").to_string(), - "build_time_relative": duration_to_str(time), - "output": null, - "build_status": true - }, - "limits": limits.for_website(), - }); - - assert_eq!(correct_json, serde_json::to_value(&builds).unwrap()); - - builds.metadata = None; - let correct_json = json!({ - "metadata": null, - "builds": [{ - "id": 22, - "rustc_version": "rustc 1.43.0 (4fb7144ed 2020-04-20)", - "cratesfyi_version": "docsrs 0.6.0 (3dd32ec 2020-05-01)", - "build_time": time.format("%+").to_string(), - "build_time_relative": duration_to_str(time), - "output": null, - "build_status": true - }], - "build_details": { - "id": 22, - "rustc_version": "rustc 1.43.0 (4fb7144ed 2020-04-20)", - "cratesfyi_version": "docsrs 0.6.0 (3dd32ec 2020-05-01)", - "build_time": time.format("%+").to_string(), - "build_time_relative": duration_to_str(time), - "output": null, - "build_status": true - }, - "limits": limits.for_website(), - }); - - assert_eq!(correct_json, serde_json::to_value(&builds).unwrap()); - - builds.builds = Vec::new(); - let correct_json = json!({ - "metadata": null, - "builds": [], - "build_details": { - "id": 22, - "rustc_version": "rustc 1.43.0 (4fb7144ed 2020-04-20)", - "cratesfyi_version": "docsrs 0.6.0 (3dd32ec 2020-05-01)", - "build_time": time.format("%+").to_string(), - "build_time_relative": duration_to_str(time), - "output": null, - "build_status": true - }, - "limits": limits.for_website() - }); - - assert_eq!(correct_json, serde_json::to_value(&builds).unwrap()); - - builds.build_details = None; - let correct_json = json!({ - "metadata": null, - "builds": [], - "build_details": null, - "limits": limits.for_website(), - }); - - assert_eq!(correct_json, serde_json::to_value(&builds).unwrap()); + } + .into_response(req) } } diff --git a/src/web/crate_details.rs b/src/web/crate_details.rs index 4e5ff15a9..ef0a1c676 100644 --- a/src/web/crate_details.rs +++ b/src/web/crate_details.rs @@ -1,23 +1,17 @@ use super::error::Nope; -use super::page::Page; -use super::{ - duration_to_str, match_version, redirect_base, render_markdown, MatchSemver, MetaData, -}; -use crate::db::Pool; +use super::{match_version, redirect_base, render_markdown, MatchSemver, MetaData}; +use crate::{db::Pool, impl_webpage, web::page::WebPage}; use chrono::{DateTime, NaiveDateTime, Utc}; use iron::prelude::*; use iron::{status, Url}; use postgres::Connection; use router::Router; -use serde::{ - ser::{SerializeStruct, Serializer}, - Serialize, -}; +use serde::{ser::Serializer, Serialize}; use serde_json::Value; // TODO: Add target name and versions -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Serialize)] pub struct CrateDetails { name: String, version: String, @@ -26,7 +20,9 @@ pub struct CrateDetails { owners: Vec<(String, String)>, authors_json: Option, dependencies: Option, + #[serde(serialize_with = "optional_markdown")] readme: Option, + #[serde(serialize_with = "optional_markdown")] rustdoc: Option, // this is description_long in database release_time: DateTime, build_status: bool, @@ -50,60 +46,16 @@ pub struct CrateDetails { documentation_url: Option, } -impl Serialize for CrateDetails { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - // Make sure that the length parameter passed to serde is correct by - // adding the someness of `readme` and `rustdoc` to the total. `true` - // is 1 and `false` is 0, so it increments if the value is some (and therefore - // needs to be serialized) - let mut state = serializer.serialize_struct( - "CrateDetails", - 26 + self.readme.is_some() as usize + self.rustdoc.is_some() as usize, - )?; - - state.serialize_field("metadata", &self.metadata)?; - state.serialize_field("name", &self.name)?; - state.serialize_field("version", &self.version)?; - state.serialize_field("description", &self.description)?; - state.serialize_field("authors", &self.authors)?; - state.serialize_field("owners", &self.owners)?; - state.serialize_field("authors_json", &self.authors_json)?; - state.serialize_field("dependencies", &self.dependencies)?; - - if let Some(ref readme) = self.readme { - state.serialize_field("readme", &render_markdown(&readme))?; - } - - if let Some(ref rustdoc) = self.rustdoc { - state.serialize_field("rustdoc", &render_markdown(&rustdoc))?; - } - - state.serialize_field("release_time", &duration_to_str(self.release_time))?; - state.serialize_field("build_status", &self.build_status)?; - state.serialize_field("last_successful_build", &self.last_successful_build)?; - state.serialize_field("rustdoc_status", &self.rustdoc_status)?; - state.serialize_field("repository_url", &self.repository_url)?; - state.serialize_field("homepage_url", &self.homepage_url)?; - state.serialize_field("keywords", &self.keywords)?; - state.serialize_field("have_examples", &self.have_examples)?; - state.serialize_field("target_name", &self.target_name)?; - state.serialize_field("releases", &self.releases)?; - state.serialize_field("github", &self.github)?; - state.serialize_field("github_stars", &self.github_stars)?; - state.serialize_field("github_forks", &self.github_forks)?; - state.serialize_field("github_issues", &self.github_issues)?; - state.serialize_field("metadata", &self.metadata)?; - state.serialize_field("is_library", &self.is_library)?; - state.serialize_field("doc_targets", &self.doc_targets)?; - state.serialize_field("yanked", &self.yanked)?; - state.serialize_field("license", &self.license)?; - state.serialize_field("documentation_url", &self.documentation_url)?; - - state.end() +fn optional_markdown(markdown: &Option, serializer: S) -> Result +where + S: Serializer, +{ + if let Some(ref markdown) = markdown { + Some(render_markdown(&markdown)) + } else { + None } + .serialize(serializer) } #[derive(Debug, Clone, Eq, PartialEq, Serialize)] @@ -286,56 +238,14 @@ impl CrateDetails { .find(|release| !release.yanked) .unwrap_or(&self.releases[0]) } - - #[cfg(test)] - pub fn default_tester(release_time: DateTime) -> Self { - Self { - name: "rcc".to_string(), - version: "100.0.0".to_string(), - description: None, - authors: vec![], - owners: vec![], - authors_json: None, - dependencies: None, - readme: None, - rustdoc: None, - release_time, - build_status: true, - last_successful_build: None, - rustdoc_status: true, - repository_url: None, - homepage_url: None, - keywords: None, - yanked: false, - have_examples: true, - target_name: "x86_64-unknown-linux-gnu".to_string(), - releases: vec![], - github: true, - github_stars: None, - github_forks: None, - github_issues: None, - metadata: MetaData { - name: "serde".to_string(), - version: "1.0.0".to_string(), - description: Some("serde does stuff".to_string()), - target_name: None, - rustdoc_status: true, - default_target: "x86_64-unknown-linux-gnu".to_string(), - }, - is_library: true, - doc_targets: vec![], - license: None, - documentation_url: None, - } - } } fn map_to_release(conn: &Connection, crate_id: i32, version: String) -> Release { let rows = conn .query( "SELECT build_status, yanked - FROM releases - WHERE releases.crate_id = $1 and releases.version = $2;", + FROM releases + WHERE releases.crate_id = $1 and releases.version = $2;", &[&crate_id, &version], ) .unwrap(); @@ -353,31 +263,44 @@ fn map_to_release(conn: &Connection, crate_id: i32, version: String) -> Release } } +#[derive(Debug, Clone, PartialEq, Serialize)] +struct CrateDetailsPage { + details: CrateDetails, +} + +impl_webpage! { + CrateDetailsPage = "crate/details.html", +} + pub fn crate_details_handler(req: &mut Request) -> IronResult { let router = extension!(req, Router); // this handler must always called with a crate name - let name = cexpect!(router.find("name")); + let name = cexpect!(req, router.find("name")); let req_version = router.find("version"); let conn = extension!(req, Pool).get()?; match match_version(&conn, &name, req_version).and_then(|m| m.assume_exact()) { Some(MatchSemver::Exact((version, _))) => { - let details = CrateDetails::new(&conn, &name, &version); + let details = cexpect!(req, CrateDetails::new(&conn, &name, &version)); - Page::new(details) - .set_true("show_package_navigation") - .set_true("javascript_highlightjs") - .set_true("package_navigation_crate_tab") - .to_resp("crate_details") + CrateDetailsPage { details }.into_response(req) } + Some(MatchSemver::Semver((version, _))) => { - let url = ctry!(Url::parse( - &format!("{}/crate/{}/{}", redirect_base(req), name, version)[..] - )); + let url = ctry!( + req, + Url::parse(&format!( + "{}/crate/{}/{}", + redirect_base(req), + name, + version + )), + ); Ok(super::redirect(url)) } + None => Err(IronError::new(Nope::CrateNotFound, status::NotFound)), } } @@ -386,9 +309,7 @@ pub fn crate_details_handler(req: &mut Request) -> IronResult { mod tests { use super::*; use crate::test::TestDatabase; - use chrono::Utc; use failure::Error; - use serde_json::json; fn assert_last_successful_build_equals( db: &TestDatabase, @@ -633,82 +554,4 @@ mod tests { Ok(()) }) } - - #[test] - fn serialize_crate_details() { - let time = Utc::now(); - let mut details = CrateDetails::default_tester(time); - - let mut correct_json = json!({ - "name": "rcc", - "version": "100.0.0", - "description": null, - "authors": [], - "owners": [], - "authors_json": null, - "dependencies": null, - "release_time": super::super::duration_to_str(time), - "build_status": true, - "last_successful_build": null, - "rustdoc_status": true, - "repository_url": null, - "homepage_url": null, - "keywords": null, - "have_examples": true, - "target_name": "x86_64-unknown-linux-gnu", - "releases": [], - "github": true, - "yanked": false, - "github_stars": null, - "github_forks": null, - "github_issues": null, - "metadata": { - "name": "serde", - "version": "1.0.0", - "description": "serde does stuff", - "target_name": null, - "rustdoc_status": true, - "default_target": "x86_64-unknown-linux-gnu" - }, - "is_library": true, - "doc_targets": [], - "license": null, - "documentation_url": null - }); - - assert_eq!(correct_json, serde_json::to_value(&details).unwrap()); - - let authors = vec![("Somebody".to_string(), "somebody@somebody.com".to_string())]; - let owners = vec![("Owner".to_string(), "owner@ownsstuff.com".to_string())]; - let description = "serde does stuff".to_string(); - - correct_json["description"] = Value::String(description.clone()); - correct_json["owners"] = serde_json::to_value(&owners).unwrap(); - correct_json["authors_json"] = serde_json::to_value(&authors).unwrap(); - correct_json["authors"] = serde_json::to_value(&authors).unwrap(); - - details.description = Some(description); - details.owners = owners; - details.authors_json = Some(serde_json::to_value(&authors).unwrap()); - details.authors = authors; - - assert_eq!(correct_json, serde_json::to_value(&details).unwrap()); - } - - #[test] - fn serialize_releases() { - let release = Release { - version: "idkman".to_string(), - build_status: true, - yanked: true, - }; - - let correct_json = json!({ - "version": "idkman", - "build_status": true, - "yanked": true, - }); - - assert_eq!(correct_json, serde_json::to_value(&release).unwrap()); - } } diff --git a/src/web/error.rs b/src/web/error.rs index 2c9060c36..0abcc3e92 100644 --- a/src/web/error.rs +++ b/src/web/error.rs @@ -1,12 +1,9 @@ use crate::{ db::PoolError, - web::{ - page::{Page, WebPage}, - releases::Search, - }, + web::{page::WebPage, releases::Search, ErrorPage}, }; use failure::Fail; -use iron::{status, Handler, IronError, IronResult, Plugin, Request, Response}; +use iron::{status::Status, Handler, IronError, IronResult, Plugin, Request, Response}; use params::{Params, Value}; use std::{error::Error, fmt}; @@ -36,18 +33,24 @@ impl Handler for Nope { match *self { Nope::ResourceNotFound => { // user tried to navigate to a resource (doc page/file) that doesn't exist - Page::new("no such resource".to_owned()) - .set_status(status::NotFound) - .title("The requested resource does not exist") - .to_resp("error") + // TODO: Display the attempted page + ErrorPage { + title: "The requested resource does not exist", + message: Some("no such resource".into()), + status: Status::NotFound, + } + .into_response(req) } Nope::CrateNotFound => { // user tried to navigate to a crate that doesn't exist - Page::new("no such crate".to_owned()) - .set_status(status::NotFound) - .title("The requested crate does not exist") - .to_resp("error") + // TODO: Display the attempted crate and a link to a search for said crate + ErrorPage { + title: "The requested crate does not exist", + message: Some("no such crate".into()), + status: Status::NotFound, + } + .into_response(req) } Nope::NoResults => { @@ -58,7 +61,7 @@ impl Handler for Nope { Search { title: format!("No crates found matching '{}'", query), search_query: Some(query.to_owned()), - status: status::NotFound, + status: Status::NotFound, ..Default::default() } .into_response(req) @@ -66,7 +69,7 @@ impl Handler for Nope { // user did a search with no search terms Search { title: "No results given for empty search query".to_owned(), - status: status::NotFound, + status: Status::NotFound, ..Default::default() } .into_response(req) @@ -75,10 +78,12 @@ impl Handler for Nope { Nope::InternalServerError => { // something went wrong, details should have been logged - Page::new("internal server error".to_owned()) - .set_status(status::InternalServerError) - .title("Internal server error") - .to_resp("error") + ErrorPage { + title: "Internal server error", + message: Some("internal server error".into()), + status: Status::InternalServerError, + } + .into_response(req) } } } @@ -86,6 +91,6 @@ impl Handler for Nope { impl From for IronError { fn from(err: PoolError) -> IronError { - IronError::new(err.compat(), status::InternalServerError) + IronError::new(err.compat(), Status::InternalServerError) } } diff --git a/src/web/metrics.rs b/src/web/metrics.rs index dd891c726..e0eeeaf37 100644 --- a/src/web/metrics.rs +++ b/src/web/metrics.rs @@ -154,9 +154,9 @@ pub fn metrics_handler(req: &mut Request) -> IronResult { USED_DB_CONNECTIONS.set(pool.used_connections() as i64); IDLE_DB_CONNECTIONS.set(pool.idle_connections() as i64); - QUEUED_CRATES_COUNT.set(ctry!(queue.pending_count()) as i64); - PRIORITIZED_CRATES_COUNT.set(ctry!(queue.prioritized_count()) as i64); - FAILED_CRATES_COUNT.set(ctry!(queue.failed_count()) as i64); + QUEUED_CRATES_COUNT.set(ctry!(req, queue.pending_count()) as i64); + PRIORITIZED_CRATES_COUNT.set(ctry!(req, queue.prioritized_count()) as i64); + FAILED_CRATES_COUNT.set(ctry!(req, queue.failed_count()) as i64); #[cfg(target_os = "linux")] { @@ -169,12 +169,12 @@ pub fn metrics_handler(req: &mut Request) -> IronResult { let mut buffer = Vec::new(); let families = prometheus::gather(); - ctry!(TextEncoder::new().encode(&families, &mut buffer)); + ctry!(req, TextEncoder::new().encode(&families, &mut buffer)); let mut resp = Response::with(buffer); resp.status = Some(Status::Ok); - resp.headers - .set(ContentType("text/plain; version=0.0.4".parse().unwrap())); + resp.headers.set(ContentType::plaintext()); + Ok(resp) } diff --git a/src/web/mod.rs b/src/web/mod.rs index cc12c5365..d74d5a92c 100644 --- a/src/web/mod.rs +++ b/src/web/mod.rs @@ -7,15 +7,23 @@ use log::{debug, info}; /// ctry! (cratesfyitry) is extremely similar to try! and itry! /// except it returns an error page response instead of plain Err. macro_rules! ctry { - ($result:expr) => { + ($req:expr, $result:expr $(,)?) => { match $result { - Ok(v) => v, - Err(e) => { - log::error!("{}\n{:?}", e, backtrace::Backtrace::new()); - return $crate::web::page::Page::new(format!("{}", e)) - .title("Internal Server Error") - .set_status(::iron::status::BadRequest) - .to_resp("error"); + Ok(success) => success, + Err(error) => { + ::log::error!("{}\n{:?}", error, ::backtrace::Backtrace::new()); + + // This is very ugly, but it makes it impossible to get a type inference error + // from this macro + let error = $crate::web::ErrorPage { + title: "Internal Server Error", + message: ::std::option::Option::Some(::std::borrow::Cow::Owned( + ::std::format!("{}", error), + )), + status: ::iron::status::BadRequest, + }; + + return $crate::web::page::WebPage::into_response(error, $req); } } }; @@ -24,18 +32,24 @@ macro_rules! ctry { /// cexpect will check an option and if it's not Some /// it will return an error page response macro_rules! cexpect { - ($option:expr) => { + ($req:expr, $option:expr $(,)?) => { match $option { - Some(v) => v, + Some(success) => success, None => { - log::error!( + ::log::error!( "called cexpect!() on a `None` value\n{:?}", - backtrace::Backtrace::new() + ::backtrace::Backtrace::new(), ); - return $crate::web::page::Page::new("Internal Server Error".to_owned()) - .title("Internal Server Error") - .set_status(::iron::status::BadRequest) - .to_resp("error"); + + // This is very ugly, but it makes it impossible to get a type inference error + // from this macro + let error = $crate::web::ErrorPage { + title: "Internal Server Error", + message: None, + status: ::iron::status::BadRequest, + }; + + return $crate::web::page::WebPage::into_response(error, $req); } } }; @@ -43,9 +57,12 @@ macro_rules! cexpect { /// Gets an extension from Request macro_rules! extension { - ($req:expr, $ext:ty) => { - cexpect!($req.extensions.get::<$ext>()) - }; + ($req:expr, $ext:ty) => {{ + // Bind $req so we can have good type errors and avoid re-evaluation + let request: &::iron::Request = $req; + + cexpect!(request, request.extensions.get::<$ext>()) + }}; } mod builds; @@ -60,23 +77,25 @@ mod rustdoc; mod sitemap; mod source; -use self::extensions::InjectExtensions; -use self::page::TemplateData; -use crate::config::Config; -use crate::db::Pool; -use crate::BuildQueue; +use crate::{config::Config, db::Pool, impl_webpage, BuildQueue}; use chrono::{DateTime, Utc}; +use extensions::InjectExtensions; use failure::Error; -use handlebars_iron::{DirectorySource, HandlebarsEngine, SourceError}; -use iron::headers::{CacheControl, CacheDirective, ContentType, Expires, HttpDate}; -use iron::modifiers::Redirect; -use iron::prelude::*; -use iron::{self, status, Handler, Listening, Url}; +use iron::{ + self, + headers::{CacheControl, CacheDirective, ContentType, Expires, HttpDate}, + modifiers::Redirect, + status, + status::Status, + Chain, Handler, Iron, IronError, IronResult, Listening, Request, Response, Url, +}; +use page::TemplateData; use postgres::Connection; use router::NoRoute; use semver::{Version, VersionReq}; +use serde::Serialize; use staticfile::Static; -use std::{env, fmt, net::SocketAddr, path::PathBuf, sync::Arc, time::Duration}; +use std::{borrow::Cow, env, fmt, net::SocketAddr, path::PathBuf, sync::Arc, time::Duration}; /// Duration of static files for staticfile and DatabaseFileHandler (in seconds) const STATIC_FILE_CACHE_DURATION: u64 = 60 * 60 * 24 * 30 * 12; // 12 months @@ -87,17 +106,6 @@ const OPENSEARCH_XML: &[u8] = include_bytes!("opensearch.xml"); const DEFAULT_BIND: &str = "0.0.0.0:3000"; -fn handlebars_engine() -> Result { - // TODO: Use DocBuilderOptions for paths - let mut hbse = HandlebarsEngine::new(); - hbse.add(Box::new(DirectorySource::new("./templates", ".hbs"))); - - // load templates - hbse.reload()?; - - Ok(hbse) -} - struct CratesfyiHandler { shared_resource_handler: Box, router_handler: Box, @@ -108,11 +116,9 @@ struct CratesfyiHandler { impl CratesfyiHandler { fn chain(inject_extensions: InjectExtensions, base: H) -> Chain { - let hbse = handlebars_engine().expect("Failed to load handlebar templates"); - let mut chain = Chain::new(base); chain.link_before(inject_extensions); - chain.link_after(hbse); + chain } @@ -557,16 +563,17 @@ fn ico_handler(req: &mut Request) -> IronResult { } else { // if we're looking for something like "favicon-20190317-1.35.0-nightly-c82834e2b.ico", // redirect to the plain one so that the above branch can trigger with the correct filename - let url = ctry!(Url::parse( - &format!("{}/favicon.ico", redirect_base(req))[..] - )); + let url = ctry!( + req, + Url::parse(&format!("{}/favicon.ico", redirect_base(req))), + ); Ok(redirect(url)) } } /// MetaData used in header -#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] pub(crate) struct MetaData { pub(crate) name: String, pub(crate) version: String, @@ -606,13 +613,25 @@ impl MetaData { } } +#[derive(Debug, Clone, PartialEq, Serialize)] +pub(crate) struct ErrorPage { + /// The title of the page + pub title: &'static str, + /// The error message, displayed as a description + pub message: Option>, + #[serde(skip)] + pub status: Status, +} + +impl_webpage! { + ErrorPage = "error.html", + status = |err| err.status, +} + #[cfg(test)] mod test { use super::*; - use crate::{ - test::*, - web::{handlebars_engine, match_version}, - }; + use crate::{test::*, web::match_version}; use kuchiki::traits::TendrilSink; use serde_json::json; @@ -821,11 +840,6 @@ mod test { }); } - #[test] - fn test_templates_are_valid() { - handlebars_engine().expect("Failed to load handlebar templates"); - } - #[test] fn serialize_metadata() { let mut metadata = MetaData { diff --git a/src/web/page/handlebars.rs b/src/web/page/handlebars.rs deleted file mode 100644 index c493a8f8e..000000000 --- a/src/web/page/handlebars.rs +++ /dev/null @@ -1,196 +0,0 @@ -//! Generic page struct - -use handlebars_iron::Template; -use iron::response::Response; -use iron::{status, IronResult, Set}; -use once_cell::sync::Lazy; -use serde::{ - ser::{SerializeStruct, Serializer}, - Serialize, -}; -use serde_json::Value; -use std::collections::BTreeMap; - -static RUSTC_RESOURCE_SUFFIX: Lazy = - Lazy::new(|| load_rustc_resource_suffix().unwrap_or_else(|_| "???".into())); - -fn load_rustc_resource_suffix() -> Result { - // New instances of the configuration or the connection pool shouldn't be created inside the - // application, but we're removing handlebars so this is not going to be a problem in the long - // term. To avoid wasting resources, the pool is hardcoded to only keep one connection alive. - let mut config = crate::Config::from_env()?; - config.max_pool_size = 1; - config.min_pool_idle = 1; - let pool = crate::db::Pool::new(&config)?; - let conn = pool.get()?; - - let res = conn.query( - "SELECT value FROM config WHERE name = 'rustc_version';", - &[], - )?; - if res.is_empty() { - failure::bail!("missing rustc version"); - } - - if let Some(Ok(vers)) = res.get(0).get_opt::<_, Value>("value") { - if let Some(vers_str) = vers.as_str() { - return Ok(crate::utils::parse_rustc_version(vers_str)?); - } - } - - failure::bail!("failed to parse the rustc version"); -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Page { - title: Option, - content: T, - status: status::Status, - varss: BTreeMap, - varsb: BTreeMap, - varsi: BTreeMap, - rustc_resource_suffix: &'static str, -} - -impl Page { - pub fn new(content: T) -> Page { - Page { - title: None, - content, - status: status::Ok, - varss: BTreeMap::new(), - varsb: BTreeMap::new(), - varsi: BTreeMap::new(), - rustc_resource_suffix: &RUSTC_RESOURCE_SUFFIX, - } - } - - /// Sets a string variable - pub fn set(mut self, var: &str, val: &str) -> Page { - self.varss.insert(var.to_owned(), val.to_owned()); - self - } - - /// Sets a boolean variable - pub fn set_bool(mut self, var: &str, val: bool) -> Page { - self.varsb.insert(var.to_owned(), val); - self - } - - /// Sets a boolean variable to true - pub fn set_true(mut self, var: &str) -> Page { - self.varsb.insert(var.to_owned(), true); - self - } - - /// Sets title of page - pub fn title(mut self, title: &str) -> Page { - self.title = Some(title.to_owned()); - self - } - - /// Sets status code for response - pub fn set_status(mut self, s: status::Status) -> Page { - self.status = s; - self - } - - #[allow(clippy::wrong_self_convention)] - pub fn to_resp(self, template: &str) -> IronResult { - let mut resp = Response::new(); - let status = self.status; - let temp = Template::new(template, self); - resp.set_mut(temp).set_mut(status); - - Ok(resp) - } -} - -impl Serialize for Page { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - // Make sure that the length parameter passed to serde is correct by - // adding the someness of the global alert to the total. `true` - // is 1 and `false` is 0, so it increments if the value is some (and therefore - // needs to be serialized) - let mut state = serializer.serialize_struct( - "Page", - 8 + crate::GLOBAL_ALERT.is_some() as usize + self.title.is_some() as usize, - )?; - - if let Some(ref title) = self.title { - state.serialize_field("title", title)?; - } - - state.serialize_field("has_global_alert", &crate::GLOBAL_ALERT.is_some())?; - if let Some(ref global_alert) = crate::GLOBAL_ALERT { - state.serialize_field("global_alert", global_alert)?; - } - - state.serialize_field("content", &self.content)?; - state.serialize_field("rustc_resource_suffix", self.rustc_resource_suffix)?; - state.serialize_field("cratesfyi_version", crate::BUILD_VERSION)?; - state.serialize_field( - "cratesfyi_version_safe", - &build_version_safe(crate::BUILD_VERSION), - )?; - state.serialize_field("varss", &self.varss)?; - state.serialize_field("varsb", &self.varsb)?; - state.serialize_field("varsi", &self.varsi)?; - - state.end() - } -} - -fn build_version_safe(version: &str) -> String { - version.replace(" ", "-").replace("(", "").replace(")", "") -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::web::releases; - use iron::Url; - - #[test] - fn load_page_from_releases() { - crate::test::wrapper(|env| { - let db = env.db(); - db.fake_release().name("foo").version("0.1.0").create()?; - let packages = releases::get_releases(&db.conn(), 1, 1, releases::Order::ReleaseTime); - - let mut varsb = BTreeMap::new(); - varsb.insert("show_search_form".into(), true); - varsb.insert("hide_package_navigation".into(), true); - - let correct_page = Page { - title: None, - content: packages.clone(), - status: status::Status::Ok, - varss: BTreeMap::new(), - varsb, - varsi: BTreeMap::new(), - rustc_resource_suffix: &RUSTC_RESOURCE_SUFFIX, - }; - - let page = Page::new(packages) - .set_true("show_search_form") - .set_true("hide_package_navigation"); - - assert_eq!(page, correct_page); - - Ok(()) - }) - } - - #[test] - fn build_version_url_safe() { - let safe = format!( - "https://docs.rs/builds/{}", - build_version_safe(crate::BUILD_VERSION) - ); - assert!(Url::parse(&safe).is_ok()); - } -} diff --git a/src/web/page/mod.rs b/src/web/page/mod.rs index c8c1ad175..b8a42458e 100644 --- a/src/web/page/mod.rs +++ b/src/web/page/mod.rs @@ -1,8 +1,6 @@ -mod handlebars; mod templates; mod web_page; -pub use handlebars::*; pub(crate) use templates::TemplateData; pub(crate) use web_page::WebPage; diff --git a/src/web/page/templates.rs b/src/web/page/templates.rs index 801de6c29..8d02af143 100644 --- a/src/web/page/templates.rs +++ b/src/web/page/templates.rs @@ -16,7 +16,7 @@ use std::{ use tera::{Result as TeraResult, Tera}; use walkdir::WalkDir; -const TEMPLATES_DIRECTORY: &str = "tera-templates"; +const TEMPLATES_DIRECTORY: &str = "templates"; /// Holds all data relevant to templating #[derive(Debug)] @@ -45,7 +45,7 @@ impl TemplateData { let mut watcher = watcher(tx, Duration::from_secs(2)).unwrap(); watcher - .watch("tera-templates", RecursiveMode::Recursive) + .watch(TEMPLATES_DIRECTORY, RecursiveMode::Recursive) .unwrap(); thread::spawn(move || { diff --git a/src/web/releases.rs b/src/web/releases.rs index 33f25fa46..0bad8244e 100644 --- a/src/web/releases.rs +++ b/src/web/releases.rs @@ -1,14 +1,10 @@ //! Releases web handlers use crate::{ + build_queue::QueuedCrate, db::Pool, impl_webpage, - web::{ - error::Nope, - match_version, - page::{Page, WebPage}, - redirect_base, - }, + web::{error::Nope, match_version, page::WebPage, redirect_base}, BuildQueue, }; use chrono::{DateTime, NaiveDateTime, Utc}; @@ -456,10 +452,10 @@ pub fn author_handler(req: &mut Request) -> IronResult { .find("page") .and_then(|page_num| page_num.parse().ok()) .unwrap_or(1); - let author = ctry!(router + let author = router .find("author") // TODO: Accurate error here, the author wasn't provided - .ok_or_else(|| IronError::new(Nope::CrateNotFound, status::NotFound))); + .ok_or_else(|| IronError::new(Nope::CrateNotFound, status::NotFound))?; let (author_name, releases) = { let conn = extension!(req, Pool).get()?; @@ -472,7 +468,7 @@ pub fn author_handler(req: &mut Request) -> IronResult { page_number, RELEASES_IN_RELEASES, // TODO: Is this fallible? - cexpect!(author.nth(1)), + cexpect!(req, author.nth(1)), ) } else { get_releases_by_author(&conn, page_number, RELEASES_IN_RELEASES, author) @@ -540,7 +536,7 @@ impl_webpage! { pub fn search_handler(req: &mut Request) -> IronResult { use params::{Params, Value}; - let params = ctry!(req.get::()); + let params = ctry!(req, req.get::()); let query = params.find(&["query"]); let conn = extension!(req, Pool).get()?; @@ -554,8 +550,10 @@ pub fn search_handler(req: &mut Request) -> IronResult { // FIXME: This is a fast query but using a constant // There are currently 280 crates with docs and 100+ // starts. This should be fine for a while. - let rows = ctry!(conn.query( - "SELECT crates.name, + let rows = ctry!( + req, + conn.query( + "SELECT crates.name, releases.version, releases.target_name FROM crates @@ -563,20 +561,24 @@ pub fn search_handler(req: &mut Request) -> IronResult { ON crates.latest_version_id = releases.id WHERE github_stars >= 100 AND rustdoc_status = true OFFSET FLOOR(RANDOM() * 280) LIMIT 1", - &[] - )); + &[] + ), + ); let row = rows.into_iter().next().unwrap(); let name: String = row.get("name"); let version: String = row.get("version"); let target_name: String = row.get("target_name"); - let url = ctry!(Url::parse(&format!( - "{}/{}/{}/{}", - redirect_base(req), - name, - version, - target_name - ))); + let url = ctry!( + req, + Url::parse(&format!( + "{}/{}/{}/{}", + redirect_base(req), + name, + version, + target_name + )), + ); let mut resp = Response::with((status::Found, Redirect(url))); resp.headers.set(Expires(HttpDate(time::now()))); @@ -595,12 +597,15 @@ pub fn search_handler(req: &mut Request) -> IronResult { // match_version should handle this instead of this code block. // This block is introduced to fix #163 let rustdoc_status = { - let rows = ctry!(conn.query( - "SELECT rustdoc_status + let rows = ctry!( + req, + conn.query( + "SELECT rustdoc_status FROM releases WHERE releases.id = $1", - &[&id] - )); + &[&id] + ), + ); rows.into_iter() .next() @@ -609,19 +614,20 @@ pub fn search_handler(req: &mut Request) -> IronResult { }; let url = if rustdoc_status { - ctry!(Url::parse(&format!( - "{}/{}/{}", - redirect_base(req), - query, - version, - ))) + ctry!( + req, + Url::parse(&format!("{}/{}/{}", redirect_base(req), query, version)), + ) } else { - ctry!(Url::parse(&format!( - "{}/crate/{}/{}", - redirect_base(req), - query, - version, - ))) + ctry!( + req, + Url::parse(&format!( + "{}/crate/{}/{}", + redirect_base(req), + query, + version, + )), + ) }; let mut resp = Response::with((status::Found, Redirect(url))); @@ -651,44 +657,60 @@ pub fn search_handler(req: &mut Request) -> IronResult { } } +#[derive(Debug, Clone, PartialEq, Serialize)] +struct ReleaseActivity { + description: &'static str, + activity_data: Value, +} + +impl_webpage! { + ReleaseActivity = "releases/activity.html", +} + pub fn activity_handler(req: &mut Request) -> IronResult { let conn = extension!(req, Pool).get()?; - let release_activity_data: Value = ctry!(conn.query( - "SELECT value FROM config WHERE name = 'release_activity'", - &[] - )) + let activity_data: Value = ctry!( + req, + conn.query( + "SELECT value FROM config WHERE name = 'release_activity'", + &[] + ), + ) .iter() .next() .map_or(Value::Null, |row| row.get("value")); - Page::new(release_activity_data) - .title("Releases") - .set("description", "Monthly release activity") - .set_true("show_releases_navigation") - .set_true("releases_navigation_activity_tab") - .set_true("javascript_highchartjs") - .to_resp("releases_activity") + ReleaseActivity { + description: "Monthly release activity", + activity_data, + } + .into_response(req) } -pub fn build_queue_handler(req: &mut Request) -> IronResult { - let queue = extension!(req, BuildQueue); +#[derive(Debug, Clone, PartialEq, Serialize)] +struct BuildQueuePage { + description: &'static str, + queue: Vec, +} - let mut crates = ctry!(queue.queued_crates()); - for krate in &mut crates { +impl_webpage! { + BuildQueuePage = "releases/build_queue.html", +} + +pub fn build_queue_handler(req: &mut Request) -> IronResult { + let mut queue = ctry!(req, extension!(req, BuildQueue).queued_crates()); + for krate in queue.iter_mut() { // The priority here is inverted: in the database if a crate has a higher priority it // will be built after everything else, which is counter-intuitive for people not // familiar with docs.rs's inner workings. krate.priority = -krate.priority; } - let is_empty = crates.is_empty(); - Page::new(crates) - .title("Build queue") - .set("description", "List of crates scheduled to build") - .set_bool("queue_empty", is_empty) - .set_true("show_releases_navigation") - .set_true("releases_queue_tab") - .to_resp("releases_queue") + BuildQueuePage { + description: "List of crates scheduled to build", + queue, + } + .into_response(req) } #[cfg(test)] diff --git a/src/web/rustdoc.rs b/src/web/rustdoc.rs index a5dc242d0..322c0f972 100644 --- a/src/web/rustdoc.rs +++ b/src/web/rustdoc.rs @@ -1,55 +1,22 @@ //! rustdoc handler -use super::crate_details::CrateDetails; -use super::error::Nope; -use super::file::File; -use super::metrics; -use super::page::Page; -use super::redirect_base; -use super::{match_version, MatchSemver}; -use crate::db::Pool; -use crate::utils; -use crate::Config; -use iron::headers::{CacheControl, CacheDirective, Expires, HttpDate}; -use iron::modifiers::Redirect; -use iron::prelude::*; -use iron::Handler; -use iron::{status, Url}; +use crate::{ + db::Pool, + impl_webpage, utils, + web::{ + crate_details::CrateDetails, error::Nope, file::File, match_version, metrics, + page::WebPage, redirect_base, MatchSemver, + }, + Config, +}; +use iron::{ + headers::{CacheControl, CacheDirective, Expires, HttpDate}, + modifiers::Redirect, + status, Handler, IronError, IronResult, Plugin, Request, Response, Url, +}; use postgres::Connection; use router::Router; -use serde::ser::{Serialize, SerializeStruct, Serializer}; - -#[derive(Debug, Default)] -struct RustdocPage { - head: String, - body: String, - body_class: String, - name: String, - full: String, - version: String, - description: Option, - crate_details: Option, -} - -impl Serialize for RustdocPage { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let mut state = serializer.serialize_struct("RustdocPage", 9)?; - state.serialize_field("rustdoc_head", &self.head)?; - state.serialize_field("rustdoc_body", &self.body)?; - state.serialize_field("rustdoc_body_class", &self.body_class)?; - state.serialize_field("rustdoc_full", &self.full)?; - state.serialize_field("rustdoc_status", &true)?; - state.serialize_field("name", &self.name)?; - state.serialize_field("version", &self.version)?; - state.serialize_field("description", &self.description)?; - state.serialize_field("crate_details", &self.crate_details)?; - - state.end() - } -} +use serde::Serialize; #[derive(Clone)] pub struct RustLangRedirector { @@ -63,6 +30,7 @@ impl RustLangRedirector { .join(target) .expect("failed to append crate name to rust-lang.org base URL"); let url = Url::from_generic_url(url).expect("failed to convert url::Url to iron::Url"); + Self { url } } } @@ -101,7 +69,7 @@ pub fn rustdoc_redirector_handler(req: &mut Request) -> IronResult { url_str.push('?'); url_str.push_str(query); } - let url = ctry!(Url::parse(&url_str[..])); + let url = ctry!(req, Url::parse(&url_str)); let mut resp = Response::with((status::Found, Redirect(url))); resp.headers.set(Expires(HttpDate(time::now()))); @@ -109,9 +77,10 @@ pub fn rustdoc_redirector_handler(req: &mut Request) -> IronResult { } fn redirect_to_crate(req: &Request, name: &str, vers: &str) -> IronResult { - let url = ctry!(Url::parse( - &format!("{}/crate/{}/{}", redirect_base(req), name, vers)[..] - )); + let url = ctry!( + req, + Url::parse(&format!("{}/crate/{}/{}", redirect_base(req), name, vers)), + ); let mut resp = Response::with((status::Found, Redirect(url))); resp.headers.set(Expires(HttpDate(time::now()))); @@ -163,7 +132,7 @@ pub fn rustdoc_redirector_handler(req: &mut Request) -> IronResult { let conn = extension!(req, Pool).get()?; // this handler should never called without crate pattern - let crate_name = cexpect!(router.find("crate")); + let crate_name = cexpect!(req, router.find("crate")); let mut crate_name = percent_decode(crate_name.as_bytes()) .decode_utf8() .unwrap_or_else(|_| crate_name.into()) @@ -190,12 +159,15 @@ pub fn rustdoc_redirector_handler(req: &mut Request) -> IronResult { // get target name and whether it has docs // FIXME: This is a bit inefficient but allowing us to use less code in general let (target_name, has_docs): (String, bool) = { - let rows = ctry!(conn.query( - "SELECT target_name, rustdoc_status - FROM releases - WHERE releases.id = $1", - &[&id] - )); + let rows = ctry!( + req, + conn.query( + "SELECT target_name, rustdoc_status + FROM releases + WHERE releases.id = $1", + &[&id] + ), + ); (rows.get(0).get(0), rows.get(0).get(1)) }; @@ -211,6 +183,22 @@ pub fn rustdoc_redirector_handler(req: &mut Request) -> IronResult { } } +#[derive(Debug, Clone, PartialEq, Serialize)] +struct RustdocPage { + latest_path: String, + latest_version: String, + inner_path: String, + is_latest_version: bool, + rustdoc_head: String, + rustdoc_body: String, + rustdoc_body_class: String, + krate: CrateDetails, +} + +impl_webpage! { + RustdocPage = "rustdoc/page.html", +} + /// Serves documentation generated by rustdoc. /// /// This includes all HTML files for an individual crate, as well as the `search-index.js`, which is @@ -245,7 +233,7 @@ pub fn rustdoc_html_server_handler(req: &mut Request) -> IronResult { vers, path.join("/") ); - let url = ctry!(Url::parse(&redirect_path)); + let url = ctry!(req, Url::parse(&redirect_path)); Ok(super::redirect(url)) }; @@ -285,11 +273,11 @@ pub fn rustdoc_html_server_handler(req: &mut Request) -> IronResult { // Get the crate's details from the database // NOTE: we know this crate must exist because we just checked it above (or else `match_version` is buggy) - let crate_details = cexpect!(CrateDetails::new(&conn, &name, &version)); + let krate = cexpect!(req, CrateDetails::new(&conn, &name, &version)); // if visiting the full path to the default target, remove the target from the path // expects a req_path that looks like `[/:target]/.*` - if req_path.get(0).copied() == Some(&crate_details.metadata.default_target) { + if req_path.get(0).copied() == Some(&krate.metadata.default_target) { return redirect(&name, &version, &req_path[1..]); } @@ -328,21 +316,22 @@ pub fn rustdoc_html_server_handler(req: &mut Request) -> IronResult { rendering_time.step("parse html"); - let file_content = ctry!(String::from_utf8(file.0.content)); + let file_content = ctry!(req, String::from_utf8(file.0.content)); // Extract the head and body of the rustdoc file so that we can insert it into our own html - let (head, body, mut body_class) = ctry!(utils::extract_head_and_body(&file_content)); + let (rustdoc_head, rustdoc_body, mut rustdoc_body_class) = + ctry!(req, utils::extract_head_and_body(&file_content)); // Add the `rustdoc` classes to the html body - if body_class.is_empty() { - body_class = "rustdoc container-rustdoc".to_string(); + if rustdoc_body_class.is_empty() { + rustdoc_body_class = "rustdoc container-rustdoc".to_string(); } else { // rustdoc adds its own "rustdoc" class to the body - body_class.push_str(" container-rustdoc"); + rustdoc_body_class.push_str(" container-rustdoc"); } rendering_time.step("find latest path"); - let latest_release = crate_details.latest_release(); + let latest_release = krate.latest_release(); // Get the latest version of the crate let latest_version = latest_release.version.to_owned(); @@ -362,7 +351,7 @@ pub fn rustdoc_html_server_handler(req: &mut Request) -> IronResult { "/{}/{}/{}", name, latest_version, - path_for_version(&latest_path, &crate_details.doc_targets, &conn, &config) + path_for_version(&latest_path, &krate.doc_targets, &conn, &config) ) } else { format!("/crate/{}/{}", name, latest_version) @@ -377,7 +366,7 @@ pub fn rustdoc_html_server_handler(req: &mut Request) -> IronResult { // Drop the `rustdoc/:crate/:version[/:platform]` prefix inner_path.drain(..3).for_each(drop); - if inner_path.len() > 1 && crate_details.doc_targets.iter().any(|s| s == inner_path[0]) { + if inner_path.len() > 1 && krate.doc_targets.iter().any(|s| s == inner_path[0]) { inner_path.remove(0); } @@ -385,27 +374,17 @@ pub fn rustdoc_html_server_handler(req: &mut Request) -> IronResult { }; // Build the page of documentation - let content = RustdocPage { - head, - body, - body_class, - name, - full: file_content, - version, - crate_details: Some(crate_details), - ..Default::default() - }; - - // Build the page served to the user while setting options for templating - Page::new(content) - .set_true("show_package_navigation") - .set_true("package_navigation_documentation_tab") - .set_true("package_navigation_show_platforms_tab") - .set_bool("is_latest_version", is_latest_version) - .set("latest_path", &latest_path) - .set("latest_version", &latest_version) - .set("inner_path", &inner_path) - .to_resp("rustdoc") + RustdocPage { + latest_path, + latest_version, + inner_path, + is_latest_version, + rustdoc_head, + rustdoc_body, + rustdoc_body_class, + krate, + } + .into_response(req) } /// Checks whether the given path exists. @@ -458,8 +437,8 @@ fn path_for_version( pub fn target_redirect_handler(req: &mut Request) -> IronResult { let router = extension!(req, Router); - let name = cexpect!(router.find("name")); - let version = cexpect!(router.find("version")); + let name = cexpect!(req, router.find("name")); + let version = cexpect!(req, router.find("version")); let conn = extension!(req, Pool).get()?; let config = extension!(req, Config); @@ -496,7 +475,7 @@ pub fn target_redirect_handler(req: &mut Request) -> IronResult { path = path ); - let url = ctry!(Url::parse(&url)); + let url = ctry!(req, Url::parse(&url)); let mut resp = Response::with((status::Found, Redirect(url))); resp.headers.set(Expires(HttpDate(time::now()))); @@ -509,24 +488,27 @@ pub fn badge_handler(req: &mut Request) -> IronResult { use params::{Params, Value}; let version = { - let params = ctry!(req.get_ref::()); + let params = ctry!(req, req.get_ref::()); match params.find(&["version"]) { Some(&Value::String(ref version)) => version.clone(), _ => "*".to_owned(), } }; - let name = cexpect!(extension!(req, Router).find("crate")); + let name = cexpect!(req, extension!(req, Router).find("crate")); let conn = extension!(req, Pool).get()?; let options = match match_version(&conn, &name, Some(&version)).and_then(|m| m.assume_exact()) { Some(MatchSemver::Exact((version, id))) => { - let rows = ctry!(conn.query( - "SELECT rustdoc_status - FROM releases - WHERE releases.id = $1", - &[&id] - )); + let rows = ctry!( + req, + conn.query( + "SELECT rustdoc_status + FROM releases + WHERE releases.id = $1", + &[&id] + ), + ); if !rows.is_empty() && rows.get(0).get(0) { BadgeOptions { subject: "docs".to_owned(), @@ -544,11 +526,11 @@ pub fn badge_handler(req: &mut Request) -> IronResult { Some(MatchSemver::Semver((version, _))) => { let base_url = format!("{}/{}/badge.svg", redirect_base(req), name); - let url = ctry!(iron::url::Url::parse_with_params( - &base_url, - &[("version", version)] - )); - let iron_url = ctry!(Url::from_generic_url(url)); + let url = ctry!( + req, + iron::url::Url::parse_with_params(&base_url, &[("version", version)]), + ); + let iron_url = ctry!(req, Url::from_generic_url(url)); return Ok(super::redirect(iron_url)); } @@ -559,7 +541,7 @@ pub fn badge_handler(req: &mut Request) -> IronResult { }, }; - let mut resp = Response::with((status::Ok, ctry!(Badge::new(options)).to_svg())); + let mut resp = Response::with((status::Ok, ctry!(req, Badge::new(options)).to_svg())); resp.headers .set(ContentType("image/svg+xml".parse().unwrap())); resp.headers.set(Expires(HttpDate(time::now()))); @@ -599,12 +581,9 @@ impl Handler for SharedResourceHandler { #[cfg(test)] mod test { - use super::*; use crate::test::*; - use chrono::Utc; use kuchiki::traits::TendrilSink; use reqwest::StatusCode; - use serde_json::json; use std::{collections::BTreeMap, iter::FromIterator}; fn try_latest_version_redirect( @@ -1381,7 +1360,7 @@ mod test { #[test] fn test_target_redirect_not_found() { - crate::test::wrapper(|env| { + wrapper(|env| { let web = env.frontend(); assert_eq!( web.get("/crate/fdsafdsafdsafdsa/0.1.0/target-redirect/x86_64-apple-darwin/") @@ -1395,7 +1374,7 @@ mod test { #[test] fn test_fully_yanked_crate_404s() { - crate::test::wrapper(|env| { + wrapper(|env| { let db = env.db(); db.fake_release() @@ -1421,7 +1400,7 @@ mod test { #[test] // regression test for https://github.com/rust-lang/docs.rs/issues/856 fn test_no_trailing_slash() { - crate::test::wrapper(|env| { + wrapper(|env| { let db = env.db(); db.fake_release().name("dummy").version("0.1.0").create()?; let web = env.frontend(); @@ -1450,99 +1429,79 @@ mod test { } #[test] - fn serialize_rustdoc_page() { - let time = Utc::now(); - - let details = json!({ - "name": "rcc", - "version": "100.0.0", - "description": null, - "authors": [], - "owners": [], - "authors_json": null, - "dependencies": null, - "release_time": super::super::duration_to_str(time), - "build_status": true, - "last_successful_build": null, - "rustdoc_status": true, - "repository_url": null, - "homepage_url": null, - "keywords": null, - "have_examples": true, - "target_name": "x86_64-unknown-linux-gnu", - "releases": [], - "github": true, - "yanked": false, - "github_stars": null, - "github_forks": null, - "github_issues": null, - "metadata": { - "name": "serde", - "version": "1.0.0", - "description": "serde does stuff", - "target_name": null, - "rustdoc_status": true, - "default_target": "x86_64-unknown-linux-gnu" - }, - "is_library": true, - "doc_targets": [], - "license": null, - "documentation_url": null - }); - - let mut page = RustdocPage { - head: "Whee".to_string(), - body: "

idk

".to_string(), - body_class: "docsrs-body".to_string(), - name: "rcc".to_string(), - full: "??".to_string(), - version: "100.0.100".to_string(), - description: Some("a Rust compiler in C. Wait, maybe the other way around".to_string()), - crate_details: Some(CrateDetails::default_tester(time)), - }; - - let correct_json = json!({ - "rustdoc_head": "Whee", - "rustdoc_body": "

idk

", - "rustdoc_body_class": "docsrs-body", - "rustdoc_full": "??", - "rustdoc_status": true, - "name": "rcc", - "version": "100.0.100", - "description": "a Rust compiler in C. Wait, maybe the other way around", - "crate_details": details - }); + // regression test for https://github.com/rust-lang/docs.rs/pull/885#issuecomment-655147643 + fn test_no_panic_on_missing_kind() { + wrapper(|env| { + let db = env.db(); + let id = db.fake_release().name("strum").version("0.13.0").create()?; + // https://stackoverflow.com/questions/18209625/how-do-i-modify-fields-inside-the-new-postgresql-json-datatype + db.conn().query( + r#"UPDATE releases SET dependencies = dependencies::jsonb #- '{0,2}' WHERE id = $1"#, + &[&id], + )?; + let web = env.frontend(); + assert_success("/strum/0.13.0/strum/", web)?; + assert_success("/crate/strum/0.13.0/", web)?; + Ok(()) + }) + } - assert_eq!(correct_json, serde_json::to_value(&page).unwrap()); - - page.description = None; - let correct_json = json!({ - "rustdoc_head": "Whee", - "rustdoc_body": "

idk

", - "rustdoc_body_class": "docsrs-body", - "rustdoc_full": "??", - "rustdoc_status": true, - "name": "rcc", - "version": "100.0.100", - "description": null, - "crate_details": details - }); + #[test] + // regression test for https://github.com/rust-lang/docs.rs/pull/885#issuecomment-655154405 + fn test_readme_rendered_as_html() { + wrapper(|env| { + let db = env.db(); + let readme = "# Overview"; + db.fake_release() + .name("strum") + .version("0.18.0") + .readme(readme) + .create()?; + let page = kuchiki::parse_html() + .one(env.frontend().get("/crate/strum/0.18.0").send()?.text()?); + let rendered = page.select_first("#main").expect("missing readme"); + println!("{}", rendered.text_contents()); + rendered + .as_node() + .select_first("h1") + .expect("`# Overview` was not rendered as HTML"); + Ok(()) + }) + } - assert_eq!(correct_json, serde_json::to_value(&page).unwrap()); - - page.crate_details = None; - let correct_json = json!({ - "rustdoc_head": "Whee", - "rustdoc_body": "

idk

", - "rustdoc_body_class": "docsrs-body", - "rustdoc_full": "??", - "rustdoc_status": true, - "name": "rcc", - "version": "100.0.100", - "description": null, - "crate_details": null - }); + #[test] + // regression test for https://github.com/rust-lang/docs.rs/pull/885#issuecomment-655149288 + fn test_build_status_is_accurate() { + wrapper(|env| { + let db = env.db(); + db.fake_release() + .name("hexponent") + .version("0.3.0") + .create()?; + db.fake_release() + .name("hexponent") + .version("0.2.0") + .build_result_successful(false) + .create()?; + let web = env.frontend(); - assert_eq!(correct_json, serde_json::to_value(&page).unwrap()); + let status = |version| -> Result<_, failure::Error> { + let page = + kuchiki::parse_html().one(web.get("/crate/hexponent/0.3.0").send()?.text()?); + let selector = format!(r#"ul > li a[href="/crate/hexponent/{}""#, version); + let anchor = page + .select(&selector) + .unwrap() + .find(|a| a.text_contents().trim() == version) + .unwrap(); + let attributes = anchor.as_node().as_element().unwrap().attributes.borrow(); + let classes = attributes.get("class").unwrap(); + Ok(classes.split(' ').all(|c| c != "warn")) + }; + + assert!(status("0.3.0")?); + assert!(!status("0.2.0")?); + Ok(()) + }) } } diff --git a/src/web/sitemap.rs b/src/web/sitemap.rs index ad0a6f1f7..c612528a6 100644 --- a/src/web/sitemap.rs +++ b/src/web/sitemap.rs @@ -67,7 +67,10 @@ impl_webpage!(About = "core/about.html"); pub fn about_handler(req: &mut Request) -> IronResult { let conn = extension!(req, Pool).get()?; - let res = ctry!(conn.query("SELECT value FROM config WHERE name = 'rustc_version'", &[])); + let res = ctry!( + req, + conn.query("SELECT value FROM config WHERE name = 'rustc_version'", &[]), + ); let rustc_version = res.iter().next().and_then(|row| { if let Some(Ok(Value::String(version))) = row.get_opt(0) { diff --git a/src/web/source.rs b/src/web/source.rs index 49bca35e1..912f78c5f 100644 --- a/src/web/source.rs +++ b/src/web/source.rs @@ -1,69 +1,34 @@ //! Source code browser -use super::file::File as DbFile; -use super::page::Page; -use super::MetaData; -use crate::db::Pool; -use crate::Config; -use iron::prelude::*; +use crate::{ + db::Pool, + impl_webpage, + web::{error::Nope, file::File as DbFile, page::WebPage, MetaData}, + Config, +}; +use iron::{status::Status, IronError, IronResult, Request, Response}; use postgres::Connection; use router::Router; -use serde::ser::{Serialize, SerializeStruct, Serializer}; +use serde::Serialize; use serde_json::Value; use std::cmp::Ordering; -use std::collections::HashMap; -/// A source file's type -#[derive(PartialEq, PartialOrd)] -enum FileType { - Dir, - Text, - Binary, - RustSource, -} - -/// A source file -#[derive(PartialEq, PartialOrd)] +/// A source file's name and mime type +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Serialize)] struct File { + /// The name of the file name: String, - file_type: FileType, + /// The mime type of the file + mime: String, } /// A list of source files +#[derive(Debug, Clone, PartialEq, Serialize)] struct FileList { metadata: MetaData, files: Vec, } -impl Serialize for FileList { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let mut state = serializer.serialize_struct("FileList", 2)?; - state.serialize_field("metadata", &self.metadata)?; - - let mut files = Vec::with_capacity(self.files.len()); - for file in &self.files { - let mut map = HashMap::with_capacity(2); - map.insert("name", Value::String(file.name.to_owned())); - - let file_type = match file.file_type { - FileType::Dir => "file_type_dir", - FileType::Text => "file_type_text", - FileType::Binary => "file_type_binary", - FileType::RustSource => "file_type_rust_source", - }; - map.insert(file_type, Value::Bool(true)); - - files.push(map); - } - state.serialize_field("files", &files)?; - - state.end() - } -} - impl FileList { /// Gets FileList from a request path /// @@ -126,19 +91,15 @@ impl FileList { let path_splited: Vec<&str> = path.split('/').collect(); // if path have '/' it is a directory - let ftype = if path_splited.len() > 1 { - FileType::Dir - } else if mime.starts_with("text") && path_splited[0].ends_with(".rs") { - FileType::RustSource - } else if mime.starts_with("text") { - FileType::Text + let mime = if path_splited.len() > 1 { + "dir".to_owned() } else { - FileType::Binary + mime.to_owned() }; let file = File { name: path_splited[0].to_owned(), - file_type: ftype, + mime, }; // avoid adding duplicates, a directory may occur more than once @@ -155,9 +116,9 @@ impl FileList { file_list.sort_by(|a, b| { // directories must be listed first - if a.file_type == FileType::Dir && b.file_type != FileType::Dir { + if a.mime == "dir" && b.mime != "dir" { Ordering::Less - } else if a.file_type != FileType::Dir && b.file_type == FileType::Dir { + } else if a.mime != "dir" && b.mime == "dir" { Ordering::Greater } else { a.name.to_lowercase().cmp(&b.name.to_lowercase()) @@ -181,10 +142,22 @@ impl FileList { } } +#[derive(Debug, Clone, PartialEq, Serialize)] +struct SourcePage { + file_list: FileList, + show_parent_link: bool, + file_content: Option, + is_rust_source: bool, +} + +impl_webpage! { + SourcePage = "crate/source.html", +} + pub fn source_browser_handler(req: &mut Request) -> IronResult { let router = extension!(req, Router); - let name = cexpect!(router.find("name")); - let version = cexpect!(router.find("version")); + let name = cexpect!(req, router.find("name")); + let version = cexpect!(req, router.find("version")); // get path (req_path) for FileList::from_path and actual path for super::file::File::from_path let (req_path, file_path) = { @@ -222,7 +195,7 @@ pub fn source_browser_handler(req: &mut Request) -> IronResult { None }; - let (content, is_rust_source) = if let Some(file) = file { + let (file_content, is_rust_source) = if let Some(file) = file { // serve the file with DatabaseFileHandler if file isn't text and not empty if !file.0.mime.starts_with("text") && !file.is_empty() { return Ok(file.serve()); @@ -238,77 +211,21 @@ pub fn source_browser_handler(req: &mut Request) -> IronResult { (None, false) }; - let list = FileList::from_path(&conn, &name, &version, &req_path); - if list.is_none() { - use super::error::Nope; - use iron::status; - return Err(IronError::new(Nope::NoResults, status::NotFound)); - } - - let page = Page::new(list) - .set_bool("show_parent_link", !req_path.is_empty()) - .set_true("javascript_highlightjs") - .set_true("show_package_navigation") - .set_true("package_source_tab"); + let file_list = FileList::from_path(&conn, &name, &version, &req_path) + .ok_or_else(|| IronError::new(Nope::NoResults, Status::NotFound))?; - if let Some(content) = content { - page.set("file_content", &content) - .set_bool("file_content_rust_source", is_rust_source) - .to_resp("source") - } else { - page.to_resp("source") + SourcePage { + file_list, + show_parent_link: !req_path.is_empty(), + file_content, + is_rust_source, } + .into_response(req) } #[cfg(test)] mod tests { - use super::*; use crate::test::{assert_success, wrapper}; - use serde_json::json; - - #[test] - fn serialize_file_list() { - let file_list = FileList { - metadata: MetaData { - name: "rcc".to_string(), - version: "0.0.0".to_string(), - description: Some("it compiles an unholy language".to_string()), - target_name: None, - rustdoc_status: true, - default_target: "x86_64-unknown-linux-gnu".to_string(), - }, - files: vec![ - File { - name: "main.rs".to_string(), - file_type: FileType::RustSource, - }, - File { - name: "lib.rs".to_string(), - file_type: FileType::RustSource, - }, - ], - }; - - let correct_json = json!({ - "metadata": { - "name": "rcc", - "version": "0.0.0", - "description": "it compiles an unholy language", - "target_name": null, - "rustdoc_status": true, - "default_target": "x86_64-unknown-linux-gnu" - }, - "files": [{ - "name": "main.rs", - "file_type_rust_source": true - }, { - "name": "lib.rs", - "file_type_rust_source": true - }], - }); - - assert_eq!(correct_json, serde_json::to_value(&file_list).unwrap(),); - } #[test] fn cargo_ok_not_skipped() { diff --git a/tera-templates/base.html b/templates/base.html similarity index 100% rename from tera-templates/base.html rename to templates/base.html diff --git a/templates/builds.hbs b/templates/builds.hbs deleted file mode 100644 index 0fefd76d6..000000000 --- a/templates/builds.hbs +++ /dev/null @@ -1,66 +0,0 @@ -{{> header}} - -{{#with content}} -
-
- - {{#if build_details}} -
- Build #{{build_details.id}} {{build_details.build_time}} -
-
$ rustc --version
-{{build_details.rustc_version}}
-$ cratesfyi --version
-{{build_details.cratesfyi_version}}
-$ cratesfyi ...
-{{build_details.output}}
- {{/if}} - -
- Builds -
- - - -
-

{{metadata.name}}'s sandbox limits

- -

- All the builds on docs.rs are executed inside a sandbox with limited - resources. The limits for this crate are the following: -

- - - - {{#each limits}} - - - - - {{/each}} - -
{{{@key}}}{{this}}
-

- If a build fails because it hit one of those limits please - open an issue - to get them increased. -

-
-
-
-{{/with}} - -{{> footer}} diff --git a/tera-templates/core/Cargo.toml.example b/templates/core/Cargo.toml.example similarity index 100% rename from tera-templates/core/Cargo.toml.example rename to templates/core/Cargo.toml.example diff --git a/tera-templates/core/about.html b/templates/core/about.html similarity index 100% rename from tera-templates/core/about.html rename to templates/core/about.html diff --git a/tera-templates/core/home.html b/templates/core/home.html similarity index 99% rename from tera-templates/core/home.html rename to templates/core/home.html index 2ef1435ba..0ce0164c0 100644 --- a/tera-templates/core/home.html +++ b/templates/core/home.html @@ -77,6 +77,7 @@

Docs.rs

if ("key" in ev && typeof ev.key != "undefined") { return ev.key; } + return String.fromCharCode(ev.charCode || ev.keyCode); } @@ -94,6 +95,7 @@

Docs.rs

if (ev.ctrlKey || ev.altKey || ev.metaKey || document.activeElement.tagName === "INPUT") { return; } + switch (getKey(ev)) { case "s": case "S": diff --git a/tera-templates/core/sitemap.xml b/templates/core/sitemap.xml similarity index 100% rename from tera-templates/core/sitemap.xml rename to templates/core/sitemap.xml diff --git a/templates/crate/builds.html b/templates/crate/builds.html new file mode 100644 index 000000000..2982ec495 --- /dev/null +++ b/templates/crate/builds.html @@ -0,0 +1,84 @@ +{%- extends "base.html" -%} +{%- import "header/package_navigation.html" as navigation -%} + +{%- block title -%} + {{ macros::doc_title(name=metadata.name, version=metadata.version) }} +{%- endblock title -%} + +{%- block header -%} + {{ navigation::package_navigation(metadata=metadata, active_tab="builds") }} +{%- endblock header -%} + +{%- block body -%} +
+
+ {# If there is a build log then show it #} + {# TODO: When viewing a build log, show a back button or a hide button #} + {%- if build_details -%} +
+ Build #{{ build_details.id }} {{ build_details.build_time | date(format="%+") }} +
+ + {%- filter dedent -%} +
+                        # rustc version
+                        {{ build_details.rustc_version }}
+                        # docs.rs version
+                        {{ build_details.docsrs_version }}
+
+                        # build log
+                        {{ build_details.output }}
+                    
+ {%- endfilter -%} + {%- endif -%} + +
+ Builds +
+ + + +
+ {# BuildsPage.metadata is an `Option`, so accessing it can fail #} + {%- if metadata -%} +

{{ metadata.name }}'s sandbox limits

+ {%- else -%} +

Sandbox limits

+ {%- endif -%} + +

+ All the builds on docs.rs are executed inside a sandbox with limited + resources. The limits for this crate are the following: +

+ + {{ macros::crate_limits(limits=limits) }} + +

+ If a build fails because it hit one of those limits please + open an issue + to get them increased. +

+
+
+
+{%- endblock body -%} diff --git a/templates/crate/details.html b/templates/crate/details.html new file mode 100644 index 000000000..15a5e8c3f --- /dev/null +++ b/templates/crate/details.html @@ -0,0 +1,177 @@ +{%- extends "base.html" -%} +{%- import "header/package_navigation.html" as navigation -%} + +{%- block title -%} + {{ macros::doc_title(name=details.name, version=details.version) }} +{%- endblock title -%} + +{%- block header -%} + {# Set the active tab to the `crate` tab #} + {{ navigation::package_navigation(metadata=details.metadata, active_tab="crate") }} +{%- endblock header -%} + +{%- block body -%} +
+
+
+
+ +
+
+ +
+ {# If the release is not a library #} + {%- if not details.is_library -%} +
+ {{ details.name }}-{{ details.version }} is not a library. +
+ + {# If the release has been yanked and is a library #} + {%- elif details.yanked -%} +
+ {{ details.name }}-{{ details.version }} has been yanked. +
+ + {# If the build succeeded, isn't yanked and is a library #} + {%- elif details.build_status -%} + {# If there are no docs display a warning #} + {%- if not details.rustdoc_status -%} +
{{ details.name }}-{{ details.version }} doesn't have any documentation.
+ {%- endif -%} + + {# If there's a readme, display it #} + {%- if details.readme -%} + {{ details.readme | safe }} + + {# If there's not a readme then attempt to display the long description #} + {%- elif details.rustdoc -%} + {{ details.rustdoc | safe }} + {%- endif -%} + + {# If the build failed, the release isn't yanked and the release is a library #} + {%- else -%} + {# Display a warning telling the user we failed to build the docs #} +
+ docs.rs failed to build {{ details.name }}-{{ details.version }}
Please check the + build logs and, if you believe this is + docs.rs' fault, open an issue. +
+ + {# If there is one, display the most next most recent successful build #} + {%- if details.last_successful_build -%} + + {%- endif -%} + {%- endif -%} +
+
+
+{%- endblock body -%} + +{%- block css -%} + {{ macros::highlight_css() }} +{%- endblock css -%} + +{%- block javascript -%} + {# Enable and load Rust and TOML syntax highlighting #} + {{ macros::highlight_js(languages=["rust", "ini"]) }} +{% endblock javascript -%} diff --git a/templates/crate/source.html b/templates/crate/source.html new file mode 100644 index 000000000..ea87a06d1 --- /dev/null +++ b/templates/crate/source.html @@ -0,0 +1,105 @@ +{%- extends "base.html" -%} +{%- import "header/package_navigation.html" as navigation -%} + +{%- block title -%} + {{ macros::doc_title(name=file_list.metadata.name, version=file_list.metadata.version) }} +{%- endblock title -%} + +{%- block header -%} + {# Set the active tab to the `source` tab #} + {{ navigation::package_navigation(metadata=file_list.metadata, active_tab="source") }} +{%- endblock header -%} + +{%- block body -%} +
+
+
+
+ +
+
+ + {# If the file has content, then display it in a codeblock #} + {%- if file_content -%} +
+
{{ file_content }}
+
+ {%- endif -%} +
+
+{%- endblock body -%} + +{%- block css -%} + {# Highlight.js CSS #} + {{ macros::highlight_css() }} +{%- endblock css -%} + +{%- block javascript -%} + {# Highlight.js JavaScript #} + {{ macros::highlight_js(languages=["rust", "ini", "markdown"]) }} +{%- endblock javascript -%} diff --git a/templates/crate_details.hbs b/templates/crate_details.hbs deleted file mode 100644 index 1a1385708..000000000 --- a/templates/crate_details.hbs +++ /dev/null @@ -1,105 +0,0 @@ -{{> header}} - - -{{#with content}} -
-
-
-
- -
-
-
- {{#unless is_library}} -
{{name}}-{{version}} is not a library.
- {{else}} - {{#if yanked}} -
{{name}}-{{version}} has been yanked.
- {{else}} - {{#unless build_status}} -
docs.rs failed to build {{name}}-{{version}}
Please check the build logs and, if you believe this is docs.rs' fault, open an issue.
- {{#if last_successful_build}} -
Visit the last successful build: {{name}}-{{last_successful_build}}
- {{/if}} - {{else}} - {{#unless rustdoc_status}} -
{{name}}-{{version}} doesn't have any documentation.
- {{/unless}} - {{/unless}} - {{/if}} - {{/unless}} - {{#if readme}} - {{{readme}}} - {{else}} - {{{rustdoc}}} - {{/if}} -
-
- -
-{{/with}} - - -{{> footer}} diff --git a/templates/error.hbs b/templates/error.hbs deleted file mode 100644 index 5a1ca0840..000000000 --- a/templates/error.hbs +++ /dev/null @@ -1,3 +0,0 @@ -{{> header}} -{{content}} -{{> footer}} diff --git a/templates/error.html b/templates/error.html new file mode 100644 index 000000000..05a5c8dfb --- /dev/null +++ b/templates/error.html @@ -0,0 +1,12 @@ +{%- extends "base.html" -%} + +{%- block header -%} +
+
+

{{ title }}

+
+ {{ message | default(value="") }} +
+
+
+{%- endblock header -%} diff --git a/templates/footer.hbs b/templates/footer.hbs deleted file mode 100644 index 17ca3265c..000000000 --- a/templates/footer.hbs +++ /dev/null @@ -1,5 +0,0 @@ -{{#if varsb.javascript_highlightjs}}{{/if}} - - - - diff --git a/templates/header.hbs b/templates/header.hbs deleted file mode 100644 index d1a001cfd..000000000 --- a/templates/header.hbs +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - {{#if varsb.javascript_highlightjs}} - - - - {{/if}} - {{#if varsb.javascript_highchartjs}} - - {{/if}} - {{#if title}}{{title}} - {{/if}}{{#if content.metadata.name}}{{content.metadata.name}} {{content.metadata.version}} - {{/if}}Docs.rs - - - - {{> navigation}} diff --git a/tera-templates/header/global_alert.html b/templates/header/global_alert.html similarity index 100% rename from tera-templates/header/global_alert.html rename to templates/header/global_alert.html diff --git a/templates/header/package_navigation.html b/templates/header/package_navigation.html new file mode 100644 index 000000000..d832e660b --- /dev/null +++ b/templates/header/package_navigation.html @@ -0,0 +1,99 @@ +{# + The standard package navigation menu + + * `title` A possibly-null string. If it is null, `metadata.name metadata.version` will be used as the title + * `metadata` A non-null instance of the MetaData struct + * `platforms` A possibly-null vector of strings + * `active_tab` A string with one of the following values: + * `crate` + * `source` + * `builds` + + Note: `false` here is acting as a pseudo-null value since you can't directly construct null values + and tera requires all parameters without defaults to be filled +#} +{% macro package_navigation(title=false, metadata, platforms=false, active_tab) %} +
+
+ {# Page title #} +

+ {%- if title -%} + {{ title }} + {%- else -%} + {{ metadata.name }} {{ metadata.version }} + + {%- endif -%} +

+ + {# Page description #} +
+ {%- if metadata.description -%} + {{ metadata.description }} + {%- endif -%} +
+ +
+ {# If there are platforms, show a dropdown with them #} + {%- if platforms -%} + + {%- endif -%} + +
    + {# The partial path of the crate, `:name/:release` #} + {%- set crate_path = metadata.name ~ "/" ~ metadata.version -%} + + {# If docs are built, show a tab for them #} + {%- if metadata.rustdoc_status -%} +
  • + {# The docs tab redirects to the docs, so the tab will never be selected and seen #} + + + Documentation + +
  • + {%- endif -%} + + {# The crate information tab #} +
  • + + Crate + +
  • + + {# The source view tab #} +
  • + + + Source + +
  • + + {# The builds tab #} +
  • + + + Builds + +
  • +
+
+
+
+{% endmacro package_navigation %} diff --git a/tera-templates/header/topbar.html b/templates/header/topbar.html similarity index 100% rename from tera-templates/header/topbar.html rename to templates/header/topbar.html diff --git a/templates/macros.html b/templates/macros.html new file mode 100644 index 000000000..0984ea34b --- /dev/null +++ b/templates/macros.html @@ -0,0 +1,122 @@ +{# + Makes the appropriate JS imports for highlighting + * `languages` An array of strings where each is a valid highlight.js language +#} +{% macro highlight_js(languages) %} + {# Load the highlight script #} + + + {# Load the script for each provided language #} + {%- for language in languages -%} + + {%- endfor -%} + + {# Activate highlighting #} + +{% endmacro highlight_js %} + +{# Makes the appropriate CSS imports for highlighting #} +{% macro highlight_css() %} + {# Load the highlighting theme css #} + +{% endmacro highlight_css %} + +{# + Creates a formatted table showing the resource limits of a crate + * `limits` A non-null `Limits` struct +#} +{% macro crate_limits(limits) %} + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Available RAM{{ limits.memory | filesizeformat }}
Maximum rustdoc execution time{{ limits.timeout.secs | timeformat }}
Maximum size of a build log{{ limits.max_log_size | filesizeformat }}
Network access + {%- if limits.networking -%} + allowed + {%- else -%} + blocked + {%- endif -%} +
Maximum number of build targets{{ limits.targets }}
+{% endmacro crate_limits %} + +{# Constructs a title based on the given crate name and version #} +{% macro doc_title(name, version) %} + {%- if name -%} + {{ name }} {{ version | default(value="") }} - Docs.rs + {%- else -%} + Docs.rs + {%- endif -%} +{% endmacro doc_title %} + +{# + Constructs a list of a crate's releases + * `name` The crate's name as a string + * `releases` A list of crate releases where each release has the following fields: + * `version` A string of the release's version + * `yanked` A boolean of the release's yanked status + * `build_status` A boolean of the crate's build status (true for built, false for failed build) +#} +{% macro releases_list(name, releases) %} + {%- for release in releases -%} + {# The url for the release, `/crate/:name/:version` #} + {%- set release_url = "/crate/" ~ name ~ "/" ~ release.version -%} + {# The release's name and version, `:name-:version` #} + {%- set release_name = name ~ "-" ~ release.version -%} + +
  • + {# If the release is yanked but built, display an warning #} + {%- if release.yanked and release.build_status -%} + + {{ release.version }} + + + {# If the release has been yanked and failed to build, display a warning #} + {%- elif release.yanked and not release.build_status -%} + + {{ release.version }} + + + {# If the release failed to build, display a warning #} + {%- elif not release.build_status -%} + + {{ release.version }} + + + {# Otherwise just display the version #} + {%- else -%} + + {{ release.version }} + + {%- endif -%} +
  • + {%- endfor -%} +{% endmacro releases_list %} diff --git a/templates/navigation.hbs b/templates/navigation.hbs deleted file mode 100644 index eefaa13d8..000000000 --- a/templates/navigation.hbs +++ /dev/null @@ -1,93 +0,0 @@ - - - {{#unless varsb.hide_package_navigation}} -
    -
    -

    - {{#if title}} - {{title}} - {{else}} - {{content.metadata.name}} {{content.metadata.version}} - - {{/if}} -

    -
    {{#if content.metadata.description }}{{content.metadata.description}}{{else}}{{varss.description}}{{/if}}
    - - {{#if ../varsb.show_package_navigation}} -
    - {{#if ../varsb.package_navigation_show_platforms_tab}} - - {{/if}} - {{#with content.metadata}} - - {{/with}} -
    - {{/if}} - - {{#if varsb.show_releases_navigation}} -
    - -
    - {{/if}} -
    -
    - {{/unless}} diff --git a/templates/navigation_global_alert.hbs b/templates/navigation_global_alert.hbs deleted file mode 100644 index 428bb02c3..000000000 --- a/templates/navigation_global_alert.hbs +++ /dev/null @@ -1,9 +0,0 @@ -{{#if ../has_global_alert}} -
  • - - - {{../global_alert.text}} - -
  • -{{/if}} - diff --git a/templates/navigation_rustdoc.hbs b/templates/navigation_rustdoc.hbs deleted file mode 100644 index f17aedbdc..000000000 --- a/templates/navigation_rustdoc.hbs +++ /dev/null @@ -1,139 +0,0 @@ - diff --git a/templates/releases/activity.html b/templates/releases/activity.html new file mode 100644 index 000000000..ea11f1e62 --- /dev/null +++ b/templates/releases/activity.html @@ -0,0 +1,59 @@ +{%- extends "base.html" -%} +{%- import "releases/header.html" as release_macros -%} + +{%- block title -%}Releases - Docs.rs{%- endblock title -%} + +{%- block header -%} + {{ release_macros::header(title="Releases", description=description, tab="activity") }} +{%- endblock header -%} + +{%- block body -%} +
    +
    +
    +{%- endblock body -%} + +{# TODO: Do this with tera alone #} +{%- block javascript -%} + + + +{%- endblock javascript -%} diff --git a/templates/releases/build_queue.html b/templates/releases/build_queue.html new file mode 100644 index 000000000..37366aea9 --- /dev/null +++ b/templates/releases/build_queue.html @@ -0,0 +1,38 @@ +{%- extends "base.html" -%} +{%- import "releases/header.html" as release_macros -%} + +{%- block title -%}Build Queue - Docs.rs{%- endblock title -%} + +{%- block header -%} + {{ release_macros::header(title="Build Queue", description=description, tab="queue") }} +{%- endblock header -%} + +{%- block body -%} +
    +
    + +
    + {% set queue_length = queue | length -%} + {%- if queue_length == 0 -%} + There is nothing in the queue + {%- else -%} + Queue + {%- endif %} +
    + +
      + {% for crate in queue -%} +
    1. + + {{ crate.name }} {{ crate.version }} + + + {% if crate.priority != 0 -%} + (priority: {{ crate.priority }}) + {%- endif %} +
    2. + {%- endfor %} +
    +
    +
    +{%- endblock body -%} diff --git a/tera-templates/releases/feed.xml b/templates/releases/feed.xml similarity index 100% rename from tera-templates/releases/feed.xml rename to templates/releases/feed.xml diff --git a/tera-templates/releases/header.html b/templates/releases/header.html similarity index 100% rename from tera-templates/releases/header.html rename to templates/releases/header.html diff --git a/tera-templates/releases/releases.html b/templates/releases/releases.html similarity index 64% rename from tera-templates/releases/releases.html rename to templates/releases/releases.html index 5f6ab863a..e4c745f4b 100644 --- a/tera-templates/releases/releases.html +++ b/templates/releases/releases.html @@ -87,30 +87,59 @@ return String.fromCharCode(ev.charCode || ev.keyCode); } - document.getElementById("i-am-feeling-lucky-button").onclick = function () { - var form = document.getElementsByClassName("landing-search-form"); - var input = document.createElement('input'); - input.type = 'hidden'; - input.name = 'i-am-feeling-lucky'; - input.value = 1; - document.getElementsByClassName("landing-search-form")[0].appendChild(input); - return true; - }; - - function handleShortcut(ev) { + var active = null; + function handleKey(ev) { if (ev.ctrlKey || ev.altKey || ev.metaKey || document.activeElement.tagName === "INPUT") { return; } - switch (getKey(ev)) { - case "s": - case "S": - ev.preventDefault(); - document.getElementById("search").focus(); - break; + + if (ev.which === 40) { // Down arrow + ev.preventDefault(); + if (active === null) { + active = document.getElementsByClassName("recent-releases-container")[0].getElementsByTagName("li")[0]; + } else if (active.nextElementSibling) { + active.classList.remove("selected"); + active = active.nextElementSibling; + } + active.classList.add("selected"); + } else if (ev.which === 38) { // Up arrow + ev.preventDefault(); + if (active === null) { + active = document.getElementsByClassName("recent-releases-container")[0].getElementsByTagName("li")[0]; + } else if (active.previousElementSibling) { + active.classList.remove("selected"); + active = active.previousElementSibling; + } + active.classList.add("selected"); + active.focus(); + } else if (ev.which === 13) { // Return + if (active !== null) { + document.location.href = active.getElementsByTagName("a")[0].href; + } + } else { + switch (getKey(ev)) { + case "s": + case "S": + ev.preventDefault(); + document.getElementsByClassName("search-input-nav")[0].focus(); + break; + } } } - document.onkeypress = handleShortcut; - document.onkeydown = handleShortcut; + document.onkeypress = handleKey; + document.onkeydown = handleKey; + + var crates = Array.prototype.slice.call(document.getElementsByClassName("recent-releases-container")[0].getElementsByTagName("li")); + for (var i = 0; i < crates.length; ++i) { + crates[i].addEventListener("mouseover", function (event) { + this.classList.remove("selected"); + active = null; + }); + crates[i].addEventListener("mouseout", function (event) { + this.classList.remove("selected"); + active = null; + }); + } {%- endblock javascript -%} diff --git a/templates/releases_activity.hbs b/templates/releases_activity.hbs deleted file mode 100644 index c82617b1a..000000000 --- a/templates/releases_activity.hbs +++ /dev/null @@ -1,41 +0,0 @@ -{{> header}} -{{#with content}} - -
    -
    -
    - - -{{/with}} -{{> footer}} diff --git a/templates/releases_queue.hbs b/templates/releases_queue.hbs deleted file mode 100644 index 438f997e3..000000000 --- a/templates/releases_queue.hbs +++ /dev/null @@ -1,29 +0,0 @@ -{{> header}} - -
    -
    - -
    - {{#if varsb.queue_empty}} - There is nothing in queue - {{else}} - Queue - {{/if}} -
    - -
      - {{#each content}} -
    1. - - {{this.name}} {{this.version}} - - {{#if this.priority}} - (priority: {{this.priority}}) - {{/if}} -
    2. - {{/each}} -
    -
    -
    - -{{> footer}} diff --git a/templates/rustdoc.hbs b/templates/rustdoc.hbs deleted file mode 100644 index f39289c3d..000000000 --- a/templates/rustdoc.hbs +++ /dev/null @@ -1,34 +0,0 @@ - - - - {{{content.rustdoc_head}}} - - - - - - - -{{> navigation_rustdoc}} -
    - {{{content.rustdoc_body}}} -
    - - - - diff --git a/templates/rustdoc/navigation.html b/templates/rustdoc/navigation.html new file mode 100644 index 000000000..690ee27ec --- /dev/null +++ b/templates/rustdoc/navigation.html @@ -0,0 +1,220 @@ +{%- import "macros.html" as macros -%} + +{# The url of the current release, `/crate/:name/:version` #} +{%- set crate_url = "/crate/" ~ krate.name ~ "/" ~ krate.version -%} + + diff --git a/templates/rustdoc/page.html b/templates/rustdoc/page.html new file mode 100644 index 000000000..1720a9806 --- /dev/null +++ b/templates/rustdoc/page.html @@ -0,0 +1,50 @@ +{%- import "macros.html" as macros -%} + + + + + + {# Include any head that rustdoc requires #} + {{ rustdoc_head | safe }} + + + + + + + + {{ macros::doc_title(name=krate.name, version=krate.version) }} + + + + {%- include "rustdoc/navigation.html" -%} + + {# Make the body of the page the documentation generated by rustdoc #} +
    + {{ rustdoc_body | safe }} +
    + + + + + + + diff --git a/templates/source.hbs b/templates/source.hbs deleted file mode 100644 index 8ab5a72ad..000000000 --- a/templates/source.hbs +++ /dev/null @@ -1,38 +0,0 @@ -{{> header}} - - -{{#with content}} -
    -
    - - {{#if ../varss.file_content}} -
    -
    {{ ../varss.file_content }}
    -
    - {{/if}} -
    -
    -{{/with}} - -{{> footer}} diff --git a/tera-templates/macros.html b/tera-templates/macros.html deleted file mode 100644 index 7aa586290..000000000 --- a/tera-templates/macros.html +++ /dev/null @@ -1,68 +0,0 @@ -{# - Makes the appropriate JS imports for highlighting - * `languages` An array of strings where each is a valid highlight.js language -#} -{% macro highlight_js(languages) %} - {# Load the highlight script #} - - - {# Load the script for each provided language #} - {%- for language in languages -%} - - {%- endfor -%} - - {# Activate highlighting #} - -{% endmacro highlight_js %} - -{# Makes the appropriate CSS imports for highlighting #} -{% macro highlight_css() %} - {# Load the highlighting theme css #} - -{% endmacro highlight_css %} - -{# - Creates a formatted table showing the resource limits of a crate - * `limits` A non-null `Limits` struct -#} -{% macro crate_limits(limits) %} - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Available RAM{{ limits.memory | filesizeformat }}
    Maximum rustdoc execution time{{ limits.timeout.secs | timeformat }}
    Maximum size of a build log{{ limits.max_log_size | filesizeformat }}
    Network access - {%- if limits.networking -%} - allowed - {%- else -%} - blocked - {%- endif -%} -
    Maximum number of build targets{{ limits.targets }}
    -{% endmacro crate_limits %}