From 93874edebf7dbd6252b3346cb15162f0f7121867 Mon Sep 17 00:00:00 2001 From: Chris Spiegel Date: Wed, 29 Nov 2017 20:02:58 -0800 Subject: [PATCH 1/4] Add a create-missing option to book.toml. --- book-example/src/format/config.md | 19 ++++++++++++++++++- src/bin/build.rs | 4 ++-- src/book/mod.rs | 12 ++++++++---- src/config.rs | 31 +++++++++++++++++++++++++++++++ 4 files changed, 59 insertions(+), 7 deletions(-) diff --git a/book-example/src/format/config.md b/book-example/src/format/config.md index 8b7bdd00..3100b930 100644 --- a/book-example/src/format/config.md +++ b/book-example/src/format/config.md @@ -10,6 +10,9 @@ title = "Example book" author = "John Doe" description = "The example book covers examples." +[build] +create-missing = false + [output.html] destination = "my-example-book" additional-css = ["custom.css"] @@ -45,6 +48,20 @@ src = "my-src" # the source files will be found in `root/my-src` instead of `ro build-dir = "build" ``` +### Build options + +This controls the build process of your book. + +- **create-missing:** By default, any missing files specified in `SUMMARY.md` + will be created when the book is built. If this is `false` then the build + process will instead exit with an error if any files do not exist. + +**book.toml** +```toml +[build] +create-missing = false +``` + ### HTML renderer options The HTML renderer has a couple of options as well. All the options for the renderer need to be specified under the TOML table `[output.html]`. @@ -134,4 +151,4 @@ if let Some(baz) = book_config.get_deserialized::>("output.random.baz" } // start the rendering process -``` \ No newline at end of file +``` diff --git a/src/bin/build.rs b/src/bin/build.rs index f3b595e1..a7278c66 100644 --- a/src/bin/build.rs +++ b/src/bin/build.rs @@ -14,7 +14,7 @@ pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> { book{n}(Defaults to ./book when omitted)'", ) .arg_from_usage( - "--no-create 'Will not create non-existent files linked from SUMMARY.md'", + "--no-create 'Will not create non-existent files linked from SUMMARY.md (deprecated: use book.toml instead)'", ) .arg_from_usage( "[dir] 'A directory for your book{n}(Defaults to Current Directory \ @@ -32,7 +32,7 @@ pub fn execute(args: &ArgMatches) -> Result<()> { } if args.is_present("no-create") { - book.create_missing = false; + book.create_missing = Some(false); } book.build()?; diff --git a/src/book/mod.rs b/src/book/mod.rs index fc757a90..a412bc55 100644 --- a/src/book/mod.rs +++ b/src/book/mod.rs @@ -25,8 +25,10 @@ pub struct MDBook { pub livereload: Option, /// Should `mdbook build` create files referenced from SUMMARY.md if they - /// don't exist - pub create_missing: bool, + /// don't exist? If `None`, use the setting specified in `book.toml`, + /// otherwise use this. + /// This is for backward compatibility only. + pub create_missing: Option, } impl MDBook { @@ -71,7 +73,7 @@ impl MDBook { renderer: Box::new(HtmlHandlebars::new()), livereload: None, - create_missing: true, + create_missing: None, } } @@ -179,7 +181,9 @@ impl MDBook { let path = self.get_source().join(&ch.path); if !path.exists() { - if !self.create_missing { + let create_missing = self.create_missing.unwrap_or(self.config.build.create_missing); + + if !create_missing { return Err( format!("'{}' referenced from SUMMARY.md does not exist.", path.to_string_lossy()).into(), ); diff --git a/src/config.rs b/src/config.rs index fd2ed2f0..dac67b66 100644 --- a/src/config.rs +++ b/src/config.rs @@ -12,6 +12,7 @@ use errors::*; pub struct Config { /// Metadata about the book. pub book: BookConfig, + pub build: BuildConfig, rest: Table, } @@ -171,8 +172,14 @@ impl<'de> Deserialize<'de> for Config { let book: BookConfig = table.remove("book") .and_then(|value| value.try_into().ok()) .unwrap_or_default(); + + let build: BuildConfig = table.remove("build") + .and_then(|value| value.try_into().ok()) + .unwrap_or_default(); + Ok(Config { book: book, + build: build, rest: table, }) } @@ -217,6 +224,23 @@ impl Default for BookConfig { } } +/// Configuration for the build procedure. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(default, rename_all = "kebab-case")] +pub struct BuildConfig { + /// Should non-existent markdown files specified in `SETTINGS.md` be created + /// if they don't exist? + pub create_missing: bool, +} + +impl Default for BuildConfig { + fn default() -> BuildConfig { + BuildConfig { + create_missing: true, + } + } +} + #[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] #[serde(default, rename_all = "kebab-case")] pub struct HtmlConfig { @@ -250,6 +274,9 @@ mod tests { src = "source" build-dir = "outputs" + [build] + create-missing = false + [output.html] theme = "./themedir" curly-quotes = true @@ -274,6 +301,9 @@ mod tests { build_dir: PathBuf::from("outputs"), ..Default::default() }; + let build_should_be = BuildConfig { + create_missing: false, + }; let playpen_should_be = Playpen { editable: true, editor: PathBuf::from("ace"), @@ -290,6 +320,7 @@ mod tests { let got = Config::from_str(src).unwrap(); assert_eq!(got.book, book_should_be); + assert_eq!(got.build, build_should_be); assert_eq!(got.html_config().unwrap(), html_should_be); } From b0b09bad3f8bbed1059b6f4fade47082f43f23a7 Mon Sep 17 00:00:00 2001 From: Chris Spiegel Date: Thu, 30 Nov 2017 07:26:30 -0800 Subject: [PATCH 2/4] Clean up build configuration. This rolls all "create missing" handling into BuildConfig, and moves the build-dir option from the "book" table to the "build" table. Some documentation cleanup surrounding the build table is also updated. --- book-example/src/format/config.md | 13 +++++++------ src/bin/build.rs | 5 +++-- src/bin/serve.rs | 2 +- src/bin/watch.rs | 2 +- src/book/mod.rs | 13 ++----------- src/config.rs | 27 +++++++++++++++++---------- src/lib.rs | 2 +- tests/init.rs | 2 +- 8 files changed, 33 insertions(+), 33 deletions(-) diff --git a/book-example/src/format/config.md b/book-example/src/format/config.md index 3100b930..d1980a21 100644 --- a/book-example/src/format/config.md +++ b/book-example/src/format/config.md @@ -11,10 +11,10 @@ author = "John Doe" description = "The example book covers examples." [build] +build-dir = "my-example-book" create-missing = false [output.html] -destination = "my-example-book" additional-css = ["custom.css"] ``` @@ -35,8 +35,6 @@ This is general information about your book. - **src:** By default, the source directory is found in the directory named `src` directly under the root folder. But this is configurable with the `src` key in the configuration file. -- **build-dir:** The directory to put the rendered book in. By default this is - `book/` in the book's root directory. **book.toml** ```toml @@ -45,20 +43,23 @@ title = "Example book" authors = ["John Doe", "Jane Doe"] description = "The example book covers examples." src = "my-src" # the source files will be found in `root/my-src` instead of `root/src` -build-dir = "build" ``` ### Build options This controls the build process of your book. +- **build-dir:** The directory to put the rendered book in. By default this is + `book/` in the book's root directory. - **create-missing:** By default, any missing files specified in `SUMMARY.md` - will be created when the book is built. If this is `false` then the build - process will instead exit with an error if any files do not exist. + will be created when the book is built (i.e. `create-missing = true`). If this + is `false` then the build process will instead exit with an error if any files + do not exist. **book.toml** ```toml [build] +build-dir = "build" create-missing = false ``` diff --git a/src/bin/build.rs b/src/bin/build.rs index a7278c66..54dffbd1 100644 --- a/src/bin/build.rs +++ b/src/bin/build.rs @@ -28,11 +28,12 @@ pub fn execute(args: &ArgMatches) -> Result<()> { let mut book = MDBook::new(&book_dir).read_config()?; if let Some(dest_dir) = args.value_of("dest-dir") { - book.config.book.build_dir = PathBuf::from(dest_dir); + book.config.build.build_dir = PathBuf::from(dest_dir); } + // This flag is deprecated in favor of being set via `book.toml`. if args.is_present("no-create") { - book.create_missing = Some(false); + book.config.build.create_missing = false; } book.build()?; diff --git a/src/bin/serve.rs b/src/bin/serve.rs index 87099411..ac1ba144 100644 --- a/src/bin/serve.rs +++ b/src/bin/serve.rs @@ -52,7 +52,7 @@ pub fn execute(args: &ArgMatches) -> Result<()> { let mut book = MDBook::new(&book_dir).read_config()?; if let Some(dest_dir) = args.value_of("dest-dir") { - book.config.book.build_dir = PathBuf::from(dest_dir); + book.config.build.build_dir = PathBuf::from(dest_dir); } let port = args.value_of("port").unwrap_or("3000"); diff --git a/src/bin/watch.rs b/src/bin/watch.rs index 39862d18..29772715 100644 --- a/src/bin/watch.rs +++ b/src/bin/watch.rs @@ -30,7 +30,7 @@ pub fn execute(args: &ArgMatches) -> Result<()> { let mut book = MDBook::new(&book_dir).read_config()?; if let Some(dest_dir) = args.value_of("dest-dir") { - book.config.book.build_dir = PathBuf::from(dest_dir); + book.config.build.build_dir = PathBuf::from(dest_dir); } if args.is_present("open") { diff --git a/src/book/mod.rs b/src/book/mod.rs index a412bc55..ada24fe1 100644 --- a/src/book/mod.rs +++ b/src/book/mod.rs @@ -23,12 +23,6 @@ pub struct MDBook { renderer: Box, pub livereload: Option, - - /// Should `mdbook build` create files referenced from SUMMARY.md if they - /// don't exist? If `None`, use the setting specified in `book.toml`, - /// otherwise use this. - /// This is for backward compatibility only. - pub create_missing: Option, } impl MDBook { @@ -73,7 +67,6 @@ impl MDBook { renderer: Box::new(HtmlHandlebars::new()), livereload: None, - create_missing: None, } } @@ -181,9 +174,7 @@ impl MDBook { let path = self.get_source().join(&ch.path); if !path.exists() { - let create_missing = self.create_missing.unwrap_or(self.config.build.create_missing); - - if !create_missing { + if !self.config.build.create_missing { return Err( format!("'{}' referenced from SUMMARY.md does not exist.", path.to_string_lossy()).into(), ); @@ -388,7 +379,7 @@ impl MDBook { } pub fn get_destination(&self) -> PathBuf { - self.root.join(&self.config.book.build_dir) + self.root.join(&self.config.build.build_dir) } pub fn get_source(&self) -> PathBuf { diff --git a/src/config.rs b/src/config.rs index dac67b66..d23e7a80 100644 --- a/src/config.rs +++ b/src/config.rs @@ -92,7 +92,7 @@ impl Config { get_and_insert!(table, "description" => cfg.book.description); // This complicated chain of and_then's is so we can move - // "output.html.destination" to "book.build_dir" and parse it into a + // "output.html.destination" to "build.build_dir" and parse it into a // PathBuf. let destination: Option = table.get_mut("output") .and_then(|output| output.as_table_mut()) @@ -102,7 +102,7 @@ impl Config { .and_then(|dest| dest.try_into().ok()); if let Some(dest) = destination { - cfg.book.build_dir = dest; + cfg.build.build_dir = dest; } cfg.rest = table; @@ -163,8 +163,10 @@ impl<'de> Deserialize<'de> for Config { warn!("It looks like you are using the legacy book.toml format."); warn!("We'll parse it for now, but you should probably convert to the new format."); warn!("See the mdbook documentation for more details, although as a rule of thumb"); - warn!("just move all top level configuration entries like `title`, `author` and "); - warn!("`description` under a table called `[book]` and it should all work."); + warn!("just move all top level configuration entries like `title`, `author` and"); + warn!("`description` under a table called `[book]`, move the `destination` entry"); + warn!("from `[output.html]`, renamed to `build-dir`, under a table called"); + warn!("`[build]`, and it should all work."); warn!("Documentation: http://rust-lang-nursery.github.io/mdBook/format/config.html"); return Ok(Config::from_legacy(table)); } @@ -205,8 +207,6 @@ pub struct BookConfig { pub description: Option, /// Location of the book source relative to the book's root directory. pub src: PathBuf, - /// Where to put built artefacts relative to the book's root directory. - pub build_dir: PathBuf, /// Does this book support more than one language? pub multilingual: bool, } @@ -218,7 +218,6 @@ impl Default for BookConfig { authors: Vec::new(), description: None, src: PathBuf::from("src"), - build_dir: PathBuf::from("book"), multilingual: false, } } @@ -228,6 +227,8 @@ impl Default for BookConfig { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(default, rename_all = "kebab-case")] pub struct BuildConfig { + /// Where to put built artefacts relative to the book's root directory. + pub build_dir: PathBuf, /// Should non-existent markdown files specified in `SETTINGS.md` be created /// if they don't exist? pub create_missing: bool, @@ -236,6 +237,7 @@ pub struct BuildConfig { impl Default for BuildConfig { fn default() -> BuildConfig { BuildConfig { + build_dir: PathBuf::from("book"), create_missing: true, } } @@ -272,9 +274,9 @@ mod tests { description = "A completely useless book" multilingual = true src = "source" - build-dir = "outputs" [build] + build-dir = "outputs" create-missing = false [output.html] @@ -298,10 +300,10 @@ mod tests { description: Some(String::from("A completely useless book")), multilingual: true, src: PathBuf::from("source"), - build_dir: PathBuf::from("outputs"), ..Default::default() }; let build_should_be = BuildConfig { + build_dir: PathBuf::from("outputs"), create_missing: false, }; let playpen_should_be = Playpen { @@ -397,11 +399,15 @@ mod tests { "Create book from markdown files. Like Gitbook but implemented in Rust", )), authors: vec![String::from("Mathieu David")], - build_dir: PathBuf::from("my-book"), src: PathBuf::from("./source"), ..Default::default() }; + let build_should_be = BuildConfig { + build_dir: PathBuf::from("my-book"), + create_missing: true, + }; + let html_should_be = HtmlConfig { theme: Some(PathBuf::from("my-theme")), curly_quotes: true, @@ -413,6 +419,7 @@ mod tests { let got = Config::from_str(src).unwrap(); assert_eq!(got.book, book_should_be); + assert_eq!(got.build, build_should_be); assert_eq!(got.html_config().unwrap(), html_should_be); } } diff --git a/src/lib.rs b/src/lib.rs index 2cf5e3e7..3991ae95 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,7 +28,7 @@ //! //! // tweak the book configuration a bit //! md.config.book.src = PathBuf::from("source"); -//! md.config.book.build_dir = PathBuf::from("book"); +//! md.config.build.build_dir = PathBuf::from("book"); //! //! // Render the book //! md.build().unwrap(); diff --git a/tests/init.rs b/tests/init.rs index 0dff7db3..e35edee6 100644 --- a/tests/init.rs +++ b/tests/init.rs @@ -42,7 +42,7 @@ fn run_mdbook_init_with_custom_book_and_src_locations() { let mut md = MDBook::new(temp.path()); md.config.book.src = PathBuf::from("in"); - md.config.book.build_dir = PathBuf::from("out"); + md.config.build.build_dir = PathBuf::from("out"); md.init().unwrap(); From dacc274e0d4cd8ccaaac3529e5dffb212a1c8080 Mon Sep 17 00:00:00 2001 From: Chris Spiegel Date: Thu, 30 Nov 2017 07:54:43 -0800 Subject: [PATCH 3/4] Add myself to the contributors list. --- book-example/src/misc/contributors.md | 1 + 1 file changed, 1 insertion(+) diff --git a/book-example/src/misc/contributors.md b/book-example/src/misc/contributors.md index de01338e..f0184c15 100644 --- a/book-example/src/misc/contributors.md +++ b/book-example/src/misc/contributors.md @@ -12,3 +12,4 @@ If you have contributed to mdBook and I forgot to add you, don't hesitate to add - [funnkill](https://github.com/funkill) - Fu Gangqiang ([FuGangqiang](https://github.com/FuGangqiang)) - [Michael-F-Bryan](https://github.com/Michael-F-Bryan) +- [Chris Spiegel](https://github.com/cspiegel) From 803df90efa37eb89a5df3dfe7403d4510630fa25 Mon Sep 17 00:00:00 2001 From: Chris Spiegel Date: Sat, 2 Dec 2017 22:53:05 -0800 Subject: [PATCH 4/4] Add tests to check create-missing. --- tests/rendered_output.rs | 51 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/tests/rendered_output.rs b/tests/rendered_output.rs index fa2b155b..f8904bdd 100644 --- a/tests/rendered_output.rs +++ b/tests/rendered_output.rs @@ -2,17 +2,21 @@ extern crate mdbook; #[macro_use] extern crate pretty_assertions; extern crate select; +extern crate tempdir; extern crate walkdir; mod dummy_book; use dummy_book::{assert_contains_strings, DummyBook}; +use std::fs::{File, remove_file}; +use std::io::Write; use std::path::Path; use std::ffi::OsStr; use walkdir::{DirEntry, WalkDir, WalkDirIterator}; use select::document::Document; use select::predicate::{Class, Name, Predicate}; +use tempdir::TempDir; use mdbook::errors::*; use mdbook::utils::fs::file_to_string; use mdbook::MDBook; @@ -227,3 +231,50 @@ fn check_spacers() { doc.find(Class("chapter").descendant(Name("li").and(Class("spacer")))).count(); assert_eq!(num_spacers, should_be); } + +/// Ensure building fails if `create-missing` is false and one of the files does +/// not exist. +#[test] +fn failure_on_missing_file() { + let (md, _temp) = create_missing_setup(Some(false)); + + // On failure, `build()` does not return a specific error, so assume + // any error is a failure due to a missing file. + assert!(md.read_config().unwrap().build().is_err()); +} + +/// Ensure a missing file is created if `create-missing` is true. +#[test] +fn create_missing_file_with_config() { + let (md, temp) = create_missing_setup(Some(true)); + + md.read_config().unwrap().build().unwrap(); + assert!(temp.path().join("src").join("intro.md").exists()); +} + +/// Ensure a missing file is created if `create-missing` is not set (the default +/// is true). +#[test] +fn create_missing_file_without_config() { + let (md, temp) = create_missing_setup(None); + + md.read_config().unwrap().build().unwrap(); + assert!(temp.path().join("src").join("intro.md").exists()); +} + +fn create_missing_setup(create_missing: Option) -> (MDBook, TempDir) { + let temp = DummyBook::new().build().unwrap(); + let md = MDBook::new(temp.path()); + + let mut file = File::create(temp.path().join("book.toml")).unwrap(); + match create_missing { + Some(true) => file.write_all(b"[build]\ncreate-missing = true\n").unwrap(), + Some(false) => file.write_all(b"[build]\ncreate-missing = false\n").unwrap(), + None => (), + } + file.flush().unwrap(); + + remove_file(temp.path().join("src").join("intro.md")).unwrap(); + + (md, temp) +}