From 672e102caebbf8039bdd2b073b09e3060d30783e Mon Sep 17 00:00:00 2001 From: Tobias Stolzmann Date: Sat, 27 Jan 2018 12:48:36 +0100 Subject: [PATCH 01/18] Add variant 'VirtualChapter' to 'BookItem' --- src/book/book.rs | 33 ++++++++++++++++++++ src/renderer/html_handlebars/hbs_renderer.rs | 3 ++ 2 files changed, 36 insertions(+) diff --git a/src/book/book.rs b/src/book/book.rs index 7da28a6871..8f9b5a1d61 100644 --- a/src/book/book.rs +++ b/src/book/book.rs @@ -129,6 +129,8 @@ where pub enum BookItem { /// A nested chapter. Chapter(Chapter), + /// A nested virtual chapter. + VirtualChapter(VirtualChapter), /// A section separator. Separator, } @@ -139,6 +141,12 @@ impl From for BookItem { } } +impl From for BookItem { + fn from(other: VirtualChapter) -> BookItem { + BookItem::VirtualChapter(other) + } +} + /// 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)] @@ -175,6 +183,31 @@ impl Chapter { } } +/// The representation of a "virtual chapter", available for namespacing +/// purposes and not mapping to a file on disk. +#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] +pub struct VirtualChapter { + /// 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, + /// Nested items. + pub sub_items: Vec, +} + +impl VirtualChapter { + /// Create a new chapter with the provided content. + pub fn new(name: &str, content: String) -> VirtualChapter { + VirtualChapter { + 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 diff --git a/src/renderer/html_handlebars/hbs_renderer.rs b/src/renderer/html_handlebars/hbs_renderer.rs index fbaad5239a..d89459e2fb 100644 --- a/src/renderer/html_handlebars/hbs_renderer.rs +++ b/src/renderer/html_handlebars/hbs_renderer.rs @@ -434,6 +434,9 @@ fn make_data(root: &Path, book: &Book, config: &Config, html_config: &HtmlConfig .chain_err(|| "Could not convert path to str")?; chapter.insert("path".to_owned(), json!(path)); } + BookItem::VirtualChapter(ref ch) => { + unimplemented!(); // TODO + } BookItem::Separator => { chapter.insert("spacer".to_owned(), json!("_spacer_")); } From bae55fbc6ad11cc06e1be8bea288f54798431491 Mon Sep 17 00:00:00 2001 From: Tobias Stolzmann Date: Sat, 27 Jan 2018 13:10:35 +0100 Subject: [PATCH 02/18] Add variant 'VirtualLink' to 'SummaryItem' --- src/book/book.rs | 1 + src/book/summary.rs | 57 +++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 54 insertions(+), 4 deletions(-) diff --git a/src/book/book.rs b/src/book/book.rs index 8f9b5a1d61..e3b08fd4f8 100644 --- a/src/book/book.rs +++ b/src/book/book.rs @@ -245,6 +245,7 @@ fn load_summary_item>( SummaryItem::Link(ref link) => { load_chapter(link, src_dir, parent_names).map(|c| BookItem::Chapter(c)) } + SummaryItem::VirtualLink(ref link) => unimplemented!(), } } diff --git a/src/book/summary.rs b/src/book/summary.rs index ceb38ebc49..7554bd20ae 100644 --- a/src/book/summary.rs +++ b/src/book/summary.rs @@ -61,10 +61,9 @@ pub struct Summary { pub suffix_chapters: Vec, } -/// A struct representing an entry in the `SUMMARY.md`, possibly with nested -/// entries. +/// A linked chapter in the `SUMMARY.md`, possibly with nested 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, Serialize, Deserialize)] pub struct Link { /// The name of the chapter. @@ -101,11 +100,48 @@ impl Default for Link { } } -/// An item in `SUMMARY.md` which could be either a separator or a `Link`. +/// A linked virtual chapter in the `SUMMARY.md`, possibly with nested entries. +/// +/// This is roughly the equivalent of `- Some virtual section`. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct VirtualLink { + /// The name of the chapter. + pub name: String, + /// The section number, if this virtual chapter is in the numbered section. + pub number: Option, + /// Any nested items this virtual chapter may contain. + pub nested_items: Vec, +} + +impl VirtualLink { + /// Create a new virtual link with no nested items. + pub fn new>(name: S) -> VirtualLink { + VirtualLink { + name: name.into(), + number: None, + nested_items: Vec::new(), + } + } +} + +impl Default for VirtualLink { + fn default() -> Self { + VirtualLink { + name: String::new(), + number: None, + nested_items: Vec::new(), + } + } +} + +/// An entry in the `SUMMARY.md` which could be either a `Link`, a +/// `VirtualLink` or separator. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum SummaryItem { /// A link to a chapter. Link(Link), + /// A link to a virtual chapter. + VirtualLink(VirtualLink), /// A separator (`---`). Separator, } @@ -117,6 +153,13 @@ impl SummaryItem { _ => None, } } + + fn maybe_virtual_link_mut(&mut self) -> Option<&mut VirtualLink> { + match *self { + SummaryItem::VirtualLink(ref mut l) => Some(l), + _ => None, + } + } } impl From for SummaryItem { @@ -125,6 +168,12 @@ impl From for SummaryItem { } } +impl From for SummaryItem { + fn from(other: VirtualLink) -> SummaryItem { + SummaryItem::VirtualLink(other) + } +} + /// A recursive descent (-ish) parser for a `SUMMARY.md`. /// /// From 598c8e36b0891f80eead3ca1336bc635f9f3b55c Mon Sep 17 00:00:00 2001 From: Tobias Stolzmann Date: Sat, 27 Jan 2018 13:43:36 +0100 Subject: [PATCH 03/18] Reduce technical depts This includes: - Use 'Self' instead of '' where possible. - Match enums in order of their variants. --- src/book/book.rs | 9 +++++---- src/book/summary.rs | 16 ++++++++-------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/book/book.rs b/src/book/book.rs index e3b08fd4f8..3833e97880 100644 --- a/src/book/book.rs +++ b/src/book/book.rs @@ -136,13 +136,13 @@ pub enum BookItem { } impl From for BookItem { - fn from(other: Chapter) -> BookItem { + fn from(other: Chapter) -> Self { BookItem::Chapter(other) } } impl From for BookItem { - fn from(other: VirtualChapter) -> BookItem { + fn from(other: VirtualChapter) -> Self { BookItem::VirtualChapter(other) } } @@ -199,8 +199,8 @@ pub struct VirtualChapter { impl VirtualChapter { /// Create a new chapter with the provided content. - pub fn new(name: &str, content: String) -> VirtualChapter { - VirtualChapter { + pub fn new(name: &str, content: String) -> Self { + Self { name: name.to_string(), content: content, ..Default::default() @@ -246,6 +246,7 @@ fn load_summary_item>( load_chapter(link, src_dir, parent_names).map(|c| BookItem::Chapter(c)) } SummaryItem::VirtualLink(ref link) => unimplemented!(), + SummaryItem::Separator => Ok(BookItem::Separator), } } diff --git a/src/book/summary.rs b/src/book/summary.rs index 7554bd20ae..372b5ebf03 100644 --- a/src/book/summary.rs +++ b/src/book/summary.rs @@ -79,8 +79,8 @@ pub struct Link { impl Link { /// Create a new link with no nested items. - pub fn new, P: AsRef>(name: S, location: P) -> Link { - Link { + pub fn new, P: AsRef>(name: S, location: P) -> Self { + Self { name: name.into(), location: location.as_ref().to_path_buf(), number: None, @@ -91,7 +91,7 @@ impl Link { impl Default for Link { fn default() -> Self { - Link { + Self { name: String::new(), location: PathBuf::new(), number: None, @@ -115,8 +115,8 @@ pub struct VirtualLink { impl VirtualLink { /// Create a new virtual link with no nested items. - pub fn new>(name: S) -> VirtualLink { - VirtualLink { + pub fn new>(name: S) -> Self { + Self { name: name.into(), number: None, nested_items: Vec::new(), @@ -126,7 +126,7 @@ impl VirtualLink { impl Default for VirtualLink { fn default() -> Self { - VirtualLink { + Self { name: String::new(), number: None, nested_items: Vec::new(), @@ -163,13 +163,13 @@ impl SummaryItem { } impl From for SummaryItem { - fn from(other: Link) -> SummaryItem { + fn from(other: Link) -> Self { SummaryItem::Link(other) } } impl From for SummaryItem { - fn from(other: VirtualLink) -> SummaryItem { + fn from(other: VirtualLink) -> Self { SummaryItem::VirtualLink(other) } } From 7397e63291836d503cca76c0b7606cee8ba1c07f Mon Sep 17 00:00:00 2001 From: Tobias Stolzmann Date: Sat, 27 Jan 2018 14:24:54 +0100 Subject: [PATCH 04/18] Extend load_summary_item() in order to support virtual chapters --- src/book/book.rs | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/src/book/book.rs b/src/book/book.rs index 3833e97880..e7e4abc699 100644 --- a/src/book/book.rs +++ b/src/book/book.rs @@ -4,7 +4,7 @@ use std::collections::VecDeque; use std::fs::{self, File}; use std::io::{Read, Write}; -use super::summary::{parse_summary, Link, SectionNumber, Summary, SummaryItem}; +use super::summary::{parse_summary, Link, SectionNumber, Summary, SummaryItem, VirtualLink}; use config::BuildConfig; use errors::*; @@ -245,8 +245,8 @@ fn load_summary_item>( SummaryItem::Link(ref link) => { load_chapter(link, src_dir, parent_names).map(|c| BookItem::Chapter(c)) } - SummaryItem::VirtualLink(ref link) => unimplemented!(), - SummaryItem::Separator => Ok(BookItem::Separator), + SummaryItem::VirtualLink(ref link) => + load_virtual_chapter(link, src_dir).map(VirtualChapter::into), } } @@ -290,6 +290,31 @@ fn load_chapter>( Ok(ch) } +fn load_virtual_chapter>(link: &VirtualLink, src_dir: P) -> Result { + let src_dir = src_dir.as_ref(); + + let content = String::new(); + /* TODO Open question: Do we need "pseudo content" for virtual chapters? + * If not: Remove this and corresponding field in `VirtualChapter`! + */ + + let mut ch = VirtualChapter::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::>>()?; + + ch.sub_items = sub_items; + + Ok(ch) + /* TODO Open question: Do we really want to return a `Result<_>` here? + * Pro: Function would have almost signature as load_chapter() + * Con: We do not use it! + */ +} + /// A depth-first iterator over the items in a book. /// /// # Note From bcab4e20448a2ed8e21a4849dcd2eb7fb77c3e2a Mon Sep 17 00:00:00 2001 From: Tobias Stolzmann Date: Sat, 27 Jan 2018 21:30:11 +0100 Subject: [PATCH 05/18] Remove unnecessary field 'content' in 'VirtualChapter' --- src/book/book.rs | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/book/book.rs b/src/book/book.rs index e7e4abc699..f5702a0c6e 100644 --- a/src/book/book.rs +++ b/src/book/book.rs @@ -189,8 +189,6 @@ impl Chapter { pub struct VirtualChapter { /// 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, /// Nested items. @@ -198,11 +196,10 @@ pub struct VirtualChapter { } impl VirtualChapter { - /// Create a new chapter with the provided content. - pub fn new(name: &str, content: String) -> Self { + /// Create a new virtual chapter with the given name. + pub fn new(name: &str) -> Self { Self { name: name.to_string(), - content: content, ..Default::default() } } @@ -293,12 +290,7 @@ fn load_chapter>( fn load_virtual_chapter>(link: &VirtualLink, src_dir: P) -> Result { let src_dir = src_dir.as_ref(); - let content = String::new(); - /* TODO Open question: Do we need "pseudo content" for virtual chapters? - * If not: Remove this and corresponding field in `VirtualChapter`! - */ - - let mut ch = VirtualChapter::new(&link.name, content); + let mut ch = VirtualChapter::new(&link.name); ch.number = link.number.clone(); let sub_items = link.nested_items From 95995cb8db714dddf4ef85078a5e2206c4c53aa1 Mon Sep 17 00:00:00 2001 From: Tobias Stolzmann Date: Sat, 27 Jan 2018 21:33:25 +0100 Subject: [PATCH 06/18] Remove resolved open question --- src/book/book.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/book/book.rs b/src/book/book.rs index f5702a0c6e..da76fbe903 100644 --- a/src/book/book.rs +++ b/src/book/book.rs @@ -301,10 +301,6 @@ fn load_virtual_chapter>(link: &VirtualLink, src_dir: P) -> Resul ch.sub_items = sub_items; Ok(ch) - /* TODO Open question: Do we really want to return a `Result<_>` here? - * Pro: Function would have almost signature as load_chapter() - * Con: We do not use it! - */ } /// A depth-first iterator over the items in a book. From 62056588ac8221e6efbefef470cd9a0e2ba3393a Mon Sep 17 00:00:00 2001 From: Tobias Stolzmann Date: Sun, 28 Jan 2018 00:02:03 +0100 Subject: [PATCH 07/18] Implement parsing '- [Some virtual chapter]()' as 'VirtualLink' --- src/book/summary.rs | 62 +++++++++++++++++++++++++++------------------ 1 file changed, 38 insertions(+), 24 deletions(-) diff --git a/src/book/summary.rs b/src/book/summary.rs index 372b5ebf03..0593f52037 100644 --- a/src/book/summary.rs +++ b/src/book/summary.rs @@ -1,3 +1,4 @@ +use std::borrow::Cow; use std::fmt::{self, Display, Formatter}; use std::iter::FromIterator; use std::ops::{Deref, DerefMut}; @@ -191,9 +192,11 @@ impl From for SummaryItem { /// numbered_chapters ::= dotted_item+ /// dotted_item ::= INDENT* DOT_POINT item /// item ::= link +/// | virtual_link /// | separator -/// separator ::= "---" /// link ::= "[" TEXT "]" "(" TEXT ")" +/// virtual_link ::= "[" TEXT "]" "()" +/// separator ::= "---" /// DOT_POINT ::= "-" /// | "*" /// ``` @@ -304,10 +307,7 @@ impl<'a> SummaryParser<'a> { bail!(self.parse_error("Suffix chapters cannot be followed by a list")); } } - Some(Event::Start(Tag::Link(href, _))) => { - let link = self.parse_link(href.to_string())?; - items.push(SummaryItem::Link(link)); - } + Some(Event::Start(Tag::Link(href, _))) => items.push(self.parse_item(href)), Some(Event::Start(Tag::Rule)) => items.push(SummaryItem::Separator), Some(_) => {} None => break, @@ -317,19 +317,18 @@ impl<'a> SummaryParser<'a> { Ok(items) } - fn parse_link(&mut self, href: String) -> Result { - let link_content = collect_events!(self.stream, end Tag::Link(..)); - let name = stringify_events(link_content); + fn parse_item(&mut self, href: Cow<'a, str>) -> SummaryItem { + let name = { + let link_content = collect_events!(self.stream, end Tag::Link(..)); + stringify_events(link_content) + }; if href.is_empty() { - Err(self.parse_error("You can't have an empty link.")) + let link = VirtualLink::new(name); + SummaryItem::VirtualLink(link) } else { - Ok(Link { - name: name, - location: PathBuf::from(href.to_string()), - number: None, - nested_items: Vec::new(), - }) + let link = Link::new(name, href.into_owned()); + SummaryItem::Link(link) } } @@ -441,20 +440,35 @@ impl<'a> SummaryParser<'a> { match self.next_event() { Some(Event::Start(Tag::Paragraph)) => continue, Some(Event::Start(Tag::Link(href, _))) => { - let mut link = self.parse_link(href.to_string())?; + let mut item = self.parse_item(href); let mut number = parent.clone(); number.0.push(num_existing_items as u32 + 1); - trace!( - "Found chapter: {} {} ({})", - number, - link.name, - link.location.display() - ); - link.number = Some(number); + match item { + SummaryItem::Link(ref mut link) => { + trace!( + "Found chapter: {} {} ({})", + number, + link.name, + link.location.display(), + ); + + link.number = Some(number); + }, + SummaryItem::VirtualLink(ref mut link) => { + trace!( + "Found virtual chapter: {} {}", + number, + link.name, + ); + + link.number = Some(number); + }, + SummaryItem::Separator => panic!(), + } - return Ok(SummaryItem::Link(link)); + return Ok(item); } other => { warn!("Expected a start of a link, actually got {:?}", other); From cac7b0657bc594e97686fd98f15809c20a4c4ecd Mon Sep 17 00:00:00 2001 From: Tobias Stolzmann Date: Sun, 28 Jan 2018 00:36:51 +0100 Subject: [PATCH 08/18] Adapt basic tests for 'SummaryParser' This includes - a modification to parse_a_link() since parse_link() was modified and renamed to parse_item(), - a new test parse_a_virtual_link() and - the removal of an_empty_link_location_is_an_error() since an empty link location is parsed into a 'VirtualLink' from now on. --- src/book/summary.rs | 48 +++++++++++++++++++++++++++++---------------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/src/book/summary.rs b/src/book/summary.rs index 0593f52037..4c523e8b25 100644 --- a/src/book/summary.rs +++ b/src/book/summary.rs @@ -675,22 +675,46 @@ mod tests { #[test] fn parse_a_link() { - let src = "[First](./first.md)"; - let should_be = Link { - name: String::from("First"), - location: PathBuf::from("./first.md"), - ..Default::default() + let src = "[Chapter](./chapter.md)"; + let should_be = SummaryItem::Link( + Link { + name: String::from("Chapter"), + location: PathBuf::from("./chapter.md"), + ..Default::default() + } + ); + + let mut parser = SummaryParser::new(src); + let _ = parser.stream.next(); // skip past start of paragraph + + let href = match parser.stream.next() { + Some(Event::Start(Tag::Link(href, _))) => href, + other => panic!("Unreachable, {:?}", other), }; + let got = parser.parse_item(href); + assert_eq!(got, should_be); + } + + #[test] + fn parse_a_virtual_link() { + let src = "[Virtual chapter]()"; + let should_be = SummaryItem::VirtualLink( + VirtualLink { + name: String::from("Virtual chapter"), + ..Default::default() + } + ); + let mut parser = SummaryParser::new(src); let _ = parser.stream.next(); // skip past start of paragraph let href = match parser.stream.next() { - Some(Event::Start(Tag::Link(href, _))) => href.to_string(), + Some(Event::Start(Tag::Link(href, _))) => href, other => panic!("Unreachable, {:?}", other), }; - let got = parser.parse_link(href).unwrap(); + let got = parser.parse_item(href); assert_eq!(got, should_be); } @@ -776,14 +800,4 @@ mod tests { assert_eq!(got, should_be); } - - #[test] - fn an_empty_link_location_is_an_error() { - let src = "- [Empty]()\n"; - let mut parser = SummaryParser::new(src); - parser.stream.next(); - - let got = parser.parse_numbered(); - assert!(got.is_err()); - } } From 3c25b3fd21feee59f2ee0bc4ce74376c5069cc99 Mon Sep 17 00:00:00 2001 From: Tobias Stolzmann Date: Sun, 28 Jan 2018 00:46:40 +0100 Subject: [PATCH 09/18] Fix example in the documentation --- src/book/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/book/mod.rs b/src/book/mod.rs index 03871e7d11..cf2dc760a6 100644 --- a/src/book/mod.rs +++ b/src/book/mod.rs @@ -107,6 +107,7 @@ impl MDBook { /// for item in book.iter() { /// match *item { /// BookItem::Chapter(ref chapter) => {}, + /// BookItem::VirtualChapter(ref virtual_chapter) => {}, /// BookItem::Separator => {}, /// } /// } From c9605a0253b2fccfa53dc6b3896302ae91591532 Mon Sep 17 00:00:00 2001 From: Tobias Stolzmann Date: Sun, 28 Jan 2018 02:06:12 +0100 Subject: [PATCH 10/18] Fix 1640e5990bb36b187a9f31590546347f249d3343 parse_nested_item() had to be adapted. --- src/book/summary.rs | 48 +++++++++++++++++++++++++++++++++------------ 1 file changed, 35 insertions(+), 13 deletions(-) diff --git a/src/book/summary.rs b/src/book/summary.rs index 4c523e8b25..b57a9bf8de 100644 --- a/src/book/summary.rs +++ b/src/book/summary.rs @@ -412,15 +412,31 @@ impl<'a> SummaryParser<'a> { } Some(Event::Start(Tag::List(..))) => { // recurse to parse the nested list - let (_, last_item) = get_last_link(&mut items)?; - let last_item_number = last_item - .number - .as_ref() - .expect("All numbered chapters have numbers"); + let (_, last_item) = get_last_linklike(&mut items)?; - let sub_items = self.parse_nested_numbered(last_item_number)?; + match *last_item { + SummaryItem::Link(ref mut last_link) => { + let last_item_number = last_link + .number + .as_ref() + .expect("All numbered chapters have numbers"); - last_item.nested_items = sub_items; + let sub_items = self.parse_nested_numbered(last_item_number)?; + + last_link.nested_items = sub_items; + }, + SummaryItem::VirtualLink(ref mut last_link) => { + let last_item_number = last_link + .number + .as_ref() + .expect("All numbered chapters have numbers"); + + let sub_items = self.parse_nested_numbered(last_item_number)?; + + last_link.nested_items = sub_items; + }, + SummaryItem::Separator => unreachable!(), + }; } Some(Event::End(Tag::List(..))) => break, Some(_) => {} @@ -511,17 +527,23 @@ fn update_section_numbers(sections: &mut [SummaryItem], level: usize, by: u32) { } } -/// 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 +/// Gets a pointer to the last `Link` or `VirtualLink` in a list of +/// `SummaryItem`s, and its index. +fn get_last_linklike(items: &mut [SummaryItem]) -> Result<(usize, &mut SummaryItem)> { + items .iter_mut() .enumerate() - .filter_map(|(i, item)| item.maybe_link_mut().map(|l| (i, l))) + .filter(|&(_, ref item)| { + match **item { + SummaryItem::Link(_) => true, + SummaryItem::VirtualLink(_) => true, + SummaryItem::Separator => false, + } + }) .rev() .next() .ok_or_else(|| { - "Unable to get last link because the list of SummaryItems doesn't contain any Links" + "Unable to get last Link or VirtualLink because the list of SummaryItems doesn't contain any" .into() }) } From 4a3d994eed2cb763b4a5cb404da3314683fcf420 Mon Sep 17 00:00:00 2001 From: Tobias Stolzmann Date: Sun, 28 Jan 2018 02:34:11 +0100 Subject: [PATCH 11/18] Rename parse_item() to parse_linklike() --- src/book/summary.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/book/summary.rs b/src/book/summary.rs index b57a9bf8de..ad5a83470d 100644 --- a/src/book/summary.rs +++ b/src/book/summary.rs @@ -307,7 +307,7 @@ impl<'a> SummaryParser<'a> { bail!(self.parse_error("Suffix chapters cannot be followed by a list")); } } - Some(Event::Start(Tag::Link(href, _))) => items.push(self.parse_item(href)), + Some(Event::Start(Tag::Link(href, _))) => items.push(self.parse_linklike(href)), Some(Event::Start(Tag::Rule)) => items.push(SummaryItem::Separator), Some(_) => {} None => break, @@ -317,7 +317,7 @@ impl<'a> SummaryParser<'a> { Ok(items) } - fn parse_item(&mut self, href: Cow<'a, str>) -> SummaryItem { + fn parse_linklike(&mut self, href: Cow<'a, str>) -> SummaryItem { let name = { let link_content = collect_events!(self.stream, end Tag::Link(..)); stringify_events(link_content) @@ -456,7 +456,7 @@ impl<'a> SummaryParser<'a> { match self.next_event() { Some(Event::Start(Tag::Paragraph)) => continue, Some(Event::Start(Tag::Link(href, _))) => { - let mut item = self.parse_item(href); + let mut item = self.parse_linklike(href); let mut number = parent.clone(); number.0.push(num_existing_items as u32 + 1); @@ -714,7 +714,7 @@ mod tests { other => panic!("Unreachable, {:?}", other), }; - let got = parser.parse_item(href); + let got = parser.parse_linklike(href); assert_eq!(got, should_be); } @@ -736,7 +736,7 @@ mod tests { other => panic!("Unreachable, {:?}", other), }; - let got = parser.parse_item(href); + let got = parser.parse_linklike(href); assert_eq!(got, should_be); } From ed8fd3822f7163955dd99aa4e358917a24ffe9d1 Mon Sep 17 00:00:00 2001 From: Tobias Stolzmann Date: Sun, 18 Feb 2018 13:38:23 +0100 Subject: [PATCH 12/18] Add variant __NonExhaustive to BookItem --- src/book/book.rs | 3 +++ src/renderer/html_handlebars/hbs_renderer.rs | 1 + 2 files changed, 4 insertions(+) diff --git a/src/book/book.rs b/src/book/book.rs index da76fbe903..782763a381 100644 --- a/src/book/book.rs +++ b/src/book/book.rs @@ -133,6 +133,9 @@ pub enum BookItem { VirtualChapter(VirtualChapter), /// A section separator. Separator, + // To make sure clients have a `_ =>` case + #[doc(hidden)] + __NonExhaustive, } impl From for BookItem { diff --git a/src/renderer/html_handlebars/hbs_renderer.rs b/src/renderer/html_handlebars/hbs_renderer.rs index d89459e2fb..af835a58a5 100644 --- a/src/renderer/html_handlebars/hbs_renderer.rs +++ b/src/renderer/html_handlebars/hbs_renderer.rs @@ -440,6 +440,7 @@ fn make_data(root: &Path, book: &Book, config: &Config, html_config: &HtmlConfig BookItem::Separator => { chapter.insert("spacer".to_owned(), json!("_spacer_")); } + _ => unimplemented!(), } chapters.push(chapter); From 40b98d67c2509862faf5781ebc6d989606319072 Mon Sep 17 00:00:00 2001 From: Mathieu David Date: Thu, 12 Apr 2018 17:32:30 +0200 Subject: [PATCH 13/18] remove unused import --- src/bin/init.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/bin/init.rs b/src/bin/init.rs index f22618ba46..0f66a647e2 100644 --- a/src/bin/init.rs +++ b/src/bin/init.rs @@ -4,7 +4,6 @@ use std::process::Command; use clap::{App, ArgMatches, SubCommand}; use mdbook::MDBook; use mdbook::errors::Result; -use mdbook::utils; use mdbook::config; use get_book_dir; From 9e5d5bb12bfee7695fae431d65d9e2d7256f107f Mon Sep 17 00:00:00 2001 From: Mathieu David Date: Sat, 14 Apr 2018 00:36:29 +0200 Subject: [PATCH 14/18] fix missing parameters --- src/book/book.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/book/book.rs b/src/book/book.rs index 782763a381..02a9a14195 100644 --- a/src/book/book.rs +++ b/src/book/book.rs @@ -246,7 +246,7 @@ fn load_summary_item>( load_chapter(link, src_dir, parent_names).map(|c| BookItem::Chapter(c)) } SummaryItem::VirtualLink(ref link) => - load_virtual_chapter(link, src_dir).map(VirtualChapter::into), + load_virtual_chapter(link, src_dir, parent_names).map(VirtualChapter::into), } } @@ -290,7 +290,7 @@ fn load_chapter>( Ok(ch) } -fn load_virtual_chapter>(link: &VirtualLink, src_dir: P) -> Result { +fn load_virtual_chapter>(link: &VirtualLink, src_dir: P, parent_names: Vec) -> Result { let src_dir = src_dir.as_ref(); let mut ch = VirtualChapter::new(&link.name); @@ -298,7 +298,7 @@ fn load_virtual_chapter>(link: &VirtualLink, src_dir: P) -> Resul let sub_items = link.nested_items .iter() - .map(|i| load_summary_item(i, src_dir)) + .map(|i| load_summary_item(i, src_dir, parent_names.clone())) .collect::>>()?; ch.sub_items = sub_items; From 82197b7d41f35d99e93df4cf543c0c2af0ca241f Mon Sep 17 00:00:00 2001 From: Mathieu David Date: Sat, 14 Apr 2018 00:12:25 +0200 Subject: [PATCH 15/18] Make virtual chapters work with the renderer --- book-example/src/SUMMARY.md | 1 + src/renderer/html_handlebars/hbs_renderer.rs | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/book-example/src/SUMMARY.md b/book-example/src/SUMMARY.md index 8d7324a68a..1472da6cfa 100644 --- a/book-example/src/SUMMARY.md +++ b/book-example/src/SUMMARY.md @@ -10,6 +10,7 @@ - [clean](cli/clean.md) - [Format](format/format.md) - [SUMMARY.md](format/summary.md) + - [Virtual Chapter]() - [Configuration](format/config.md) - [Theme](format/theme/theme.md) - [index.hbs](format/theme/index-hbs.md) diff --git a/src/renderer/html_handlebars/hbs_renderer.rs b/src/renderer/html_handlebars/hbs_renderer.rs index af835a58a5..450a63752e 100644 --- a/src/renderer/html_handlebars/hbs_renderer.rs +++ b/src/renderer/html_handlebars/hbs_renderer.rs @@ -435,7 +435,11 @@ fn make_data(root: &Path, book: &Book, config: &Config, html_config: &HtmlConfig chapter.insert("path".to_owned(), json!(path)); } BookItem::VirtualChapter(ref ch) => { - unimplemented!(); // TODO + if let Some(ref section) = ch.number { + chapter.insert("section".to_owned(), json!(section.to_string())); + } + + chapter.insert("name".to_owned(), json!(ch.name)); } BookItem::Separator => { chapter.insert("spacer".to_owned(), json!("_spacer_")); From ee45e5f23c4ae7292ec6b4a0375b2580769849b6 Mon Sep 17 00:00:00 2001 From: Mathieu David Date: Sat, 14 Apr 2018 00:59:55 +0200 Subject: [PATCH 16/18] fix test missing enum variant --- src/book/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/book/mod.rs b/src/book/mod.rs index cf2dc760a6..780f206a2b 100644 --- a/src/book/mod.rs +++ b/src/book/mod.rs @@ -109,6 +109,7 @@ impl MDBook { /// BookItem::Chapter(ref chapter) => {}, /// BookItem::VirtualChapter(ref virtual_chapter) => {}, /// BookItem::Separator => {}, + /// _ => {}, /// } /// } /// From a5e7421cff485adb695fa2e696ec484ddefdb4dc Mon Sep 17 00:00:00 2001 From: Mathieu David Date: Sat, 14 Apr 2018 02:20:32 +0200 Subject: [PATCH 17/18] Generate empty index.html when first chapter is virtual --- src/renderer/html_handlebars/hbs_renderer.rs | 46 ++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/src/renderer/html_handlebars/hbs_renderer.rs b/src/renderer/html_handlebars/hbs_renderer.rs index 450a63752e..d8423e9250 100644 --- a/src/renderer/html_handlebars/hbs_renderer.rs +++ b/src/renderer/html_handlebars/hbs_renderer.rs @@ -87,6 +87,52 @@ impl HtmlHandlebars { self.render_index(ch, &ctx.destination)?; } } + // If the first chapter is a virtual chapter, create an empty index.html + BookItem::VirtualChapter(ref ch) => { + + if ctx.is_index { + + let content = String::new(); + + // Update the context with data for this file + let filepath = Path::new("index.md") + .with_extension("html"); + let filepathstr = filepath.to_str() + .chain_err(|| "Could not convert HTML path to str")?; + let filepathstr = utils::fs::normalize_path(filepathstr); + + // Non-lexical lifetimes needed :'( + let title: String; + { + let book_title = ctx.data + .get("book_title") + .and_then(serde_json::Value::as_str) + .unwrap_or(""); + title = book_title.to_string(); + } + + ctx.data.insert("path".to_owned(), json!("index.md".to_string())); + ctx.data.insert("content".to_owned(), json!(content)); + ctx.data.insert("chapter_title".to_owned(), json!("".to_string())); + ctx.data.insert("title".to_owned(), json!(title)); + ctx.data.insert("path_to_root".to_owned(), + json!(utils::fs::path_to_root(Path::new("index.md")))); + + // Render the handlebars template with the data + debug!("Render template"); + let rendered = ctx.handlebars.render("index", &ctx.data)?; + + let rendered = self.post_process( + rendered, + &filepathstr, + &ctx.html_config.playpen, + ); + + // Write to file + debug!("Creating {} ✓", filepathstr); + utils::fs::write_file(&ctx.destination, &filepath, &rendered.into_bytes())?; + } + } _ => {} } From 3990398c3994b9974a7456ed68d8d9da03f12abc Mon Sep 17 00:00:00 2001 From: Mathieu David Date: Sat, 14 Apr 2018 13:00:02 +0200 Subject: [PATCH 18/18] Fix bug where sub_items of virtual chapters were not visited --- src/book/book.rs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/book/book.rs b/src/book/book.rs index 02a9a14195..5c63c373aa 100644 --- a/src/book/book.rs +++ b/src/book/book.rs @@ -291,6 +291,7 @@ fn load_chapter>( } fn load_virtual_chapter>(link: &VirtualLink, src_dir: P, parent_names: Vec) -> Result { + debug!("Loading {}", link.name); let src_dir = src_dir.as_ref(); let mut ch = VirtualChapter::new(&link.name); @@ -324,11 +325,20 @@ impl<'a> Iterator for BookItems<'a> { fn next(&mut self) -> Option { let item = self.items.pop_front(); - if let Some(&BookItem::Chapter(ref ch)) = item { - // if we wanted a breadth-first iterator we'd `extend()` here - for sub_item in ch.sub_items.iter().rev() { - self.items.push_front(sub_item); + match item { + Some(&BookItem::Chapter(ref ch)) => { + // if we wanted a breadth-first iterator we'd `extend()` here + for sub_item in ch.sub_items.iter().rev() { + self.items.push_front(sub_item); + } + }, + Some(&BookItem::VirtualChapter(ref ch)) => { + // if we wanted a breadth-first iterator we'd `extend()` here + for sub_item in ch.sub_items.iter().rev() { + self.items.push_front(sub_item); + } } + _ => {}, } item