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
This commit is contained in:
Ruin0x11 2020-08-28 02:05:21 -07:00
parent 85ab4d39cd
commit 282fdaa3ac
8 changed files with 138 additions and 69 deletions

View File

@ -18,27 +18,37 @@ pub fn load_book<P: AsRef<Path>>(
if cfg.language.has_localized_dir_structure() { if cfg.language.has_localized_dir_structure() {
match build_opts.language_ident { match build_opts.language_ident {
// Build a single book's translation. // 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. // Build all available translations at once.
None => { None => {
let mut translations = HashMap::new(); let mut translations = HashMap::new();
for (lang_ident, _) in cfg.language.0.iter() { 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); translations.insert(lang_ident.clone(), book);
} }
Ok(LoadedBook::Localized(LocalizedBooks(translations))) Ok(LoadedBook::Localized(LocalizedBooks(translations)))
} }
} }
} else { } 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<P: AsRef<Path>>(root_dir: P, cfg: &Config, language_ident: &Option<String>) -> Result<Book> { fn load_single_book_translation<P: AsRef<Path>>(
let localized_src_dir = root_dir.as_ref().join( root_dir: P,
cfg.get_localized_src_path(language_ident.as_ref()) cfg: &Config,
.unwrap(), language_ident: &Option<String>,
); ) -> Result<Book> {
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 fallback_src_dir = root_dir.as_ref().join(cfg.get_fallback_src_path());
let summary_md = localized_src_dir.join("SUMMARY.md"); let summary_md = localized_src_dir.join("SUMMARY.md");
@ -172,9 +182,7 @@ impl LocalizedBooks {
items.extend(book.iter().items); items.extend(book.iter().items);
} }
BookItems { BookItems { items: items }
items: items
}
} }
/// Recursively apply a closure to each item in the book, allowing you to /// Recursively apply a closure to each item in the book, allowing you to
@ -239,7 +247,7 @@ impl LoadedBook {
pub fn first(&self) -> &Book { pub fn first(&self) -> &Book {
match self { match self {
LoadedBook::Localized(books) => books.0.iter().next().unwrap().1, LoadedBook::Localized(books) => books.0.iter().next().unwrap().1,
LoadedBook::Single(book) => &book LoadedBook::Single(book) => &book,
} }
} }
} }

View File

@ -10,15 +10,15 @@ mod book;
mod init; mod init;
mod summary; 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::init::BookBuilder;
pub use self::summary::{parse_summary, Link, SectionNumber, Summary, SummaryItem}; pub use self::summary::{parse_summary, Link, SectionNumber, Summary, SummaryItem};
use std::collections::HashMap;
use std::io::Write; use std::io::Write;
use std::path::PathBuf; use std::path::PathBuf;
use std::process::Command; use std::process::Command;
use std::string::ToString; use std::string::ToString;
use std::collections::HashMap;
use tempfile::Builder as TempFileBuilder; use tempfile::Builder as TempFileBuilder;
use tempfile::TempDir; use tempfile::TempDir;
use toml::Value; use toml::Value;
@ -133,8 +133,12 @@ impl MDBook {
.unwrap(), .unwrap(),
); );
let fallback_src_dir = root.join(config.get_fallback_src_path()); let fallback_src_dir = root.join(config.get_fallback_src_path());
let book = let book = LoadedBook::Single(book::load_book_from_disk(
LoadedBook::Single(book::load_book_from_disk(&summary, localized_src_dir, fallback_src_dir, &config)?); &summary,
localized_src_dir,
fallback_src_dir,
&config,
)?);
let renderers = determine_renderers(&config); let renderers = determine_renderers(&config);
let preprocessors = determine_preprocessors(&config)?; let preprocessors = determine_preprocessors(&config)?;
@ -223,13 +227,16 @@ impl MDBook {
let mut new_books = HashMap::new(); let mut new_books = HashMap::new();
for (ident, book) in books.0.iter() { 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); new_books.insert(ident.clone(), preprocessed_book);
} }
LoadedBook::Localized(LocalizedBooks(new_books)) 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(); let name = renderer.name();

View File

@ -92,7 +92,7 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
Some(lang_ident) => Some(lang_ident.clone()), Some(lang_ident) => Some(lang_ident.clone()),
// If not, it will be at the root. // If not, it will be at the root.
None => None, None => None,
} },
}; };
let sockaddr: SocketAddr = address let sockaddr: SocketAddr = address
@ -177,9 +177,6 @@ async fn serve(
}); });
// A warp Filter that serves from the filesystem. // A warp Filter that serves from the filesystem.
let book_route = warp::fs::dir(build_dir.clone()); 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| { std::panic::set_hook(Box::new(move |panic_info| {
// exit if serve panics // exit if serve panics
@ -189,13 +186,31 @@ async fn serve(
if let Some(lang_ident) = language { if let Some(lang_ident) = language {
// Redirect root to the default translation directory, if serving a localized book. // 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. // NOTE: This can't be `/{lang_ident}`, or the static assets won't get loaded.
let index_for_language = format!("/{}/index.html", lang_ident).parse::<Uri>().unwrap(); // BUG: Redirects get cached if you change the --language parameter,
let redirect_to_index = warp::path::end().map(move || warp::redirect(index_for_language.clone())); // meaning you'll get a 404 unless you disable cache in the developer tools.
let routes = livereload.or(redirect_to_index).or(book_route).or(fallback_route); let index_for_language = format!("/{}/index.html", lang_ident)
.parse::<Uri>()
.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; 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); let routes = livereload.or(book_route).or(fallback_route);
warp::serve(routes).run(address).await; warp::serve(routes).run(address).await;
}; };

View File

@ -199,7 +199,8 @@ mod tests {
); );
let mut buffer = Vec::new(); 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(); let (got_ctx, got_book) = CmdPreprocessor::parse_input(buffer.as_slice()).unwrap();

View File

@ -62,19 +62,30 @@ impl HtmlHandlebars {
let mut path = src_dir.clone(); let mut path = src_dir.clone();
path.push(lang_ident); path.push(lang_ident);
path path
}, }
// `src_dir` is where index.html and the other extra files // `src_dir` is where index.html and the other extra files
// are, so use that. // 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(()) Ok(())
} }
fn render_book<'a>(&self, fn render_book<'a>(
&self,
ctx: &RenderContext, ctx: &RenderContext,
book: &Book, book: &Book,
src_dir: &PathBuf, src_dir: &PathBuf,
@ -86,7 +97,14 @@ impl HtmlHandlebars {
theme: &Theme, theme: &Theme,
) -> Result<()> { ) -> Result<()> {
let build_dir = ctx.root.join(build_dir); 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 // Print version
let mut print_content = String::new(); let mut print_content = String::new();
@ -110,7 +128,14 @@ impl HtmlHandlebars {
// Render 404 page // Render 404 page
if html_config.input_404 != Some("".to_string()) { 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 // Print version
@ -123,7 +148,8 @@ impl HtmlHandlebars {
debug!("Render template"); debug!("Render template");
let rendered = handlebars.render("index", &data)?; 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())?; utils::fs::write_file(&destination, "print.html", rendered.as_bytes())?;
debug!("Creating print.html ✓"); debug!("Creating print.html ✓");
@ -147,12 +173,17 @@ impl HtmlHandlebars {
.context("Unable to emit redirects")?; .context("Unable to emit redirects")?;
// Copy all remaining files, avoid a recursive copy from/to the book build dir // 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(()) Ok(())
} }
fn render_item( fn render_item(
&self, &self,
item: &BookItem, item: &BookItem,
@ -461,7 +492,10 @@ impl HtmlHandlebars {
handlebars.register_helper("previous", Box::new(helpers::navigation::previous)); handlebars.register_helper("previous", Box::new(helpers::navigation::previous));
handlebars.register_helper("next", Box::new(helpers::navigation::next)); handlebars.register_helper("next", Box::new(helpers::navigation::next));
handlebars.register_helper("theme_option", Box::new(helpers::theme::theme_option)); 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 /// Copy across any additional CSS and JavaScript files which the book
@ -842,7 +876,7 @@ fn make_data(
languages.sort(); languages.sort();
data.insert("languages".to_owned(), json!(languages)); data.insert("languages".to_owned(), json!(languages));
data.insert("language_config".to_owned(), json!(config.language.clone())); data.insert("language_config".to_owned(), json!(config.language.clone()));
}, }
LoadedBook::Single(_) => { LoadedBook::Single(_) => {
data.insert("languages_enabled".to_owned(), json!(false)); data.insert("languages_enabled".to_owned(), json!(false));
} }

View File

@ -1,6 +1,6 @@
use crate::config::LanguageConfig;
use handlebars::{Context, Handlebars, Helper, Output, RenderContext, RenderError}; use handlebars::{Context, Handlebars, Helper, Output, RenderContext, RenderError};
use std::path::Path; use std::path::Path;
use crate::config::LanguageConfig;
pub fn language_option( pub fn language_option(
h: &Helper<'_, '_>, h: &Helper<'_, '_>,
@ -40,9 +40,10 @@ pub fn language_option(
.ok_or_else(|| RenderError::new("Type error for `path_to_root`, string expected"))? .ok_or_else(|| RenderError::new("Type error for `path_to_root`, string expected"))?
.to_string(); .to_string();
let language = languages.0.get(param).ok_or_else(|| { let language = languages
RenderError::new(format!("Unknown language identifier '{}'", param)) .0
})?; .get(param)
.ok_or_else(|| RenderError::new(format!("Unknown language identifier '{}'", param)))?;
let mut href = String::new(); let mut href = String::new();
href.push_str(&path_to_root); href.push_str(&path_to_root);
@ -51,7 +52,10 @@ pub fn language_option(
href.push_str("/"); href.push_str("/");
href.push_str(&rendered_path); href.push_str(&rendered_path);
out.write(&format!("<a href=\"{}\"><button role=\"menuitem\" class=\"language\" id=\"light\">", href))?; out.write(&format!(
"<a href=\"{}\"><button role=\"menuitem\" class=\"language\" id=\"light\">",
href
))?;
out.write(&language.name)?; out.write(&language.name)?;
out.write("</button></a>")?; out.write("</button></a>")?;

View File

@ -1,4 +1,4 @@
pub mod language;
pub mod navigation; pub mod navigation;
pub mod theme; pub mod theme;
pub mod toc; pub mod toc;
pub mod language;

View File

@ -1,4 +1,4 @@
use crate::book::{BookItem, LoadedBook, Book}; use crate::book::{Book, BookItem, LoadedBook};
use crate::errors::*; use crate::errors::*;
use crate::renderer::{RenderContext, Renderer}; use crate::renderer::{RenderContext, Renderer};
use crate::utils; use crate::utils;