From 3c92d36d25311e5784c3bd64d70ccfbfb4b15474 Mon Sep 17 00:00:00 2001 From: Chase Wilson Date: Fri, 3 Jul 2020 11:33:49 -0500 Subject: [PATCH 01/30] Builds --- src/web/builds.rs | 263 +++--------------- templates/builds.hbs | 66 ----- tera-templates/crate/builds.html | 83 ++++++ tera-templates/header/package_navigation.html | 99 +++++++ tera-templates/macros.html | 9 + 5 files changed, 228 insertions(+), 292 deletions(-) delete mode 100644 templates/builds.hbs create mode 100644 tera-templates/crate/builds.html create mode 100644 tera-templates/header/package_navigation.html diff --git a/src/web/builds.rs b/src/web/builds.rs index 1e0fe9934..b8ec7176a 100644 --- a/src/web/builds.rs +++ b/src/web/builds.rs @@ -1,62 +1,39 @@ -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, - build_details: Option, + build_log: Option, 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 { @@ -88,24 +65,24 @@ pub fn build_list_handler(req: &mut Request) -> IronResult { &[&name, &version] )); - let mut build_details = None; + let mut build_log = 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 { - build_details = Some(build.clone()); + build_log = Some(build.clone()); } build @@ -113,19 +90,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 +104,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, - build_details, + builds, + build_log, 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/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/crate/builds.html b/tera-templates/crate/builds.html new file mode 100644 index 000000000..9f6fe8ade --- /dev/null +++ b/tera-templates/crate/builds.html @@ -0,0 +1,83 @@ +{%- 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_log -%} +
+ Build #{{ build_log.id }} {{ build_log.build_time | date(format="%+") }} +
+ + {%- filter dedent -%} +
+                        $ rustc --version
+                        {{ build_log.rustc_version }}
+                        $ docsrs --version
+                        {{ build_log.docsrs_version }}
+                        $ docsrs ...
+                        {{ build_log.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/tera-templates/header/package_navigation.html b/tera-templates/header/package_navigation.html new file mode 100644 index 000000000..d832e660b --- /dev/null +++ b/tera-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/macros.html b/tera-templates/macros.html index 7aa586290..b9b18d6d2 100644 --- a/tera-templates/macros.html +++ b/tera-templates/macros.html @@ -66,3 +66,12 @@ {% 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 %} From d83cccd41b04e1d899258f6d80fed28c31698ee5 Mon Sep 17 00:00:00 2001 From: Chase Wilson Date: Fri, 3 Jul 2020 12:28:15 -0500 Subject: [PATCH 02/30] Error, ctry! and cexpect! --- src/docbuilder/limits.rs | 100 +---------------------------------- src/web/builds.rs | 17 +++--- src/web/crate_details.rs | 14 +++-- src/web/error.rs | 45 +++++++++------- src/web/metrics.rs | 12 ++--- src/web/mod.rs | 103 ++++++++++++++++++++++++++----------- src/web/page/handlebars.rs | 6 --- src/web/releases.rs | 85 +++++++++++++++++------------- src/web/rustdoc.rs | 71 +++++++++++++------------ src/web/sitemap.rs | 5 +- src/web/source.rs | 4 +- templates/error.hbs | 3 -- tera-templates/error.html | 12 +++++ 13 files changed, 231 insertions(+), 246 deletions(-) delete mode 100644 templates/error.hbs create mode 100644 tera-templates/error.html diff --git a/src/docbuilder/limits.rs b/src/docbuilder/limits.rs index 39d3cba95..06b8b6b02 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/web/builds.rs b/src/web/builds.rs index b8ec7176a..243ed7b98 100644 --- a/src/web/builds.rs +++ b/src/web/builds.rs @@ -38,15 +38,17 @@ impl_webpage! { 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, @@ -62,8 +64,9 @@ 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_log = None; // FIXME: getting builds.output may cause performance issues when release have tons of builds diff --git a/src/web/crate_details.rs b/src/web/crate_details.rs index 4e5ff15a9..130b9bf22 100644 --- a/src/web/crate_details.rs +++ b/src/web/crate_details.rs @@ -356,7 +356,7 @@ fn map_to_release(conn: &Connection, crate_id: i32, version: String) -> Release 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()?; @@ -372,9 +372,15 @@ pub fn crate_details_handler(req: &mut Request) -> IronResult { .to_resp("crate_details") } 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)) } diff --git a/src/web/error.rs b/src/web/error.rs index 2c9060c36..5c6f55a6a 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".into(), + 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".into(), + 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".into(), + 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..7a949b471 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: ::std::borrow::Cow::Borrowed("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: ::std::borrow::Cow::Borrowed("Internal Server Error"), + message: None, + status: ::iron::status::BadRequest, + }; + + return $crate::web::page::WebPage::into_response(error, $req); } } }; @@ -44,7 +58,7 @@ macro_rules! cexpect { /// Gets an extension from Request macro_rules! extension { ($req:expr, $ext:ty) => { - cexpect!($req.extensions.get::<$ext>()) + cexpect!($req, $req.extensions.get::<$ext>()) }; } @@ -60,23 +74,26 @@ 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 @@ -557,9 +574,10 @@ 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)) } @@ -606,6 +624,31 @@ impl MetaData { } } +#[derive(Debug, Clone, PartialEq, Serialize)] +pub(crate) struct ErrorPage { + /// The title of the page + pub title: Cow<'static, str>, + /// The error message, displayed as a description + pub message: Option>, + #[serde(skip)] + pub status: Status, +} + +impl Default for ErrorPage { + fn default() -> Self { + Self { + title: Cow::Borrowed(""), + message: None, + status: Status::NotFound, + } + } +} + +impl_webpage! { + ErrorPage = "error.html", + status = |err| err.status, +} + #[cfg(test)] mod test { use super::*; diff --git a/src/web/page/handlebars.rs b/src/web/page/handlebars.rs index c493a8f8e..7975fd9f5 100644 --- a/src/web/page/handlebars.rs +++ b/src/web/page/handlebars.rs @@ -89,12 +89,6 @@ impl Page { 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(); diff --git a/src/web/releases.rs b/src/web/releases.rs index 33f25fa46..d8fb5736e 100644 --- a/src/web/releases.rs +++ b/src/web/releases.rs @@ -456,10 +456,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 +472,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 +540,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 +554,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 +565,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 +601,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 +618,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))); @@ -653,10 +663,13 @@ pub fn search_handler(req: &mut Request) -> IronResult { 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 release_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")); @@ -673,7 +686,7 @@ pub fn activity_handler(req: &mut Request) -> IronResult { pub fn build_queue_handler(req: &mut Request) -> IronResult { let queue = extension!(req, BuildQueue); - let mut crates = ctry!(queue.queued_crates()); + let mut crates = ctry!(req, queue.queued_crates()); for krate in &mut crates { // 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 diff --git a/src/web/rustdoc.rs b/src/web/rustdoc.rs index a5dc242d0..07f7df170 100644 --- a/src/web/rustdoc.rs +++ b/src/web/rustdoc.rs @@ -101,7 +101,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 +109,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 +164,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 +191,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)) }; @@ -245,7 +249,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,7 +289,7 @@ 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 crate_details = 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]/.*` @@ -328,9 +332,9 @@ 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 (head, body, mut body_class) = ctry!(req, utils::extract_head_and_body(&file_content)); // Add the `rustdoc` classes to the html body if body_class.is_empty() { @@ -458,8 +462,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 +500,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 +513,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 +551,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 +566,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()))); 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..cada08144 100644 --- a/src/web/source.rs +++ b/src/web/source.rs @@ -183,8 +183,8 @@ impl FileList { 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) = { 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/tera-templates/error.html b/tera-templates/error.html new file mode 100644 index 000000000..05a5c8dfb --- /dev/null +++ b/tera-templates/error.html @@ -0,0 +1,12 @@ +{%- extends "base.html" -%} + +{%- block header -%} +
+
+

{{ title }}

+
+ {{ message | default(value="") }} +
+
+
+{%- endblock header -%} From 5ee117825cde880f81fdb3b6b6969c2630f04cc7 Mon Sep 17 00:00:00 2001 From: Chase Wilson Date: Fri, 3 Jul 2020 14:55:21 -0500 Subject: [PATCH 03/30] Crate Details --- src/web/crate_details.rs | 95 +++------- templates/crate_details.hbs | 105 ----------- tera-templates/crate/details.html | 303 ++++++++++++++++++++++++++++++ 3 files changed, 330 insertions(+), 173 deletions(-) delete mode 100644 templates/crate_details.hbs create mode 100644 tera-templates/crate/details.html diff --git a/src/web/crate_details.rs b/src/web/crate_details.rs index 130b9bf22..58ca43c2c 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)] @@ -353,6 +305,15 @@ fn map_to_release(conn: &Connection, crate_id: i32, version: String) -> Release } } +#[derive(Debug, Clone, PartialEq, Serialize)] +struct CrateDetailsPage { + details: Option, +} + +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 @@ -365,12 +326,9 @@ pub fn crate_details_handler(req: &mut Request) -> IronResult { Some(MatchSemver::Exact((version, _))) => { let details = 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!( req, @@ -384,6 +342,7 @@ pub fn crate_details_handler(req: &mut Request) -> IronResult { Ok(super::redirect(url)) } + None => Err(IronError::new(Nope::CrateNotFound, status::NotFound)), } } 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/tera-templates/crate/details.html b/tera-templates/crate/details.html new file mode 100644 index 000000000..162ad149f --- /dev/null +++ b/tera-templates/crate/details.html @@ -0,0 +1,303 @@ +{%- 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 docs #} + {%- 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 -%} From 884b8aa133541a49e0e7e51baa044453c0e7bcc8 Mon Sep 17 00:00:00 2001 From: Chase Wilson Date: Fri, 3 Jul 2020 15:49:48 -0500 Subject: [PATCH 04/30] Source --- src/web/source.rs | 165 ++++++++----------------------- templates/source.hbs | 38 ------- tera-templates/crate/source.html | 105 ++++++++++++++++++++ 3 files changed, 146 insertions(+), 162 deletions(-) delete mode 100644 templates/source.hbs create mode 100644 tera-templates/crate/source.html diff --git a/src/web/source.rs b/src/web/source.rs index cada08144..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,6 +142,18 @@ 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!(req, router.find("name")); @@ -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/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/crate/source.html b/tera-templates/crate/source.html new file mode 100644 index 000000000..ea87a06d1 --- /dev/null +++ b/tera-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 -%} From efac6855d3145c74c0f9fab3fc03d1636ba4a756 Mon Sep 17 00:00:00 2001 From: Chase Wilson Date: Fri, 3 Jul 2020 16:09:01 -0500 Subject: [PATCH 05/30] Rustdoc --- src/web/rustdoc.rs | 231 ++++++----------------- templates/navigation_rustdoc.hbs | 139 -------------- templates/rustdoc.hbs | 34 ---- tera-templates/rustdoc/navigation.html | 248 +++++++++++++++++++++++++ tera-templates/rustdoc/page.html | 56 ++++++ 5 files changed, 357 insertions(+), 351 deletions(-) delete mode 100644 templates/navigation_rustdoc.hbs delete mode 100644 templates/rustdoc.hbs create mode 100644 tera-templates/rustdoc/navigation.html create mode 100644 tera-templates/rustdoc/page.html diff --git a/src/web/rustdoc.rs b/src/web/rustdoc.rs index 07f7df170..ef982df3d 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 } } } @@ -215,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 @@ -289,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!(req, 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..]); } @@ -334,19 +318,20 @@ pub fn rustdoc_html_server_handler(req: &mut Request) -> IronResult { 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!(req, 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(); @@ -366,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) @@ -381,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); } @@ -389,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. @@ -606,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( @@ -1455,101 +1427,4 @@ mod test { Ok(()) }) } - - #[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 - }); - - 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 - }); - - 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 - }); - - assert_eq!(correct_json, serde_json::to_value(&page).unwrap()); - } } 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/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/tera-templates/rustdoc/navigation.html b/tera-templates/rustdoc/navigation.html new file mode 100644 index 000000000..d87346608 --- /dev/null +++ b/tera-templates/rustdoc/navigation.html @@ -0,0 +1,248 @@ +{# The url of the current release, `/crate/:name/:version` #} +{%- set crate_url = "/crate/" ~ krate.name ~ "/" ~ krate.version -%} + + diff --git a/tera-templates/rustdoc/page.html b/tera-templates/rustdoc/page.html new file mode 100644 index 000000000..1cac66fd3 --- /dev/null +++ b/tera-templates/rustdoc/page.html @@ -0,0 +1,56 @@ +{%- import "macros.html" as macros -%} + + + + + + {# Include any head that rustdoc requires #} + {{ rustdoc_head | safe }} + + + + + + + + {# Highlight.js CSS #} + {{ macros::highlight_css() }} + + {{ 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 }} +
+ + + + + + + {# Highlight.js JavaScript #} + {{ macros::highlight_js(languages=["rust", "ini"]) }} + + From f68d7be6f9b49d35d330130a262dcb1a1ffca4cb Mon Sep 17 00:00:00 2001 From: Chase Wilson Date: Fri, 3 Jul 2020 16:16:39 -0500 Subject: [PATCH 06/30] Release Activity --- src/web/releases.rs | 25 ++++++++---- templates/releases_activity.hbs | 41 ------------------- tera-templates/releases/activity.html | 57 +++++++++++++++++++++++++++ 3 files changed, 74 insertions(+), 49 deletions(-) delete mode 100644 templates/releases_activity.hbs create mode 100644 tera-templates/releases/activity.html diff --git a/src/web/releases.rs b/src/web/releases.rs index d8fb5736e..2589f77dc 100644 --- a/src/web/releases.rs +++ b/src/web/releases.rs @@ -22,6 +22,7 @@ use postgres::Connection; use router::Router; use serde::Serialize; use serde_json::Value; +use std::borrow::Cow; /// Number of release in home page const RELEASES_IN_HOME: i64 = 15; @@ -661,9 +662,19 @@ pub fn search_handler(req: &mut Request) -> IronResult { } } +#[derive(Debug, Clone, PartialEq, Serialize)] +struct ReleaseActivity { + description: Cow<'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!( + let activity_data: Value = ctry!( req, conn.query( "SELECT value FROM config WHERE name = 'release_activity'", @@ -674,13 +685,11 @@ pub fn activity_handler(req: &mut Request) -> IronResult { .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: Cow::Borrowed("Monthly release activity"), + activity_data, + } + .into_response(req) } pub fn build_queue_handler(req: &mut Request) -> IronResult { 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/tera-templates/releases/activity.html b/tera-templates/releases/activity.html new file mode 100644 index 000000000..a8e846d2c --- /dev/null +++ b/tera-templates/releases/activity.html @@ -0,0 +1,57 @@ +{%- 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 -%} From cd53bc799f1b6be7b8c4ce810203a3b76188280b Mon Sep 17 00:00:00 2001 From: Chase Wilson Date: Fri, 3 Jul 2020 16:34:15 -0500 Subject: [PATCH 07/30] Build Queue --- src/build_queue.rs | 2 +- src/web/releases.rs | 37 +++---- templates/releases_queue.hbs | 29 ------ tera-templates/releases/build_queue.html | 126 +++++++++++++++++++++++ 4 files changed, 146 insertions(+), 48 deletions(-) delete mode 100644 templates/releases_queue.hbs create mode 100644 tera-templates/releases/build_queue.html 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/web/releases.rs b/src/web/releases.rs index 2589f77dc..04fd93205 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}; @@ -692,25 +688,30 @@ pub fn activity_handler(req: &mut Request) -> IronResult { .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: Cow<'static, str>, + queue: Vec, +} + +impl_webpage! { + BuildQueuePage = "releases/build_queue.html", +} - let mut crates = ctry!(req, queue.queued_crates()); - for krate in &mut crates { +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: Cow::Borrowed("List of crates scheduled to build"), + queue, + } + .into_response(req) } #[cfg(test)] 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/tera-templates/releases/build_queue.html b/tera-templates/releases/build_queue.html new file mode 100644 index 000000000..b92857109 --- /dev/null +++ b/tera-templates/releases/build_queue.html @@ -0,0 +1,126 @@ +{%- 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 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 -%} + +{%- block javascript -%} + +{%- endblock javascript -%} From efdb5fcba13ef44e30c4041a93ba095d7d53d371 Mon Sep 17 00:00:00 2001 From: Chase Wilson Date: Fri, 3 Jul 2020 16:41:40 -0500 Subject: [PATCH 08/30] Deleted Handlebars and renamed tera-templates to templates --- Cargo.lock | 110 ---------- Cargo.toml | 1 - dockerfiles/Dockerfile | 1 - src/web/mod.rs | 28 +-- src/web/page/handlebars.rs | 190 ------------------ src/web/page/mod.rs | 2 - src/web/page/templates.rs | 4 +- {tera-templates => templates}/base.html | 0 .../core/Cargo.toml.example | 0 {tera-templates => templates}/core/about.html | 0 {tera-templates => templates}/core/home.html | 0 .../core/sitemap.xml | 0 .../crate/builds.html | 0 .../crate/details.html | 0 .../crate/source.html | 0 {tera-templates => templates}/error.html | 0 templates/footer.hbs | 5 - templates/header.hbs | 27 --- .../header/global_alert.html | 0 .../header/package_navigation.html | 0 .../header/topbar.html | 0 {tera-templates => templates}/macros.html | 0 templates/navigation.hbs | 93 --------- templates/navigation_global_alert.hbs | 9 - .../releases/activity.html | 0 .../releases/build_queue.html | 0 .../releases/feed.xml | 0 .../releases/header.html | 0 .../releases/releases.html | 0 .../rustdoc/navigation.html | 0 .../rustdoc/page.html | 0 31 files changed, 5 insertions(+), 465 deletions(-) delete mode 100644 src/web/page/handlebars.rs rename {tera-templates => templates}/base.html (100%) rename {tera-templates => templates}/core/Cargo.toml.example (100%) rename {tera-templates => templates}/core/about.html (100%) rename {tera-templates => templates}/core/home.html (100%) rename {tera-templates => templates}/core/sitemap.xml (100%) rename {tera-templates => templates}/crate/builds.html (100%) rename {tera-templates => templates}/crate/details.html (100%) rename {tera-templates => templates}/crate/source.html (100%) rename {tera-templates => templates}/error.html (100%) delete mode 100644 templates/footer.hbs delete mode 100644 templates/header.hbs rename {tera-templates => templates}/header/global_alert.html (100%) rename {tera-templates => templates}/header/package_navigation.html (100%) rename {tera-templates => templates}/header/topbar.html (100%) rename {tera-templates => templates}/macros.html (100%) delete mode 100644 templates/navigation.hbs delete mode 100644 templates/navigation_global_alert.hbs rename {tera-templates => templates}/releases/activity.html (100%) rename {tera-templates => templates}/releases/build_queue.html (100%) rename {tera-templates => templates}/releases/feed.xml (100%) rename {tera-templates => templates}/releases/header.html (100%) rename {tera-templates => templates}/releases/releases.html (100%) rename {tera-templates => templates}/rustdoc/navigation.html (100%) rename {tera-templates => templates}/rustdoc/page.html (100%) 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/web/mod.rs b/src/web/mod.rs index 7a949b471..02e0ba78b 100644 --- a/src/web/mod.rs +++ b/src/web/mod.rs @@ -78,7 +78,6 @@ 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::{ self, headers::{CacheControl, CacheDirective, ContentType, Expires, HttpDate}, @@ -104,17 +103,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, @@ -125,11 +113,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 } @@ -584,7 +570,7 @@ fn ico_handler(req: &mut Request) -> IronResult { } /// 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, @@ -652,10 +638,7 @@ impl_webpage! { #[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; @@ -864,11 +847,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 7975fd9f5..000000000 --- a/src/web/page/handlebars.rs +++ /dev/null @@ -1,190 +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 - } - - #[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/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/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 100% rename from tera-templates/core/home.html rename to templates/core/home.html 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/tera-templates/crate/builds.html b/templates/crate/builds.html similarity index 100% rename from tera-templates/crate/builds.html rename to templates/crate/builds.html diff --git a/tera-templates/crate/details.html b/templates/crate/details.html similarity index 100% rename from tera-templates/crate/details.html rename to templates/crate/details.html diff --git a/tera-templates/crate/source.html b/templates/crate/source.html similarity index 100% rename from tera-templates/crate/source.html rename to templates/crate/source.html diff --git a/tera-templates/error.html b/templates/error.html similarity index 100% rename from tera-templates/error.html rename to templates/error.html 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/tera-templates/header/package_navigation.html b/templates/header/package_navigation.html similarity index 100% rename from tera-templates/header/package_navigation.html rename to templates/header/package_navigation.html 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/tera-templates/macros.html b/templates/macros.html similarity index 100% rename from tera-templates/macros.html rename to templates/macros.html 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/tera-templates/releases/activity.html b/templates/releases/activity.html similarity index 100% rename from tera-templates/releases/activity.html rename to templates/releases/activity.html diff --git a/tera-templates/releases/build_queue.html b/templates/releases/build_queue.html similarity index 100% rename from tera-templates/releases/build_queue.html rename to templates/releases/build_queue.html 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 100% rename from tera-templates/releases/releases.html rename to templates/releases/releases.html diff --git a/tera-templates/rustdoc/navigation.html b/templates/rustdoc/navigation.html similarity index 100% rename from tera-templates/rustdoc/navigation.html rename to templates/rustdoc/navigation.html diff --git a/tera-templates/rustdoc/page.html b/templates/rustdoc/page.html similarity index 100% rename from tera-templates/rustdoc/page.html rename to templates/rustdoc/page.html From 377f3838b16e6d46bd947bd4c1eb8c5cfb222fa3 Mon Sep 17 00:00:00 2001 From: Chase Wilson Date: Fri, 3 Jul 2020 21:42:24 -0600 Subject: [PATCH 09/30] Don't let release activity panic with zero releases --- src/web/crate_details.rs | 122 ------------------------------- templates/releases/activity.html | 12 +-- test.txt | 0 3 files changed, 7 insertions(+), 127 deletions(-) create mode 100644 test.txt diff --git a/src/web/crate_details.rs b/src/web/crate_details.rs index 58ca43c2c..47bb23ba7 100644 --- a/src/web/crate_details.rs +++ b/src/web/crate_details.rs @@ -238,48 +238,6 @@ 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 { @@ -351,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, @@ -598,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/templates/releases/activity.html b/templates/releases/activity.html index a8e846d2c..ea11f1e62 100644 --- a/templates/releases/activity.html +++ b/templates/releases/activity.html @@ -30,9 +30,11 @@ }, xAxis: { categories: [ - {% for date in activity_data.dates %} - {{ "'" ~ date ~ "'," }} - {% endfor %} + {% if activity_data.dates -%} + {%- for date in activity_data.dates -%} + {{ "'" ~ date ~ "'," }} + {%- endfor -%} + {%- endif %} ] }, yAxis: { @@ -47,10 +49,10 @@ }, series: [{ name: 'Releases', - data: [{{ activity_data.counts | join(sep=", ") }}] + data: [{{ activity_data.counts | default(value=[]) | join(sep=", ") }}] }, { name: 'Build Failures', - data: [{{ activity_data.failures | join(sep=", ") }}] + data: [{{ activity_data.failures | default(value=[]) | join(sep=", ") }}] }] }); diff --git a/test.txt b/test.txt new file mode 100644 index 000000000..e69de29bb From f33e06ab4632bcede3f2c779308d079c359a6c29 Mon Sep 17 00:00:00 2001 From: Chase Wilson Date: Fri, 3 Jul 2020 21:47:57 -0600 Subject: [PATCH 10/30] Rustfmt --- src/docbuilder/limits.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/docbuilder/limits.rs b/src/docbuilder/limits.rs index 06b8b6b02..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::{time::Duration}; +use std::time::Duration; #[derive(Debug, Clone, PartialEq, Eq, Serialize)] pub(crate) struct Limits { From f6340fdffe7727f946749bb9e4d36ad4dbc985b1 Mon Sep 17 00:00:00 2001 From: Chase Wilson Date: Sat, 4 Jul 2020 18:34:07 -0600 Subject: [PATCH 11/30] Renamed build_log and made the builds page use comments over shell cmds --- src/web/builds.rs | 8 ++++---- templates/crate/builds.html | 17 +++++++++-------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/web/builds.rs b/src/web/builds.rs index 243ed7b98..f4bc80e6e 100644 --- a/src/web/builds.rs +++ b/src/web/builds.rs @@ -28,7 +28,7 @@ pub(crate) struct Build { struct BuildsPage { metadata: Option, builds: Vec, - build_log: Option, + build_details: Option, limits: Limits, } @@ -68,7 +68,7 @@ pub fn build_list_handler(req: &mut Request) -> IronResult { ) ); - let mut build_log = None; + let mut build_details = None; // FIXME: getting builds.output may cause performance issues when release have tons of builds let mut builds = query .into_iter() @@ -85,7 +85,7 @@ pub fn build_list_handler(req: &mut Request) -> IronResult { }; if id == req_build_id { - build_log = Some(build.clone()); + build_details = Some(build.clone()); } build @@ -113,7 +113,7 @@ pub fn build_list_handler(req: &mut Request) -> IronResult { BuildsPage { metadata: MetaData::from_crate(&conn, &name, &version), builds, - build_log, + build_details, limits, } .into_response(req) diff --git a/templates/crate/builds.html b/templates/crate/builds.html index 9f6fe8ade..2982ec495 100644 --- a/templates/crate/builds.html +++ b/templates/crate/builds.html @@ -14,19 +14,20 @@
    {# 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_log -%} + {%- if build_details -%}
    - Build #{{ build_log.id }} {{ build_log.build_time | date(format="%+") }} + Build #{{ build_details.id }} {{ build_details.build_time | date(format="%+") }}
    {%- filter dedent -%}
    -                        $ rustc --version
    -                        {{ build_log.rustc_version }}
    -                        $ docsrs --version
    -                        {{ build_log.docsrs_version }}
    -                        $ docsrs ...
    -                        {{ build_log.output }}
    +                        # rustc version
    +                        {{ build_details.rustc_version }}
    +                        # docs.rs version
    +                        {{ build_details.docsrs_version }}
    +
    +                        # build log
    +                        {{ build_details.output }}
                         
    {%- endfilter -%} {%- endif -%} From d865d87e4f7d97d94f8914102ca800ddcb481ab1 Mon Sep 17 00:00:00 2001 From: Chase Wilson Date: Sat, 4 Jul 2020 18:38:56 -0600 Subject: [PATCH 12/30] Don't re-eval req in cexpect! --- src/web/mod.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/web/mod.rs b/src/web/mod.rs index 02e0ba78b..5d46d2864 100644 --- a/src/web/mod.rs +++ b/src/web/mod.rs @@ -57,9 +57,12 @@ macro_rules! cexpect { /// Gets an extension from Request macro_rules! extension { - ($req:expr, $ext:ty) => { - cexpect!($req, $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; From fcb76a8f3042f7339bbf6a6d194141744edc3951 Mon Sep 17 00:00:00 2001 From: Chase Wilson Date: Sat, 4 Jul 2020 18:40:43 -0600 Subject: [PATCH 13/30] Remove useless Default for ErrorPage --- src/web/mod.rs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/web/mod.rs b/src/web/mod.rs index 5d46d2864..af9019a46 100644 --- a/src/web/mod.rs +++ b/src/web/mod.rs @@ -623,16 +623,6 @@ pub(crate) struct ErrorPage { pub status: Status, } -impl Default for ErrorPage { - fn default() -> Self { - Self { - title: Cow::Borrowed(""), - message: None, - status: Status::NotFound, - } - } -} - impl_webpage! { ErrorPage = "error.html", status = |err| err.status, From cffe56f5672e897206761cb0fa91c867ca80e6ea Mon Sep 17 00:00:00 2001 From: Chase Wilson Date: Sat, 4 Jul 2020 18:42:18 -0600 Subject: [PATCH 14/30] Don't use a Cow for BuildQueuePage --- src/web/releases.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/web/releases.rs b/src/web/releases.rs index 04fd93205..282a238ee 100644 --- a/src/web/releases.rs +++ b/src/web/releases.rs @@ -690,7 +690,7 @@ pub fn activity_handler(req: &mut Request) -> IronResult { #[derive(Debug, Clone, PartialEq, Serialize)] struct BuildQueuePage { - description: Cow<'static, str>, + description: &'static str, queue: Vec, } @@ -708,7 +708,7 @@ pub fn build_queue_handler(req: &mut Request) -> IronResult { } BuildQueuePage { - description: Cow::Borrowed("List of crates scheduled to build"), + description: "List of crates scheduled to build", queue, } .into_response(req) From d837b48666a7afd4dd3fdc9feb3f0c42e9cd7244 Mon Sep 17 00:00:00 2001 From: Chase Wilson Date: Sat, 4 Jul 2020 18:49:00 -0600 Subject: [PATCH 15/30] Removed accidental file --- test.txt | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 test.txt diff --git a/test.txt b/test.txt deleted file mode 100644 index e69de29bb..000000000 From db4e428cdaa336118edcf3534bae9700b103b475 Mon Sep 17 00:00:00 2001 From: Chase Wilson Date: Sat, 4 Jul 2020 18:57:47 -0600 Subject: [PATCH 16/30] Added TODO to crate details --- templates/crate/details.html | 1 + 1 file changed, 1 insertion(+) diff --git a/templates/crate/details.html b/templates/crate/details.html index 162ad149f..6c6998e63 100644 --- a/templates/crate/details.html +++ b/templates/crate/details.html @@ -51,6 +51,7 @@
  • {# If the repo link is for github, show some github stats #} + {# TODO: add support for hosts besides github (#35) #} {%- if details.github -%} {{ details.github_stars }} From d056442200c63016701d59298e0530619f53c7cf Mon Sep 17 00:00:00 2001 From: Chase Wilson Date: Sat, 4 Jul 2020 18:58:14 -0600 Subject: [PATCH 17/30] Grammar --- templates/crate/details.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/crate/details.html b/templates/crate/details.html index 6c6998e63..1dd816d5c 100644 --- a/templates/crate/details.html +++ b/templates/crate/details.html @@ -69,7 +69,7 @@ {# Show a link to the crate's Crates.io page #}
  • + title="See {{ details.name }} on crates.io"> Crates.io
  • From db6243839be2fc40d7a27ce46d44c2b19ecd5b30 Mon Sep 17 00:00:00 2001 From: Chase Wilson Date: Sat, 4 Jul 2020 19:04:55 -0600 Subject: [PATCH 18/30] Spelling --- templates/rustdoc/navigation.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/rustdoc/navigation.html b/templates/rustdoc/navigation.html index d87346608..96275c06f 100644 --- a/templates/rustdoc/navigation.html +++ b/templates/rustdoc/navigation.html @@ -228,7 +228,7 @@ Platform - {# Build the dropdown list showing avaliable targets #} + {# Build the dropdown list showing available targets #}
      {%- for target in krate.doc_targets -%} {%- set target_url = "/crate/" ~ krate.name ~ "/" ~ krate.version ~ "/target-redirect/" ~ target ~ "/" ~ inner_path -%} From 5d308f43f0a80de7d5f5adf9af77925fa89b0bcc Mon Sep 17 00:00:00 2001 From: Chase Wilson Date: Sat, 4 Jul 2020 19:06:32 -0600 Subject: [PATCH 19/30] Grammar --- templates/releases/build_queue.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/releases/build_queue.html b/templates/releases/build_queue.html index b92857109..1164a205a 100644 --- a/templates/releases/build_queue.html +++ b/templates/releases/build_queue.html @@ -14,7 +14,7 @@
      {% set queue_length = queue | length -%} {%- if queue_length == 0 -%} - There is nothing in queue + There is nothing in the queue {%- else -%} Queue {%- endif %} From ad50f680ed3c7004aa5ad3b697ac6658b98c4e62 Mon Sep 17 00:00:00 2001 From: Chase Wilson Date: Sat, 4 Jul 2020 19:13:57 -0600 Subject: [PATCH 20/30] Don't escape the description --- templates/crate/details.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/crate/details.html b/templates/crate/details.html index 1dd816d5c..1eeabf31c 100644 --- a/templates/crate/details.html +++ b/templates/crate/details.html @@ -168,9 +168,9 @@ {%- if details.readme -%} {{ details.readme | safe }} - {# If there's not a readme then attempt to display docs #} + {# If there's not a readme then attempt to display the long description #} {%- elif details.rustdoc -%} - {{ details.rustdoc | safe }} + {{ details.rustdoc }} {%- endif -%} {# If the build failed, the release isn't yanked and the release is a library #} From 6a53a71d2ceb032359edc06967099847ecb934d0 Mon Sep 17 00:00:00 2001 From: Chase Wilson Date: Tue, 7 Jul 2020 09:44:31 -0600 Subject: [PATCH 21/30] Macro'd out the release list, uncow'd some things --- src/web/mod.rs | 6 +- src/web/releases.rs | 5 +- templates/crate/details.html | 129 +----------------------------- templates/macros.html | 45 +++++++++++ templates/rustdoc/navigation.html | 39 +-------- 5 files changed, 54 insertions(+), 170 deletions(-) diff --git a/src/web/mod.rs b/src/web/mod.rs index af9019a46..d74d5a92c 100644 --- a/src/web/mod.rs +++ b/src/web/mod.rs @@ -16,7 +16,7 @@ macro_rules! ctry { // This is very ugly, but it makes it impossible to get a type inference error // from this macro let error = $crate::web::ErrorPage { - title: ::std::borrow::Cow::Borrowed("Internal Server Error"), + title: "Internal Server Error", message: ::std::option::Option::Some(::std::borrow::Cow::Owned( ::std::format!("{}", error), )), @@ -44,7 +44,7 @@ macro_rules! cexpect { // This is very ugly, but it makes it impossible to get a type inference error // from this macro let error = $crate::web::ErrorPage { - title: ::std::borrow::Cow::Borrowed("Internal Server Error"), + title: "Internal Server Error", message: None, status: ::iron::status::BadRequest, }; @@ -616,7 +616,7 @@ impl MetaData { #[derive(Debug, Clone, PartialEq, Serialize)] pub(crate) struct ErrorPage { /// The title of the page - pub title: Cow<'static, str>, + pub title: &'static str, /// The error message, displayed as a description pub message: Option>, #[serde(skip)] diff --git a/src/web/releases.rs b/src/web/releases.rs index 282a238ee..0bad8244e 100644 --- a/src/web/releases.rs +++ b/src/web/releases.rs @@ -18,7 +18,6 @@ use postgres::Connection; use router::Router; use serde::Serialize; use serde_json::Value; -use std::borrow::Cow; /// Number of release in home page const RELEASES_IN_HOME: i64 = 15; @@ -660,7 +659,7 @@ pub fn search_handler(req: &mut Request) -> IronResult { #[derive(Debug, Clone, PartialEq, Serialize)] struct ReleaseActivity { - description: Cow<'static, str>, + description: &'static str, activity_data: Value, } @@ -682,7 +681,7 @@ pub fn activity_handler(req: &mut Request) -> IronResult { .map_or(Value::Null, |row| row.get("value")); ReleaseActivity { - description: Cow::Borrowed("Monthly release activity"), + description: "Monthly release activity", activity_data, } .into_response(req) diff --git a/templates/crate/details.html b/templates/crate/details.html index 1eeabf31c..7db4d6602 100644 --- a/templates/crate/details.html +++ b/templates/crate/details.html @@ -96,37 +96,7 @@ @@ -204,101 +174,4 @@ {%- block javascript -%} {# Enable and load Rust and TOML syntax highlighting #} {{ macros::highlight_js(languages=["rust", "ini"]) }} - - {% endblock javascript -%} diff --git a/templates/macros.html b/templates/macros.html index b9b18d6d2..bd6964731 100644 --- a/templates/macros.html +++ b/templates/macros.html @@ -75,3 +75,48 @@ 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 has been yanked and failed to build, display a warning #} + {%- if release.yanked and release.build_status -%} + + {{ release.version }} + + + {# If the release is yanked but built, display an warning #} + {%- elif release.yanked and not release.build_status -%} + + {{ release.version }} + + + {# If the release failed to build, display a warning #} + {%- elif release.build_status -%} + + {{ release.version }} + + + {# Otherwise just display the version #} + {%- else -%} + + {{ release.version }} + + {%- endif -%} +
    • + {%- endfor -%} +{% endmacro releases_list %} diff --git a/templates/rustdoc/navigation.html b/templates/rustdoc/navigation.html index 96275c06f..c56d1f947 100644 --- a/templates/rustdoc/navigation.html +++ b/templates/rustdoc/navigation.html @@ -135,45 +135,12 @@
      • Versions
      • - - {# Build the list of every release this crate has #} +
      • From b8740021693859c406b9f2ba934830eef06169b9 Mon Sep 17 00:00:00 2001 From: Chase Wilson Date: Tue, 7 Jul 2020 09:58:00 -0600 Subject: [PATCH 22/30] Untangled JS --- templates/core/home.html | 2 + templates/releases/build_queue.html | 88 ----------------------------- templates/releases/releases.html | 67 +++++++++++++++------- 3 files changed, 50 insertions(+), 107 deletions(-) diff --git a/templates/core/home.html b/templates/core/home.html index 2ef1435ba..0ce0164c0 100644 --- a/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/templates/releases/build_queue.html b/templates/releases/build_queue.html index 1164a205a..37366aea9 100644 --- a/templates/releases/build_queue.html +++ b/templates/releases/build_queue.html @@ -36,91 +36,3 @@
      {%- endblock body -%} - -{%- block javascript -%} - -{%- endblock javascript -%} diff --git a/templates/releases/releases.html b/templates/releases/releases.html index 5f6ab863a..e4c745f4b 100644 --- a/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 -%} From 72949ae2b888c99f4dac2c066443270b071823fb Mon Sep 17 00:00:00 2001 From: Chase Wilson Date: Tue, 7 Jul 2020 10:19:32 -0600 Subject: [PATCH 23/30] Clippy --- src/web/error.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/web/error.rs b/src/web/error.rs index 5c6f55a6a..0abcc3e92 100644 --- a/src/web/error.rs +++ b/src/web/error.rs @@ -35,7 +35,7 @@ impl Handler for Nope { // user tried to navigate to a resource (doc page/file) that doesn't exist // TODO: Display the attempted page ErrorPage { - title: "The requested resource does not exist".into(), + title: "The requested resource does not exist", message: Some("no such resource".into()), status: Status::NotFound, } @@ -46,7 +46,7 @@ impl Handler for Nope { // user tried to navigate to a crate that doesn't exist // TODO: Display the attempted crate and a link to a search for said crate ErrorPage { - title: "The requested crate does not exist".into(), + title: "The requested crate does not exist", message: Some("no such crate".into()), status: Status::NotFound, } @@ -79,7 +79,7 @@ impl Handler for Nope { Nope::InternalServerError => { // something went wrong, details should have been logged ErrorPage { - title: "Internal server error".into(), + title: "Internal server error", message: Some("internal server error".into()), status: Status::InternalServerError, } From cd6172cbc9b51092fff7b3239f2bbc61eda49f56 Mon Sep 17 00:00:00 2001 From: Chase Wilson Date: Tue, 7 Jul 2020 12:59:23 -0600 Subject: [PATCH 24/30] Missing macro import --- templates/rustdoc/navigation.html | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/templates/rustdoc/navigation.html b/templates/rustdoc/navigation.html index c56d1f947..94c0efddb 100644 --- a/templates/rustdoc/navigation.html +++ b/templates/rustdoc/navigation.html @@ -1,3 +1,5 @@ +{%- import "macros.html" as macros -%} + {# The url of the current release, `/crate/:name/:version` #} {%- set crate_url = "/crate/" ~ krate.name ~ "/" ~ krate.version -%} @@ -6,7 +8,10 @@
      - + +
      From bc7775b5bf0fa3e2cf78abf97ed8c70d0448fc22 Mon Sep 17 00:00:00 2001 From: Chase Wilson Date: Tue, 7 Jul 2020 17:35:35 -0600 Subject: [PATCH 25/30] Fixed bugs --- src/web/crate_details.rs | 8 ++++---- templates/crate/details.html | 8 ++++---- templates/macros.html | 2 +- templates/rustdoc/navigation.html | 2 +- templates/rustdoc/page.html | 8 +------- 5 files changed, 11 insertions(+), 17 deletions(-) diff --git a/src/web/crate_details.rs b/src/web/crate_details.rs index 47bb23ba7..ef0a1c676 100644 --- a/src/web/crate_details.rs +++ b/src/web/crate_details.rs @@ -244,8 +244,8 @@ 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(); @@ -265,7 +265,7 @@ fn map_to_release(conn: &Connection, crate_id: i32, version: String) -> Release #[derive(Debug, Clone, PartialEq, Serialize)] struct CrateDetailsPage { - details: Option, + details: CrateDetails, } impl_webpage! { @@ -282,7 +282,7 @@ pub fn crate_details_handler(req: &mut Request) -> IronResult { 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)); CrateDetailsPage { details }.into_response(req) } diff --git a/templates/crate/details.html b/templates/crate/details.html index 7db4d6602..15a5e8c3f 100644 --- a/templates/crate/details.html +++ b/templates/crate/details.html @@ -83,7 +83,7 @@
    • {{ dep[0] }} {{ dep[1] }} - {{ dep[2] }} + {{ dep[2] | default(value="") }}
    • {%- endfor -%} @@ -140,7 +140,7 @@ {# If there's not a readme then attempt to display the long description #} {%- elif details.rustdoc -%} - {{ details.rustdoc }} + {{ details.rustdoc | safe }} {%- endif -%} {# If the build failed, the release isn't yanked and the release is a library #} @@ -148,7 +148,7 @@ {# 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 + build logs and, if you believe this is docs.rs' fault, open an issue.
      @@ -157,7 +157,7 @@ {%- endif -%} diff --git a/templates/macros.html b/templates/macros.html index bd6964731..bb7ddb569 100644 --- a/templates/macros.html +++ b/templates/macros.html @@ -106,7 +106,7 @@ {# If the release failed to build, display a warning #} - {%- elif release.build_status -%} + {%- elif not release.build_status -%} {{ release.version }} diff --git a/templates/rustdoc/navigation.html b/templates/rustdoc/navigation.html index 94c0efddb..690ee27ec 100644 --- a/templates/rustdoc/navigation.html +++ b/templates/rustdoc/navigation.html @@ -127,7 +127,7 @@
    • {{ dep[0] }} {{ dep[1] }} - {{ dep[2] }} + {{ dep[2] | default(value="") }}
    • {%- endfor -%} diff --git a/templates/rustdoc/page.html b/templates/rustdoc/page.html index 1cac66fd3..2fa4c8bcc 100644 --- a/templates/rustdoc/page.html +++ b/templates/rustdoc/page.html @@ -16,9 +16,6 @@ - {# Highlight.js CSS #} - {{ macros::highlight_css() }} - {{ macros::doc_title(name=krate.name, version=krate.version) }} @@ -50,7 +47,4 @@ } - {# Highlight.js JavaScript #} - {{ macros::highlight_js(languages=["rust", "ini"]) }} - - + \ No newline at end of file From a04dc342043ce8d45e680d59628ed6bbf24808f0 Mon Sep 17 00:00:00 2001 From: Joshua Nelson Date: Sun, 12 Jul 2020 19:50:51 -0400 Subject: [PATCH 26/30] Add test for missing dependency kind --- src/test/fakes.rs | 1 + src/web/rustdoc.rs | 24 +++++++++++++++++++++--- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/test/fakes.rs b/src/test/fakes.rs index fa899d75e..7c55aa32b 100644 --- a/src/test/fakes.rs +++ b/src/test/fakes.rs @@ -153,6 +153,7 @@ impl<'a> FakeRelease<'a> { self } + /// Returns the release_id pub(crate) fn create(self) -> Result { use std::collections::HashSet; use std::fs; diff --git a/src/web/rustdoc.rs b/src/web/rustdoc.rs index ef982df3d..54f5e7536 100644 --- a/src/web/rustdoc.rs +++ b/src/web/rustdoc.rs @@ -1360,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/") @@ -1374,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() @@ -1400,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(); @@ -1427,4 +1427,22 @@ mod test { Ok(()) }) } + + #[test] + // 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(()) + }) + } } From ef3270642fa0240782850316ae2c6ea3e9bc1a4b Mon Sep 17 00:00:00 2001 From: Joshua Nelson Date: Sun, 12 Jul 2020 20:21:52 -0400 Subject: [PATCH 27/30] Add test that readme is rendered --- src/test/fakes.rs | 17 +++++++++++++++-- src/web/rustdoc.rs | 22 ++++++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/src/test/fakes.rs b/src/test/fakes.rs index 7c55aa32b..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,12 @@ 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; @@ -223,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/rustdoc.rs b/src/web/rustdoc.rs index 54f5e7536..a08b7505f 100644 --- a/src/web/rustdoc.rs +++ b/src/web/rustdoc.rs @@ -1445,4 +1445,26 @@ mod test { Ok(()) }) } + + #[test] + 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(()) + }) + } } From a35262a74ffae6b831d895a806557207df3fb0c6 Mon Sep 17 00:00:00 2001 From: Joshua Nelson Date: Sun, 12 Jul 2020 20:55:53 -0400 Subject: [PATCH 28/30] Add test case for build status --- src/web/rustdoc.rs | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/web/rustdoc.rs b/src/web/rustdoc.rs index a08b7505f..322c0f972 100644 --- a/src/web/rustdoc.rs +++ b/src/web/rustdoc.rs @@ -1447,6 +1447,7 @@ mod test { } #[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(); @@ -1467,4 +1468,40 @@ mod test { Ok(()) }) } + + #[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(); + + 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(()) + }) + } } From 63649451e426d3375f73796eeb2bbdc51d012055 Mon Sep 17 00:00:00 2001 From: Chase Wilson Date: Sun, 12 Jul 2020 20:47:38 -0500 Subject: [PATCH 29/30] Added newline to page.html --- templates/rustdoc/page.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/rustdoc/page.html b/templates/rustdoc/page.html index 2fa4c8bcc..1720a9806 100644 --- a/templates/rustdoc/page.html +++ b/templates/rustdoc/page.html @@ -47,4 +47,4 @@ } - \ No newline at end of file + From 171d4f4d14ea51a02017e16e714d64954d4c11d4 Mon Sep 17 00:00:00 2001 From: Chase Wilson Date: Mon, 13 Jul 2020 13:03:43 -0600 Subject: [PATCH 30/30] Update templates/macros.html Co-authored-by: Joshua Nelson --- templates/macros.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/macros.html b/templates/macros.html index bb7ddb569..0984ea34b 100644 --- a/templates/macros.html +++ b/templates/macros.html @@ -92,13 +92,13 @@ {%- set release_name = name ~ "-" ~ release.version -%}
    • - {# If the release has been yanked and failed to build, display a warning #} + {# If the release is yanked but built, display an warning #} {%- if release.yanked and release.build_status -%} {{ release.version }} - {# If the release is yanked but built, display an warning #} + {# If the release has been yanked and failed to build, display a warning #} {%- elif release.yanked and not release.build_status -%}