Added the prefix chapter parsing step and a failing test for separators
This commit is contained in:
parent
dcc8368543
commit
df2ee64b2e
|
@ -3,9 +3,9 @@
|
||||||
#![deny(missing_docs)]
|
#![deny(missing_docs)]
|
||||||
|
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::error::Error;
|
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
|
use errors::*;
|
||||||
|
|
||||||
mod summary;
|
mod summary;
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ impl Loader {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse the `SUMMARY.md` file.
|
/// Parse the `SUMMARY.md` file.
|
||||||
pub fn parse_summary(&self) -> Result<Summary, Box<Error>> {
|
pub fn parse_summary(&self) -> Result<Summary> {
|
||||||
let path = self.source_directory.join("SUMMARY.md");
|
let path = self.source_directory.join("SUMMARY.md");
|
||||||
|
|
||||||
let mut summary_content = String::new();
|
let mut summary_content = String::new();
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
use std::error::Error;
|
#![allow(dead_code, unused_variables)]
|
||||||
|
|
||||||
use std::fmt::{self, Formatter, Display};
|
use std::fmt::{self, Formatter, Display};
|
||||||
use std::io::{Error as IoError, ErrorKind};
|
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use pulldown_cmark::{self, Event, Tag};
|
use pulldown_cmark::{self, Event, Tag};
|
||||||
|
|
||||||
|
use errors::*;
|
||||||
|
|
||||||
|
|
||||||
/// Parse the text from a `SUMMARY.md` file into a sort of "recipe" to be
|
/// Parse the text from a `SUMMARY.md` file into a sort of "recipe" to be
|
||||||
/// used when loading a book from disk.
|
/// used when loading a book from disk.
|
||||||
|
@ -42,7 +44,7 @@ use pulldown_cmark::{self, Event, Tag};
|
||||||
///
|
///
|
||||||
/// All other elements are unsupported and will be ignored at best or result in
|
/// All other elements are unsupported and will be ignored at best or result in
|
||||||
/// an error.
|
/// an error.
|
||||||
pub fn parse_summary(summary: &str) -> Result<Summary, Box<Error>> {
|
pub fn parse_summary(summary: &str) -> Result<Summary> {
|
||||||
let parser = SummaryParser::new(summary);
|
let parser = SummaryParser::new(summary);
|
||||||
parser.parse()
|
parser.parse()
|
||||||
}
|
}
|
||||||
|
@ -51,6 +53,9 @@ pub fn parse_summary(summary: &str) -> Result<Summary, Box<Error>> {
|
||||||
#[derive(Debug, Clone, Default, PartialEq)]
|
#[derive(Debug, Clone, Default, PartialEq)]
|
||||||
pub struct Summary {
|
pub struct Summary {
|
||||||
title: Option<String>,
|
title: Option<String>,
|
||||||
|
prefix_chapters: Vec<SummaryItem>,
|
||||||
|
numbered_chapters: Vec<SummaryItem>,
|
||||||
|
suffix_chapters: Vec<SummaryItem>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A struct representing an entry in the `SUMMARY.md`, possibly with nested
|
/// A struct representing an entry in the `SUMMARY.md`, possibly with nested
|
||||||
|
@ -61,6 +66,7 @@ pub struct Summary {
|
||||||
struct Link {
|
struct Link {
|
||||||
name: String,
|
name: String,
|
||||||
location: PathBuf,
|
location: PathBuf,
|
||||||
|
number: Option<SectionNumber>,
|
||||||
nested_items: Vec<SummaryItem>,
|
nested_items: Vec<SummaryItem>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,28 +159,55 @@ impl<'a> SummaryParser<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse the text the `SummaryParser` was created with.
|
/// Parse the text the `SummaryParser` was created with.
|
||||||
fn parse(mut self) -> Result<Summary, Box<Error>> {
|
fn parse(mut self) -> Result<Summary> {
|
||||||
self.summary.title = self.parse_title();
|
self.summary.title = self.parse_title();
|
||||||
|
|
||||||
Ok(self.summary)
|
Ok(self.summary)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn step(&mut self) -> Result<(), Box<Error>> {
|
fn step(&mut self) -> Result<()> {
|
||||||
let next_event = self.stream.next().expect("TODO: error-chain");
|
let next_event = self.stream.next().expect("TODO: error-chain");
|
||||||
trace!("[*] Current state = {:?}, Next Event = {:?}", self.state, next_event);
|
trace!("[*] Current state = {:?}, Next Event = {:?}", self.state, next_event);
|
||||||
|
|
||||||
match self.state {
|
match self.state {
|
||||||
State::Begin => self.step_start(next_event),
|
State::Begin => self.step_start(next_event)?,
|
||||||
other => unimplemented!()
|
State::PrefixChapters => self.step_prefix(next_event)?,
|
||||||
|
_ => unimplemented!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The very first state, we should see a `BeginParagraph` token or
|
/// The very first state, we should see a `BeginParagraph` token or
|
||||||
/// it's an error...
|
/// it's an error...
|
||||||
fn step_start(&mut self, event: Event<'a>) -> Result<(), Box<Error>> {
|
fn step_start(&mut self, event: Event<'a>) -> Result<()> {
|
||||||
match event {
|
match event {
|
||||||
Event::Start(Tag::Paragraph) => self.state = State::PrefixChapters,
|
Event::Start(Tag::Paragraph) => self.state = State::PrefixChapters,
|
||||||
other => panic!("Unexpected tag! {:?}", other),
|
other => bail!("Unexpected tag! {:?}", other),
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// In the second step we look out for links and horizontal rules to add
|
||||||
|
/// to the prefix.
|
||||||
|
fn step_prefix(&mut self, event: Event<'a>) -> Result<()> {
|
||||||
|
match event {
|
||||||
|
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(),
|
||||||
|
};
|
||||||
|
self.summary.prefix_chapters.push(SummaryItem::Link(link));
|
||||||
|
}
|
||||||
|
|
||||||
|
other => {
|
||||||
|
debug!("[*] Skipping unexpected token in summary: {:?}", other);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -196,7 +229,7 @@ impl<'a> SummaryParser<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a single item (`[Some Chapter Name](./path/to/chapter.md)`).
|
/// Parse a single item (`[Some Chapter Name](./path/to/chapter.md)`).
|
||||||
fn parse_item(&mut self) -> Result<Link, Box<Error>> {
|
fn parse_item(&mut self) -> Result<Link> {
|
||||||
let next = self.stream.next();
|
let next = self.stream.next();
|
||||||
|
|
||||||
if let Some(Event::Start(Tag::Link(dest, _))) = next {
|
if let Some(Event::Start(Tag::Link(dest, _))) = next {
|
||||||
|
@ -205,10 +238,11 @@ impl<'a> SummaryParser<'a> {
|
||||||
Ok(Link {
|
Ok(Link {
|
||||||
name: stringify_events(content),
|
name: stringify_events(content),
|
||||||
location: PathBuf::from(dest.to_string()),
|
location: PathBuf::from(dest.to_string()),
|
||||||
|
number: None,
|
||||||
nested_items: Vec::new(),
|
nested_items: Vec::new(),
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
Err(Box::new(IoError::new(ErrorKind::Other, format!("Expected a link, got {:?}", next))))
|
bail!("Expected a link, got {:?}", next)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -301,6 +335,7 @@ mod tests {
|
||||||
let should_be = Link {
|
let should_be = Link {
|
||||||
name: String::from("A Chapter"),
|
name: String::from("A Chapter"),
|
||||||
location: PathBuf::from("./path/to/chapter"),
|
location: PathBuf::from("./path/to/chapter"),
|
||||||
|
number: None,
|
||||||
nested_items: Vec::new(),
|
nested_items: Vec::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -333,4 +368,52 @@ mod tests {
|
||||||
parser.step().unwrap();
|
parser.step().unwrap();
|
||||||
assert_eq!(parser.state, should_be);
|
assert_eq!(parser.state, should_be);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn first_token_must_be_open_paragraph() {
|
||||||
|
let src = "hello world";
|
||||||
|
|
||||||
|
let mut parser = SummaryParser::new(src);
|
||||||
|
let _ = parser.stream.next(); // manually step past the Start Paragraph
|
||||||
|
assert!(parser.step().is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_parse_prefix_chapter_links() {
|
||||||
|
let src = "[Hello World](./foo/bar/baz)";
|
||||||
|
let should_be = Link {
|
||||||
|
name: String::from("Hello World"),
|
||||||
|
location: PathBuf::from("./foo/bar/baz"),
|
||||||
|
number: None,
|
||||||
|
nested_items: Vec::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut parser = SummaryParser::new(src);
|
||||||
|
parser.state = State::PrefixChapters;
|
||||||
|
assert!(parser.summary.prefix_chapters.is_empty());
|
||||||
|
|
||||||
|
let _ = parser.stream.next(); // manually step past the Start Paragraph
|
||||||
|
parser.step().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(parser.summary.prefix_chapters.len(), 1);
|
||||||
|
assert_eq!(parser.summary.prefix_chapters[0], SummaryItem::Link(should_be));
|
||||||
|
assert_eq!(parser.state, State::PrefixChapters);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_parse_prefix_chapter_horizontal_rules() {
|
||||||
|
let src = "---";
|
||||||
|
let should_be = SummaryItem::Separator;
|
||||||
|
|
||||||
|
let mut parser = SummaryParser::new(src);
|
||||||
|
parser.state = State::PrefixChapters;
|
||||||
|
assert!(parser.summary.prefix_chapters.is_empty());
|
||||||
|
|
||||||
|
let _ = parser.stream.next(); // manually step past the Start Paragraph
|
||||||
|
parser.step().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(parser.summary.prefix_chapters.len(), 1);
|
||||||
|
assert_eq!(parser.summary.prefix_chapters[0], should_be);
|
||||||
|
assert_eq!(parser.state, State::PrefixChapters);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue