Make mdbook init output multilingual structure

This commit is contained in:
Ruin0x11 2020-08-28 11:35:42 -07:00
parent 5e223e074e
commit a042cfc72b
6 changed files with 59 additions and 29 deletions

View File

@ -3,7 +3,7 @@ use std::io::Write;
use std::path::PathBuf; use std::path::PathBuf;
use super::MDBook; use super::MDBook;
use crate::config::Config; use crate::config::{Config, Language};
use crate::errors::*; use crate::errors::*;
use crate::theme; use crate::theme;
@ -14,22 +14,46 @@ pub struct BookBuilder {
create_gitignore: bool, create_gitignore: bool,
config: Config, config: Config,
copy_theme: bool, 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 { impl BookBuilder {
/// Create a new `BookBuilder` which will generate a book in the provided /// Create a new `BookBuilder` which will generate a book in the provided
/// root directory. /// root directory.
pub fn new<P: Into<PathBuf>>(root: P) -> BookBuilder { pub fn new<P: Into<PathBuf>>(root: P) -> BookBuilder {
let language_ident = String::from("en");
let mut cfg = Config::default();
add_default_language(&mut cfg, language_ident.clone());
BookBuilder { BookBuilder {
root: root.into(), root: root.into(),
create_gitignore: false, create_gitignore: false,
config: Config::default(), config: cfg,
copy_theme: false, copy_theme: false,
language_ident: language_ident
} }
} }
/// Set the [`Config`] to be used. /// Get the output source directory of the builder.
pub fn with_config(&mut self, cfg: Config) -> &mut BookBuilder { 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.config = cfg;
self self
} }
@ -101,8 +125,8 @@ impl BookBuilder {
File::create(book_toml) File::create(book_toml)
.with_context(|| "Couldn't create book.toml")? .with_context(|| "Couldn't create book.toml")?
.write_all(&cfg) .write_all(&cfg)
.with_context(|| "Unable to write config to book.toml")?; .with_context(|| "Unable to write config to book.toml")?;
Ok(()) Ok(())
} }
@ -172,7 +196,7 @@ impl BookBuilder {
fn create_stub_files(&self) -> Result<()> { fn create_stub_files(&self) -> Result<()> {
debug!("Creating example book contents"); 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"); let summary = src_dir.join("SUMMARY.md");
if !summary.exists() { if !summary.exists() {
@ -193,10 +217,10 @@ impl BookBuilder {
} }
fn create_directory_structure(&self) -> Result<()> { fn create_directory_structure(&self) -> Result<()> {
debug!("Creating directory tree"); debug!("Creating directory tree at {}", self.root.display());
fs::create_dir_all(&self.root)?; 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)?; fs::create_dir_all(&src)?;
let build = self.root.join(&self.config.build.build_dir); let build = self.root.join(&self.config.build.build_dir);

View File

@ -84,7 +84,7 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
} }
builder.build()?; builder.build()?;
println!("\nAll done, no errors..."); println!("\nCreated new book at {}", builder.source_dir().display());
Ok(()) Ok(())
} }

View File

@ -197,8 +197,8 @@ async fn serve(
warp::path::end().map(move || warp::redirect(index_for_language.clone())); warp::path::end().map(move || warp::redirect(index_for_language.clone()));
// BUG: It is not possible to conditionally redirect to the correct 404 // 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 // page depending on the URL using warp, so just redirect to the one in
// default language. // the default language.
// See: https://github.com/seanmonstar/warp/issues/171 // See: https://github.com/seanmonstar/warp/issues/171
let fallback_route = warp::fs::file(build_dir.join(lang_ident).join(file_404)) 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)); .map(|reply| warp::reply::with_status(reply, warp::http::StatusCode::NOT_FOUND));

View File

@ -259,8 +259,8 @@ impl Config {
// Languages have been specified, assume directory structure with // Languages have been specified, assume directory structure with
// language subfolders. // language subfolders.
Some(ref default) => match index { Some(ref default) => match index {
// Make sure that the language we passed was actually // Make sure that the language we passed was actually declared
// declared in the config, and return `None` if not. // in the config, and return an `Err` if not.
Some(lang_ident) => match self.language.0.get(lang_ident.as_ref()) { Some(lang_ident) => match self.language.0.get(lang_ident.as_ref()) {
Some(_) => Ok(Some(lang_ident.as_ref().into())), Some(_) => Ok(Some(lang_ident.as_ref().into())),
None => Err(anyhow!( None => Err(anyhow!(
@ -272,7 +272,7 @@ impl Config {
None => Ok(Some(default.to_string())), 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 { None => match index {
// We passed in a language from the frontend, but the config // We passed in a language from the frontend, but the config
// offers no languages. // offers no languages.
@ -298,8 +298,8 @@ impl Config {
Ok(buf) Ok(buf)
} }
// No default language was configured in book.toml. Preserve // No [language] table was declared in book.toml. Preserve backwards
// backwards compatibility by just returning `src`. // compatibility by just returning `src`.
None => Ok(self.book.src.clone()), None => Ok(self.book.src.clone()),
} }
} }
@ -491,6 +491,11 @@ impl Serialize for Config {
table.insert("rust", rust_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) table.serialize(s)
} }
} }
@ -551,7 +556,7 @@ impl Default for BookConfig {
authors: Vec::new(), authors: Vec::new(),
description: None, description: None,
src: PathBuf::from("src"), src: PathBuf::from("src"),
multilingual: false, multilingual: true,
language: Some(String::from("en")), language: Some(String::from("en")),
} }
} }

View File

@ -53,21 +53,22 @@ impl HtmlHandlebars {
} }
} }
LoadedBook::Single(ref book) => { 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 { 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) => { Some(lang_ident) => {
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
// are, so use that.
None => src_dir.clone(), None => src_dir.clone(),
}; };
self.render_book( self.render_book(
ctx, ctx,
&book, &book,

View File

@ -10,7 +10,7 @@ use tempfile::Builder as TempFileBuilder;
/// are created. /// are created.
#[test] #[test]
fn base_mdbook_init_should_create_default_content() { 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(); let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
for file in &created_files { 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(); let contents = fs::read_to_string(temp.path().join("book.toml")).unwrap();
assert_eq!( assert_eq!(
contents, 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 created_files = vec!["intro.md", "first.md", "outro.md"];
let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap(); 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(); fs::create_dir_all(src_dir.clone()).unwrap();
static SUMMARY: &str = r#"# Summary static SUMMARY: &str = r#"# Summary
@ -66,7 +66,7 @@ fn run_mdbook_init_should_create_content_from_summary() {
/// files, then call `mdbook init`. /// files, then call `mdbook init`.
#[test] #[test]
fn run_mdbook_init_with_custom_book_and_src_locations() { 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(); let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
for file in &created_files { for file in &created_files {