From a042cfc72b0a100b7f0e411cbce5ccde2afeaa50 Mon Sep 17 00:00:00 2001 From: Ruin0x11 Date: Fri, 28 Aug 2020 11:35:42 -0700 Subject: [PATCH] Make `mdbook init` output multilingual structure --- src/book/init.rs | 42 +++++++++++++++----- src/cmd/init.rs | 2 +- src/cmd/serve.rs | 4 +- src/config.rs | 17 +++++--- src/renderer/html_handlebars/hbs_renderer.rs | 15 +++---- tests/init.rs | 8 ++-- 6 files changed, 59 insertions(+), 29 deletions(-) diff --git a/src/book/init.rs b/src/book/init.rs index 264c113d3d..3f489cdd40 100644 --- a/src/book/init.rs +++ b/src/book/init.rs @@ -3,7 +3,7 @@ use std::io::Write; use std::path::PathBuf; use super::MDBook; -use crate::config::Config; +use crate::config::{Config, Language}; use crate::errors::*; use crate::theme; @@ -14,22 +14,46 @@ pub struct BookBuilder { create_gitignore: bool, config: Config, copy_theme: bool, + language_ident: String +} + +fn add_default_language(cfg: &mut Config, language_ident: String) { + let language = Language { + name: String::from("English"), + default: true, + title: None, + authors: None, + description: None, + }; + cfg.language.0.insert(language_ident, language); } impl BookBuilder { /// Create a new `BookBuilder` which will generate a book in the provided /// root directory. pub fn new>(root: P) -> BookBuilder { + let language_ident = String::from("en"); + let mut cfg = Config::default(); + add_default_language(&mut cfg, language_ident.clone()); + BookBuilder { root: root.into(), create_gitignore: false, - config: Config::default(), + config: cfg, copy_theme: false, + language_ident: language_ident } } - /// Set the [`Config`] to be used. - pub fn with_config(&mut self, cfg: Config) -> &mut BookBuilder { + /// Get the output source directory of the builder. + pub fn source_dir(&self) -> PathBuf { + let src = self.config.get_localized_src_path(Some(&self.language_ident)).unwrap(); + self.root.join(src) + } + + /// Set the `Config` to be used. + pub fn with_config(&mut self, mut cfg: Config) -> &mut BookBuilder { + add_default_language(&mut cfg, self.language_ident.clone()); self.config = cfg; self } @@ -101,8 +125,8 @@ impl BookBuilder { File::create(book_toml) .with_context(|| "Couldn't create book.toml")? - .write_all(&cfg) - .with_context(|| "Unable to write config to book.toml")?; + .write_all(&cfg) + .with_context(|| "Unable to write config to book.toml")?; Ok(()) } @@ -172,7 +196,7 @@ impl BookBuilder { fn create_stub_files(&self) -> Result<()> { debug!("Creating example book contents"); - let src_dir = self.root.join(&self.config.book.src); + let src_dir = self.source_dir(); let summary = src_dir.join("SUMMARY.md"); if !summary.exists() { @@ -193,10 +217,10 @@ impl BookBuilder { } fn create_directory_structure(&self) -> Result<()> { - debug!("Creating directory tree"); + debug!("Creating directory tree at {}", self.root.display()); fs::create_dir_all(&self.root)?; - let src = self.root.join(&self.config.book.src); + let src = self.source_dir(); fs::create_dir_all(&src)?; let build = self.root.join(&self.config.build.build_dir); diff --git a/src/cmd/init.rs b/src/cmd/init.rs index ed0aa17d64..bfe13fe0d5 100644 --- a/src/cmd/init.rs +++ b/src/cmd/init.rs @@ -84,7 +84,7 @@ pub fn execute(args: &ArgMatches) -> Result<()> { } builder.build()?; - println!("\nAll done, no errors..."); + println!("\nCreated new book at {}", builder.source_dir().display()); Ok(()) } diff --git a/src/cmd/serve.rs b/src/cmd/serve.rs index d18d338844..d768cadb90 100644 --- a/src/cmd/serve.rs +++ b/src/cmd/serve.rs @@ -197,8 +197,8 @@ async fn serve( 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. + // page depending on the URL using 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)); diff --git a/src/config.rs b/src/config.rs index 8eeced258a..2341cc0624 100644 --- a/src/config.rs +++ b/src/config.rs @@ -259,8 +259,8 @@ impl Config { // Languages have been specified, assume directory structure with // language subfolders. Some(ref default) => match index { - // Make sure that the language we passed was actually - // declared in the config, and return `None` if not. + // Make sure that the language we passed was actually declared + // in the config, and return an `Err` if not. Some(lang_ident) => match self.language.0.get(lang_ident.as_ref()) { Some(_) => Ok(Some(lang_ident.as_ref().into())), None => Err(anyhow!( @@ -272,7 +272,7 @@ impl Config { None => Ok(Some(default.to_string())), }, - // No default language was configured in book.toml. + // No [language] table was declared in book.toml. None => match index { // We passed in a language from the frontend, but the config // offers no languages. @@ -298,8 +298,8 @@ impl Config { Ok(buf) } - // No default language was configured in book.toml. Preserve - // backwards compatibility by just returning `src`. + // No [language] table was declared in book.toml. Preserve backwards + // compatibility by just returning `src`. None => Ok(self.book.src.clone()), } } @@ -491,6 +491,11 @@ impl Serialize for Config { table.insert("rust", rust_config); } + if !self.language.0.is_empty() { + let language_config = Value::try_from(&self.language).expect("should always be serializable"); + table.insert("language", language_config); + } + table.serialize(s) } } @@ -551,7 +556,7 @@ impl Default for BookConfig { authors: Vec::new(), description: None, src: PathBuf::from("src"), - multilingual: false, + multilingual: true, language: Some(String::from("en")), } } diff --git a/src/renderer/html_handlebars/hbs_renderer.rs b/src/renderer/html_handlebars/hbs_renderer.rs index 0eb3a76fb8..eac3c469e8 100644 --- a/src/renderer/html_handlebars/hbs_renderer.rs +++ b/src/renderer/html_handlebars/hbs_renderer.rs @@ -53,21 +53,22 @@ impl HtmlHandlebars { } } LoadedBook::Single(ref book) => { + // `src_dir` points to the root source directory. If this book + // is actually multilingual and we specified a single language + // to build on the command line, then `src_dir` will not be + // pointing at the subdirectory with the specified translation's + // index/summary files. We have to append the language + // identifier to prevent the files from the other translations + // from being copied in the final step. let extra_file_dir = match &ctx.build_opts.language_ident { - // `src_dir` points to the root source directory, not the - // subdirectory with the translation's index/summary files. - // We have to append the language identifier to prevent the - // files from the other translations from being copied in - // the final step. Some(lang_ident) => { 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(), }; + self.render_book( ctx, &book, diff --git a/tests/init.rs b/tests/init.rs index 4deb840194..454f3bf0b9 100644 --- a/tests/init.rs +++ b/tests/init.rs @@ -10,7 +10,7 @@ use tempfile::Builder as TempFileBuilder; /// are created. #[test] fn base_mdbook_init_should_create_default_content() { - let created_files = vec!["book", "src", "src/SUMMARY.md", "src/chapter_1.md"]; + let created_files = vec!["book", "src", "src/en", "src/en/SUMMARY.md", "src/en/chapter_1.md"]; let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap(); for file in &created_files { @@ -28,7 +28,7 @@ fn base_mdbook_init_should_create_default_content() { let contents = fs::read_to_string(temp.path().join("book.toml")).unwrap(); assert_eq!( contents, - "[book]\nauthors = []\nlanguage = \"en\"\nmultilingual = false\nsrc = \"src\"\n" + "[book]\nauthors = []\nlanguage = \"en\"\nmultilingual = true\nsrc = \"src\"\n[language.en]\ndefault = true\nname = \"English\"\n" ); } @@ -39,7 +39,7 @@ fn run_mdbook_init_should_create_content_from_summary() { let created_files = vec!["intro.md", "first.md", "outro.md"]; let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap(); - let src_dir = temp.path().join("src"); + let src_dir = temp.path().join("src").join("en"); fs::create_dir_all(src_dir.clone()).unwrap(); static SUMMARY: &str = r#"# Summary @@ -66,7 +66,7 @@ fn run_mdbook_init_should_create_content_from_summary() { /// files, then call `mdbook init`. #[test] fn run_mdbook_init_with_custom_book_and_src_locations() { - let created_files = vec!["out", "in", "in/SUMMARY.md", "in/chapter_1.md"]; + let created_files = vec!["out", "in", "in/en", "in/en/SUMMARY.md", "in/en/chapter_1.md"]; let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap(); for file in &created_files {