diff --git a/src/book/book.rs b/src/book/book.rs index 8ec2e5ca..e806f9fe 100644 --- a/src/book/book.rs +++ b/src/book/book.rs @@ -2,26 +2,59 @@ use std::fmt::{self, Display, Formatter}; use std::path::{Path, PathBuf}; use std::collections::VecDeque; use std::fs::File; -use std::io::Read; +use std::io::{Read, Write}; use super::summary::{parse_summary, Link, SectionNumber, Summary, SummaryItem}; +use config::BuildConfig; use errors::*; /// Load a book into memory from its `src/` directory. -pub fn load_book>(src_dir: P) -> Result { +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"); let mut summary_content = String::new(); - File::open(summary_md).chain_err(|| "Couldn't open SUMMARY.md")? - .read_to_string(&mut summary_content)?; + File::open(summary_md) + .chain_err(|| "Couldn't open SUMMARY.md")? + .read_to_string(&mut summary_content)?; let summary = parse_summary(&summary_content).chain_err(|| "Summary parsing failed")?; + if cfg.create_missing { + create_missing(&src_dir, &summary).chain_err(|| "Unable to create missing chapters")?; + } + load_book_from_disk(&summary, src_dir) } +fn create_missing(src_dir: &Path, summary: &Summary) -> Result<()> { + let mut items: Vec<_> = summary + .prefix_chapters + .iter() + .chain(summary.numbered_chapters.iter()) + .chain(summary.suffix_chapters.iter()) + .collect(); + + while !items.is_empty() { + let next = items.pop().expect("already checked"); + + if let SummaryItem::Link(ref link) = *next { + let filename = src_dir.join(&link.location); + if !filename.exists() { + debug!("[*] Creating missing file {}", filename.display()); + + let mut f = File::create(&filename)?; + writeln!(f, "# {}", link.name)?; + } + + items.extend(&link.nested_items); + } + } + + Ok(()) +} + /// A dumb tree structure representing a book. /// @@ -124,22 +157,23 @@ fn load_chapter>(link: &Link, src_dir: P) -> Result { src_dir.join(&link.location) }; - let mut f = File::open(&location).chain_err(|| { - format!("Chapter file not found, {}", link.location.display()) - })?; + let mut f = File::open(&location) + .chain_err(|| format!("Chapter file not found, {}", link.location.display()))?; let mut content = String::new(); f.read_to_string(&mut content)?; - let stripped = location.strip_prefix(&src_dir) - .expect("Chapters are always inside a book"); + let stripped = location + .strip_prefix(&src_dir) + .expect("Chapters are always inside a book"); let mut ch = Chapter::new(&link.name, content, stripped); ch.number = link.number.clone(); - let sub_items = link.nested_items.iter() - .map(|i| load_summary_item(i, src_dir)) - .collect::>>()?; + let sub_items = link.nested_items + .iter() + .map(|i| load_summary_item(i, src_dir)) + .collect::>>()?; ch.sub_items = sub_items; @@ -206,9 +240,10 @@ And here is some \ let temp = TempDir::new("book").unwrap(); let chapter_path = temp.path().join("chapter_1.md"); - File::create(&chapter_path).unwrap() - .write(DUMMY_SRC.as_bytes()) - .unwrap(); + File::create(&chapter_path) + .unwrap() + .write(DUMMY_SRC.as_bytes()) + .unwrap(); let link = Link::new("Chapter 1", chapter_path); @@ -221,9 +256,10 @@ And here is some \ let second_path = temp_dir.path().join("second.md"); - File::create(&second_path).unwrap() - .write_all("Hello World!".as_bytes()) - .unwrap(); + File::create(&second_path) + .unwrap() + .write_all("Hello World!".as_bytes()) + .unwrap(); let mut second = Link::new("Nested Chapter 1", &second_path); @@ -356,11 +392,12 @@ And here is some \ assert_eq!(got.len(), 5); // checking the chapter names are in the order should be sufficient here... - let chapter_names: Vec = got.into_iter().filter_map(|i| match *i { - BookItem::Chapter(ref ch) => Some(ch.name.clone()), - _ => None, - }) - .collect(); + let chapter_names: Vec = got.into_iter() + .filter_map(|i| match *i { + BookItem::Chapter(ref ch) => Some(ch.name.clone()), + _ => None, + }) + .collect(); let should_be: Vec<_> = vec![ String::from("Chapter 1"), String::from("Hello World"), diff --git a/src/book/mod.rs b/src/book/mod.rs index d49f4580..58f19dae 100644 --- a/src/book/mod.rs +++ b/src/book/mod.rs @@ -3,6 +3,7 @@ mod book; mod init; pub use self::book::{Book, BookItem, BookItems, Chapter}; +pub use self::summary::SectionNumber; pub use self::init::BookBuilder; use std::path::{Path, PathBuf}; @@ -39,8 +40,15 @@ impl MDBook { Config::default() }; + MDBook::load_with_config(book_root, config) + } + + /// Load a book from its root directory using a custom config. + pub fn load_with_config>(book_root: P, config: Config) -> Result { + let book_root = book_root.into(); + let src_dir = book_root.join(&config.book.src); - let book = book::load_book(&src_dir)?; + let book = book::load_book(&src_dir, &config.build)?; Ok(MDBook { root: book_root, diff --git a/tests/rendered_output.rs b/tests/rendered_output.rs index 31b5228e..3dd16794 100644 --- a/tests/rendered_output.rs +++ b/tests/rendered_output.rs @@ -2,7 +2,6 @@ extern crate mdbook; #[macro_use] extern crate pretty_assertions; extern crate select; -extern crate tempdir; extern crate walkdir; mod dummy_book; @@ -15,9 +14,9 @@ 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::config::Config; use mdbook::MDBook; @@ -254,33 +253,29 @@ fn check_spacers() { /// Ensure building fails if `create-missing` is false and one of the files does /// not exist. #[test] -#[ignore] fn failure_on_missing_file() { - let (mut md, _temp) = create_missing_setup(false); + let temp = DummyBook::new().build().unwrap(); + fs::remove_file(temp.path().join("src").join("intro.md")).unwrap(); - // On failure, `build()` does not return a specific error, so assume - // any error is a failure due to a missing file. - assert!(md.build().is_err()); + let mut cfg = Config::default(); + cfg.build.create_missing = false; + + let got = MDBook::load_with_config(temp.path(), cfg); + assert!(got.is_err()); } /// Ensure a missing file is created if `create-missing` is true. #[test] -#[ignore] fn create_missing_file_with_config() { - let (mut md, temp) = create_missing_setup(true); - - md.build().unwrap(); - assert!(temp.path().join("src").join("intro.md").exists()); -} - -fn create_missing_setup(create_missing: bool) -> (MDBook, TempDir) { let temp = DummyBook::new().build().unwrap(); - let mut md = MDBook::load(temp.path()).unwrap(); - - md.config.build.create_missing = create_missing; fs::remove_file(temp.path().join("src").join("intro.md")).unwrap(); - (md, temp) + let mut cfg = Config::default(); + cfg.build.create_missing = true; + + assert!(!temp.path().join("src").join("intro.md").exists()); + let _md = MDBook::load_with_config(temp.path(), cfg).unwrap(); + assert!(temp.path().join("src").join("intro.md").exists()); } /// This makes sure you can include a Rust file with `{{#playpen example.rs}}`.