Change book source root depending on language

This commit is contained in:
Ruin0x11 2020-08-27 16:26:07 -07:00
parent e4b443cd4a
commit 24e6d6b605
15 changed files with 111 additions and 24 deletions

View File

@ -29,6 +29,7 @@ use crate::renderer::{CmdRenderer, HtmlHandlebars, MarkdownRenderer, RenderConte
use crate::utils;
use crate::config::{Config, RustEdition};
use crate::build_opts::BuildOpts;
/// The object used to manage and build a book.
pub struct MDBook {
@ -38,6 +39,10 @@ pub struct MDBook {
pub config: Config,
/// A representation of the book's contents in memory.
pub book: Book,
/// Build options passed from frontend.
pub build_opts: BuildOpts,
/// List of renderers to be run on the book.
renderers: Vec<Box<dyn Renderer>>,
/// List of pre-processors to be run on the book.
@ -47,6 +52,12 @@ pub struct MDBook {
impl MDBook {
/// Load a book from its root directory on disk.
pub fn load<P: Into<PathBuf>>(book_root: P) -> Result<MDBook> {
MDBook::load_with_build_opts(book_root, BuildOpts::default())
}
/// Load a book from its root directory on disk, passing in options from the
/// frontend.
pub fn load_with_build_opts<P: Into<PathBuf>>(book_root: P, build_opts: BuildOpts) -> Result<MDBook> {
let book_root = book_root.into();
let config_location = book_root.join("book.toml");
@ -75,11 +86,11 @@ impl MDBook {
}
}
MDBook::load_with_config(book_root, config)
MDBook::load_with_config(book_root, config, build_opts)
}
/// Load a book from its root directory using a custom `Config`.
pub fn load_with_config<P: Into<PathBuf>>(book_root: P, config: Config) -> Result<MDBook> {
/// Load a book from its root directory using a custom config.
pub fn load_with_config<P: Into<PathBuf>>(book_root: P, config: Config, build_opts: BuildOpts) -> Result<MDBook> {
let root = book_root.into();
let src_dir = root.join(&config.book.src);
@ -92,6 +103,7 @@ impl MDBook {
root,
config,
book,
build_opts,
renderers,
preprocessors,
})
@ -102,6 +114,7 @@ impl MDBook {
book_root: P,
config: Config,
summary: Summary,
build_opts: BuildOpts,
) -> Result<MDBook> {
let root = book_root.into();
@ -115,6 +128,7 @@ impl MDBook {
root,
config,
book,
build_opts,
renderers,
preprocessors,
})
@ -185,6 +199,7 @@ impl MDBook {
let mut preprocessed_book = self.book.clone();
let preprocess_ctx = PreprocessorContext::new(
self.root.clone(),
self.build_opts.clone(),
self.config.clone(),
renderer.name().to_string(),
);
@ -201,7 +216,8 @@ impl MDBook {
let mut render_context = RenderContext::new(
self.root.clone(),
preprocessed_book,
preprocessed_book.clone(),
self.build_opts.clone(),
self.config.clone(),
build_dir,
);
@ -241,7 +257,7 @@ impl MDBook {
// FIXME: Is "test" the proper renderer name to use here?
let preprocess_context =
PreprocessorContext::new(self.root.clone(), self.config.clone(), "test".to_string());
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
@ -336,7 +352,8 @@ impl MDBook {
/// Get the directory containing this book's source files.
pub fn source_dir(&self) -> PathBuf {
self.root.join(&self.config.book.src)
let src = self.config.get_localized_src_path(self.build_opts.language_ident.as_ref()).unwrap();
self.root.join(src)
}
/// Get the directory containing the theme resources for the book.

10
src/build_opts.rs Normal file
View File

@ -0,0 +1,10 @@
//! Build options.
/// Build options passed from the frontend to control how the book is built.
/// Separate from `Config`, which is global to all book languages.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
#[serde(default, rename_all = "kebab-case")]
pub struct BuildOpts {
/// Language of the book to render.
pub language_ident: Option<String>,
}

View File

@ -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::MDBook;
@ -22,7 +22,8 @@ pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
// Build command implementation
pub fn execute(args: &ArgMatches) -> Result<()> {
let book_dir = get_book_dir(args);
let mut book = MDBook::load(&book_dir)?;
let opts = get_build_opts(args);
let mut book = MDBook::load_with_build_opts(&book_dir, opts)?;
if let Some(dest_dir) = args.value_of("dest-dir") {
book.config.build.build_dir = dest_dir.into();

View File

@ -1,4 +1,4 @@
use crate::get_book_dir;
use crate::{get_book_dir, get_build_opts};
use clap::{App, Arg, ArgMatches, SubCommand};
use mdbook::errors::Result;
use mdbook::MDBook;
@ -34,7 +34,8 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
.map(std::iter::Iterator::collect)
.unwrap_or_default();
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)?;
if let Some(dest_dir) = args.value_of("dest-dir") {
book.config.build.build_dir = dest_dir.into();

View File

@ -252,6 +252,31 @@ impl Config {
self.get(&key).and_then(Value::as_table)
}
/// Get the source directory of a localized book corresponding to language ident `index`.
pub fn get_localized_src_path<I: AsRef<str>>(&self, index: Option<I>) -> Option<PathBuf> {
match self.language.default_language() {
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(|_| {
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))
}
// 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()),
}
}
}
fn from_legacy(mut table: Value) -> Config {
let mut cfg = Config::default();
@ -354,7 +379,6 @@ impl<'de> Deserialize<'de> for Config {
.count();
if default_languages != 1 {
use serde::de::Error;
return Err(D::Error::custom(
"If languages are specified, exactly one must be set as 'default'"
));
@ -727,10 +751,10 @@ pub struct Language {
impl LanguageConfig {
/// Returns the default language specified in the config.
pub fn default_language(&self) -> Option<&Language> {
pub fn default_language(&self) -> Option<&String> {
self.0.iter()
.find(|(_, lang)| lang.default)
.map(|(_, lang)| lang)
.map(|(lang_ident, _)| lang_ident)
}
}

View File

@ -98,6 +98,7 @@ extern crate serde_json;
extern crate pretty_assertions;
pub mod book;
pub mod build_opts;
pub mod config;
pub mod preprocess;
pub mod renderer;

View File

@ -9,6 +9,7 @@ use clap::{App, AppSettings, Arg, ArgMatches, Shell, SubCommand};
use env_logger::Builder;
use log::LevelFilter;
use mdbook::utils;
use mdbook::build_opts::BuildOpts;
use std::env;
use std::ffi::OsStr;
use std::io::Write;
@ -131,6 +132,14 @@ fn get_book_dir(args: &ArgMatches) -> PathBuf {
}
}
fn get_build_opts(args: &ArgMatches) -> BuildOpts {
let language = args.value_of("language");
BuildOpts {
language_ident: language.map(String::from)
}
}
fn open<P: AsRef<OsStr>>(path: P) {
info!("Opening web browser");
if let Err(e) = open::that(path) {

View File

@ -179,6 +179,7 @@ impl Preprocessor for CmdPreprocessor {
mod tests {
use super::*;
use crate::MDBook;
use crate::build_opts::BuildOpts;
use std::path::Path;
fn guide() -> MDBook {
@ -192,6 +193,7 @@ mod tests {
let md = guide();
let ctx = PreprocessorContext::new(
md.root.clone(),
BuildOpts::default(),
md.config.clone(),
"some-renderer".to_string(),
);

View File

@ -26,7 +26,7 @@ impl Preprocessor for IndexPreprocessor {
}
fn run(&self, ctx: &PreprocessorContext, mut book: Book) -> Result<Book> {
let source_dir = ctx.root.join(&ctx.config.book.src);
let source_dir = ctx.source_dir();
book.for_each_mut(|section: &mut BookItem| {
if let BookItem::Chapter(ref mut ch) = *section {
if let Some(ref mut path) = ch.path {

View File

@ -42,7 +42,7 @@ impl Preprocessor for LinkPreprocessor {
}
fn run(&self, ctx: &PreprocessorContext, mut book: Book) -> Result<Book> {
let src_dir = ctx.root.join(&ctx.config.book.src);
let src_dir = ctx.source_dir();
book.for_each_mut(|section: &mut BookItem| {
if let BookItem::Chapter(ref mut ch) = *section {

View File

@ -10,6 +10,7 @@ mod links;
use crate::book::Book;
use crate::config::Config;
use crate::build_opts::BuildOpts;
use crate::errors::*;
use std::cell::RefCell;
@ -22,6 +23,8 @@ use std::path::PathBuf;
pub struct PreprocessorContext {
/// The location of the book directory on disk.
pub root: PathBuf,
/// The build options passed from the frontend.
pub build_opts: BuildOpts,
/// The book configuration (`book.toml`).
pub config: Config,
/// The `Renderer` this preprocessor is being used with.
@ -36,9 +39,10 @@ pub struct PreprocessorContext {
impl PreprocessorContext {
/// Create a new `PreprocessorContext`.
pub(crate) fn new(root: PathBuf, config: Config, renderer: String) -> Self {
pub(crate) fn new(root: PathBuf, build_opts: BuildOpts, config: Config, renderer: String) -> Self {
PreprocessorContext {
root,
build_opts,
config,
renderer,
mdbook_version: crate::MDBOOK_VERSION.to_string(),
@ -46,6 +50,12 @@ impl PreprocessorContext {
__non_exhaustive: (),
}
}
/// Get the directory containing this book's source files.
pub fn source_dir(&self) -> PathBuf {
let src = self.config.get_localized_src_path(self.build_opts.language_ident.as_ref()).unwrap();
self.root.join(src)
}
}
/// An operation which is run immediately after loading a book into memory and

View File

@ -460,7 +460,7 @@ impl Renderer for HtmlHandlebars {
fn render(&self, ctx: &RenderContext) -> Result<()> {
let book_config = &ctx.config.book;
let html_config = ctx.config.html_config().unwrap_or_default();
let src_dir = ctx.root.join(&ctx.config.book.src);
let src_dir = ctx.source_dir();
let destination = &ctx.destination;
let book = &ctx.book;
let build_dir = ctx.root.join(&ctx.config.build.build_dir);

View File

@ -26,6 +26,7 @@ use std::process::{Command, Stdio};
use crate::book::Book;
use crate::config::Config;
use crate::build_opts::BuildOpts;
use crate::errors::*;
use toml::Value;
@ -58,6 +59,8 @@ pub struct RenderContext {
pub root: PathBuf,
/// A loaded representation of the book itself.
pub book: Book,
/// The build options passed from the frontend.
pub build_opts: BuildOpts,
/// The loaded configuration file.
pub config: Config,
/// Where the renderer *must* put any build artefacts generated. To allow
@ -72,13 +75,14 @@ pub struct RenderContext {
impl RenderContext {
/// Create a new `RenderContext`.
pub fn new<P, Q>(root: P, book: Book, config: Config, destination: Q) -> RenderContext
pub fn new<P, Q>(root: P, book: Book, build_opts: BuildOpts, config: Config, destination: Q) -> RenderContext
where
P: Into<PathBuf>,
Q: Into<PathBuf>,
{
RenderContext {
book,
build_opts,
config,
version: crate::MDBOOK_VERSION.to_string(),
root: root.into(),
@ -90,7 +94,8 @@ impl RenderContext {
/// Get the source directory's (absolute) path on disk.
pub fn source_dir(&self) -> PathBuf {
self.root.join(&self.config.book.src)
let src = self.config.get_localized_src_path(self.build_opts.language_ident.as_ref()).unwrap();
self.root.join(src)
}
/// Load a `RenderContext` from its JSON representation.

View File

@ -2,6 +2,7 @@ mod dummy_book;
use crate::dummy_book::DummyBook;
use mdbook::book::Book;
use mdbook::build_opts::BuildOpts;
use mdbook::config::Config;
use mdbook::errors::*;
use mdbook::preprocess::{Preprocessor, PreprocessorContext};
@ -48,8 +49,9 @@ fn mdbook_runs_preprocessors() {
let temp = DummyBook::new().build().unwrap();
let cfg = Config::default();
let build_opts = BuildOpts::default();
let mut book = MDBook::load_with_config(temp.path(), cfg).unwrap();
let mut book = MDBook::load_with_config(temp.path(), cfg, build_opts).unwrap();
book.with_preprocessor(Spy(Arc::clone(&spy)));
book.build().unwrap();
@ -68,8 +70,9 @@ fn mdbook_runs_renderers() {
let temp = DummyBook::new().build().unwrap();
let cfg = Config::default();
let build_opts = BuildOpts::default();
let mut book = MDBook::load_with_config(temp.path(), cfg).unwrap();
let mut book = MDBook::load_with_config(temp.path(), cfg, build_opts).unwrap();
book.with_renderer(Spy(Arc::clone(&spy)));
book.build().unwrap();

View File

@ -7,6 +7,7 @@ use crate::dummy_book::{assert_contains_strings, assert_doesnt_contain_strings,
use anyhow::Context;
use mdbook::config::Config;
use mdbook::build_opts::BuildOpts;
use mdbook::errors::*;
use mdbook::utils::fs::write_file;
use mdbook::MDBook;
@ -326,8 +327,9 @@ fn failure_on_missing_file() {
let mut cfg = Config::default();
cfg.build.create_missing = false;
let build_opts = BuildOpts::default();
let got = MDBook::load_with_config(temp.path(), cfg);
let got = MDBook::load_with_config(temp.path(), cfg, build_opts);
assert!(got.is_err());
}
@ -339,9 +341,10 @@ fn create_missing_file_with_config() {
let mut cfg = Config::default();
cfg.build.create_missing = true;
let build_opts = BuildOpts::default();
assert!(!temp.path().join("src").join("intro.md").exists());
let _md = MDBook::load_with_config(temp.path(), cfg).unwrap();
let _md = MDBook::load_with_config(temp.path(), cfg, build_opts).unwrap();
assert!(temp.path().join("src").join("intro.md").exists());
}
@ -429,7 +432,8 @@ fn by_default_mdbook_use_index_preprocessor_to_convert_readme_to_index() {
let mut cfg = Config::default();
cfg.set("book.src", "src2")
.expect("Couldn't set config.book.src to \"src2\".");
let md = MDBook::load_with_config(temp.path(), cfg).unwrap();
let build_opts = BuildOpts::default();
let md = MDBook::load_with_config(temp.path(), cfg, build_opts).unwrap();
md.build().unwrap();
let first_index = temp.path().join("book").join("first").join("index.html");