Add `--auto-summary` option.
Add a `Summary::from_source` function to generate the book's summary from the sources directory structure.
This commit is contained in:
parent
ffa8284743
commit
f767167808
|
@ -1,3 +1,4 @@
|
|||
use std::borrow::Cow;
|
||||
use std::collections::VecDeque;
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
use std::fs::{self, File};
|
||||
|
@ -11,19 +12,27 @@ use crate::errors::*;
|
|||
/// Load a book into memory from its `src/` directory.
|
||||
pub fn load_book<P: AsRef<Path>>(src_dir: P, cfg: &BuildConfig) -> Result<Book> {
|
||||
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)
|
||||
.with_context(|| format!("Couldn't open SUMMARY.md in {:?} directory", src_dir))?
|
||||
.read_to_string(&mut summary_content)?;
|
||||
let summary = if !cfg.auto_summary {
|
||||
let summary_md = src_dir.join("SUMMARY.md");
|
||||
|
||||
let summary = parse_summary(&summary_content)
|
||||
.with_context(|| format!("Summary parsing failed for file={:?}", summary_md))?;
|
||||
let mut summary_content = String::new();
|
||||
File::open(&summary_md)
|
||||
.with_context(|| format!("Couldn't open SUMMARY.md in {:?} directory", src_dir))?
|
||||
.read_to_string(&mut summary_content)?;
|
||||
|
||||
if cfg.create_missing {
|
||||
create_missing(src_dir, &summary).with_context(|| "Unable to create missing chapters")?;
|
||||
}
|
||||
let summary = parse_summary(&summary_content)
|
||||
.with_context(|| format!("Summary parsing failed for file={:?}", summary_md))?;
|
||||
|
||||
if cfg.create_missing {
|
||||
create_missing(src_dir, &summary)
|
||||
.with_context(|| "Unable to create missing chapters")?;
|
||||
}
|
||||
|
||||
summary
|
||||
} else {
|
||||
Summary::from_sources(src_dir)?
|
||||
};
|
||||
|
||||
load_book_from_disk(&summary, src_dir)
|
||||
}
|
||||
|
@ -53,7 +62,10 @@ fn create_missing(src_dir: &Path, summary: &Summary) -> Result<()> {
|
|||
let mut f = File::create(&filename).with_context(|| {
|
||||
format!("Unable to create missing file: {}", filename.display())
|
||||
})?;
|
||||
writeln!(f, "# {}", link.name)?;
|
||||
|
||||
if let Some(name) = &link.name {
|
||||
writeln!(f, "# {}", name)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -253,7 +265,7 @@ fn load_chapter<P: AsRef<Path>>(
|
|||
let src_dir = src_dir.as_ref();
|
||||
|
||||
let mut ch = if let Some(ref link_location) = link.location {
|
||||
debug!("Loading {} ({})", link.name, link_location.display());
|
||||
debug!("Loading {}", link);
|
||||
|
||||
let location = if link_location.is_absolute() {
|
||||
link_location.clone()
|
||||
|
@ -265,9 +277,8 @@ fn load_chapter<P: AsRef<Path>>(
|
|||
.with_context(|| format!("Chapter file not found, {}", link_location.display()))?;
|
||||
|
||||
let mut content = String::new();
|
||||
f.read_to_string(&mut content).with_context(|| {
|
||||
format!("Unable to read \"{}\" ({})", link.name, location.display())
|
||||
})?;
|
||||
f.read_to_string(&mut content)
|
||||
.with_context(|| format!("Unable to read {}", link))?;
|
||||
|
||||
if content.as_bytes().starts_with(b"\xef\xbb\xbf") {
|
||||
content.replace_range(..3, "");
|
||||
|
@ -277,16 +288,21 @@ fn load_chapter<P: AsRef<Path>>(
|
|||
.strip_prefix(&src_dir)
|
||||
.expect("Chapters are always inside a book");
|
||||
|
||||
Chapter::new(&link.name, content, stripped, parent_names.clone())
|
||||
let name = match &link.name {
|
||||
Some(name) => Cow::Borrowed(name.as_str()),
|
||||
None => Cow::Owned(read_chapter_title(&content)),
|
||||
};
|
||||
|
||||
Chapter::new(&name, content, stripped, parent_names.clone())
|
||||
} else {
|
||||
Chapter::new_draft(&link.name, parent_names.clone())
|
||||
Chapter::new_draft(link.name.as_ref().unwrap().as_str(), parent_names.clone())
|
||||
};
|
||||
|
||||
let mut sub_item_parents = parent_names;
|
||||
|
||||
ch.number = link.number.clone();
|
||||
|
||||
sub_item_parents.push(link.name.clone());
|
||||
sub_item_parents.push(ch.name.clone());
|
||||
let sub_items = link
|
||||
.nested_items
|
||||
.iter()
|
||||
|
@ -298,6 +314,23 @@ fn load_chapter<P: AsRef<Path>>(
|
|||
Ok(ch)
|
||||
}
|
||||
|
||||
/// Read a chapter title from its source file.
|
||||
fn read_chapter_title(content: &str) -> String {
|
||||
let mut pulldown_parser = pulldown_cmark::Parser::new(content);
|
||||
|
||||
while let Some(event) = pulldown_parser.next() {
|
||||
if let pulldown_cmark::Event::Start(pulldown_cmark::Tag::Heading(1)) = event {
|
||||
if let Some(pulldown_cmark::Event::Text(title)) = pulldown_parser.next() {
|
||||
return title.to_string();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"Untitled".to_string()
|
||||
}
|
||||
|
||||
/// A depth-first iterator over the items in a book.
|
||||
///
|
||||
/// # Note
|
||||
|
@ -604,7 +637,7 @@ And here is some \
|
|||
let (_, temp) = dummy_link();
|
||||
let summary = Summary {
|
||||
numbered_chapters: vec![SummaryItem::Link(Link {
|
||||
name: String::from("Empty"),
|
||||
name: Some(String::from("Empty")),
|
||||
location: Some(PathBuf::from("")),
|
||||
..Default::default()
|
||||
})],
|
||||
|
@ -624,7 +657,7 @@ And here is some \
|
|||
|
||||
let summary = Summary {
|
||||
numbered_chapters: vec![SummaryItem::Link(Link {
|
||||
name: String::from("nested"),
|
||||
name: Some(String::from("nested")),
|
||||
location: Some(dir),
|
||||
..Default::default()
|
||||
})],
|
||||
|
|
|
@ -81,7 +81,7 @@ impl BookBuilder {
|
|||
|
||||
self.write_book_toml()?;
|
||||
|
||||
match MDBook::load(&self.root) {
|
||||
match MDBook::load(&self.root, false) {
|
||||
Ok(book) => Ok(book),
|
||||
Err(e) => {
|
||||
error!("{}", e);
|
||||
|
|
|
@ -47,7 +47,10 @@ 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> {
|
||||
///
|
||||
/// If `auto_summary` is set, the book's summary is automatically generated
|
||||
/// from the root directory structure.
|
||||
pub fn load<P: Into<PathBuf>>(book_root: P, auto_summary: bool) -> Result<MDBook> {
|
||||
let book_root = book_root.into();
|
||||
let config_location = book_root.join("book.toml");
|
||||
|
||||
|
@ -68,6 +71,10 @@ impl MDBook {
|
|||
Config::default()
|
||||
};
|
||||
|
||||
if auto_summary {
|
||||
config.build.auto_summary = true;
|
||||
}
|
||||
|
||||
config.update_from_env();
|
||||
|
||||
if log_enabled!(log::Level::Trace) {
|
||||
|
@ -128,7 +135,7 @@ impl MDBook {
|
|||
/// ```no_run
|
||||
/// # use mdbook::MDBook;
|
||||
/// # use mdbook::book::BookItem;
|
||||
/// # let book = MDBook::load("mybook").unwrap();
|
||||
/// # let book = MDBook::load("mybook", false).unwrap();
|
||||
/// for item in book.iter() {
|
||||
/// match *item {
|
||||
/// BookItem::Chapter(ref chapter) => {},
|
||||
|
|
|
@ -66,6 +66,91 @@ pub struct Summary {
|
|||
pub suffix_chapters: Vec<SummaryItem>,
|
||||
}
|
||||
|
||||
impl Summary {
|
||||
/// Create a summary from the book's sources directory.
|
||||
///
|
||||
/// Each file is imported as a book chapter.
|
||||
/// Each folder is imported as a book chapter and must contain
|
||||
/// a `README.md` file defining the chapter's title and content.
|
||||
/// Any file/folder inside the directory is imported as a sub-chapter.
|
||||
/// The file/folder name is used to compose the chapter's link.
|
||||
///
|
||||
/// Chapters are added to the book in alphabetical order, using the file/folder name.
|
||||
pub fn from_sources<P: AsRef<Path>>(src_dir: P) -> std::io::Result<Summary> {
|
||||
let mut summary = Summary {
|
||||
title: None,
|
||||
prefix_chapters: Vec::new(),
|
||||
numbered_chapters: Vec::new(),
|
||||
suffix_chapters: Vec::new(),
|
||||
};
|
||||
|
||||
// Checks if the given path must be considered.
|
||||
fn include_path(path: &Path) -> bool {
|
||||
if let Some(name) = path.file_name() {
|
||||
if name == "README.md" || name == "SUMMARY.md" {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
// Read a directory recursively and return the found summary items.
|
||||
fn read_dir<P: AsRef<Path>>(dir: P) -> std::io::Result<Vec<SummaryItem>> {
|
||||
let mut links = Vec::new();
|
||||
|
||||
for entry in std::fs::read_dir(dir)? {
|
||||
let entry = entry?;
|
||||
let entry_path = entry.path();
|
||||
|
||||
if include_path(&entry_path) {
|
||||
let metadata = std::fs::metadata(&entry_path)?;
|
||||
|
||||
if metadata.is_file() {
|
||||
links.push(Link::new_unnamed(entry_path))
|
||||
} else {
|
||||
let chapter_path = entry_path.join("README.md");
|
||||
if chapter_path.is_file() {
|
||||
let mut link = Link::new_unnamed(chapter_path);
|
||||
link.nested_items = read_dir(entry_path)?;
|
||||
links.push(link)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Items are sorted by name.
|
||||
links.sort_by(|a, b| {
|
||||
a.location
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.cmp(b.location.as_ref().unwrap())
|
||||
});
|
||||
|
||||
Ok(links.into_iter().map(SummaryItem::Link).collect())
|
||||
}
|
||||
|
||||
// Associate the correct section number to each summary item.
|
||||
fn number_items(items: &mut [SummaryItem], number: &[u32]) {
|
||||
let mut n = 1;
|
||||
for item in items {
|
||||
if let SummaryItem::Link(link) = item {
|
||||
let mut entry_number = number.to_vec();
|
||||
entry_number.push(n);
|
||||
n += 1;
|
||||
number_items(&mut link.nested_items, &entry_number);
|
||||
link.number = Some(SectionNumber(entry_number))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
summary.numbered_chapters = read_dir(src_dir)?;
|
||||
number_items(&mut summary.numbered_chapters, &[]);
|
||||
|
||||
Ok(summary)
|
||||
}
|
||||
}
|
||||
|
||||
/// A struct representing an entry in the `SUMMARY.md`, possibly with nested
|
||||
/// entries.
|
||||
///
|
||||
|
@ -73,7 +158,7 @@ pub struct Summary {
|
|||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Link {
|
||||
/// The name of the chapter.
|
||||
pub name: String,
|
||||
pub name: Option<String>,
|
||||
/// The location of the chapter's source file, taking the book's `src`
|
||||
/// directory as the root.
|
||||
pub location: Option<PathBuf>,
|
||||
|
@ -87,7 +172,17 @@ impl Link {
|
|||
/// Create a new link with no nested items.
|
||||
pub fn new<S: Into<String>, P: AsRef<Path>>(name: S, location: P) -> Link {
|
||||
Link {
|
||||
name: name.into(),
|
||||
name: Some(name.into()),
|
||||
location: Some(location.as_ref().to_path_buf()),
|
||||
number: None,
|
||||
nested_items: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new unnamed link with no nested items.
|
||||
pub fn new_unnamed<P: AsRef<Path>>(location: P) -> Link {
|
||||
Link {
|
||||
name: None,
|
||||
location: Some(location.as_ref().to_path_buf()),
|
||||
number: None,
|
||||
nested_items: Vec::new(),
|
||||
|
@ -98,7 +193,7 @@ impl Link {
|
|||
impl Default for Link {
|
||||
fn default() -> Self {
|
||||
Link {
|
||||
name: String::new(),
|
||||
name: None,
|
||||
location: Some(PathBuf::new()),
|
||||
number: None,
|
||||
nested_items: Vec::new(),
|
||||
|
@ -106,6 +201,22 @@ impl Default for Link {
|
|||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Link {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match &self.name {
|
||||
Some(name) => write!(f, "\"{}\"", name)?,
|
||||
None => write!(f, "unnamed chapter")?,
|
||||
}
|
||||
|
||||
match &self.location {
|
||||
Some(location) => write!(f, " ({})", location.display())?,
|
||||
None => write!(f, " [draft]")?,
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// An item in `SUMMARY.md` which could be either a separator or a `Link`.
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub enum SummaryItem {
|
||||
|
@ -344,7 +455,7 @@ impl<'a> SummaryParser<'a> {
|
|||
};
|
||||
|
||||
Link {
|
||||
name,
|
||||
name: Some(name),
|
||||
location: path,
|
||||
number: None,
|
||||
nested_items: Vec::new(),
|
||||
|
@ -489,15 +600,7 @@ impl<'a> SummaryParser<'a> {
|
|||
|
||||
let mut number = parent.clone();
|
||||
number.0.push(num_existing_items as u32 + 1);
|
||||
trace!(
|
||||
"Found chapter: {} {} ({})",
|
||||
number,
|
||||
link.name,
|
||||
link.location
|
||||
.as_ref()
|
||||
.map(|p| p.to_str().unwrap_or(""))
|
||||
.unwrap_or("[draft]")
|
||||
);
|
||||
trace!("Found chapter: {} {}", number, link);
|
||||
|
||||
link.number = Some(number);
|
||||
|
||||
|
@ -676,12 +779,12 @@ mod tests {
|
|||
|
||||
let should_be = vec![
|
||||
SummaryItem::Link(Link {
|
||||
name: String::from("First"),
|
||||
name: Some(String::from("First")),
|
||||
location: Some(PathBuf::from("./first.md")),
|
||||
..Default::default()
|
||||
}),
|
||||
SummaryItem::Link(Link {
|
||||
name: String::from("Second"),
|
||||
name: Some(String::from("Second")),
|
||||
location: Some(PathBuf::from("./second.md")),
|
||||
..Default::default()
|
||||
}),
|
||||
|
@ -717,7 +820,7 @@ mod tests {
|
|||
fn parse_a_link() {
|
||||
let src = "[First](./first.md)";
|
||||
let should_be = Link {
|
||||
name: String::from("First"),
|
||||
name: Some(String::from("First")),
|
||||
location: Some(PathBuf::from("./first.md")),
|
||||
..Default::default()
|
||||
};
|
||||
|
@ -738,7 +841,7 @@ mod tests {
|
|||
fn parse_a_numbered_chapter() {
|
||||
let src = "- [First](./first.md)\n";
|
||||
let link = Link {
|
||||
name: String::from("First"),
|
||||
name: Some(String::from("First")),
|
||||
location: Some(PathBuf::from("./first.md")),
|
||||
number: Some(SectionNumber(vec![1])),
|
||||
..Default::default()
|
||||
|
@ -759,18 +862,18 @@ mod tests {
|
|||
|
||||
let should_be = vec![
|
||||
SummaryItem::Link(Link {
|
||||
name: String::from("First"),
|
||||
name: Some(String::from("First")),
|
||||
location: Some(PathBuf::from("./first.md")),
|
||||
number: Some(SectionNumber(vec![1])),
|
||||
nested_items: vec![SummaryItem::Link(Link {
|
||||
name: String::from("Nested"),
|
||||
name: Some(String::from("Nested")),
|
||||
location: Some(PathBuf::from("./nested.md")),
|
||||
number: Some(SectionNumber(vec![1, 1])),
|
||||
nested_items: Vec::new(),
|
||||
})],
|
||||
}),
|
||||
SummaryItem::Link(Link {
|
||||
name: String::from("Second"),
|
||||
name: Some(String::from("Second")),
|
||||
location: Some(PathBuf::from("./second.md")),
|
||||
number: Some(SectionNumber(vec![2])),
|
||||
nested_items: Vec::new(),
|
||||
|
@ -791,13 +894,13 @@ mod tests {
|
|||
|
||||
let should_be = vec![
|
||||
SummaryItem::Link(Link {
|
||||
name: String::from("First"),
|
||||
name: Some(String::from("First")),
|
||||
location: Some(PathBuf::from("./first.md")),
|
||||
number: Some(SectionNumber(vec![1])),
|
||||
nested_items: Vec::new(),
|
||||
}),
|
||||
SummaryItem::Link(Link {
|
||||
name: String::from("Second"),
|
||||
name: Some(String::from("Second")),
|
||||
location: Some(PathBuf::from("./second.md")),
|
||||
number: Some(SectionNumber(vec![2])),
|
||||
nested_items: Vec::new(),
|
||||
|
@ -819,24 +922,24 @@ mod tests {
|
|||
|
||||
let should_be = vec![
|
||||
SummaryItem::Link(Link {
|
||||
name: String::from("First"),
|
||||
name: Some(String::from("First")),
|
||||
location: Some(PathBuf::from("./first.md")),
|
||||
number: Some(SectionNumber(vec![1])),
|
||||
nested_items: Vec::new(),
|
||||
}),
|
||||
SummaryItem::Link(Link {
|
||||
name: String::from("Second"),
|
||||
name: Some(String::from("Second")),
|
||||
location: Some(PathBuf::from("./second.md")),
|
||||
number: Some(SectionNumber(vec![2])),
|
||||
nested_items: Vec::new(),
|
||||
}),
|
||||
SummaryItem::PartTitle(String::from("Title 2")),
|
||||
SummaryItem::Link(Link {
|
||||
name: String::from("Third"),
|
||||
name: Some(String::from("Third")),
|
||||
location: Some(PathBuf::from("./third.md")),
|
||||
number: Some(SectionNumber(vec![3])),
|
||||
nested_items: vec![SummaryItem::Link(Link {
|
||||
name: String::from("Fourth"),
|
||||
name: Some(String::from("Fourth")),
|
||||
location: Some(PathBuf::from("./fourth.md")),
|
||||
number: Some(SectionNumber(vec![3, 1])),
|
||||
nested_items: Vec::new(),
|
||||
|
@ -859,13 +962,13 @@ mod tests {
|
|||
let src = "- [First](./first.md)\n\n## Subheading\n\n- [Second](./second.md)\n";
|
||||
let should_be = vec![
|
||||
SummaryItem::Link(Link {
|
||||
name: String::from("First"),
|
||||
name: Some(String::from("First")),
|
||||
location: Some(PathBuf::from("./first.md")),
|
||||
number: Some(SectionNumber(vec![1])),
|
||||
nested_items: Vec::new(),
|
||||
}),
|
||||
SummaryItem::Link(Link {
|
||||
name: String::from("Second"),
|
||||
name: Some(String::from("Second")),
|
||||
location: Some(PathBuf::from("./second.md")),
|
||||
number: Some(SectionNumber(vec![2])),
|
||||
nested_items: Vec::new(),
|
||||
|
@ -887,7 +990,7 @@ mod tests {
|
|||
|
||||
let got = parser.parse_numbered(&mut 0, &mut SectionNumber::default());
|
||||
let should_be = vec![SummaryItem::Link(Link {
|
||||
name: String::from("Empty"),
|
||||
name: Some(String::from("Empty")),
|
||||
location: None,
|
||||
number: Some(SectionNumber(vec![1])),
|
||||
nested_items: Vec::new(),
|
||||
|
@ -905,21 +1008,21 @@ mod tests {
|
|||
"- [First](./first.md)\n---\n- [Second](./second.md)\n---\n- [Third](./third.md)\n";
|
||||
let should_be = vec![
|
||||
SummaryItem::Link(Link {
|
||||
name: String::from("First"),
|
||||
name: Some(String::from("First")),
|
||||
location: Some(PathBuf::from("./first.md")),
|
||||
number: Some(SectionNumber(vec![1])),
|
||||
nested_items: Vec::new(),
|
||||
}),
|
||||
SummaryItem::Separator,
|
||||
SummaryItem::Link(Link {
|
||||
name: String::from("Second"),
|
||||
name: Some(String::from("Second")),
|
||||
location: Some(PathBuf::from("./second.md")),
|
||||
number: Some(SectionNumber(vec![2])),
|
||||
nested_items: Vec::new(),
|
||||
}),
|
||||
SummaryItem::Separator,
|
||||
SummaryItem::Link(Link {
|
||||
name: String::from("Third"),
|
||||
name: Some(String::from("Third")),
|
||||
location: Some(PathBuf::from("./third.md")),
|
||||
number: Some(SectionNumber(vec![3])),
|
||||
nested_items: Vec::new(),
|
||||
|
@ -940,7 +1043,7 @@ mod tests {
|
|||
fn add_space_for_multi_line_chapter_names() {
|
||||
let src = "- [Chapter\ntitle](./chapter.md)";
|
||||
let should_be = vec![SummaryItem::Link(Link {
|
||||
name: String::from("Chapter title"),
|
||||
name: Some(String::from("Chapter title")),
|
||||
location: Some(PathBuf::from("./chapter.md")),
|
||||
number: Some(SectionNumber(vec![1])),
|
||||
nested_items: Vec::new(),
|
||||
|
@ -959,13 +1062,13 @@ mod tests {
|
|||
let src = "- [test1](./test%20link1.md)\n- [test2](<./test link2.md>)";
|
||||
let should_be = vec![
|
||||
SummaryItem::Link(Link {
|
||||
name: String::from("test1"),
|
||||
name: Some(String::from("test1")),
|
||||
location: Some(PathBuf::from("./test link1.md")),
|
||||
number: Some(SectionNumber(vec![1])),
|
||||
nested_items: Vec::new(),
|
||||
}),
|
||||
SummaryItem::Link(Link {
|
||||
name: String::from("test2"),
|
||||
name: Some(String::from("test2")),
|
||||
location: Some(PathBuf::from("./test link2.md")),
|
||||
number: Some(SectionNumber(vec![2])),
|
||||
nested_items: Vec::new(),
|
||||
|
@ -1030,7 +1133,7 @@ mod tests {
|
|||
|
||||
let new_affix_item = |name, location| {
|
||||
SummaryItem::Link(Link {
|
||||
name: String::from(name),
|
||||
name: Some(String::from(name)),
|
||||
location: Some(PathBuf::from(location)),
|
||||
..Default::default()
|
||||
})
|
||||
|
@ -1048,7 +1151,7 @@ mod tests {
|
|||
|
||||
let new_numbered_item = |name, location, numbers: &[u32], nested_items| {
|
||||
SummaryItem::Link(Link {
|
||||
name: String::from(name),
|
||||
name: Some(String::from(name)),
|
||||
location: Some(PathBuf::from(location)),
|
||||
number: Some(SectionNumber(numbers.to_vec())),
|
||||
nested_items,
|
||||
|
|
|
@ -17,12 +17,16 @@ pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
|
|||
(Defaults to the Current Directory when omitted)'",
|
||||
)
|
||||
.arg_from_usage("-o, --open 'Opens the compiled book in a web browser'")
|
||||
.arg_from_usage(
|
||||
"--auto-summary 'Automatically generate the book's summary{n}\
|
||||
from the sources directory structure.'",
|
||||
)
|
||||
}
|
||||
|
||||
// Build command implementation
|
||||
pub fn execute(args: &ArgMatches) -> Result<()> {
|
||||
let book_dir = get_book_dir(args);
|
||||
let mut book = MDBook::load(&book_dir)?;
|
||||
let mut book = MDBook::load(&book_dir, args.is_present("auto-summary"))?;
|
||||
|
||||
if let Some(dest_dir) = args.value_of("dest-dir") {
|
||||
book.config.build.build_dir = dest_dir.into();
|
||||
|
|
|
@ -18,12 +18,16 @@ pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
|
|||
"[dir] 'Root directory for the book{n}\
|
||||
(Defaults to the Current Directory when omitted)'",
|
||||
)
|
||||
.arg_from_usage(
|
||||
"--auto-summary 'Automatically generate the book's summary{n}\
|
||||
from the sources directory structure.'",
|
||||
)
|
||||
}
|
||||
|
||||
// Clean command implementation
|
||||
pub fn execute(args: &ArgMatches) -> mdbook::errors::Result<()> {
|
||||
let book_dir = get_book_dir(args);
|
||||
let book = MDBook::load(&book_dir)?;
|
||||
let book = MDBook::load(&book_dir, args.is_present("auto-summary"))?;
|
||||
|
||||
let dir_to_remove = match args.value_of("dest-dir") {
|
||||
Some(dest_dir) => dest_dir.into(),
|
||||
|
|
|
@ -49,12 +49,16 @@ pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
|
|||
.help("Port to use for HTTP connections"),
|
||||
)
|
||||
.arg_from_usage("-o, --open 'Opens the book server in a web browser'")
|
||||
.arg_from_usage(
|
||||
"--auto-summary 'Automatically generate the book's summary{n}\
|
||||
from the sources directory structure.'",
|
||||
)
|
||||
}
|
||||
|
||||
// Serve command implementation
|
||||
pub fn execute(args: &ArgMatches) -> Result<()> {
|
||||
let book_dir = get_book_dir(args);
|
||||
let mut book = MDBook::load(&book_dir)?;
|
||||
let mut book = MDBook::load(&book_dir, args.is_present("auto-summary"))?;
|
||||
|
||||
let port = args.value_of("port").unwrap();
|
||||
let hostname = args.value_of("hostname").unwrap();
|
||||
|
@ -110,7 +114,7 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
|
|||
info!("Building book...");
|
||||
|
||||
// FIXME: This area is really ugly because we need to re-set livereload :(
|
||||
let result = MDBook::load(&book_dir).and_then(|mut b| {
|
||||
let result = MDBook::load(&book_dir, args.is_present("auto-summary")).and_then(|mut b| {
|
||||
update_config(&mut b);
|
||||
b.build()
|
||||
});
|
||||
|
|
|
@ -25,6 +25,10 @@ pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
|
|||
.multiple(true)
|
||||
.empty_values(false)
|
||||
.help("A comma-separated list of directories to add to {n}the crate search path when building tests"))
|
||||
.arg_from_usage(
|
||||
"--auto-summary 'Automatically generate the book's summary{n}\
|
||||
from the sources directory structure.'"
|
||||
)
|
||||
}
|
||||
|
||||
// test command implementation
|
||||
|
@ -34,7 +38,7 @@ 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 mut book = MDBook::load(&book_dir, args.is_present("auto-summary"))?;
|
||||
|
||||
if let Some(dest_dir) = args.value_of("dest-dir") {
|
||||
book.config.build.build_dir = dest_dir.into();
|
||||
|
|
|
@ -23,12 +23,16 @@ pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
|
|||
(Defaults to the Current Directory when omitted)'",
|
||||
)
|
||||
.arg_from_usage("-o, --open 'Open the compiled book in a web browser'")
|
||||
.arg_from_usage(
|
||||
"--auto-summary 'Automatically generate the book's summary{n}\
|
||||
from the sources directory structure.'",
|
||||
)
|
||||
}
|
||||
|
||||
// Watch command implementation
|
||||
pub fn execute(args: &ArgMatches) -> Result<()> {
|
||||
let book_dir = get_book_dir(args);
|
||||
let mut book = MDBook::load(&book_dir)?;
|
||||
let mut book = MDBook::load(&book_dir, args.is_present("auto-summary"))?;
|
||||
|
||||
let update_config = |book: &mut MDBook| {
|
||||
if let Some(dest_dir) = args.value_of("dest-dir") {
|
||||
|
@ -44,7 +48,7 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
|
|||
|
||||
trigger_on_change(&book, |paths, book_dir| {
|
||||
info!("Files changed: {:?}\nBuilding book...\n", paths);
|
||||
let result = MDBook::load(&book_dir).and_then(|mut b| {
|
||||
let result = MDBook::load(&book_dir, args.is_present("auto-summary")).and_then(|mut b| {
|
||||
update_config(&mut b);
|
||||
b.build()
|
||||
});
|
||||
|
|
|
@ -444,6 +444,8 @@ pub struct BuildConfig {
|
|||
/// Should the default preprocessors always be used when they are
|
||||
/// compatible with the renderer?
|
||||
pub use_default_preprocessors: bool,
|
||||
/// Automatically build the book's summary from the directory structure.
|
||||
pub auto_summary: bool,
|
||||
}
|
||||
|
||||
impl Default for BuildConfig {
|
||||
|
@ -452,6 +454,7 @@ impl Default for BuildConfig {
|
|||
build_dir: PathBuf::from("book"),
|
||||
create_missing: true,
|
||||
use_default_preprocessors: true,
|
||||
auto_summary: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -769,6 +772,7 @@ mod tests {
|
|||
build_dir: PathBuf::from("outputs"),
|
||||
create_missing: false,
|
||||
use_default_preprocessors: true,
|
||||
auto_summary: false,
|
||||
};
|
||||
let rust_should_be = RustConfig { edition: None };
|
||||
let playground_should_be = Playground {
|
||||
|
@ -962,6 +966,7 @@ mod tests {
|
|||
build_dir: PathBuf::from("my-book"),
|
||||
create_missing: true,
|
||||
use_default_preprocessors: true,
|
||||
auto_summary: false,
|
||||
};
|
||||
|
||||
let html_should_be = HtmlConfig {
|
||||
|
|
|
@ -52,7 +52,7 @@
|
|||
//!
|
||||
//! let root_dir = "/path/to/book/root";
|
||||
//!
|
||||
//! let mut md = MDBook::load(root_dir)
|
||||
//! let mut md = MDBook::load(root_dir, false)
|
||||
//! .expect("Unable to load the book");
|
||||
//! md.build().expect("Building failed");
|
||||
//! ```
|
||||
|
|
|
@ -183,7 +183,7 @@ mod tests {
|
|||
|
||||
fn guide() -> MDBook {
|
||||
let example = Path::new(env!("CARGO_MANIFEST_DIR")).join("guide");
|
||||
MDBook::load(example).unwrap()
|
||||
MDBook::load(example, false).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -33,7 +33,7 @@ fn example_doesnt_support_not_supported() {
|
|||
fn ask_the_preprocessor_to_blow_up() {
|
||||
let dummy_book = DummyBook::new();
|
||||
let temp = dummy_book.build().unwrap();
|
||||
let mut md = MDBook::load(temp.path()).unwrap();
|
||||
let mut md = MDBook::load(temp.path(), false).unwrap();
|
||||
md.with_preprocessor(example());
|
||||
|
||||
md.config
|
||||
|
@ -49,7 +49,7 @@ fn ask_the_preprocessor_to_blow_up() {
|
|||
fn process_the_dummy_book() {
|
||||
let dummy_book = DummyBook::new();
|
||||
let temp = dummy_book.build().unwrap();
|
||||
let mut md = MDBook::load(temp.path()).unwrap();
|
||||
let mut md = MDBook::load(temp.path(), false).unwrap();
|
||||
md.with_preprocessor(example());
|
||||
|
||||
md.build().unwrap();
|
||||
|
|
|
@ -95,7 +95,7 @@ fn run_mdbook_init_with_custom_book_and_src_locations() {
|
|||
let contents = fs::read_to_string(temp.path().join("book.toml")).unwrap();
|
||||
assert_eq!(
|
||||
contents,
|
||||
"[book]\nauthors = []\nlanguage = \"en\"\nmultilingual = false\nsrc = \"in\"\n\n[build]\nbuild-dir = \"out\"\ncreate-missing = true\nuse-default-preprocessors = true\n"
|
||||
"[book]\nauthors = []\nlanguage = \"en\"\nmultilingual = false\nsrc = \"in\"\n\n[build]\nauto-summary = false\nbuild-dir = \"out\"\ncreate-missing = true\nuse-default-preprocessors = true\n"
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ const TOC_SECOND_LEVEL: &[&str] = &[
|
|||
#[test]
|
||||
fn build_the_dummy_book() {
|
||||
let temp = DummyBook::new().build().unwrap();
|
||||
let md = MDBook::load(temp.path()).unwrap();
|
||||
let md = MDBook::load(temp.path(), false).unwrap();
|
||||
|
||||
md.build().unwrap();
|
||||
}
|
||||
|
@ -50,7 +50,7 @@ fn build_the_dummy_book() {
|
|||
#[test]
|
||||
fn by_default_mdbook_generates_rendered_content_in_the_book_directory() {
|
||||
let temp = DummyBook::new().build().unwrap();
|
||||
let md = MDBook::load(temp.path()).unwrap();
|
||||
let md = MDBook::load(temp.path(), false).unwrap();
|
||||
|
||||
assert!(!temp.path().join("book").exists());
|
||||
md.build().unwrap();
|
||||
|
@ -63,7 +63,7 @@ fn by_default_mdbook_generates_rendered_content_in_the_book_directory() {
|
|||
#[test]
|
||||
fn make_sure_bottom_level_files_contain_links_to_chapters() {
|
||||
let temp = DummyBook::new().build().unwrap();
|
||||
let md = MDBook::load(temp.path()).unwrap();
|
||||
let md = MDBook::load(temp.path(), false).unwrap();
|
||||
md.build().unwrap();
|
||||
|
||||
let dest = temp.path().join("book");
|
||||
|
@ -85,7 +85,7 @@ fn make_sure_bottom_level_files_contain_links_to_chapters() {
|
|||
#[test]
|
||||
fn check_correct_cross_links_in_nested_dir() {
|
||||
let temp = DummyBook::new().build().unwrap();
|
||||
let md = MDBook::load(temp.path()).unwrap();
|
||||
let md = MDBook::load(temp.path(), false).unwrap();
|
||||
md.build().unwrap();
|
||||
|
||||
let first = temp.path().join("book").join("first");
|
||||
|
@ -117,7 +117,7 @@ fn check_correct_cross_links_in_nested_dir() {
|
|||
#[test]
|
||||
fn check_correct_relative_links_in_print_page() {
|
||||
let temp = DummyBook::new().build().unwrap();
|
||||
let md = MDBook::load(temp.path()).unwrap();
|
||||
let md = MDBook::load(temp.path(), false).unwrap();
|
||||
md.build().unwrap();
|
||||
|
||||
let first = temp.path().join("book");
|
||||
|
@ -138,7 +138,7 @@ fn check_correct_relative_links_in_print_page() {
|
|||
#[test]
|
||||
fn rendered_code_has_playground_stuff() {
|
||||
let temp = DummyBook::new().build().unwrap();
|
||||
let md = MDBook::load(temp.path()).unwrap();
|
||||
let md = MDBook::load(temp.path(), false).unwrap();
|
||||
md.build().unwrap();
|
||||
|
||||
let nested = temp.path().join("book/first/nested.html");
|
||||
|
@ -153,7 +153,7 @@ fn rendered_code_has_playground_stuff() {
|
|||
#[test]
|
||||
fn anchors_include_text_between_but_not_anchor_comments() {
|
||||
let temp = DummyBook::new().build().unwrap();
|
||||
let md = MDBook::load(temp.path()).unwrap();
|
||||
let md = MDBook::load(temp.path(), false).unwrap();
|
||||
md.build().unwrap();
|
||||
|
||||
let nested = temp.path().join("book/first/nested.html");
|
||||
|
@ -167,7 +167,7 @@ fn anchors_include_text_between_but_not_anchor_comments() {
|
|||
#[test]
|
||||
fn rustdoc_include_hides_the_unspecified_part_of_the_file() {
|
||||
let temp = DummyBook::new().build().unwrap();
|
||||
let md = MDBook::load(temp.path()).unwrap();
|
||||
let md = MDBook::load(temp.path(), false).unwrap();
|
||||
md.build().unwrap();
|
||||
|
||||
let nested = temp.path().join("book/first/nested.html");
|
||||
|
@ -191,7 +191,7 @@ fn chapter_content_appears_in_rendered_document() {
|
|||
];
|
||||
|
||||
let temp = DummyBook::new().build().unwrap();
|
||||
let md = MDBook::load(temp.path()).unwrap();
|
||||
let md = MDBook::load(temp.path(), false).unwrap();
|
||||
md.build().unwrap();
|
||||
|
||||
let destination = temp.path().join("book");
|
||||
|
@ -251,7 +251,7 @@ fn root_index_html() -> Result<Document> {
|
|||
let temp = DummyBook::new()
|
||||
.build()
|
||||
.with_context(|| "Couldn't create the dummy book")?;
|
||||
MDBook::load(temp.path())?
|
||||
MDBook::load(temp.path(), false)?
|
||||
.build()
|
||||
.with_context(|| "Book building failed")?;
|
||||
|
||||
|
@ -350,7 +350,7 @@ fn create_missing_file_with_config() {
|
|||
#[test]
|
||||
fn able_to_include_playground_files_in_chapters() {
|
||||
let temp = DummyBook::new().build().unwrap();
|
||||
let md = MDBook::load(temp.path()).unwrap();
|
||||
let md = MDBook::load(temp.path(), false).unwrap();
|
||||
md.build().unwrap();
|
||||
|
||||
let second = temp.path().join("book/second.html");
|
||||
|
@ -368,7 +368,7 @@ fn able_to_include_playground_files_in_chapters() {
|
|||
#[test]
|
||||
fn able_to_include_files_in_chapters() {
|
||||
let temp = DummyBook::new().build().unwrap();
|
||||
let md = MDBook::load(temp.path()).unwrap();
|
||||
let md = MDBook::load(temp.path(), false).unwrap();
|
||||
md.build().unwrap();
|
||||
|
||||
let includes = temp.path().join("book/first/includes.html");
|
||||
|
@ -386,7 +386,7 @@ fn able_to_include_files_in_chapters() {
|
|||
#[test]
|
||||
fn recursive_includes_are_capped() {
|
||||
let temp = DummyBook::new().build().unwrap();
|
||||
let md = MDBook::load(temp.path()).unwrap();
|
||||
let md = MDBook::load(temp.path(), false).unwrap();
|
||||
md.build().unwrap();
|
||||
|
||||
let recursive = temp.path().join("book/first/recursive.html");
|
||||
|
@ -400,7 +400,7 @@ Around the world, around the world"];
|
|||
fn example_book_can_build() {
|
||||
let example_book_dir = dummy_book::new_copy_of_example_book().unwrap();
|
||||
|
||||
let md = MDBook::load(example_book_dir.path()).unwrap();
|
||||
let md = MDBook::load(example_book_dir.path(), false).unwrap();
|
||||
|
||||
md.build().unwrap();
|
||||
}
|
||||
|
@ -418,7 +418,7 @@ fn book_with_a_reserved_filename_does_not_build() {
|
|||
let mut summary_file = fs::File::create(summary_path).unwrap();
|
||||
writeln!(summary_file, "[print](print.md)").unwrap();
|
||||
|
||||
let md = MDBook::load(tmp_dir.path()).unwrap();
|
||||
let md = MDBook::load(tmp_dir.path(), false).unwrap();
|
||||
let got = md.build();
|
||||
assert!(got.is_err());
|
||||
}
|
||||
|
@ -457,7 +457,7 @@ fn theme_dir_overrides_work_correctly() {
|
|||
|
||||
write_file(&theme_dir, "index.hbs", &index).unwrap();
|
||||
|
||||
let md = MDBook::load(book_dir).unwrap();
|
||||
let md = MDBook::load(book_dir, false).unwrap();
|
||||
md.build().unwrap();
|
||||
|
||||
let built_index = book_dir.join("book").join("index.html");
|
||||
|
@ -467,7 +467,7 @@ fn theme_dir_overrides_work_correctly() {
|
|||
#[test]
|
||||
fn no_index_for_print_html() {
|
||||
let temp = DummyBook::new().build().unwrap();
|
||||
let md = MDBook::load(temp.path()).unwrap();
|
||||
let md = MDBook::load(temp.path(), false).unwrap();
|
||||
md.build().unwrap();
|
||||
|
||||
let print_html = temp.path().join("book/print.html");
|
||||
|
@ -480,7 +480,7 @@ fn no_index_for_print_html() {
|
|||
#[test]
|
||||
fn markdown_options() {
|
||||
let temp = DummyBook::new().build().unwrap();
|
||||
let md = MDBook::load(temp.path()).unwrap();
|
||||
let md = MDBook::load(temp.path(), false).unwrap();
|
||||
md.build().unwrap();
|
||||
|
||||
let path = temp.path().join("book/first/markdown.html");
|
||||
|
@ -516,7 +516,7 @@ fn markdown_options() {
|
|||
#[test]
|
||||
fn redirects_are_emitted_correctly() {
|
||||
let temp = DummyBook::new().build().unwrap();
|
||||
let mut md = MDBook::load(temp.path()).unwrap();
|
||||
let mut md = MDBook::load(temp.path(), false).unwrap();
|
||||
|
||||
// override the "outputs.html.redirect" table
|
||||
let redirects: HashMap<PathBuf, String> = vec![
|
||||
|
@ -555,7 +555,7 @@ fn edit_url_has_default_src_dir_edit_url() {
|
|||
|
||||
write_file(temp.path(), "book.toml", book_toml.as_bytes()).unwrap();
|
||||
|
||||
let md = MDBook::load(temp.path()).unwrap();
|
||||
let md = MDBook::load(temp.path(), false).unwrap();
|
||||
md.build().unwrap();
|
||||
|
||||
let index_html = temp.path().join("book").join("index.html");
|
||||
|
@ -581,7 +581,7 @@ fn edit_url_has_configured_src_dir_edit_url() {
|
|||
|
||||
write_file(temp.path(), "book.toml", book_toml.as_bytes()).unwrap();
|
||||
|
||||
let md = MDBook::load(temp.path()).unwrap();
|
||||
let md = MDBook::load(temp.path(), false).unwrap();
|
||||
md.build().unwrap();
|
||||
|
||||
let index_html = temp.path().join("book").join("index.html");
|
||||
|
@ -619,7 +619,7 @@ mod search {
|
|||
#[allow(clippy::float_cmp)]
|
||||
fn book_creates_reasonable_search_index() {
|
||||
let temp = DummyBook::new().build().unwrap();
|
||||
let md = MDBook::load(temp.path()).unwrap();
|
||||
let md = MDBook::load(temp.path(), false).unwrap();
|
||||
md.build().unwrap();
|
||||
|
||||
let index = read_book_index(temp.path());
|
||||
|
@ -671,7 +671,7 @@ mod search {
|
|||
fn get_fixture() -> serde_json::Value {
|
||||
if GENERATE_FIXTURE {
|
||||
let temp = DummyBook::new().build().unwrap();
|
||||
let md = MDBook::load(temp.path()).unwrap();
|
||||
let md = MDBook::load(temp.path(), false).unwrap();
|
||||
md.build().unwrap();
|
||||
|
||||
let src = read_book_index(temp.path());
|
||||
|
@ -699,7 +699,7 @@ mod search {
|
|||
#[test]
|
||||
fn search_index_hasnt_changed_accidentally() {
|
||||
let temp = DummyBook::new().build().unwrap();
|
||||
let md = MDBook::load(temp.path()).unwrap();
|
||||
let md = MDBook::load(temp.path(), false).unwrap();
|
||||
md.build().unwrap();
|
||||
|
||||
let book_index = read_book_index(temp.path());
|
||||
|
|
|
@ -7,7 +7,7 @@ use mdbook::MDBook;
|
|||
#[test]
|
||||
fn mdbook_can_correctly_test_a_passing_book() {
|
||||
let temp = DummyBook::new().with_passing_test(true).build().unwrap();
|
||||
let mut md = MDBook::load(temp.path()).unwrap();
|
||||
let mut md = MDBook::load(temp.path(), false).unwrap();
|
||||
|
||||
let result = md.test(vec![]);
|
||||
assert!(
|
||||
|
@ -20,7 +20,7 @@ fn mdbook_can_correctly_test_a_passing_book() {
|
|||
#[test]
|
||||
fn mdbook_detects_book_with_failing_tests() {
|
||||
let temp = DummyBook::new().with_passing_test(false).build().unwrap();
|
||||
let mut md = MDBook::load(temp.path()).unwrap();
|
||||
let mut md = MDBook::load(temp.path(), false).unwrap();
|
||||
|
||||
assert!(md.test(vec![]).is_err());
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue