Created a Loader for loading a book using the Summary
This is a squashed commit. It roughly encompasses the following changes. --- \# Book - Created another private submodule, mdbook::loader::book - This submodule contains the data types representing a Book - For now the Book just contains a list of BookItems (either chapters or separators) - A Chapter contains its name, contents (as one long string), an optional section number (only numbered chapters have numbers, obviously), and any nested chapters - There's a function for loading a single Chapter from disk using it's associated Link entry from the SUMMARY.md - Another function builds up the Book by recursively visiting all Links and separators in the Summary and joining them into a single Vec<SummaryItem>. This is the only non-dumb-data-type item which is actually exported from the book module \# Loader - Made the loader use the book::load_book_from_disk function for loading a book in the loader's directory. \# Tests - Made sure you can load from disk by writing some files to a temporary directory - Made sure the Loader can load the entire example-book from disk and doesn't crash or hit an error - Increased test coverage from 34.4% to 47.7% (as reported by cargo kcov)
This commit is contained in:
parent
6d0d4bf379
commit
1b5a58902f
29
src/lib.rs
29
src/lib.rs
|
@ -3,8 +3,7 @@
|
||||||
//! **mdBook** is similar to Gitbook but implemented in Rust.
|
//! **mdBook** is similar to Gitbook but implemented in Rust.
|
||||||
//! It offers a command line interface, but can also be used as a regular crate.
|
//! It offers a command line interface, but can also be used as a regular crate.
|
||||||
//!
|
//!
|
||||||
//! This is the API doc, but you can find a [less "low-level" documentation
|
//! This is the API doc, but you can find a [less "low-level" documentation here](../index.html) that
|
||||||
//! here](../index.html) that
|
|
||||||
//! contains information about the command line tool, format, structure etc.
|
//! contains information about the command line tool, format, structure etc.
|
||||||
//! It is also rendered with mdBook to showcase the features and default theme.
|
//! It is also rendered with mdBook to showcase the features and default theme.
|
||||||
//!
|
//!
|
||||||
|
@ -26,23 +25,17 @@
|
||||||
//! # #[allow(unused_variables)]
|
//! # #[allow(unused_variables)]
|
||||||
//! fn main() {
|
//! fn main() {
|
||||||
//! let mut book = MDBook::new("my-book") // Path to root
|
//! let mut book = MDBook::new("my-book") // Path to root
|
||||||
//! .with_source("src") // Path from root to
|
//! .with_source("src") // Path from root to source directory
|
||||||
//! source directory
|
//! .with_destination("book") // Path from root to output directory
|
||||||
//! .with_destination("book") // Path from root to
|
//! .read_config() // Parse book.toml configuration file
|
||||||
//! output directory
|
//! .expect("I don't handle configuration file errors, but you should!");
|
||||||
//! .read_config() // Parse book.toml
|
|
||||||
//! configuration file
|
|
||||||
//! .expect("I don't handle configuration file errors,
|
|
||||||
//! but you should!");
|
|
||||||
//!
|
|
||||||
//! book.build().unwrap(); // Render the book
|
//! book.build().unwrap(); // Render the book
|
||||||
//! }
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! ## Implementing a new Renderer
|
//! ## Implementing a new Renderer
|
||||||
//!
|
//!
|
||||||
//! If you want to create a new renderer for mdBook, the only thing you have to
|
//! If you want to create a new renderer for mdBook, the only thing you have to do is to implement
|
||||||
//! do is to implement
|
|
||||||
//! the [Renderer trait](renderer/renderer/trait.Renderer.html)
|
//! the [Renderer trait](renderer/renderer/trait.Renderer.html)
|
||||||
//!
|
//!
|
||||||
//! And then you can swap in your renderer like this:
|
//! And then you can swap in your renderer like this:
|
||||||
|
@ -60,17 +53,15 @@
|
||||||
//! let book = MDBook::new("my-book").set_renderer(Box::new(your_renderer));
|
//! let book = MDBook::new("my-book").set_renderer(Box::new(your_renderer));
|
||||||
//! # }
|
//! # }
|
||||||
//! ```
|
//! ```
|
||||||
//! If you make a renderer, you get the book constructed in form of
|
//! If you make a renderer, you get the book constructed in form of `Vec<BookItems>` and you get
|
||||||
//! `Vec<BookItems>` and you get
|
|
||||||
//! the book config in a `BookConfig` struct.
|
//! the book config in a `BookConfig` struct.
|
||||||
//!
|
//!
|
||||||
//! It's your responsability to create the necessary files in the correct
|
//! It's your responsibility to create the necessary files in the correct
|
||||||
//! directories.
|
//! directories.
|
||||||
//!
|
//!
|
||||||
//! ## utils
|
//! ## utils
|
||||||
//!
|
//!
|
||||||
//! I have regrouped some useful functions in the [utils](utils/index.html)
|
//! I have regrouped some useful functions in the [utils](utils/index.html) module, like the
|
||||||
//! module, like the
|
|
||||||
//! following function [`utils::fs::create_file(path:
|
//! following function [`utils::fs::create_file(path:
|
||||||
//! &Path)`](utils/fs/fn.create_file.html)
|
//! &Path)`](utils/fs/fn.create_file.html)
|
||||||
//!
|
//!
|
||||||
|
@ -99,6 +90,8 @@ extern crate tempdir;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate pretty_assertions;
|
extern crate pretty_assertions;
|
||||||
|
#[cfg(test)]
|
||||||
|
extern crate tempdir;
|
||||||
|
|
||||||
mod parse;
|
mod parse;
|
||||||
mod preprocess;
|
mod preprocess;
|
||||||
|
|
|
@ -0,0 +1,234 @@
|
||||||
|
#![allow(missing_docs, unused_variables, unused_imports, dead_code)]
|
||||||
|
|
||||||
|
use std::path::Path;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::Read;
|
||||||
|
|
||||||
|
use loader::summary::{Summary, Link, SummaryItem, SectionNumber};
|
||||||
|
use errors::*;
|
||||||
|
|
||||||
|
|
||||||
|
/// A dumb tree structure representing a book.
|
||||||
|
///
|
||||||
|
/// For the moment a book is just a collection of `BookItems`.
|
||||||
|
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct Book {
|
||||||
|
/// The sections in this book.
|
||||||
|
pub sections: Vec<BookItem>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Book {
|
||||||
|
/// Create an empty book.
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enum representing any type of item which can be added to a book.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub enum BookItem {
|
||||||
|
/// A nested chapter.
|
||||||
|
Chapter(Chapter),
|
||||||
|
/// A section separator.
|
||||||
|
Separator,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The representation of a "chapter", usually mapping to a single file on
|
||||||
|
/// disk however it may contain multiple sub-chapters.
|
||||||
|
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct Chapter {
|
||||||
|
/// The chapter's name.
|
||||||
|
pub name: String,
|
||||||
|
/// The chapter's contents.
|
||||||
|
pub content: String,
|
||||||
|
/// The chapter's section number, if it has one.
|
||||||
|
pub number: Option<SectionNumber>,
|
||||||
|
/// Nested items.
|
||||||
|
pub sub_items: Vec<BookItem>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Chapter {
|
||||||
|
/// Create a new chapter with the provided content.
|
||||||
|
pub fn new(name: &str, content: String) -> Chapter {
|
||||||
|
Chapter {
|
||||||
|
name: name.to_string(),
|
||||||
|
content: content,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Use the provided `Summary` to load a `Book` from disk.
|
||||||
|
///
|
||||||
|
/// You need to pass in the book's source directory because all the links in
|
||||||
|
/// `SUMMARY.md` give the chapter locations relative to it.
|
||||||
|
pub fn load_book_from_disk<P: AsRef<Path>>(summary: &Summary, src_dir: P) -> Result<Book> {
|
||||||
|
debug!("[*] Loading the book from disk");
|
||||||
|
let src_dir = src_dir.as_ref();
|
||||||
|
|
||||||
|
let prefix = summary.prefix_chapters.iter();
|
||||||
|
let numbered = summary.numbered_chapters.iter();
|
||||||
|
let suffix = summary.suffix_chapters.iter();
|
||||||
|
|
||||||
|
let summary_items = prefix.chain(numbered).chain(suffix);
|
||||||
|
|
||||||
|
let chapters = summary_items
|
||||||
|
.map(|i| load_summary_item(i, src_dir))
|
||||||
|
.collect::<Result<_>>()
|
||||||
|
.chain_err(|| "Couldn't load chapters from disk")?;
|
||||||
|
|
||||||
|
Ok(Book { sections: chapters })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_summary_item<P: AsRef<Path>>(item: &SummaryItem, src_dir: P) -> Result<BookItem> {
|
||||||
|
match *item {
|
||||||
|
SummaryItem::Separator => Ok(BookItem::Separator),
|
||||||
|
SummaryItem::Link(ref link) => load_chapter(link, src_dir).map(|c| BookItem::Chapter(c)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_chapter<P: AsRef<Path>>(link: &Link, src_dir: P) -> Result<Chapter> {
|
||||||
|
debug!("[*] Loading {} ({})", link.name, link.location.display());
|
||||||
|
let src_dir = src_dir.as_ref();
|
||||||
|
|
||||||
|
let location = if link.location.is_absolute() {
|
||||||
|
link.location.clone()
|
||||||
|
} else {
|
||||||
|
src_dir.join(&link.location)
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut f = File::open(location).chain_err(|| {
|
||||||
|
format!("Chapter file not found, {}", link.location.display())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let mut content = String::new();
|
||||||
|
f.read_to_string(&mut content)?;
|
||||||
|
|
||||||
|
let mut ch = Chapter::new(&link.name, content);
|
||||||
|
ch.number = link.number.clone();
|
||||||
|
|
||||||
|
let sub_items = link.nested_items
|
||||||
|
.iter()
|
||||||
|
.map(|i| load_summary_item(i, src_dir))
|
||||||
|
.collect::<Result<Vec<_>>>()?;
|
||||||
|
|
||||||
|
ch.sub_items = sub_items;
|
||||||
|
|
||||||
|
Ok(ch)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use tempdir::TempDir;
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
const DUMMY_SRC: &'static str = "
|
||||||
|
# Dummy Chapter
|
||||||
|
|
||||||
|
this is some dummy text.
|
||||||
|
|
||||||
|
And here is some more text.
|
||||||
|
";
|
||||||
|
|
||||||
|
/// Create a dummy `Link` in a temporary directory.
|
||||||
|
fn dummy_link() -> (Link, TempDir) {
|
||||||
|
let temp = TempDir::new("book").unwrap();
|
||||||
|
|
||||||
|
let chapter_path = temp.path().join("chapter_1.md");
|
||||||
|
File::create(&chapter_path)
|
||||||
|
.unwrap()
|
||||||
|
.write(DUMMY_SRC.as_bytes())
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let link = Link::new("Chapter 1", chapter_path);
|
||||||
|
|
||||||
|
(link, temp)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a nested `Link` written to a temporary directory.
|
||||||
|
fn nested_links() -> (Link, TempDir) {
|
||||||
|
let (mut root, temp_dir) = dummy_link();
|
||||||
|
|
||||||
|
let second_path = temp_dir.path().join("second.md");
|
||||||
|
|
||||||
|
File::create(&second_path)
|
||||||
|
.unwrap()
|
||||||
|
.write_all("Hello World!".as_bytes())
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
|
||||||
|
let mut second = Link::new("Nested Chapter 1", &second_path);
|
||||||
|
second.number = Some(SectionNumber(vec![1, 2]));
|
||||||
|
|
||||||
|
root.push_item(second.clone());
|
||||||
|
root.push_item(SummaryItem::Separator);
|
||||||
|
root.push_item(second.clone());
|
||||||
|
|
||||||
|
(root, temp_dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn load_a_single_chapter_from_disk() {
|
||||||
|
let (link, temp_dir) = dummy_link();
|
||||||
|
let should_be = Chapter::new("Chapter 1", DUMMY_SRC.to_string());
|
||||||
|
|
||||||
|
let got = load_chapter(&link, temp_dir.path()).unwrap();
|
||||||
|
assert_eq!(got, should_be);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cant_load_a_nonexistent_chapter() {
|
||||||
|
let link = Link::new("Chapter 1", "/foo/bar/baz.md");
|
||||||
|
|
||||||
|
let got = load_chapter(&link, "");
|
||||||
|
assert!(got.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn load_recursive_link_with_separators() {
|
||||||
|
let (root, _temp) = nested_links();
|
||||||
|
|
||||||
|
let nested = Chapter {
|
||||||
|
name: String::from("Nested Chapter 1"),
|
||||||
|
content: String::from("Hello World!"),
|
||||||
|
number: Some(SectionNumber(vec![1, 2])),
|
||||||
|
sub_items: Vec::new(),
|
||||||
|
};
|
||||||
|
let should_be = BookItem::Chapter(Chapter {
|
||||||
|
name: String::from("Chapter 1"),
|
||||||
|
content: String::from(DUMMY_SRC),
|
||||||
|
number: None,
|
||||||
|
sub_items: vec![
|
||||||
|
BookItem::Chapter(nested.clone()),
|
||||||
|
BookItem::Separator,
|
||||||
|
BookItem::Chapter(nested.clone()),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
let got = load_summary_item(&SummaryItem::Link(root), "").unwrap();
|
||||||
|
assert_eq!(got, should_be);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn load_a_book_with_a_single_chapter() {
|
||||||
|
let (link, temp) = dummy_link();
|
||||||
|
let summary = Summary {
|
||||||
|
numbered_chapters: vec![SummaryItem::Link(link)],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let should_be = Book {
|
||||||
|
sections: vec![
|
||||||
|
BookItem::Chapter(Chapter {
|
||||||
|
name: String::from("Chapter 1"),
|
||||||
|
content: String::from(DUMMY_SRC),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
let got = load_book_from_disk(&summary, "").unwrap();
|
||||||
|
|
||||||
|
assert_eq!(got, should_be);
|
||||||
|
}
|
||||||
|
}
|
|
@ -46,8 +46,10 @@ use std::io::Read;
|
||||||
use errors::*;
|
use errors::*;
|
||||||
|
|
||||||
mod summary;
|
mod summary;
|
||||||
|
mod book;
|
||||||
|
|
||||||
pub use self::summary::{Summary, Link, SummaryItem, parse_summary, SectionNumber};
|
pub use self::summary::{Summary, Link, SummaryItem, parse_summary, SectionNumber};
|
||||||
|
pub use self::book::{Book, load_book_from_disk, BookItem, Chapter};
|
||||||
|
|
||||||
|
|
||||||
/// The object in charge of parsing the source directory into a usable
|
/// The object in charge of parsing the source directory into a usable
|
||||||
|
@ -64,21 +66,33 @@ impl Loader {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse the summary file and use it to load a book from disk.
|
/// Parse the summary file and use it to load a book from disk.
|
||||||
pub fn load(&self) -> Result<()> {
|
pub fn load(&self) -> Result<Book> {
|
||||||
let summary = self.parse_summary().chain_err(
|
let summary_md = self.find_summary().chain_err(
|
||||||
|
|| "Couldn't find `SUMMARY.md`",
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let summary = self.parse_summary(&summary_md).chain_err(
|
||||||
|| "Couldn't parse `SUMMARY.md`",
|
|| "Couldn't parse `SUMMARY.md`",
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
unimplemented!()
|
let src_dir = match summary_md.parent() {
|
||||||
|
Some(parent) => parent,
|
||||||
|
None => bail!("SUMMARY.md doesn't have a parent... wtf?"),
|
||||||
|
};
|
||||||
|
load_book_from_disk(&summary, src_dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse the `SUMMARY.md` file.
|
/// Parse a `SUMMARY.md` file.
|
||||||
pub fn parse_summary(&self) -> Result<Summary> {
|
pub fn parse_summary<P: AsRef<Path>>(&self, summary_md: P) -> Result<Summary> {
|
||||||
let path = self.source_directory.join("SUMMARY.md");
|
|
||||||
|
|
||||||
let mut summary_content = String::new();
|
let mut summary_content = String::new();
|
||||||
File::open(&path)?.read_to_string(&mut summary_content)?;
|
File::open(summary_md)?.read_to_string(&mut summary_content)?;
|
||||||
|
|
||||||
summary::parse_summary(&summary_content)
|
summary::parse_summary(&summary_content)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn find_summary(&self) -> Result<PathBuf> {
|
||||||
|
// TODO: use Piston's find_folder to make locating SUMMARY.md easier.
|
||||||
|
// https://github.com/PistonDevelopers/find_folder
|
||||||
|
Ok(self.source_directory.join("SUMMARY.md"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,7 +50,7 @@ pub fn parse_summary(summary: &str) -> Result<Summary> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The parsed `SUMMARY.md`, specifying how the book should be laid out.
|
/// The parsed `SUMMARY.md`, specifying how the book should be laid out.
|
||||||
#[derive(Debug, Clone, Default, PartialEq)]
|
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct Summary {
|
pub struct Summary {
|
||||||
/// An optional title for the `SUMMARY.md`, currently just ignored.
|
/// An optional title for the `SUMMARY.md`, currently just ignored.
|
||||||
pub title: Option<String>,
|
pub title: Option<String>,
|
||||||
|
@ -66,7 +66,7 @@ pub struct Summary {
|
||||||
/// entries.
|
/// entries.
|
||||||
///
|
///
|
||||||
/// This is roughly the equivalent of `[Some section](./path/to/file.md)`.
|
/// This is roughly the equivalent of `[Some section](./path/to/file.md)`.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct Link {
|
pub struct Link {
|
||||||
/// The name of the chapter.
|
/// The name of the chapter.
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
@ -89,6 +89,11 @@ impl Link {
|
||||||
nested_items: Vec::new(),
|
nested_items: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add an item to this link's `nested_items`.
|
||||||
|
pub fn push_item<I: Into<SummaryItem>>(&mut self, item: I) {
|
||||||
|
self.nested_items.push(item.into());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Link {
|
impl Default for Link {
|
||||||
|
@ -103,7 +108,7 @@ impl Default for Link {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An item in `SUMMARY.md` which could be either a separator or a `Link`.
|
/// An item in `SUMMARY.md` which could be either a separator or a `Link`.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
pub enum SummaryItem {
|
pub enum SummaryItem {
|
||||||
/// A link to a chapter.
|
/// A link to a chapter.
|
||||||
Link(Link),
|
Link(Link),
|
||||||
|
@ -120,6 +125,12 @@ impl SummaryItem {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<Link> for SummaryItem {
|
||||||
|
fn from(other: Link) -> SummaryItem {
|
||||||
|
SummaryItem::Link(other)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||||
enum State {
|
enum State {
|
||||||
Begin,
|
Begin,
|
||||||
|
@ -310,8 +321,9 @@ impl<'a> SummaryParser<'a> {
|
||||||
fn step_numbered(&mut self, event: Event, nesting: u32) -> Result<()> {
|
fn step_numbered(&mut self, event: Event, nesting: u32) -> Result<()> {
|
||||||
match event {
|
match event {
|
||||||
Event::Start(Tag::Item) => {
|
Event::Start(Tag::Item) => {
|
||||||
let it = self.parse_item()
|
let it = self.parse_item().chain_err(
|
||||||
.chain_err(|| "List items should only contain links")?;
|
|| "List items should only contain links",
|
||||||
|
)?;
|
||||||
|
|
||||||
debug!("[*] Found a chapter: {:?} ({})", it.name, it.location.display());
|
debug!("[*] Found a chapter: {:?} ({})", it.name, it.location.display());
|
||||||
let section_number = self.push_numbered_section(SummaryItem::Link(it));
|
let section_number = self.push_numbered_section(SummaryItem::Link(it));
|
||||||
|
@ -479,7 +491,7 @@ fn stringify_events(events: Vec<Event>) -> String {
|
||||||
|
|
||||||
/// A section number like "1.2.3", basically just a newtype'd `Vec<u32>` with
|
/// A section number like "1.2.3", basically just a newtype'd `Vec<u32>` with
|
||||||
/// a pretty `Display` impl.
|
/// a pretty `Display` impl.
|
||||||
#[derive(Debug, PartialEq, Clone, Default)]
|
#[derive(Debug, PartialEq, Clone, Default, Serialize, Deserialize)]
|
||||||
pub struct SectionNumber(pub Vec<u32>);
|
pub struct SectionNumber(pub Vec<u32>);
|
||||||
|
|
||||||
impl Display for SectionNumber {
|
impl Display for SectionNumber {
|
||||||
|
|
|
@ -49,11 +49,14 @@ fn parse_summary_using_loader() {
|
||||||
let temp = TempDir::new("book").unwrap();
|
let temp = TempDir::new("book").unwrap();
|
||||||
let summary_md = temp.path().join("SUMMARY.md");
|
let summary_md = temp.path().join("SUMMARY.md");
|
||||||
|
|
||||||
File::create(&summary_md).unwrap().write_all(SUMMARY.as_bytes()).unwrap();
|
File::create(&summary_md)
|
||||||
|
.unwrap()
|
||||||
|
.write_all(SUMMARY.as_bytes())
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let loader = Loader::new(temp.path());
|
let loader = Loader::new(temp.path());
|
||||||
|
|
||||||
let got = loader.parse_summary().unwrap();
|
let got = loader.parse_summary(&summary_md).unwrap();
|
||||||
let should_be = expected_summary();
|
let should_be = expected_summary();
|
||||||
|
|
||||||
assert_eq!(got, should_be);
|
assert_eq!(got, should_be);
|
||||||
|
@ -107,3 +110,14 @@ fn expected_summary() -> Summary {
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn load_the_example_book() {
|
||||||
|
let example_src_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||||
|
.join("book-example")
|
||||||
|
.join("src");
|
||||||
|
let loader = Loader::new(example_src_dir);
|
||||||
|
|
||||||
|
let book = loader.load().unwrap();
|
||||||
|
println!("{:#?}", book);
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue