Fleshed out book creation
This commit is contained in:
parent
47eb4788cb
commit
8b21da9950
221
src/book/init.rs
221
src/book/init.rs
|
@ -1,6 +1,11 @@
|
|||
use std::fs::{self, File};
|
||||
use std::path::PathBuf;
|
||||
use std::io::Write;
|
||||
use toml;
|
||||
|
||||
use config::Config;
|
||||
use super::MDBook;
|
||||
use theme;
|
||||
use errors::*;
|
||||
|
||||
|
||||
|
@ -44,113 +49,113 @@ impl BookBuilder {
|
|||
}
|
||||
|
||||
pub fn build(&self) -> Result<MDBook> {
|
||||
unimplemented!()
|
||||
info!("Creating a new book with stub content");
|
||||
|
||||
self.create_directory_structure()
|
||||
.chain_err(|| "Unable to create directory structure")?;
|
||||
|
||||
self.create_stub_files()
|
||||
.chain_err(|| "Unable to create stub files")?;
|
||||
|
||||
if self.create_gitignore {
|
||||
self.build_gitignore()
|
||||
.chain_err(|| "Unable to create .gitignore")?;
|
||||
}
|
||||
|
||||
if self.copy_theme {
|
||||
self.copy_across_theme()
|
||||
.chain_err(|| "Unable to copy across the theme")?;
|
||||
}
|
||||
|
||||
self.write_book_toml()?;
|
||||
|
||||
let book = MDBook::load(&self.root)
|
||||
.expect("The BookBuilder should always create a valid book. \
|
||||
If you are seeing this it is a bug and should be reported.");
|
||||
|
||||
Ok(book)
|
||||
}
|
||||
|
||||
fn write_book_toml(&self) -> Result<()> {
|
||||
debug!("[*] Writing book.toml");
|
||||
let book_toml = self.root.join("book.toml");
|
||||
let cfg = toml::to_vec(&self.config)
|
||||
.chain_err(|| "Unable to serialize the config")?;
|
||||
|
||||
File::create(book_toml)
|
||||
.chain_err(|| "Couldn't create book.toml")?
|
||||
.write_all(&cfg)
|
||||
.chain_err(|| "Unable to write config to book.toml")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn copy_across_theme(&self) -> Result<()> {
|
||||
debug!("[*] Copying theme");
|
||||
|
||||
let themedir = self.config.html_config()
|
||||
.and_then(|html| html.theme)
|
||||
.unwrap_or_else(|| self.config.book.src.join("theme"));
|
||||
let themedir = self.root.join(themedir);
|
||||
|
||||
if !themedir.exists() {
|
||||
debug!("[*]: {:?} does not exist, creating the directory",
|
||||
themedir); fs::create_dir(&themedir)?;
|
||||
}
|
||||
|
||||
let mut index = File::create(themedir.join("index.hbs"))?;
|
||||
index.write_all(theme::INDEX)?;
|
||||
|
||||
let mut css = File::create(themedir.join("book.css"))?;
|
||||
css.write_all(theme::CSS)?;
|
||||
|
||||
let mut favicon = File::create(themedir.join("favicon.png"))?;
|
||||
favicon.write_all(theme::FAVICON)?;
|
||||
|
||||
let mut js = File::create(themedir.join("book.js"))?;
|
||||
js.write_all(theme::JS)?;
|
||||
|
||||
let mut highlight_css = File::create(themedir.join("highlight.css"))?;
|
||||
highlight_css.write_all(theme::HIGHLIGHT_CSS)?;
|
||||
|
||||
let mut highlight_js = File::create(themedir.join("highlight.js"))?;
|
||||
highlight_js.write_all(theme::HIGHLIGHT_JS)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn build_gitignore(&self) -> Result<()> {
|
||||
debug!("[*]: Creating .gitignore");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_stub_files(&self) -> Result<()> {
|
||||
debug!("[*] Creating example book contents");
|
||||
let src_dir = self.root.join(&self.config.book.src);
|
||||
|
||||
let summary = src_dir.join("SUMMARY.md");
|
||||
let mut f = File::create(&summary).chain_err(|| "Unable to create SUMMARY.md")?;
|
||||
writeln!(f, "# Summary")?;
|
||||
writeln!(f, "")?;
|
||||
writeln!(f, "- [Chapter 1](./chapter_1.md)")?;
|
||||
|
||||
let chapter_1 = src_dir.join("chapter_1.md");
|
||||
let mut f = File::create(&chapter_1).chain_err(|| "Unable to create chapter_1.md")?;
|
||||
writeln!(f, "# Chapter 1")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_directory_structure(&self) -> Result<()> {
|
||||
debug!("[*]: Creating directory tree");
|
||||
fs::create_dir_all(&self.root)?;
|
||||
|
||||
let src = self.root.join(&self.config.book.src);
|
||||
fs::create_dir_all(&src)?;
|
||||
|
||||
let build = self.root.join(&self.config.build.build_dir);
|
||||
fs::create_dir_all(&build)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// contents of old `init()` function:
|
||||
|
||||
// debug!("[fn]: init");
|
||||
|
||||
// if !self.root.exists() {
|
||||
// fs::create_dir_all(&self.root).unwrap();
|
||||
// info!("{:?} created", self.root.display());
|
||||
// }
|
||||
|
||||
// {
|
||||
// let dest = self.get_destination();
|
||||
// if !dest.exists() {
|
||||
// debug!("[*]: {} does not exist, trying to create directory",
|
||||
// dest.display()); fs::create_dir_all(dest)?;
|
||||
// }
|
||||
|
||||
|
||||
// let src = self.get_source();
|
||||
// if !src.exists() {
|
||||
// debug!("[*]: {} does not exist, trying to create directory",
|
||||
// src.display()); fs::create_dir_all(&src)?;
|
||||
// }
|
||||
|
||||
// let summary = src.join("SUMMARY.md");
|
||||
|
||||
// if !summary.exists() {
|
||||
// // Summary does not exist, create it
|
||||
// debug!("[*]: {:?} does not exist, trying to create SUMMARY.md",
|
||||
// &summary); let mut f = File::create(&summary)?;
|
||||
|
||||
// debug!("[*]: Writing to SUMMARY.md");
|
||||
|
||||
// writeln!(f, "# Summary")?;
|
||||
// writeln!(f, "")?;
|
||||
// writeln!(f, "- [Chapter 1](./chapter_1.md)")?;
|
||||
// }
|
||||
// }
|
||||
|
||||
// // parse SUMMARY.md, and create the missing item related file
|
||||
// self.parse_summary()?;
|
||||
|
||||
// debug!("[*]: constructing paths for missing files");
|
||||
// for item in self.iter() {
|
||||
// debug!("[*]: item: {:?}", item);
|
||||
// let ch = match *item {
|
||||
// BookItem::Spacer => continue,
|
||||
// BookItem::Chapter(_, ref ch) | BookItem::Affix(ref ch) => ch,
|
||||
// };
|
||||
// if !ch.path.as_os_str().is_empty() {
|
||||
// let path = self.get_source().join(&ch.path);
|
||||
|
||||
// if !path.exists() {
|
||||
// if !self.create_missing {
|
||||
// return Err(
|
||||
// format!("'{}' referenced from SUMMARY.md does not
|
||||
// exist.", path.to_string_lossy()).into(), );
|
||||
// }
|
||||
// debug!("[*]: {:?} does not exist, trying to create file", path);
|
||||
// ::std::fs::create_dir_all(path.parent().unwrap())?;
|
||||
// let mut f = File::create(path)?;
|
||||
|
||||
// // debug!("[*]: Writing to {:?}", path);
|
||||
// writeln!(f, "# {}", ch.name)?;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// debug!("[*]: init done");
|
||||
// Ok(())
|
||||
|
||||
// pub fn copy_theme(&self) -> Result<()> {
|
||||
// debug!("[fn]: copy_theme");
|
||||
|
||||
// let themedir = self.theme_dir();
|
||||
|
||||
// if !themedir.exists() {
|
||||
// debug!("[*]: {:?} does not exist, trying to create directory",
|
||||
// themedir); fs::create_dir(&themedir)?;
|
||||
// }
|
||||
|
||||
// // index.hbs
|
||||
// let mut index = File::create(themedir.join("index.hbs"))?;
|
||||
// index.write_all(theme::INDEX)?;
|
||||
|
||||
// // book.css
|
||||
// let mut css = File::create(themedir.join("book.css"))?;
|
||||
// css.write_all(theme::CSS)?;
|
||||
|
||||
// // favicon.png
|
||||
// let mut favicon = File::create(themedir.join("favicon.png"))?;
|
||||
// favicon.write_all(theme::FAVICON)?;
|
||||
|
||||
// // book.js
|
||||
// let mut js = File::create(themedir.join("book.js"))?;
|
||||
// js.write_all(theme::JS)?;
|
||||
|
||||
// // highlight.css
|
||||
// let mut highlight_css = File::create(themedir.join("highlight.css"))?;
|
||||
// highlight_css.write_all(theme::HIGHLIGHT_CSS)?;
|
||||
|
||||
// // highlight.js
|
||||
// let mut highlight_js = File::create(themedir.join("highlight.js"))?;
|
||||
// highlight_js.write_all(theme::HIGHLIGHT_JS)?;
|
||||
|
||||
// Ok(())
|
||||
// }
|
||||
|
|
|
@ -3,7 +3,7 @@ use std::fs::File;
|
|||
use std::io::Read;
|
||||
use toml::{self, Value};
|
||||
use toml::value::Table;
|
||||
use serde::{Deserialize, Deserializer};
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
use errors::*;
|
||||
|
||||
|
@ -187,6 +187,24 @@ impl<'de> Deserialize<'de> for Config {
|
|||
}
|
||||
}
|
||||
|
||||
impl Serialize for Config {
|
||||
fn serialize<S: Serializer>(&self, s: S) -> ::std::result::Result<S::Ok, S::Error> {
|
||||
let mut table = self.rest.clone();
|
||||
|
||||
let book_config = match Value::try_from(self.book.clone()) {
|
||||
Ok(cfg) => cfg,
|
||||
Err(_) => {
|
||||
use serde::ser::Error;
|
||||
return Err(S::Error::custom("Unable to serialize the BookConfig"));
|
||||
}
|
||||
};
|
||||
|
||||
table.insert("book".to_string(), book_config);
|
||||
|
||||
Value::Table(table).serialize(s)
|
||||
}
|
||||
}
|
||||
|
||||
fn is_legacy_format(table: &Table) -> bool {
|
||||
let top_level_items = ["title", "author", "authors"];
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ mod dummy_book;
|
|||
|
||||
use dummy_book::{assert_contains_strings, DummyBook};
|
||||
|
||||
use std::fs::{File, remove_file};
|
||||
use std::fs::{remove_file, File};
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
use std::ffi::OsStr;
|
||||
|
@ -23,10 +23,12 @@ use mdbook::MDBook;
|
|||
|
||||
|
||||
const BOOK_ROOT: &'static str = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/dummy_book");
|
||||
const TOC_TOP_LEVEL: &[&'static str] = &["1. First Chapter",
|
||||
"2. Second Chapter",
|
||||
"Conclusion",
|
||||
"Introduction"];
|
||||
const TOC_TOP_LEVEL: &[&'static str] = &[
|
||||
"1. First Chapter",
|
||||
"2. Second Chapter",
|
||||
"Conclusion",
|
||||
"Introduction",
|
||||
];
|
||||
const TOC_SECOND_LEVEL: &[&'static str] = &["1.1. Nested Chapter"];
|
||||
|
||||
/// Make sure you can load the dummy book and build it without panicking.
|
||||
|
@ -57,11 +59,13 @@ fn make_sure_bottom_level_files_contain_links_to_chapters() {
|
|||
md.build().unwrap();
|
||||
|
||||
let dest = temp.path().join("book");
|
||||
let links = vec![r#"href="intro.html""#,
|
||||
r#"href="./first/index.html""#,
|
||||
r#"href="./first/nested.html""#,
|
||||
r#"href="./second.html""#,
|
||||
r#"href="./conclusion.html""#];
|
||||
let links = vec![
|
||||
r#"href="intro.html""#,
|
||||
r#"href="./first/index.html""#,
|
||||
r#"href="./first/nested.html""#,
|
||||
r#"href="./second.html""#,
|
||||
r#"href="./conclusion.html""#,
|
||||
];
|
||||
|
||||
let files_in_bottom_dir = vec!["index.html", "intro.html", "second.html", "conclusion.html"];
|
||||
|
||||
|
@ -77,12 +81,14 @@ fn check_correct_cross_links_in_nested_dir() {
|
|||
md.build().unwrap();
|
||||
|
||||
let first = temp.path().join("book").join("first");
|
||||
let links = vec![r#"<base href="../">"#,
|
||||
r#"href="intro.html""#,
|
||||
r#"href="./first/index.html""#,
|
||||
r#"href="./first/nested.html""#,
|
||||
r#"href="./second.html""#,
|
||||
r#"href="./conclusion.html""#];
|
||||
let links = vec![
|
||||
r#"<base href="../">"#,
|
||||
r#"href="intro.html""#,
|
||||
r#"href="./first/index.html""#,
|
||||
r#"href="./first/nested.html""#,
|
||||
r#"href="./second.html""#,
|
||||
r#"href="./conclusion.html""#,
|
||||
];
|
||||
|
||||
let files_in_nested_dir = vec!["index.html", "nested.html"];
|
||||
|
||||
|
@ -90,11 +96,19 @@ fn check_correct_cross_links_in_nested_dir() {
|
|||
assert_contains_strings(first.join(filename), &links);
|
||||
}
|
||||
|
||||
assert_contains_strings(first.join("index.html"),
|
||||
&[r##"href="./first/index.html#some-section" id="some-section""##]);
|
||||
assert_contains_strings(
|
||||
first.join("index.html"),
|
||||
&[
|
||||
r##"href="./first/index.html#some-section" id="some-section""##,
|
||||
],
|
||||
);
|
||||
|
||||
assert_contains_strings(first.join("nested.html"),
|
||||
&[r##"href="./first/nested.html#some-section" id="some-section""##]);
|
||||
assert_contains_strings(
|
||||
first.join("nested.html"),
|
||||
&[
|
||||
r##"href="./first/nested.html#some-section" id="some-section""##,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -114,11 +128,13 @@ fn rendered_code_has_playpen_stuff() {
|
|||
|
||||
#[test]
|
||||
fn chapter_content_appears_in_rendered_document() {
|
||||
let content = vec![("index.html", "Here's some interesting text"),
|
||||
("second.html", "Second Chapter"),
|
||||
("first/nested.html", "testable code"),
|
||||
("first/index.html", "more text"),
|
||||
("conclusion.html", "Conclusion")];
|
||||
let content = vec![
|
||||
("index.html", "Here's some interesting text"),
|
||||
("second.html", "Second Chapter"),
|
||||
("first/nested.html", "testable code"),
|
||||
("first/index.html", "more text"),
|
||||
("conclusion.html", "Conclusion"),
|
||||
];
|
||||
|
||||
let temp = DummyBook::new().build().unwrap();
|
||||
let mut md = MDBook::load(temp.path()).unwrap();
|
||||
|
@ -153,21 +169,22 @@ fn chapter_files_were_rendered_to_html() {
|
|||
let temp = DummyBook::new().build().unwrap();
|
||||
let src = Path::new(BOOK_ROOT).join("src");
|
||||
|
||||
let chapter_files = WalkDir::new(&src).into_iter()
|
||||
.filter_entry(|entry| entry_ends_with(entry, ".md"))
|
||||
.filter_map(|entry| entry.ok())
|
||||
.map(|entry| entry.path().to_path_buf())
|
||||
.filter(|path| {
|
||||
path.file_name().and_then(OsStr::to_str)
|
||||
!= Some("SUMMARY.md")
|
||||
});
|
||||
let chapter_files = WalkDir::new(&src)
|
||||
.into_iter()
|
||||
.filter_entry(|entry| entry_ends_with(entry, ".md"))
|
||||
.filter_map(|entry| entry.ok())
|
||||
.map(|entry| entry.path().to_path_buf())
|
||||
.filter(|path| path.file_name().and_then(OsStr::to_str) != Some("SUMMARY.md"));
|
||||
|
||||
for chapter in chapter_files {
|
||||
let rendered_location = temp.path().join(chapter.strip_prefix(&src).unwrap())
|
||||
.with_extension("html");
|
||||
assert!(rendered_location.exists(),
|
||||
"{} doesn't exits",
|
||||
rendered_location.display());
|
||||
let rendered_location = temp.path()
|
||||
.join(chapter.strip_prefix(&src).unwrap())
|
||||
.with_extension("html");
|
||||
assert!(
|
||||
rendered_location.exists(),
|
||||
"{} doesn't exits",
|
||||
rendered_location.display()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -178,10 +195,12 @@ fn entry_ends_with(entry: &DirEntry, ending: &str) -> bool {
|
|||
/// Read the main page (`book/index.html`) and expose it as a DOM which we
|
||||
/// can search with the `select` crate
|
||||
fn root_index_html() -> Result<Document> {
|
||||
let temp = DummyBook::new().build()
|
||||
.chain_err(|| "Couldn't create the dummy book")?;
|
||||
MDBook::load(temp.path())?.build()
|
||||
.chain_err(|| "Book building failed")?;
|
||||
let temp = DummyBook::new()
|
||||
.build()
|
||||
.chain_err(|| "Couldn't create the dummy book")?;
|
||||
MDBook::load(temp.path())?
|
||||
.build()
|
||||
.chain_err(|| "Book building failed")?;
|
||||
|
||||
let index_page = temp.path().join("book").join("index.html");
|
||||
let html = file_to_string(&index_page).chain_err(|| "Unable to read index.html")?;
|
||||
|
@ -197,9 +216,9 @@ fn check_second_toc_level() {
|
|||
|
||||
let pred = descendants!(Class("chapter"), Name("li"), Name("li"), Name("a"));
|
||||
|
||||
let mut children_of_children: Vec<_> =
|
||||
doc.find(pred).map(|elem| elem.text().trim().to_string())
|
||||
.collect();
|
||||
let mut children_of_children: Vec<_> = doc.find(pred)
|
||||
.map(|elem| elem.text().trim().to_string())
|
||||
.collect();
|
||||
children_of_children.sort();
|
||||
|
||||
assert_eq!(children_of_children, should_be);
|
||||
|
@ -215,8 +234,9 @@ fn check_first_toc_level() {
|
|||
|
||||
let pred = descendants!(Class("chapter"), Name("li"), Name("a"));
|
||||
|
||||
let mut children: Vec<_> = doc.find(pred).map(|elem| elem.text().trim().to_string())
|
||||
.collect();
|
||||
let mut children: Vec<_> = doc.find(pred)
|
||||
.map(|elem| elem.text().trim().to_string())
|
||||
.collect();
|
||||
children.sort();
|
||||
|
||||
assert_eq!(children, should_be);
|
||||
|
@ -227,8 +247,8 @@ fn check_spacers() {
|
|||
let doc = root_index_html().unwrap();
|
||||
let should_be = 1;
|
||||
|
||||
let num_spacers =
|
||||
doc.find(Class("chapter").descendant(Name("li").and(Class("spacer")))).count();
|
||||
let num_spacers = doc.find(Class("chapter").descendant(Name("li").and(Class("spacer"))))
|
||||
.count();
|
||||
assert_eq!(num_spacers, should_be);
|
||||
}
|
||||
|
||||
|
@ -269,7 +289,8 @@ fn create_missing_setup(create_missing: Option<bool>) -> (MDBook, TempDir) {
|
|||
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(),
|
||||
Some(false) => file.write_all(b"[build]\ncreate-missing = false\n")
|
||||
.unwrap(),
|
||||
None => (),
|
||||
}
|
||||
file.flush().unwrap();
|
||||
|
|
Loading…
Reference in New Issue