Did a little refactoring to clean up the summary parser

This commit is contained in:
Michael Bryan 2017-06-30 21:25:27 +08:00
parent 131a404064
commit eb59319dc3
2 changed files with 104 additions and 64 deletions

View File

@ -1,4 +1,42 @@
//! Functionality for loading the internal book representation from disk.
//!
//! The typical use case is to create a `Loader` pointing at the correct
//! source directory then call the `load()` method. Internally this will
//! search for the `SUMMARY.md` file, parse it, then use the parsed
//! `Summary` to construct an in-memory representation of the entire book.
//!
//! # Examples
//!
//! ```rust,no_run
//! # fn run() -> mdbook::errors::Result<()> {
//! use mdbook::loader::Loader;
//! let loader = Loader::new("./src/");
//! let book = loader.load()?;
//! # Ok(())
//! # }
//! # fn main() { run().unwrap() }
//! ```
//!
//! Alternatively, if you are using the `mdbook` crate as a library and
//! only want to read the `SUMMARY.md` file without having to load the
//! entire book from disk, you can use the `parse_summary()` function.
//!
//! ```rust
//! # fn run() -> mdbook::errors::Result<()> {
//! use mdbook::loader::parse_summary;
//! let src = "# Book Summary
//!
//! [Introduction](./index.md)
//! - [First Chapter](./first/index.md)
//! - [Sub-Section](./first/subsection.md)
//! - [Second Chapter](./second/index.md)
//! ";
//! let summary = parse_summary(src)?;
//! println!("{:#?}", summary);
//! # Ok(())
//! # }
//! # fn main() { run().unwrap() }
//! ```
#![deny(missing_docs)]

View File

@ -130,7 +130,22 @@ enum State {
End,
}
/// A stateful parser for parsing a `SUMMARY.md` file.
/// A state machine parser for parsing a `SUMMARY.md` file.
///
/// The parser has roughly 5 states,
///
/// - **Begin:** the initial state
/// - **Prefix Chapters:** Parsing the prefix chapters
/// - **Numbered Chapters:** Parsing the numbered chapters, using a `usize` to
/// indicate the nesting level (because chapters can have sub-chapters)
/// - **Suffix Chapters:** pretty much identical to the Prefix Chapters
/// - **End:** The final state
///
/// The `parse()` method then continually invokes `step()` until it reaches the
/// `End` state. Parsing is guaranteed to (eventually) finish because the next
/// `Event` is read from the underlying `pulldown_cmark::Parser` and passed
/// into the current state's associated method.
///
///
/// # Grammar
///
@ -258,14 +273,9 @@ impl<'a> SummaryParser<'a> {
Event::Start(Tag::Link(location, _)) => {
let content = collect_events!(self.stream, Tag::Link(_, _));
let text = stringify_events(content);
let link = Link {
name: text,
location: PathBuf::from(location.as_ref()),
number: None,
nested_items: Vec::new(),
};
let link = Link::new(text, location.as_ref());
debug!("[*] Found a prefix chapter, {:?}", link.name);
debug!("[*] Found a prefix chapter: {:?}", link.name);
self.summary.prefix_chapters.push(SummaryItem::Link(link));
},
Event::End(Tag::Rule) => {
@ -308,18 +318,13 @@ impl<'a> SummaryParser<'a> {
trace!("[*] Section number is {}", section_number);
},
Event::Start(Tag::List(_)) => {
match self.state {
State::NumberedChapters(n) => {
let new_nest = n + 1;
self.state = State::NumberedChapters(new_nest);
trace!("[*] Nesting level increased to {}", new_nest);
},
other => unreachable!(),
if let State::NumberedChapters(n) = self.state {
self.state = State::NumberedChapters(n + 1);
trace!("[*] Nesting level increased to {}", n + 1);
}
},
Event::End(Tag::List(_)) => {
match self.state {
State::NumberedChapters(n) => {
if let State::NumberedChapters(n) = self.state {
if n == 0 {
trace!("[*] Finished parsing the numbered chapters");
self.state = State::SuffixChapters;
@ -327,8 +332,6 @@ impl<'a> SummaryParser<'a> {
trace!("[*] Nesting level decreased to {}", n - 1);
self.state = State::NumberedChapters(n - 1);
}
},
other => unreachable!(),
}
},
other => {
@ -345,14 +348,9 @@ impl<'a> SummaryParser<'a> {
Event::Start(Tag::Link(location, _)) => {
let content = collect_events!(self.stream, Tag::Link(_, _));
let text = stringify_events(content);
let link = Link {
name: text,
location: PathBuf::from(location.as_ref()),
number: None,
nested_items: Vec::new(),
};
let link = Link::new(text, location.as_ref());
debug!("[*] Found a suffix chapter, {:?}", link.name);
debug!("[*] Found a suffix chapter: {:?}", link.name);
self.summary.suffix_chapters.push(SummaryItem::Link(link));
},
Event::End(Tag::Rule) => {
@ -375,12 +373,7 @@ impl<'a> SummaryParser<'a> {
if let Some(Event::Start(Tag::Link(dest, _))) = next {
let content = collect_events!(self.stream, Tag::Link(..));
Ok(Link {
name: stringify_events(content),
location: PathBuf::from(dest.to_string()),
number: None,
nested_items: Vec::new(),
})
Ok(Link::new(stringify_events(content), dest.as_ref()))
} else {
bail!("Expected a link, got {:?}", next)
}
@ -449,23 +442,31 @@ fn push_item_at_nesting_level(links: &mut Vec<SummaryItem>, mut item: SummaryIte
let index_for_item = links.len() + 1;
// FIXME: This bit needs simplifying!
let (index, last_link) =
links
.iter_mut()
.enumerate()
.filter_map(|(i, item)| item.maybe_link_mut().map(|l| (i, l)))
.rev()
.next()
.ok_or_else(|| format!("Can't recurse any further, summary needs to be {} more levels deep", level))?;
let (index, last_link) = get_last_link(links).chain_err(|| {
format!("The list of links needs to be {} levels deeper (current position {})",
level, section_number)
})?;
section_number.push(index as u32 + 1);
push_item_at_nesting_level(&mut last_link.nested_items, item, level - 1, section_number)
}
}
/// Gets a pointer to the last `Link` in a list of `SummaryItem`s, and its
/// index.
fn get_last_link(links: &mut [SummaryItem]) -> Result<(usize, &mut Link)> {
links
.iter_mut()
.enumerate()
.filter_map(|(i, item)| item.maybe_link_mut().map(|l| (i, l)))
.rev()
.next()
.ok_or_else(|| "The list of SummaryItems doesn't contain any Links".into())
}
/// Extracts the text from formatted markdown.
/// Removes the styling from a list of Markdown events and returns just the
/// plain text.
fn stringify_events(events: Vec<Event>) -> String {
events
.into_iter()
@ -476,7 +477,8 @@ fn stringify_events(events: Vec<Event>) -> String {
.collect()
}
/// A section number like "1.2.3", basically just a newtype'd `Vec<u32>`.
/// A section number like "1.2.3", basically just a newtype'd `Vec<u32>` with
/// a pretty `Display` impl.
#[derive(Debug, PartialEq, Clone, Default)]
pub struct SectionNumber(pub Vec<u32>);