Put the `create_missing` feature back in

This commit is contained in:
Michael Bryan 2017-12-11 10:32:35 +11:00
parent 9950f69c48
commit ace0b51fb6
No known key found for this signature in database
GPG Key ID: E9C602B0D9A998DC
3 changed files with 83 additions and 43 deletions

View File

@ -2,26 +2,59 @@ use std::fmt::{self, Display, Formatter};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::collections::VecDeque; use std::collections::VecDeque;
use std::fs::File; use std::fs::File;
use std::io::Read; use std::io::{Read, Write};
use super::summary::{parse_summary, Link, SectionNumber, Summary, SummaryItem}; use super::summary::{parse_summary, Link, SectionNumber, Summary, SummaryItem};
use config::BuildConfig;
use errors::*; use errors::*;
/// Load a book into memory from its `src/` directory. /// Load a book into memory from its `src/` directory.
pub fn load_book<P: AsRef<Path>>(src_dir: P) -> Result<Book> { pub fn load_book<P: AsRef<Path>>(src_dir: P, cfg: &BuildConfig) -> Result<Book> {
let src_dir = src_dir.as_ref(); let src_dir = src_dir.as_ref();
let summary_md = src_dir.join("SUMMARY.md"); let summary_md = src_dir.join("SUMMARY.md");
let mut summary_content = String::new(); let mut summary_content = String::new();
File::open(summary_md).chain_err(|| "Couldn't open SUMMARY.md")? File::open(summary_md)
.chain_err(|| "Couldn't open SUMMARY.md")?
.read_to_string(&mut summary_content)?; .read_to_string(&mut summary_content)?;
let summary = parse_summary(&summary_content).chain_err(|| "Summary parsing failed")?; 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) 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. /// A dumb tree structure representing a book.
/// ///
@ -124,20 +157,21 @@ fn load_chapter<P: AsRef<Path>>(link: &Link, src_dir: P) -> Result<Chapter> {
src_dir.join(&link.location) src_dir.join(&link.location)
}; };
let mut f = File::open(&location).chain_err(|| { let mut f = File::open(&location)
format!("Chapter file not found, {}", link.location.display()) .chain_err(|| format!("Chapter file not found, {}", link.location.display()))?;
})?;
let mut content = String::new(); let mut content = String::new();
f.read_to_string(&mut content)?; f.read_to_string(&mut content)?;
let stripped = location.strip_prefix(&src_dir) let stripped = location
.strip_prefix(&src_dir)
.expect("Chapters are always inside a book"); .expect("Chapters are always inside a book");
let mut ch = Chapter::new(&link.name, content, stripped); let mut ch = Chapter::new(&link.name, content, stripped);
ch.number = link.number.clone(); ch.number = link.number.clone();
let sub_items = link.nested_items.iter() let sub_items = link.nested_items
.iter()
.map(|i| load_summary_item(i, src_dir)) .map(|i| load_summary_item(i, src_dir))
.collect::<Result<Vec<_>>>()?; .collect::<Result<Vec<_>>>()?;
@ -206,7 +240,8 @@ And here is some \
let temp = TempDir::new("book").unwrap(); let temp = TempDir::new("book").unwrap();
let chapter_path = temp.path().join("chapter_1.md"); let chapter_path = temp.path().join("chapter_1.md");
File::create(&chapter_path).unwrap() File::create(&chapter_path)
.unwrap()
.write(DUMMY_SRC.as_bytes()) .write(DUMMY_SRC.as_bytes())
.unwrap(); .unwrap();
@ -221,7 +256,8 @@ And here is some \
let second_path = temp_dir.path().join("second.md"); let second_path = temp_dir.path().join("second.md");
File::create(&second_path).unwrap() File::create(&second_path)
.unwrap()
.write_all("Hello World!".as_bytes()) .write_all("Hello World!".as_bytes())
.unwrap(); .unwrap();
@ -356,7 +392,8 @@ And here is some \
assert_eq!(got.len(), 5); assert_eq!(got.len(), 5);
// checking the chapter names are in the order should be sufficient here... // checking the chapter names are in the order should be sufficient here...
let chapter_names: Vec<String> = got.into_iter().filter_map(|i| match *i { let chapter_names: Vec<String> = got.into_iter()
.filter_map(|i| match *i {
BookItem::Chapter(ref ch) => Some(ch.name.clone()), BookItem::Chapter(ref ch) => Some(ch.name.clone()),
_ => None, _ => None,
}) })

View File

@ -3,6 +3,7 @@ mod book;
mod init; mod init;
pub use self::book::{Book, BookItem, BookItems, Chapter}; pub use self::book::{Book, BookItem, BookItems, Chapter};
pub use self::summary::SectionNumber;
pub use self::init::BookBuilder; pub use self::init::BookBuilder;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
@ -39,8 +40,15 @@ impl MDBook {
Config::default() 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<P: Into<PathBuf>>(book_root: P, config: Config) -> Result<MDBook> {
let book_root = book_root.into();
let src_dir = book_root.join(&config.book.src); 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 { Ok(MDBook {
root: book_root, root: book_root,

View File

@ -2,7 +2,6 @@ extern crate mdbook;
#[macro_use] #[macro_use]
extern crate pretty_assertions; extern crate pretty_assertions;
extern crate select; extern crate select;
extern crate tempdir;
extern crate walkdir; extern crate walkdir;
mod dummy_book; mod dummy_book;
@ -15,9 +14,9 @@ use std::ffi::OsStr;
use walkdir::{DirEntry, WalkDir, WalkDirIterator}; use walkdir::{DirEntry, WalkDir, WalkDirIterator};
use select::document::Document; use select::document::Document;
use select::predicate::{Class, Name, Predicate}; use select::predicate::{Class, Name, Predicate};
use tempdir::TempDir;
use mdbook::errors::*; use mdbook::errors::*;
use mdbook::utils::fs::file_to_string; use mdbook::utils::fs::file_to_string;
use mdbook::config::Config;
use mdbook::MDBook; use mdbook::MDBook;
@ -254,33 +253,29 @@ fn check_spacers() {
/// Ensure building fails if `create-missing` is false and one of the files does /// Ensure building fails if `create-missing` is false and one of the files does
/// not exist. /// not exist.
#[test] #[test]
#[ignore]
fn failure_on_missing_file() { 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 let mut cfg = Config::default();
// any error is a failure due to a missing file. cfg.build.create_missing = false;
assert!(md.build().is_err());
let got = MDBook::load_with_config(temp.path(), cfg);
assert!(got.is_err());
} }
/// Ensure a missing file is created if `create-missing` is true. /// Ensure a missing file is created if `create-missing` is true.
#[test] #[test]
#[ignore]
fn create_missing_file_with_config() { 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 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(); 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}}`. /// This makes sure you can include a Rust file with `{{#playpen example.rs}}`.