From 96d9271d6409e86e3ef6e59b1491fc67c84797ee Mon Sep 17 00:00:00 2001 From: Ruin0x11 Date: Thu, 27 Aug 2020 19:44:24 -0700 Subject: [PATCH] Specify language for book in command line args - Add a [language] table to book.toml. Each key in the table defines a new language with `name` and `default` properties. - Changes the directory structure of localized books. If the [language] table exists, mdBook will now assume the src/ directory contains subdirectories named after the keys in [language]. The behavior is backwards-compatible if you don't specify [language]. - Specify which language of book to build using the -l/--language argument to `mdbook build` and similar, or omit to use the default language. - Specify the default language by setting the `default` property to `true` in an entry in [language]. Exactly one language must have `default` set to `true` if the [language] table is defined. - Each language has its own SUMMARY.md. It can include links to files not in other translations. If a link in SUMMARY.md refers to a nonexistent file that is specified in the default language, the renderer will gracefully degrade the link to the default language's page. If it still doesn't exist, the config's `create_missing` option will be respected instead. --- .gitignore | 1 + src/book/book.rs | 155 +++++++++++++++--- src/book/mod.rs | 34 +++- src/cmd/build.rs | 5 + src/cmd/clean.rs | 10 +- src/cmd/serve.rs | 10 +- src/cmd/test.rs | 3 + src/cmd/watch.rs | 10 +- src/config.rs | 90 +++++++--- src/main.rs | 4 +- src/preprocess/cmd.rs | 2 +- src/preprocess/mod.rs | 9 +- src/renderer/mod.rs | 10 +- tests/localized_book/book.toml | 32 ++++ tests/localized_book/src/en/README.md | 3 + tests/localized_book/src/en/SUMMARY.md | 7 + tests/localized_book/src/en/chapter/1.md | 2 + tests/localized_book/src/en/chapter/2.md | 2 + tests/localized_book/src/en/chapter/README.md | 1 + tests/localized_book/src/en/untranslated.md | 3 + tests/localized_book/src/ja/README.md | 3 + tests/localized_book/src/ja/SUMMARY.md | 8 + tests/localized_book/src/ja/chapter/1.md | 1 + tests/localized_book/src/ja/chapter/2.md | 1 + tests/localized_book/src/ja/chapter/3.md | 5 + tests/localized_book/src/ja/chapter/README.md | 1 + tests/rendered_output.rs | 2 +- 27 files changed, 348 insertions(+), 66 deletions(-) create mode 100644 tests/localized_book/book.toml create mode 100644 tests/localized_book/src/en/README.md create mode 100644 tests/localized_book/src/en/SUMMARY.md create mode 100644 tests/localized_book/src/en/chapter/1.md create mode 100644 tests/localized_book/src/en/chapter/2.md create mode 100644 tests/localized_book/src/en/chapter/README.md create mode 100644 tests/localized_book/src/en/untranslated.md create mode 100644 tests/localized_book/src/ja/README.md create mode 100644 tests/localized_book/src/ja/SUMMARY.md create mode 100644 tests/localized_book/src/ja/chapter/1.md create mode 100644 tests/localized_book/src/ja/chapter/2.md create mode 100644 tests/localized_book/src/ja/chapter/3.md create mode 100644 tests/localized_book/src/ja/chapter/README.md diff --git a/.gitignore b/.gitignore index 36614264..e64ea827 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ guide/book .vscode tests/dummy_book/book/ +tests/localized_book/book/ # Ignore Jetbrains specific files. .idea/ diff --git a/src/book/book.rs b/src/book/book.rs index da2a0a3c..325383a0 100644 --- a/src/book/book.rs +++ b/src/book/book.rs @@ -5,27 +5,37 @@ use std::io::{Read, Write}; use std::path::{Path, PathBuf}; use super::summary::{parse_summary, Link, SectionNumber, Summary, SummaryItem}; -use crate::config::BuildConfig; +use crate::build_opts::BuildOpts; +use crate::config::Config; use crate::errors::*; /// Load a book into memory from its `src/` directory. -pub fn load_book>(src_dir: P, cfg: &BuildConfig) -> Result { - let src_dir = src_dir.as_ref(); - let summary_md = src_dir.join("SUMMARY.md"); +pub fn load_book>( + root_dir: P, + cfg: &Config, + build_opts: &BuildOpts, +) -> Result { + let localized_src_dir = root_dir.as_ref().join( + cfg.get_localized_src_path(build_opts.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"); let mut summary_content = String::new(); File::open(&summary_md) - .with_context(|| format!("Couldn't open SUMMARY.md in {:?} directory", src_dir))? + .with_context(|| format!("Couldn't open SUMMARY.md in {:?} directory", localized_src_dir))? .read_to_string(&mut summary_content)?; let summary = parse_summary(&summary_content) .with_context(|| format!("Summary parsing failed for file={:?}", summary_md))?; if cfg.create_missing { - create_missing(src_dir, &summary).with_context(|| "Unable to create missing chapters")?; + create_missing(localized_src_dir, &summary).with_context(|| "Unable to create missing chapters")?; } - load_book_from_disk(&summary, src_dir) + load_book_from_disk(&summary, localized_src_dir, fallback_src_dir, cfg) } fn create_missing(src_dir: &Path, summary: &Summary) -> Result<()> { @@ -208,9 +218,13 @@ impl Chapter { /// /// You need to pass in the book's source directory because all the links in /// `SUMMARY.md` give the chapter locations relative to it. -pub(crate) fn load_book_from_disk>(summary: &Summary, src_dir: P) -> Result { +pub(crate) fn load_book_from_disk>( + summary: &Summary, + localized_src_dir: P, + fallback_src_dir: P, + cfg: &Config, +) -> Result { debug!("Loading the book from disk"); - let src_dir = src_dir.as_ref(); let prefix = summary.prefix_chapters.iter(); let numbered = summary.numbered_chapters.iter(); @@ -221,7 +235,13 @@ pub(crate) fn load_book_from_disk>(summary: &Summary, src_dir: P) let mut chapters = Vec::new(); for summary_item in summary_items { - let chapter = load_summary_item(summary_item, src_dir, Vec::new())?; + let chapter = load_summary_item( + summary_item, + localized_src_dir.as_ref(), + fallback_src_dir.as_ref(), + Vec::new(), + cfg, + )?; chapters.push(chapter); } @@ -233,13 +253,16 @@ pub(crate) fn load_book_from_disk>(summary: &Summary, src_dir: P) fn load_summary_item + Clone>( item: &SummaryItem, - src_dir: P, + localized_src_dir: P, + fallback_src_dir: P, parent_names: Vec, + cfg: &Config, ) -> Result { match item { SummaryItem::Separator => Ok(BookItem::Separator), SummaryItem::Link(ref link) => { - load_chapter(link, src_dir, parent_names).map(BookItem::Chapter) + load_chapter(link, localized_src_dir, fallback_src_dir, parent_names, cfg) + .map(BookItem::Chapter) } SummaryItem::PartTitle(title) => Ok(BookItem::PartTitle(title.clone())), } @@ -247,20 +270,34 @@ fn load_summary_item + Clone>( fn load_chapter>( link: &Link, - src_dir: P, + localized_src_dir: P, + fallback_src_dir: P, parent_names: Vec, + cfg: &Config, ) -> Result { - let src_dir = src_dir.as_ref(); + let src_dir_localized = localized_src_dir.as_ref(); + let src_dir_fallback = fallback_src_dir.as_ref(); let mut ch = if let Some(ref link_location) = link.location { debug!("Loading {} ({})", link.name, link_location.display()); - let location = if link_location.is_absolute() { + let mut src_dir = src_dir_localized; + let mut location = if link_location.is_absolute() { link_location.clone() } else { src_dir.join(link_location) }; + if !location.exists() && !link_location.is_absolute() { + src_dir = src_dir_fallback; + location = src_dir.join(link_location); + debug!("Falling back to {}", location.display()); + } + if !location.exists() && cfg.build.create_missing { + create_missing(&location, &link) + .with_context(|| "Unable to create missing chapters")?; + } + let mut f = File::open(&location) .with_context(|| format!("Chapter file not found, {}", link_location.display()))?; @@ -290,7 +327,15 @@ fn load_chapter>( let sub_items = link .nested_items .iter() - .map(|i| load_summary_item(i, src_dir, sub_item_parents.clone())) + .map(|i| { + load_summary_item( + i, + src_dir_localized, + src_dir_fallback, + sub_item_parents.clone(), + cfg, + ) + }) .collect::>>()?; ch.sub_items = sub_items; @@ -347,7 +392,7 @@ mod tests { this is some dummy text. And here is some \ - more text. +more text. "; /// Create a dummy `Link` in a temporary directory. @@ -389,6 +434,7 @@ And here is some \ #[test] fn load_a_single_chapter_from_disk() { let (link, temp_dir) = dummy_link(); + let cfg = Config::default(); let should_be = Chapter::new( "Chapter 1", DUMMY_SRC.to_string(), @@ -396,7 +442,7 @@ And here is some \ Vec::new(), ); - let got = load_chapter(&link, temp_dir.path(), Vec::new()).unwrap(); + let got = load_chapter(&link, temp_dir.path(), temp_dir.path(), Vec::new(), &cfg).unwrap(); assert_eq!(got, should_be); } @@ -427,7 +473,7 @@ And here is some \ fn cant_load_a_nonexistent_chapter() { let link = Link::new("Chapter 1", "/foo/bar/baz.md"); - let got = load_chapter(&link, "", Vec::new()); + let got = load_chapter(&link, "", "", Vec::new(), &Config::default()); assert!(got.is_err()); } @@ -444,6 +490,7 @@ And here is some \ parent_names: vec![String::from("Chapter 1")], sub_items: Vec::new(), }; + let cfg = Config::default(); let should_be = BookItem::Chapter(Chapter { name: String::from("Chapter 1"), content: String::from(DUMMY_SRC), @@ -458,7 +505,14 @@ And here is some \ ], }); - let got = load_summary_item(&SummaryItem::Link(root), temp.path(), Vec::new()).unwrap(); + let got = load_summary_item( + &SummaryItem::Link(root), + temp.path(), + temp.path(), + Vec::new(), + &cfg, + ) + .unwrap(); assert_eq!(got, should_be); } @@ -469,6 +523,7 @@ And here is some \ numbered_chapters: vec![SummaryItem::Link(link)], ..Default::default() }; + let cfg = Config::default(); let should_be = Book { sections: vec![BookItem::Chapter(Chapter { name: String::from("Chapter 1"), @@ -480,7 +535,7 @@ And here is some \ ..Default::default() }; - let got = load_book_from_disk(&summary, temp.path()).unwrap(); + let got = load_book_from_disk(&summary, temp.path(), temp.path(), &cfg).unwrap(); assert_eq!(got, should_be); } @@ -611,8 +666,9 @@ And here is some \ ..Default::default() }; + let cfg = Config::default(); - let got = load_book_from_disk(&summary, temp.path()); + let got = load_book_from_disk(&summary, temp.path(), temp.path(), &cfg); assert!(got.is_err()); } @@ -630,8 +686,61 @@ And here is some \ })], ..Default::default() }; + let cfg = Config::default(); + + let got = load_book_from_disk(&summary, temp.path(), temp.path(), &cfg); + assert!(got.is_err()); + } + + #[test] + fn can_load_a_nonexistent_chapter_with_fallback() { + let (_, temp_localized) = dummy_link(); + let chapter_path = temp_localized.path().join("chapter_1.md"); + fs::remove_file(&chapter_path).unwrap(); + + let (_, temp_fallback) = dummy_link(); + + let link_relative = Link::new("Chapter 1", "chapter_1.md"); + + let summary = Summary { + numbered_chapters: vec![SummaryItem::Link(link_relative)], + ..Default::default() + }; + let mut cfg = Config::default(); + cfg.build.create_missing = false; + let should_be = Book { + sections: vec![BookItem::Chapter(Chapter { + name: String::from("Chapter 1"), + content: String::from(DUMMY_SRC), + path: Some(PathBuf::from("chapter_1.md")), + ..Default::default() + })], + ..Default::default() + }; + + let got = load_book_from_disk(&summary, temp_localized.path(), temp_fallback.path(), &cfg) + .unwrap(); + + assert_eq!(got, should_be); + } + + #[test] + fn cannot_load_a_nonexistent_absolute_link_with_fallback() { + let (link_absolute, temp_localized) = dummy_link(); + let chapter_path = temp_localized.path().join("chapter_1.md"); + fs::remove_file(&chapter_path).unwrap(); + + let (_, temp_fallback) = dummy_link(); + + let summary = Summary { + numbered_chapters: vec![SummaryItem::Link(link_absolute)], + ..Default::default() + }; + let mut cfg = Config::default(); + cfg.build.create_missing = false; + + let got = load_book_from_disk(&summary, temp_localized.path(), temp_fallback.path(), &cfg); - let got = load_book_from_disk(&summary, temp.path()); assert!(got.is_err()); } } diff --git a/src/book/mod.rs b/src/book/mod.rs index e1750e14..308aa267 100644 --- a/src/book/mod.rs +++ b/src/book/mod.rs @@ -28,8 +28,8 @@ use crate::preprocess::{ use crate::renderer::{CmdRenderer, HtmlHandlebars, MarkdownRenderer, RenderContext, Renderer}; use crate::utils; -use crate::config::{Config, RustEdition}; use crate::build_opts::BuildOpts; +use crate::config::{Config, RustEdition}; /// The object used to manage and build a book. pub struct MDBook { @@ -57,7 +57,10 @@ impl MDBook { /// Load a book from its root directory on disk, passing in options from the /// frontend. - pub fn load_with_build_opts>(book_root: P, build_opts: BuildOpts) -> Result { + pub fn load_with_build_opts>( + book_root: P, + build_opts: BuildOpts, + ) -> Result { let book_root = book_root.into(); let config_location = book_root.join("book.toml"); @@ -90,11 +93,14 @@ impl MDBook { } /// Load a book from its root directory using a custom config. - pub fn load_with_config>(book_root: P, config: Config, build_opts: BuildOpts) -> Result { + pub fn load_with_config>( + book_root: P, + config: Config, + build_opts: BuildOpts, + ) -> Result { let root = book_root.into(); - let src_dir = root.join(&config.book.src); - let book = book::load_book(&src_dir, &config.build)?; + let book = book::load_book(&root, &config, &build_opts)?; let renderers = determine_renderers(&config); let preprocessors = determine_preprocessors(&config)?; @@ -118,8 +124,14 @@ impl MDBook { ) -> Result { let root = book_root.into(); - let src_dir = root.join(&config.book.src); - let book = book::load_book_from_disk(&summary, &src_dir)?; + let localized_src_dir = root.join( + config + .get_localized_src_path(build_opts.language_ident.as_ref()) + .unwrap(), + ); + let fallback_src_dir = root.join(config.get_fallback_src_path()); + let book = + book::load_book_from_disk(&summary, localized_src_dir, fallback_src_dir, &config)?; let renderers = determine_renderers(&config); let preprocessors = determine_preprocessors(&config)?; @@ -256,8 +268,12 @@ impl MDBook { let temp_dir = TempFileBuilder::new().prefix("mdbook-").tempdir()?; // FIXME: Is "test" the proper renderer name to use here? - let preprocess_context = - PreprocessorContext::new(self.root.clone(), self.build_opts.clone(), self.config.clone(), "test".to_string()); + let preprocess_context = PreprocessorContext::new( + self.root.clone(), + self.build_opts.clone(), + self.config.clone(), + "test".to_string(), + ); let book = LinkPreprocessor::new().run(&preprocess_context, self.book.clone())?; // Index Preprocessor is disabled so that chapter paths continue to point to the diff --git a/src/cmd/build.rs b/src/cmd/build.rs index edc3be44..90e8a88a 100644 --- a/src/cmd/build.rs +++ b/src/cmd/build.rs @@ -17,6 +17,11 @@ pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> { (Defaults to the Current Directory when omitted)'", ) .arg_from_usage("-o, --open 'Opens the compiled book in a web browser'") + .arg_from_usage( + "-l, --language=[language] 'Language to render the compiled book in.{n}\ + Only valid if the [languages] table in the config is not empty.{n}\ + If omitted, defaults to the language with `default` set to true.'", + ) } // Build command implementation diff --git a/src/cmd/clean.rs b/src/cmd/clean.rs index b58f937e..ae736c79 100644 --- a/src/cmd/clean.rs +++ b/src/cmd/clean.rs @@ -1,4 +1,4 @@ -use crate::get_book_dir; +use crate::{get_book_dir, get_build_opts}; use anyhow::Context; use clap::{App, ArgMatches, SubCommand}; use mdbook::MDBook; @@ -18,12 +18,18 @@ pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> { "[dir] 'Root directory for the book{n}\ (Defaults to the Current Directory when omitted)'", ) + .arg_from_usage( + "-l, --language=[language] 'Language to render the compiled book in.{n}\ + Only valid if the [languages] table in the config is not empty.{n}\ + If omitted, defaults to the language with `default` set to true.'", + ) } // Clean command implementation pub fn execute(args: &ArgMatches) -> mdbook::errors::Result<()> { let book_dir = get_book_dir(args); - let book = MDBook::load(&book_dir)?; + let build_opts = get_build_opts(args); + let book = MDBook::load_with_build_opts(&book_dir, build_opts)?; let dir_to_remove = match args.value_of("dest-dir") { Some(dest_dir) => dest_dir.into(), diff --git a/src/cmd/serve.rs b/src/cmd/serve.rs index c5394f8a..1ea3214d 100644 --- a/src/cmd/serve.rs +++ b/src/cmd/serve.rs @@ -1,6 +1,6 @@ #[cfg(feature = "watch")] use super::watch; -use crate::{get_book_dir, open}; +use crate::{get_book_dir, get_build_opts, open}; use clap::{App, Arg, ArgMatches, SubCommand}; use futures_util::sink::SinkExt; use futures_util::StreamExt; @@ -49,12 +49,18 @@ pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> { .help("Port to use for HTTP connections"), ) .arg_from_usage("-o, --open 'Opens the book server in a web browser'") + .arg_from_usage( + "-l, --language=[language] 'Language to render the compiled book in.{n}\ + Only valid if the [languages] table in the config is not empty.{n}\ + If omitted, defaults to the language with `default` set to true.'", + ) } // Serve command implementation pub fn execute(args: &ArgMatches) -> Result<()> { let book_dir = get_book_dir(args); - let mut book = MDBook::load(&book_dir)?; + let build_opts = get_build_opts(args); + let mut book = MDBook::load_with_build_opts(&book_dir, build_opts)?; let port = args.value_of("port").unwrap(); let hostname = args.value_of("hostname").unwrap(); diff --git a/src/cmd/test.rs b/src/cmd/test.rs index c52ebfa0..0a7c7f57 100644 --- a/src/cmd/test.rs +++ b/src/cmd/test.rs @@ -25,6 +25,9 @@ pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> { .multiple(true) .empty_values(false) .help("A comma-separated list of directories to add to {n}the crate search path when building tests")) + .arg_from_usage("-l, --language=[language] 'Language to render the compiled book in.{n}\ + Only valid if the [languages] table in the config is not empty.{n}\ + If omitted, defaults to the language with `default` set to true.'") } // test command implementation diff --git a/src/cmd/watch.rs b/src/cmd/watch.rs index b27516b0..5a724b57 100644 --- a/src/cmd/watch.rs +++ b/src/cmd/watch.rs @@ -1,4 +1,4 @@ -use crate::{get_book_dir, open}; +use crate::{get_book_dir, get_build_opts, open}; use clap::{App, ArgMatches, SubCommand}; use mdbook::errors::Result; use mdbook::utils; @@ -23,12 +23,18 @@ pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> { (Defaults to the Current Directory when omitted)'", ) .arg_from_usage("-o, --open 'Open the compiled book in a web browser'") + .arg_from_usage( + "-l, --language=[language] 'Language to render the compiled book in.{n}\ + Only valid if the [languages] table in the config is not empty.{n}\ + If omitted, defaults to the language with `default` set to true.'", + ) } // Watch command implementation pub fn execute(args: &ArgMatches) -> Result<()> { let book_dir = get_book_dir(args); - let mut book = MDBook::load(&book_dir)?; + let build_opts = get_build_opts(args); + let mut book = MDBook::load_with_build_opts(&book_dir, build_opts)?; let update_config = |book: &mut MDBook| { if let Some(dest_dir) = args.value_of("dest-dir") { diff --git a/src/config.rs b/src/config.rs index b2c88eb5..c13fab3b 100644 --- a/src/config.rs +++ b/src/config.rs @@ -49,6 +49,7 @@ #![deny(missing_docs)] +use anyhow::anyhow; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::collections::HashMap; use std::env; @@ -253,27 +254,66 @@ impl Config { } /// Get the source directory of a localized book corresponding to language ident `index`. - pub fn get_localized_src_path>(&self, index: Option) -> Option { + pub fn get_localized_src_path>(&self, index: Option) -> Result { match self.language.default_language() { + // Languages have been specified, assume directory structure with + // language subfolders. Some(default) => match index { // Make sure that the language we passed was actually // declared in the config, and return `None` if not. - Some(lang_ident) => self.language.0.get(lang_ident.as_ref()).map(|_| { + Some(lang_ident) => match self.language.0.get(lang_ident.as_ref()) { + Some(_) => { + let mut buf = PathBuf::new(); + buf.push(self.book.src.clone()); + buf.push(lang_ident.as_ref()); + Ok(buf) + } + None => Err(anyhow!( + "Expected [language.{}] to be declared in book.toml", + lang_ident.as_ref() + )), + }, + // Use the default specified in book.toml. + None => { let mut buf = PathBuf::new(); buf.push(self.book.src.clone()); - buf.push(lang_ident.as_ref()); - buf - }), - // Use the default specified in book.toml. - None => Some(PathBuf::from(default)) - } + buf.push(default); + Ok(buf) + } + }, // No default language was configured in book.toml. Preserve // backwards compatibility by just returning `src`. None => match index { - Some(_) => None, - None => Some(self.book.src.clone()), + // We passed in a language from the frontend, but the config + // offers no languages. + Some(lang_ident) => Err(anyhow!( + "No [language] table in book.toml, expected [language.{}] to be declared", + lang_ident.as_ref() + )), + // Default to previous non-localized behavior. + None => Ok(self.book.src.clone()), + }, + } + } + + /// Get the fallback source directory of a book. For example, if chapters + /// are missing in a localization, the links will gracefully degrade to the + /// files that exist in this directory. + pub fn get_fallback_src_path(&self) -> PathBuf { + match self.language.default_language() { + // Languages have been specified, assume directory structure with + // language subfolders. + Some(default) => { + let mut buf = PathBuf::new(); + buf.push(self.book.src.clone()); + buf.push(default); + buf } + + // No default language was configured in book.toml. Preserve + // backwards compatibility by just returning `src`. + None => self.book.src.clone(), } } @@ -373,14 +413,11 @@ impl<'de> Deserialize<'de> for Config { .unwrap_or_default(); if !language.0.is_empty() { - let default_languages = language.0 - .iter() - .filter(|(_, lang)| lang.default) - .count(); + let default_languages = language.0.iter().filter(|(_, lang)| lang.default).count(); if default_languages != 1 { return Err(D::Error::custom( - "If languages are specified, exactly one must be set as 'default'" + "If languages are specified, exactly one must be set as 'default'", )); } } @@ -752,9 +789,10 @@ pub struct Language { impl LanguageConfig { /// Returns the default language specified in the config. pub fn default_language(&self) -> Option<&String> { - self.0.iter() - .find(|(_, lang)| lang.default) - .map(|(lang_ident, _)| lang_ident) + self.0 + .iter() + .find(|(_, lang)| lang.default) + .map(|(lang_ident, _)| lang_ident) } } @@ -874,8 +912,20 @@ mod tests { ..Default::default() }; let mut language_should_be = LanguageConfig::default(); - language_should_be.0.insert(String::from("en"), Language { name: String::from("English"), default: true }); - language_should_be.0.insert(String::from("fr"), Language { name: String::from("Français"), default: false }); + language_should_be.0.insert( + String::from("en"), + Language { + name: String::from("English"), + default: true, + }, + ); + language_should_be.0.insert( + String::from("fr"), + Language { + name: String::from("Français"), + default: false, + }, + ); let got = Config::from_str(src).unwrap(); diff --git a/src/main.rs b/src/main.rs index f0c9bd3c..94c37831 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,8 +8,8 @@ use chrono::Local; use clap::{App, AppSettings, Arg, ArgMatches, Shell, SubCommand}; use env_logger::Builder; use log::LevelFilter; -use mdbook::utils; use mdbook::build_opts::BuildOpts; +use mdbook::utils; use std::env; use std::ffi::OsStr; use std::io::Write; @@ -136,7 +136,7 @@ fn get_build_opts(args: &ArgMatches) -> BuildOpts { let language = args.value_of("language"); BuildOpts { - language_ident: language.map(String::from) + language_ident: language.map(String::from), } } diff --git a/src/preprocess/cmd.rs b/src/preprocess/cmd.rs index dbcda528..b84af3f6 100644 --- a/src/preprocess/cmd.rs +++ b/src/preprocess/cmd.rs @@ -178,8 +178,8 @@ impl Preprocessor for CmdPreprocessor { #[cfg(test)] mod tests { use super::*; - use crate::MDBook; use crate::build_opts::BuildOpts; + use crate::MDBook; use std::path::Path; fn guide() -> MDBook { diff --git a/src/preprocess/mod.rs b/src/preprocess/mod.rs index 7da381de..0fdeaa92 100644 --- a/src/preprocess/mod.rs +++ b/src/preprocess/mod.rs @@ -9,8 +9,8 @@ mod index; mod links; use crate::book::Book; -use crate::config::Config; use crate::build_opts::BuildOpts; +use crate::config::Config; use crate::errors::*; use std::cell::RefCell; @@ -39,7 +39,12 @@ pub struct PreprocessorContext { impl PreprocessorContext { /// Create a new `PreprocessorContext`. - pub(crate) fn new(root: PathBuf, build_opts: BuildOpts, config: Config, renderer: String) -> Self { + pub(crate) fn new( + root: PathBuf, + build_opts: BuildOpts, + config: Config, + renderer: String, + ) -> Self { PreprocessorContext { root, build_opts, diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 5f41dbae..78cca280 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -25,8 +25,8 @@ use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; use crate::book::Book; -use crate::config::Config; use crate::build_opts::BuildOpts; +use crate::config::Config; use crate::errors::*; use toml::Value; @@ -75,7 +75,13 @@ pub struct RenderContext { impl RenderContext { /// Create a new `RenderContext`. - pub fn new(root: P, book: Book, build_opts: BuildOpts, config: Config, destination: Q) -> RenderContext + pub fn new( + root: P, + book: Book, + build_opts: BuildOpts, + config: Config, + destination: Q, + ) -> RenderContext where P: Into, Q: Into, diff --git a/tests/localized_book/book.toml b/tests/localized_book/book.toml new file mode 100644 index 00000000..c513aade --- /dev/null +++ b/tests/localized_book/book.toml @@ -0,0 +1,32 @@ +[book] +title = "Localized Book" +description = "Testing mdBook localization features" +authors = ["Ruin0x11"] +language = "en" + +[rust] +edition = "2018" + +[output.html] +mathjax-support = true +site-url = "/mdBook/" + +[output.html.playground] +editable = true +line-numbers = true + +[output.html.search] +limit-results = 20 +use-boolean-and = true +boost-title = 2 +boost-hierarchy = 2 +boost-paragraph = 1 +expand = true +heading-split-level = 2 + +[language.en] +name = "English" +default = true + +[language.ja] +name = "日本語" diff --git a/tests/localized_book/src/en/README.md b/tests/localized_book/src/en/README.md new file mode 100644 index 00000000..c43c8b58 --- /dev/null +++ b/tests/localized_book/src/en/README.md @@ -0,0 +1,3 @@ +# Localized Book + +This is a test of the book localization features. diff --git a/tests/localized_book/src/en/SUMMARY.md b/tests/localized_book/src/en/SUMMARY.md new file mode 100644 index 00000000..904cfbf9 --- /dev/null +++ b/tests/localized_book/src/en/SUMMARY.md @@ -0,0 +1,7 @@ +# Summary + +- [README](README.md) +- [Chapter 1](chapter/README.md) + - [Section 1](chapter/1.md) + - [Section 2](chapter/2.md) +- [Untranslated Chapter](untranslated.md) diff --git a/tests/localized_book/src/en/chapter/1.md b/tests/localized_book/src/en/chapter/1.md new file mode 100644 index 00000000..a0e9a831 --- /dev/null +++ b/tests/localized_book/src/en/chapter/1.md @@ -0,0 +1,2 @@ +# First section. + diff --git a/tests/localized_book/src/en/chapter/2.md b/tests/localized_book/src/en/chapter/2.md new file mode 100644 index 00000000..17378866 --- /dev/null +++ b/tests/localized_book/src/en/chapter/2.md @@ -0,0 +1,2 @@ +# Second section. + diff --git a/tests/localized_book/src/en/chapter/README.md b/tests/localized_book/src/en/chapter/README.md new file mode 100644 index 00000000..0809d650 --- /dev/null +++ b/tests/localized_book/src/en/chapter/README.md @@ -0,0 +1 @@ +# First chapter page. diff --git a/tests/localized_book/src/en/untranslated.md b/tests/localized_book/src/en/untranslated.md new file mode 100644 index 00000000..08d515ca --- /dev/null +++ b/tests/localized_book/src/en/untranslated.md @@ -0,0 +1,3 @@ +# Untranslated chapter. + +This chapter is not available in any translation. If things work correctly, you should see this page written in the fallback language (English) if the other translations list it on their summary page. diff --git a/tests/localized_book/src/ja/README.md b/tests/localized_book/src/ja/README.md new file mode 100644 index 00000000..77229d77 --- /dev/null +++ b/tests/localized_book/src/ja/README.md @@ -0,0 +1,3 @@ +# 本の翻訳 + +これは本の翻訳のテストです。 diff --git a/tests/localized_book/src/ja/SUMMARY.md b/tests/localized_book/src/ja/SUMMARY.md new file mode 100644 index 00000000..79938511 --- /dev/null +++ b/tests/localized_book/src/ja/SUMMARY.md @@ -0,0 +1,8 @@ +# 目次 + +- [README](README.md) +- [第一章](chapter/README.md) + - [第一節](chapter/1.md) + - [第二節](chapter/2.md) + - [第三節](chapter/3.md) +- [Untranslated Chapter](untranslated.md) diff --git a/tests/localized_book/src/ja/chapter/1.md b/tests/localized_book/src/ja/chapter/1.md new file mode 100644 index 00000000..eae183f1 --- /dev/null +++ b/tests/localized_book/src/ja/chapter/1.md @@ -0,0 +1 @@ +# 第一節。 diff --git a/tests/localized_book/src/ja/chapter/2.md b/tests/localized_book/src/ja/chapter/2.md new file mode 100644 index 00000000..f23ac37b --- /dev/null +++ b/tests/localized_book/src/ja/chapter/2.md @@ -0,0 +1 @@ +# 第二節。 diff --git a/tests/localized_book/src/ja/chapter/3.md b/tests/localized_book/src/ja/chapter/3.md new file mode 100644 index 00000000..923550af --- /dev/null +++ b/tests/localized_book/src/ja/chapter/3.md @@ -0,0 +1,5 @@ +# 第三節。 + +実は、このページは英語バージョンに存在しません。 + +This page doesn't exist in the English translation. It is unique to this translation only. diff --git a/tests/localized_book/src/ja/chapter/README.md b/tests/localized_book/src/ja/chapter/README.md new file mode 100644 index 00000000..00e762ff --- /dev/null +++ b/tests/localized_book/src/ja/chapter/README.md @@ -0,0 +1 @@ +# 第一章のページ。 diff --git a/tests/rendered_output.rs b/tests/rendered_output.rs index ebae94e7..4a49cbbe 100644 --- a/tests/rendered_output.rs +++ b/tests/rendered_output.rs @@ -6,8 +6,8 @@ mod dummy_book; use crate::dummy_book::{assert_contains_strings, assert_doesnt_contain_strings, DummyBook}; use anyhow::Context; -use mdbook::config::Config; use mdbook::build_opts::BuildOpts; +use mdbook::config::Config; use mdbook::errors::*; use mdbook::utils::fs::write_file; use mdbook::MDBook;