From 06efa7a6757d00394a31ca2f4960d90e9aca3a0b Mon Sep 17 00:00:00 2001 From: Manuel Woelker Date: Wed, 10 Jun 2020 12:31:34 +0200 Subject: [PATCH] additional changes to the 404 mechanism based on feedback: - removed config output_404 - ensure serve overrides the site url, and hosts the correct 404 file - refactor 404 rendering into separate fn - formatting --- book-example/book.toml | 2 +- src/cmd/serve.rs | 21 +++- src/config.rs | 16 +-- src/renderer/html_handlebars/hbs_renderer.rs | 100 ++++++++++--------- src/utils/fs.rs | 4 + 5 files changed, 87 insertions(+), 56 deletions(-) diff --git a/book-example/book.toml b/book-example/book.toml index 6bb796ca..e73e1a54 100644 --- a/book-example/book.toml +++ b/book-example/book.toml @@ -9,7 +9,7 @@ edition = "2018" [output.html] mathjax-support = true -site-url = "/" +site-url = "/mdBook/" [output.html.playpen] editable = true diff --git a/src/cmd/serve.rs b/src/cmd/serve.rs index 843b4e9f..97de19f5 100644 --- a/src/cmd/serve.rs +++ b/src/cmd/serve.rs @@ -6,6 +6,7 @@ use futures_util::sink::SinkExt; use futures_util::StreamExt; use mdbook::errors::*; use mdbook::utils; +use mdbook::utils::fs::get_404_output_file; use mdbook::MDBook; use std::net::{SocketAddr, ToSocketAddrs}; use std::path::PathBuf; @@ -68,6 +69,8 @@ pub fn execute(args: &ArgMatches) -> Result<()> { if let Some(dest_dir) = args.value_of("dest-dir") { book.config.build.build_dir = dest_dir.into(); } + // Override site-url for local serving of the 404 file + book.config.set("output.html.site-url", "/")?; book.build()?; @@ -76,13 +79,19 @@ pub fn execute(args: &ArgMatches) -> Result<()> { .next() .ok_or_else(|| anyhow::anyhow!("no address found for {}", address))?; let build_dir = book.build_dir_for("html"); + let input_404 = book + .config + .get("output.html.input-404") + .map(toml::Value::as_str) + .flatten(); + let file_404 = get_404_output_file(input_404); // A channel used to broadcast to any websockets to reload when a file changes. let (tx, _rx) = tokio::sync::broadcast::channel::(100); let reload_tx = tx.clone(); let thread_handle = std::thread::spawn(move || { - serve(build_dir, sockaddr, reload_tx); + serve(build_dir, sockaddr, reload_tx, &file_404); }); let serving_url = format!("http://{}", address); @@ -120,7 +129,12 @@ pub fn execute(args: &ArgMatches) -> Result<()> { } #[tokio::main] -async fn serve(build_dir: PathBuf, address: SocketAddr, reload_tx: broadcast::Sender) { +async fn serve( + build_dir: PathBuf, + address: SocketAddr, + reload_tx: broadcast::Sender, + file_404: &str, +) { // A warp Filter which captures `reload_tx` and provides an `rx` copy to // receive reload messages. let sender = warp::any().map(move || reload_tx.subscribe()); @@ -144,8 +158,7 @@ async fn serve(build_dir: PathBuf, address: SocketAddr, reload_tx: broadcast::Se // A warp Filter that serves from the filesystem. let book_route = warp::fs::dir(build_dir.clone()); // The fallback route for 404 errors - let fallback_file = "404.html"; - let fallback_route = warp::fs::file(build_dir.join(fallback_file)) + let fallback_route = warp::fs::file(build_dir.join(file_404)) .map(|reply| warp::reply::with_status(reply, warp::http::StatusCode::NOT_FOUND)); let routes = livereload.or(book_route).or(fallback_route); warp::serve(routes).run(address).await; diff --git a/src/config.rs b/src/config.rs index b955e352..f6825c39 100644 --- a/src/config.rs +++ b/src/config.rs @@ -507,10 +507,8 @@ pub struct HtmlConfig { /// FontAwesome icon class to use for the Git repository link. /// Defaults to `fa-github` if `None`. pub git_repository_icon: Option, - /// Input path for the 404 file, defaults to 404.md + /// Input path for the 404 file, defaults to 404.md, set to "" to disable 404 file output pub input_404: Option, - /// Output path for 404.html file, defaults to 404.html, set to "" to disable 404 file output - pub output_404: Option, /// Absolute url to site, used to emit correct paths for the 404 page, which might be accessed in a deeply nested directory pub site_url: Option, /// This is used as a bit of a workaround for the `mdbook serve` command. @@ -545,7 +543,6 @@ impl Default for HtmlConfig { git_repository_url: None, git_repository_icon: None, input_404: None, - output_404: None, site_url: None, livereload_url: None, redirect: HashMap::new(), @@ -679,6 +676,7 @@ impl<'de, T> Updateable<'de> for T where T: Serialize + Deserialize<'de> {} #[cfg(test)] mod tests { use super::*; + use crate::utils::fs::get_404_output_file; const COMPLEX_CONFIG: &str = r#" [book] @@ -1024,7 +1022,10 @@ mod tests { let got = Config::from_str(src).unwrap(); let html_config = got.html_config().unwrap(); assert_eq!(html_config.input_404, None); - assert_eq!(html_config.output_404, None); + assert_eq!( + &get_404_output_file(html_config.input_404.as_deref()), + "404.html" + ); } #[test] @@ -1038,6 +1039,9 @@ mod tests { let got = Config::from_str(src).unwrap(); let html_config = got.html_config().unwrap(); assert_eq!(html_config.input_404, Some("missing.md".to_string())); - assert_eq!(html_config.output_404, Some("missing.html".to_string())); + assert_eq!( + &get_404_output_file(html_config.input_404.as_deref()), + "missing.html" + ); } } diff --git a/src/renderer/html_handlebars/hbs_renderer.rs b/src/renderer/html_handlebars/hbs_renderer.rs index 61088218..dcc12579 100644 --- a/src/renderer/html_handlebars/hbs_renderer.rs +++ b/src/renderer/html_handlebars/hbs_renderer.rs @@ -12,6 +12,7 @@ use std::collections::HashMap; use std::fs::{self, File}; use std::path::{Path, PathBuf}; +use crate::utils::fs::get_404_output_file; use handlebars::Handlebars; use regex::{Captures, Regex}; @@ -105,6 +106,58 @@ impl HtmlHandlebars { Ok(()) } + fn render_404( + &self, + ctx: &RenderContext, + html_config: &HtmlConfig, + src_dir: &PathBuf, + handlebars: &mut Handlebars<'_>, + data: &mut serde_json::Map, + ) -> Result<()> { + let destination = &ctx.destination; + let content_404 = if let Some(ref filename) = html_config.input_404 { + let path = src_dir.join(filename); + std::fs::read_to_string(&path) + .with_context(|| format!("unable to open 404 input file {:?}", path))? + } else { + // 404 input not explicitly configured try the default file 404.md + let default_404_location = src_dir.join("404.md"); + if default_404_location.exists() { + std::fs::read_to_string(&default_404_location).with_context(|| { + format!("unable to open 404 input file {:?}", default_404_location) + })? + } else { + "# Document not found (404)\n\nThis URL is invalid, sorry. Please use the \ + navigation bar or search to continue." + .to_string() + } + }; + let html_content_404 = utils::render_markdown(&content_404, html_config.curly_quotes); + + let mut data_404 = data.clone(); + let base_url = if let Some(site_url) = &html_config.site_url { + site_url + } else { + debug!( + "HTML 'site-url' parameter not set, defaulting to '/'. Please configure \ + this to ensure the 404 page work correctly, especially if your site is hosted in a \ + subdirectory on the HTTP server." + ); + "/" + }; + data_404.insert("base_url".to_owned(), json!(base_url)); + // Set a dummy path to ensure other paths (e.g. in the TOC) are generated correctly + data_404.insert("path".to_owned(), json!("404.md")); + data_404.insert("content".to_owned(), json!(html_content_404)); + let rendered = handlebars.render("index", &data_404)?; + + let rendered = self.post_process(rendered, &html_config.playpen, ctx.config.rust.edition); + let output_file = get_404_output_file(html_config.input_404.as_deref()); + utils::fs::write_file(&destination, output_file, rendered.as_bytes())?; + debug!("Creating 404.html ✓"); + Ok(()) + } + #[cfg_attr(feature = "cargo-clippy", allow(clippy::let_and_return))] fn post_process( &self, @@ -438,51 +491,8 @@ impl Renderer for HtmlHandlebars { } // Render 404 page - if html_config.output_404 != Some("".to_string()) { - let default_404_location = src_dir.join("404.md"); - let content_404 = if let Some(ref filename) = html_config.input_404 { - let path = src_dir.join(filename); - std::fs::read_to_string(&path).map_err(|failure| { - std::io::Error::new( - failure.kind(), - format!("Unable to open 404 input file {:?}", &path), - ) - })? - } else if default_404_location.exists() { - std::fs::read_to_string(&default_404_location).map_err(|failure| { - std::io::Error::new( - failure.kind(), - format!( - "Unable to open default 404 input file {:?}", - &default_404_location - ), - ) - })? - } else { - "# 404 - Document not found\n\nUnfortunately, this URL is no longer valid, please use the navigation bar or search to continue.".to_string() - }; - let html_content_404 = utils::render_markdown(&content_404, html_config.curly_quotes); - - let mut data_404 = data.clone(); - let base_url = if let Some(site_url) = &html_config.site_url { - site_url - } else { - warn!("HTML 'site-url' parameter not set, defaulting to '/'. Please configure this to ensure the 404 page work correctly, especially if your site is hosted in a subdirectory on the HTTP server."); - "/" - }; - data_404.insert("base_url".to_owned(), json!(base_url)); - data_404.insert("path".to_owned(), json!("404.md")); - data_404.insert("content".to_owned(), json!(html_content_404)); - let rendered = handlebars.render("index", &data_404)?; - - let rendered = - self.post_process(rendered, &html_config.playpen, ctx.config.rust.edition); - let output_file = match &html_config.output_404 { - None => "404.html", - Some(file) => &file, - }; - utils::fs::write_file(&destination, output_file, rendered.as_bytes())?; - debug!("Creating 404.html ✓"); + if html_config.input_404 != Some("".to_string()) { + self.render_404(ctx, &html_config, &src_dir, &mut handlebars, &mut data)?; } // Print version diff --git a/src/utils/fs.rs b/src/utils/fs.rs index d4c0cb5f..6b7529db 100644 --- a/src/utils/fs.rs +++ b/src/utils/fs.rs @@ -177,6 +177,10 @@ pub fn copy_files_except_ext( Ok(()) } +pub fn get_404_output_file(input_404: Option<&str>) -> String { + input_404.unwrap_or("404.md").replace(".md", ".html") +} + #[cfg(test)] mod tests { use super::copy_files_except_ext;