Did a little refactoring to clean up the summary parser
This commit is contained in:
parent
131a404064
commit
eb59319dc3
|
@ -1,4 +1,42 @@
|
||||||
//! Functionality for loading the internal book representation from disk.
|
//! 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)]
|
#![deny(missing_docs)]
|
||||||
|
|
||||||
|
|
|
@ -130,7 +130,22 @@ enum State {
|
||||||
End,
|
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
|
/// # Grammar
|
||||||
///
|
///
|
||||||
|
@ -258,14 +273,9 @@ impl<'a> SummaryParser<'a> {
|
||||||
Event::Start(Tag::Link(location, _)) => {
|
Event::Start(Tag::Link(location, _)) => {
|
||||||
let content = collect_events!(self.stream, Tag::Link(_, _));
|
let content = collect_events!(self.stream, Tag::Link(_, _));
|
||||||
let text = stringify_events(content);
|
let text = stringify_events(content);
|
||||||
let link = Link {
|
let link = Link::new(text, location.as_ref());
|
||||||
name: text,
|
|
||||||
location: PathBuf::from(location.as_ref()),
|
|
||||||
number: None,
|
|
||||||
nested_items: Vec::new(),
|
|
||||||
};
|
|
||||||
|
|
||||||
debug!("[*] Found a prefix chapter, {:?}", link.name);
|
debug!("[*] Found a prefix chapter: {:?}", link.name);
|
||||||
self.summary.prefix_chapters.push(SummaryItem::Link(link));
|
self.summary.prefix_chapters.push(SummaryItem::Link(link));
|
||||||
},
|
},
|
||||||
Event::End(Tag::Rule) => {
|
Event::End(Tag::Rule) => {
|
||||||
|
@ -308,18 +318,13 @@ impl<'a> SummaryParser<'a> {
|
||||||
trace!("[*] Section number is {}", section_number);
|
trace!("[*] Section number is {}", section_number);
|
||||||
},
|
},
|
||||||
Event::Start(Tag::List(_)) => {
|
Event::Start(Tag::List(_)) => {
|
||||||
match self.state {
|
if let State::NumberedChapters(n) = self.state {
|
||||||
State::NumberedChapters(n) => {
|
self.state = State::NumberedChapters(n + 1);
|
||||||
let new_nest = n + 1;
|
trace!("[*] Nesting level increased to {}", n + 1);
|
||||||
self.state = State::NumberedChapters(new_nest);
|
|
||||||
trace!("[*] Nesting level increased to {}", new_nest);
|
|
||||||
},
|
|
||||||
other => unreachable!(),
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Event::End(Tag::List(_)) => {
|
Event::End(Tag::List(_)) => {
|
||||||
match self.state {
|
if let State::NumberedChapters(n) = self.state {
|
||||||
State::NumberedChapters(n) => {
|
|
||||||
if n == 0 {
|
if n == 0 {
|
||||||
trace!("[*] Finished parsing the numbered chapters");
|
trace!("[*] Finished parsing the numbered chapters");
|
||||||
self.state = State::SuffixChapters;
|
self.state = State::SuffixChapters;
|
||||||
|
@ -327,8 +332,6 @@ impl<'a> SummaryParser<'a> {
|
||||||
trace!("[*] Nesting level decreased to {}", n - 1);
|
trace!("[*] Nesting level decreased to {}", n - 1);
|
||||||
self.state = State::NumberedChapters(n - 1);
|
self.state = State::NumberedChapters(n - 1);
|
||||||
}
|
}
|
||||||
},
|
|
||||||
other => unreachable!(),
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
other => {
|
other => {
|
||||||
|
@ -345,14 +348,9 @@ impl<'a> SummaryParser<'a> {
|
||||||
Event::Start(Tag::Link(location, _)) => {
|
Event::Start(Tag::Link(location, _)) => {
|
||||||
let content = collect_events!(self.stream, Tag::Link(_, _));
|
let content = collect_events!(self.stream, Tag::Link(_, _));
|
||||||
let text = stringify_events(content);
|
let text = stringify_events(content);
|
||||||
let link = Link {
|
let link = Link::new(text, location.as_ref());
|
||||||
name: text,
|
|
||||||
location: PathBuf::from(location.as_ref()),
|
|
||||||
number: None,
|
|
||||||
nested_items: Vec::new(),
|
|
||||||
};
|
|
||||||
|
|
||||||
debug!("[*] Found a suffix chapter, {:?}", link.name);
|
debug!("[*] Found a suffix chapter: {:?}", link.name);
|
||||||
self.summary.suffix_chapters.push(SummaryItem::Link(link));
|
self.summary.suffix_chapters.push(SummaryItem::Link(link));
|
||||||
},
|
},
|
||||||
Event::End(Tag::Rule) => {
|
Event::End(Tag::Rule) => {
|
||||||
|
@ -375,12 +373,7 @@ impl<'a> SummaryParser<'a> {
|
||||||
if let Some(Event::Start(Tag::Link(dest, _))) = next {
|
if let Some(Event::Start(Tag::Link(dest, _))) = next {
|
||||||
let content = collect_events!(self.stream, Tag::Link(..));
|
let content = collect_events!(self.stream, Tag::Link(..));
|
||||||
|
|
||||||
Ok(Link {
|
Ok(Link::new(stringify_events(content), dest.as_ref()))
|
||||||
name: stringify_events(content),
|
|
||||||
location: PathBuf::from(dest.to_string()),
|
|
||||||
number: None,
|
|
||||||
nested_items: Vec::new(),
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
bail!("Expected a link, got {:?}", next)
|
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;
|
let index_for_item = links.len() + 1;
|
||||||
|
|
||||||
// FIXME: This bit needs simplifying!
|
// FIXME: This bit needs simplifying!
|
||||||
let (index, last_link) =
|
let (index, last_link) = get_last_link(links).chain_err(|| {
|
||||||
links
|
format!("The list of links needs to be {} levels deeper (current position {})",
|
||||||
.iter_mut()
|
level, section_number)
|
||||||
.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))?;
|
|
||||||
|
|
||||||
section_number.push(index as u32 + 1);
|
section_number.push(index as u32 + 1);
|
||||||
push_item_at_nesting_level(&mut last_link.nested_items, item, level - 1, section_number)
|
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 {
|
fn stringify_events(events: Vec<Event>) -> String {
|
||||||
events
|
events
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
@ -476,7 +477,8 @@ fn stringify_events(events: Vec<Event>) -> String {
|
||||||
.collect()
|
.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)]
|
#[derive(Debug, PartialEq, Clone, Default)]
|
||||||
pub struct SectionNumber(pub Vec<u32>);
|
pub struct SectionNumber(pub Vec<u32>);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue