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"]) }} + +