From 282fdaa3ac5ce4708064d1545e3a6f180dda32ba Mon Sep 17 00:00:00 2001 From: Ruin0x11 Date: Fri, 28 Aug 2020 02:05:21 -0700 Subject: [PATCH] Redirect to a 404 page when serving translated We can't redirect in warp based on the URL, so redirect to the default language's 404 page instead. See: https://github.com/seanmonstar/warp/issues/171 --- src/book/book.rs | 34 +++++--- src/book/mod.rs | 21 +++-- src/cmd/serve.rs | 35 +++++--- src/preprocess/cmd.rs | 3 +- src/renderer/html_handlebars/hbs_renderer.rs | 80 +++++++++++++------ .../html_handlebars/helpers/language.rs | 30 ++++--- src/renderer/html_handlebars/helpers/mod.rs | 2 +- src/renderer/markdown_renderer.rs | 2 +- 8 files changed, 138 insertions(+), 69 deletions(-) diff --git a/src/book/book.rs b/src/book/book.rs index 0491db51..7998b704 100644 --- a/src/book/book.rs +++ b/src/book/book.rs @@ -18,27 +18,37 @@ pub fn load_book>( if cfg.language.has_localized_dir_structure() { match build_opts.language_ident { // Build a single book's translation. - Some(_) => Ok(LoadedBook::Single(load_single_book_translation(&root_dir, cfg, &build_opts.language_ident)?)), + Some(_) => Ok(LoadedBook::Single(load_single_book_translation( + &root_dir, + cfg, + &build_opts.language_ident, + )?)), // Build all available translations at once. None => { let mut translations = HashMap::new(); for (lang_ident, _) in cfg.language.0.iter() { - let book = load_single_book_translation(&root_dir, cfg, &Some(lang_ident.clone()))?; + let book = + load_single_book_translation(&root_dir, cfg, &Some(lang_ident.clone()))?; translations.insert(lang_ident.clone(), book); } Ok(LoadedBook::Localized(LocalizedBooks(translations))) } } } else { - Ok(LoadedBook::Single(load_single_book_translation(&root_dir, cfg, &None)?)) + Ok(LoadedBook::Single(load_single_book_translation( + &root_dir, cfg, &None, + )?)) } } -fn load_single_book_translation>(root_dir: P, cfg: &Config, language_ident: &Option) -> Result { - let localized_src_dir = root_dir.as_ref().join( - cfg.get_localized_src_path(language_ident.as_ref()) - .unwrap(), - ); +fn load_single_book_translation>( + root_dir: P, + cfg: &Config, + language_ident: &Option, +) -> Result { + let localized_src_dir = root_dir + .as_ref() + .join(cfg.get_localized_src_path(language_ident.as_ref()).unwrap()); let fallback_src_dir = root_dir.as_ref().join(cfg.get_fallback_src_path()); let summary_md = localized_src_dir.join("SUMMARY.md"); @@ -172,9 +182,7 @@ impl LocalizedBooks { items.extend(book.iter().items); } - BookItems { - items: items - } + BookItems { items: items } } /// Recursively apply a closure to each item in the book, allowing you to @@ -239,7 +247,7 @@ impl LoadedBook { pub fn first(&self) -> &Book { match self { LoadedBook::Localized(books) => books.0.iter().next().unwrap().1, - LoadedBook::Single(book) => &book + LoadedBook::Single(book) => &book, } } } @@ -617,7 +625,7 @@ more text. Vec::new(), &cfg, ) - .unwrap(); + .unwrap(); assert_eq!(got, should_be); } diff --git a/src/book/mod.rs b/src/book/mod.rs index 69da50e7..a30c13aa 100644 --- a/src/book/mod.rs +++ b/src/book/mod.rs @@ -10,15 +10,15 @@ mod book; mod init; mod summary; -pub use self::book::{load_book, BookItem, BookItems, Chapter, Book, LocalizedBooks, LoadedBook}; +pub use self::book::{load_book, Book, BookItem, BookItems, Chapter, LoadedBook, LocalizedBooks}; pub use self::init::BookBuilder; pub use self::summary::{parse_summary, Link, SectionNumber, Summary, SummaryItem}; +use std::collections::HashMap; use std::io::Write; use std::path::PathBuf; use std::process::Command; use std::string::ToString; -use std::collections::HashMap; use tempfile::Builder as TempFileBuilder; use tempfile::TempDir; use toml::Value; @@ -133,8 +133,12 @@ impl MDBook { .unwrap(), ); let fallback_src_dir = root.join(config.get_fallback_src_path()); - let book = - LoadedBook::Single(book::load_book_from_disk(&summary, localized_src_dir, fallback_src_dir, &config)?); + let book = LoadedBook::Single(book::load_book_from_disk( + &summary, + localized_src_dir, + fallback_src_dir, + &config, + )?); let renderers = determine_renderers(&config); let preprocessors = determine_preprocessors(&config)?; @@ -223,13 +227,16 @@ impl MDBook { let mut new_books = HashMap::new(); for (ident, book) in books.0.iter() { - let preprocessed_book = self.preprocess(&preprocess_ctx, renderer, book.clone())?; + let preprocessed_book = + self.preprocess(&preprocess_ctx, renderer, book.clone())?; new_books.insert(ident.clone(), preprocessed_book); } LoadedBook::Localized(LocalizedBooks(new_books)) - }, - LoadedBook::Single(ref book) => LoadedBook::Single(self.preprocess(&preprocess_ctx, renderer, book.clone())?), + } + LoadedBook::Single(ref book) => { + LoadedBook::Single(self.preprocess(&preprocess_ctx, renderer, book.clone())?) + } }; let name = renderer.name(); diff --git a/src/cmd/serve.rs b/src/cmd/serve.rs index bb1eb819..f1a09f74 100644 --- a/src/cmd/serve.rs +++ b/src/cmd/serve.rs @@ -92,7 +92,7 @@ pub fn execute(args: &ArgMatches) -> Result<()> { Some(lang_ident) => Some(lang_ident.clone()), // If not, it will be at the root. None => None, - } + }, }; let sockaddr: SocketAddr = address @@ -177,9 +177,6 @@ async fn serve( }); // 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_route = warp::fs::file(build_dir.join(file_404)) - .map(|reply| warp::reply::with_status(reply, warp::http::StatusCode::NOT_FOUND)); std::panic::set_hook(Box::new(move |panic_info| { // exit if serve panics @@ -189,13 +186,31 @@ async fn serve( if let Some(lang_ident) = language { // Redirect root to the default translation directory, if serving a localized book. - // BUG: This can't be `/{lang_ident}`, or the static assets won't get loaded. - let index_for_language = format!("/{}/index.html", lang_ident).parse::().unwrap(); - let redirect_to_index = warp::path::end().map(move || warp::redirect(index_for_language.clone())); - let routes = livereload.or(redirect_to_index).or(book_route).or(fallback_route); + // NOTE: This can't be `/{lang_ident}`, or the static assets won't get loaded. + // BUG: Redirects get cached if you change the --language parameter, + // meaning you'll get a 404 unless you disable cache in the developer tools. + let index_for_language = format!("/{}/index.html", lang_ident) + .parse::() + .unwrap(); + let redirect_to_index = + warp::path::end().map(move || warp::redirect(index_for_language.clone())); + + // BUG: It is not possible to conditionally redirect to the correct 404 + // page depending on the URL in warp, so just redirect to the one in the + // default language. + // See: https://github.com/seanmonstar/warp/issues/171 + let fallback_route = warp::fs::file(build_dir.join(lang_ident).join(file_404)) + .map(|reply| warp::reply::with_status(reply, warp::http::StatusCode::NOT_FOUND)); + + let routes = livereload + .or(redirect_to_index) + .or(book_route) + .or(fallback_route); warp::serve(routes).run(address).await; - } - else { + } else { + // The fallback route for 404 errors + 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/preprocess/cmd.rs b/src/preprocess/cmd.rs index 0710bc61..713e2f10 100644 --- a/src/preprocess/cmd.rs +++ b/src/preprocess/cmd.rs @@ -199,7 +199,8 @@ mod tests { ); let mut buffer = Vec::new(); - cmd.write_input(&mut buffer, &md.book.first(), &ctx).unwrap(); + cmd.write_input(&mut buffer, &md.book.first(), &ctx) + .unwrap(); let (got_ctx, got_book) = CmdPreprocessor::parse_input(buffer.as_slice()).unwrap(); diff --git a/src/renderer/html_handlebars/hbs_renderer.rs b/src/renderer/html_handlebars/hbs_renderer.rs index 501d3a88..a44e64d1 100644 --- a/src/renderer/html_handlebars/hbs_renderer.rs +++ b/src/renderer/html_handlebars/hbs_renderer.rs @@ -62,31 +62,49 @@ impl HtmlHandlebars { let mut path = src_dir.clone(); path.push(lang_ident); path - }, + } // `src_dir` is where index.html and the other extra files // are, so use that. - None => src_dir.clone() + None => src_dir.clone(), }; - self.render_book(ctx, &book, src_dir, &extra_file_dir, &ctx.destination, &ctx.config.build.build_dir, html_config, handlebars, theme)?; + self.render_book( + ctx, + &book, + src_dir, + &extra_file_dir, + &ctx.destination, + &ctx.config.build.build_dir, + html_config, + handlebars, + theme, + )?; } } Ok(()) } - fn render_book<'a>(&self, - ctx: &RenderContext, - book: &Book, - src_dir: &PathBuf, - extra_file_dir: &PathBuf, - destination: &PathBuf, - build_dir: &PathBuf, - html_config: &HtmlConfig, - handlebars: &mut Handlebars<'a>, - theme: &Theme, + fn render_book<'a>( + &self, + ctx: &RenderContext, + book: &Book, + src_dir: &PathBuf, + extra_file_dir: &PathBuf, + destination: &PathBuf, + build_dir: &PathBuf, + html_config: &HtmlConfig, + handlebars: &mut Handlebars<'a>, + theme: &Theme, ) -> Result<()> { let build_dir = ctx.root.join(build_dir); - let mut data = make_data(&ctx.root, &book, &ctx.book, &ctx.config, &html_config, &theme)?; + let mut data = make_data( + &ctx.root, + &book, + &ctx.book, + &ctx.config, + &html_config, + &theme, + )?; // Print version let mut print_content = String::new(); @@ -110,7 +128,14 @@ impl HtmlHandlebars { // Render 404 page if html_config.input_404 != Some("".to_string()) { - self.render_404(ctx, &html_config, src_dir, destination, handlebars, &mut data)?; + self.render_404( + ctx, + &html_config, + src_dir, + destination, + handlebars, + &mut data, + )?; } // Print version @@ -123,7 +148,8 @@ impl HtmlHandlebars { debug!("Render template"); let rendered = handlebars.render("index", &data)?; - let rendered = self.post_process(rendered, &html_config.playground, ctx.config.rust.edition); + let rendered = + self.post_process(rendered, &html_config.playground, ctx.config.rust.edition); utils::fs::write_file(&destination, "print.html", rendered.as_bytes())?; debug!("Creating print.html ✓"); @@ -147,12 +173,17 @@ impl HtmlHandlebars { .context("Unable to emit redirects")?; // Copy all remaining files, avoid a recursive copy from/to the book build dir - utils::fs::copy_files_except_ext(&extra_file_dir, &destination, true, Some(&build_dir), &["md"])?; + utils::fs::copy_files_except_ext( + &extra_file_dir, + &destination, + true, + Some(&build_dir), + &["md"], + )?; Ok(()) } - fn render_item( &self, item: &BookItem, @@ -229,7 +260,7 @@ impl HtmlHandlebars { ); if let Some(ref section) = ch.number { ctx.data - .insert("section".to_owned(), json!(section.to_string())); + .insert("section".to_owned(), json!(section.to_string())); } // Render the handlebars template with the data @@ -461,7 +492,10 @@ impl HtmlHandlebars { handlebars.register_helper("previous", Box::new(helpers::navigation::previous)); handlebars.register_helper("next", Box::new(helpers::navigation::next)); handlebars.register_helper("theme_option", Box::new(helpers::theme::theme_option)); - handlebars.register_helper("language_option", Box::new(helpers::language::language_option)); + handlebars.register_helper( + "language_option", + Box::new(helpers::language::language_option), + ); } /// Copy across any additional CSS and JavaScript files which the book @@ -568,7 +602,7 @@ impl HtmlHandlebars { fn maybe_wrong_theme_dir(dir: &Path) -> Result { fn entry_is_maybe_book_file(entry: fs::DirEntry) -> Result { Ok(entry.file_type()?.is_file() - && entry.path().extension().map_or(false, |ext| ext == "md")) + && entry.path().extension().map_or(false, |ext| ext == "md")) } if dir.is_dir() { @@ -842,7 +876,7 @@ fn make_data( languages.sort(); data.insert("languages".to_owned(), json!(languages)); data.insert("language_config".to_owned(), json!(config.language.clone())); - }, + } LoadedBook::Single(_) => { data.insert("languages_enabled".to_owned(), json!(false)); } @@ -1012,7 +1046,7 @@ fn add_playground_pre( "\n# #![allow(unused)]\n{}#fn main() {{\n{}#}}", attrs, code ) - .into() + .into() }; hide_lines(&content) } diff --git a/src/renderer/html_handlebars/helpers/language.rs b/src/renderer/html_handlebars/helpers/language.rs index 27654de4..3af3375a 100644 --- a/src/renderer/html_handlebars/helpers/language.rs +++ b/src/renderer/html_handlebars/helpers/language.rs @@ -1,6 +1,6 @@ +use crate::config::LanguageConfig; use handlebars::{Context, Handlebars, Helper, Output, RenderContext, RenderError}; use std::path::Path; -use crate::config::LanguageConfig; pub fn language_option( h: &Helper<'_, '_>, @@ -22,10 +22,10 @@ pub fn language_option( let current_path = rc .evaluate(ctx, "@root/path")? - .as_json() - .as_str() - .ok_or_else(|| RenderError::new("Type error for `path`, string expected"))? - .replace("\"", ""); + .as_json() + .as_str() + .ok_or_else(|| RenderError::new("Type error for `path`, string expected"))? + .replace("\"", ""); let rendered_path = Path::new(¤t_path) .with_extension("html") @@ -35,14 +35,15 @@ pub fn language_option( let path_to_root = rc .evaluate(ctx, "@root/path_to_root")? - .as_json() - .as_str() - .ok_or_else(|| RenderError::new("Type error for `path_to_root`, string expected"))? - .to_string(); + .as_json() + .as_str() + .ok_or_else(|| RenderError::new("Type error for `path_to_root`, string expected"))? + .to_string(); - let language = languages.0.get(param).ok_or_else(|| { - RenderError::new(format!("Unknown language identifier '{}'", param)) - })?; + let language = languages + .0 + .get(param) + .ok_or_else(|| RenderError::new(format!("Unknown language identifier '{}'", param)))?; let mut href = String::new(); href.push_str(&path_to_root); @@ -51,7 +52,10 @@ pub fn language_option( href.push_str("/"); href.push_str(&rendered_path); - out.write(&format!("")?; diff --git a/src/renderer/html_handlebars/helpers/mod.rs b/src/renderer/html_handlebars/helpers/mod.rs index 8a3421ea..14256f8d 100644 --- a/src/renderer/html_handlebars/helpers/mod.rs +++ b/src/renderer/html_handlebars/helpers/mod.rs @@ -1,4 +1,4 @@ +pub mod language; pub mod navigation; pub mod theme; pub mod toc; -pub mod language; diff --git a/src/renderer/markdown_renderer.rs b/src/renderer/markdown_renderer.rs index 3e49a9cf..7d824f12 100644 --- a/src/renderer/markdown_renderer.rs +++ b/src/renderer/markdown_renderer.rs @@ -1,4 +1,4 @@ -use crate::book::{BookItem, LoadedBook, Book}; +use crate::book::{Book, BookItem, LoadedBook}; use crate::errors::*; use crate::renderer::{RenderContext, Renderer}; use crate::utils;