diff --git a/book-example/src/SUMMARY.md b/book-example/src/SUMMARY.md index ff3911c7..8d7bdcdd 100644 --- a/book-example/src/SUMMARY.md +++ b/book-example/src/SUMMARY.md @@ -1,5 +1,7 @@ # Summary +[Introduction](misc/introduction.md) + - [mdBook](README.md) - [Command Line Tool](cli/cli-tool.md) - [init](cli/init.md) diff --git a/book-example/src/misc/introduction.md b/book-example/src/misc/introduction.md new file mode 100644 index 00000000..36495382 --- /dev/null +++ b/book-example/src/misc/introduction.md @@ -0,0 +1,3 @@ +# Introduction + +A frontmatter chapter. diff --git a/src/book/book.rs b/src/book/book.rs new file mode 100644 index 00000000..6b6ce03b --- /dev/null +++ b/src/book/book.rs @@ -0,0 +1,80 @@ +use book::metadata::BookMetadata; +use book::chapter::Chapter; + + +/// The `Book` struct contains the metadata and chapters for one language of the book. +/// Multiple `Book` structs are combined in the `MDBook` struct to support multi-language books. +#[derive(Debug, Clone)] +pub struct Book { + metadata: BookMetadata, + + frontmatter: Vec, + mainmatter: Vec, + backmatter: Vec, +} + +impl Book { + /// Creates a new book with the given title, chapters are added with the + /// `add_frontmatter_chapter`, `add_mainmatter_chapter`, + /// `add_backmatter_chapter` methods + pub fn new(title: &str) -> Self { + Book { + metadata: BookMetadata::new(title), + + frontmatter: Vec::new(), + mainmatter: Vec::new(), + backmatter: Vec::new(), + } + } + + /// Adds a new mainmatter chapter + pub fn add_mainmatter_chapter(&mut self, chapter: Chapter) -> &mut Self { + self.mainmatter.push(chapter); + self + } + + /// Adds a new frontmatter chapter + pub fn add_frontmatter_chapter(&mut self, chapter: Chapter) -> &mut Self { + self.frontmatter.push(chapter); + self + } + + /// Adds a new backmatter chapter + pub fn add_backmatter_chapter(&mut self, chapter: Chapter) -> &mut Self { + self.backmatter.push(chapter); + self + } + + + /// This method takes a slice `&[x, y, z]` as parameter and returns the corresponding chapter. + /// For example, to retrieve chapter 2.3 we would use: + /// ``` + /// #extern crate mdbook; + /// #use mdbook::book::Book; + /// #fn main() { + /// #let book = Book::new("Test"); + /// let chapter_2_3 = book.get_chapter(&[2, 3]); + /// #} + /// ``` + pub fn get_chapter(&self, section: &[usize]) -> Option<&Chapter> { + match section.len() { + 0 => None, + 1 => self.mainmatter.get(section[0]), + _ => { + self.mainmatter + .get(section[0]) + .and_then(|ch| ch.get_sub_chapter(§ion[1..])) + }, + } + } + + /// Returns a mutable reference to the metadata for modification + pub fn mut_metadata(&mut self) -> &mut BookMetadata { + &mut self.metadata + } + + // Returns a reference to the metadata + pub fn metadata(&self) -> &BookMetadata { + &self.metadata + } +} diff --git a/src/book/chapter.rs b/src/book/chapter.rs new file mode 100644 index 00000000..7afb3610 --- /dev/null +++ b/src/book/chapter.rs @@ -0,0 +1,71 @@ +use std::path::{Path, PathBuf}; +use book::metadata::Author; + +/// The Chapter struct holds the title of the chapter as written in the SUMMARY.md file, +/// the location of the markdown file containing the content and eventually sub-chapters + +/// TODO use in template: author, description, index, class + +#[derive(Debug, Clone)] +pub struct Chapter { + /// The title of the chapter. + title: String, + /// Path to chapter's markdown file. + file: PathBuf, + + /// TODO The author of the chapter, or the book. + author: Author, + /// TODO The description of the chapter. + description: String, + /// TODO Index number of the chapter in its level. This is the Vec index + 1. + index: i32, + /// TODO CSS class that will be added to the page-level wrap div to allow customized chapter styles. + class: String, + + sub_chapters: Vec, +} + +impl Chapter { + /// Creates a new chapter with the given title and source file and no sub-chapters + pub fn new(title: &str, file: &Path) -> Self { + Chapter { + title: title.to_owned(), + file: file.to_owned(), + + sub_chapters: Vec::new(), + + // TODO placeholder values for now + author: Author::new(""), + description: "".to_string(), + index: 0, + class: "".to_string(), + } + } + + /// This function takes a slice `&[x,y,z]` and returns the corresponding sub-chapter if it exists. + /// + /// For example: `chapter.get_sub_chapter(&[1,3])` will return the third sub-chapter of the first sub-chapter. + pub fn get_sub_chapter(&self, section: &[usize]) -> Option<&Chapter> { + match section.len() { + 0 => None, + 1 => self.sub_chapters.get(section[0]), + _ => { + // The lengt of the slice is more than one, this means that we want a sub-chapter of a sub-chapter + // We call `get_sub_chapter` recursively until we are deep enough and return the asked sub-chapter + self.sub_chapters + .get(section[0]) + .and_then(|ch| ch.get_sub_chapter(§ion[1..])) + }, + } + } + + pub fn title(&self) -> &str { + &self.title + } + pub fn file(&self) -> &Path { + &self.file + } + pub fn sub_chapters(&self) -> &[Chapter] { + &self.sub_chapters + } +} diff --git a/src/book/metadata.rs b/src/book/metadata.rs new file mode 100644 index 00000000..6fe837cd --- /dev/null +++ b/src/book/metadata.rs @@ -0,0 +1,129 @@ +use std::path::PathBuf; + +/// TODO use in template: subtitle, description, publisher, number_format, section_names + +#[derive(Debug, Clone)] +pub struct BookMetadata { + /// The title of the book. + pub title: String, + + /// TODO The subtitle, when titles are in the form of "The Immense Journey: An + /// Imaginative Naturalist Explores the Mysteries of Man and Nature" + pub subtitle: String, + + /// TODO A brief description or summary. + pub description: String, + + /// TODO Publisher's info + pub publisher: Publisher, + + pub language: Language, + + authors: Vec, + translators: Vec, + + /// TODO Chapter numbering scheme + number_format: NumberFormat, + /// TODO Section names for nested Vec structures, such as `[ + /// "Part", "Chapter", "Section" ]` + section_names: Vec, +} + +#[derive(Debug, Clone)] +pub struct Author { + name: String, + email: Option, +} + +#[derive(Debug, Clone)] +pub struct Language { + name: String, + code: String, +} + +/// TODO use Publisher in template. + +#[derive(Debug, Clone)] +pub struct Publisher { + name: String, + /// link to the sublisher's site + url: String, + /// path to publisher's logo image + logo_src: PathBuf, +} + +impl Publisher { + pub fn default() -> Publisher { + Publisher { + name: "".to_string(), + url: "".to_string(), + logo_src: PathBuf::new(), + } + } +} + +/// TODO use NumberFormat when rendering chapter titles. + +#[derive(Debug, Clone)] +pub enum NumberFormat { + /// 19 + Arabic, + /// XIX + Roman, + /// Nineteen + Word, +} + +impl BookMetadata { + pub fn new(title: &str) -> Self { + BookMetadata { + title: title.to_owned(), + description: String::new(), + + language: Language::default(), + + authors: Vec::new(), + translators: Vec::new(), + + // TODO placeholder values for now + subtitle: "".to_string(), + publisher: Publisher::default(), + number_format: NumberFormat::Arabic, + section_names: vec![], + } + } + + pub fn set_description(&mut self, description: &str) -> &mut Self { + self.description = description.to_owned(); + self + } + + pub fn add_author(&mut self, author: Author) -> &mut Self { + self.authors.push(author); + self + } +} + +impl Author { + pub fn new(name: &str) -> Self { + Author { + name: name.to_owned(), + email: None, + } + } + + pub fn with_email(mut self, email: &str) -> Self { + self.email = Some(email.to_owned()); + self + } +} + + +impl Default for Language { + fn default() -> Self { + Language { + name: String::from("English"), + code: String::from("en"), + } + } +} diff --git a/src/book/mod.rs b/src/book/mod.rs index c3594300..c7130ce7 100644 --- a/src/book/mod.rs +++ b/src/book/mod.rs @@ -1,5 +1,12 @@ pub mod bookitem; pub mod bookconfig; +pub mod metadata; +pub mod chapter; +pub mod book; + +pub use self::metadata::{Author, Language, BookMetadata}; +pub use self::chapter::Chapter; +pub use self::book::Book; pub use self::bookitem::{BookItem, BookItems}; pub use self::bookconfig::BookConfig; @@ -11,12 +18,13 @@ use std::io; use std::io::Write; use std::io::ErrorKind; use std::process::Command; +use std::collections::HashMap; use {theme, parse, utils}; use renderer::{Renderer, HtmlHandlebars}; -pub struct MDBook { +pub struct MDBook<'a> { root: PathBuf, dest: PathBuf, src: PathBuf, @@ -27,12 +35,13 @@ pub struct MDBook { pub description: String, pub content: Vec, + books: HashMap<&'a str, Book>, renderer: Box, livereload: Option, } -impl MDBook { +impl<'a> MDBook<'a> { /// Create a new `MDBook` struct with root directory `root` /// /// Default directory paths: @@ -60,6 +69,7 @@ impl MDBook { description: String::new(), content: vec![], + books: HashMap::new(), renderer: Box::new(HtmlHandlebars::new()), livereload: None,