diff options
author | Rene Leveille <rene.leveille@polymtl.ca> | 2020-07-21 17:40:39 -0400 |
---|---|---|
committer | Jan-Erik Rediger <janerik@fnordig.de> | 2021-01-06 14:16:43 +0100 |
commit | 6c96ceea1fedaddf785d96e6e1ed3d241295dd8d (patch) | |
tree | 3d44ab06c799f7fd75ff75e6c093fa5f0e2c444e | |
parent | f8f09d441e5407e5145c24628cbf64b278a2c5ac (diff) |
Allow custom ToC markers through configuration
-rw-r--r-- | README.md | 45 | ||||
-rw-r--r-- | src/lib.rs | 204 |
2 files changed, 218 insertions, 31 deletions
@@ -6,7 +6,7 @@ A preprocessor for [mdbook][] to add inline Table of Contents support. It turns this: -``` +```md <!-- toc --> ``` @@ -16,13 +16,13 @@ into a Table of Contents based on all top- and second-level headings of the chap If you want to use only this preprocessor, install the tool: -``` +```sh cargo install mdbook-toc ``` Add it as a preprocessor to your `book.toml`: -``` +```toml [preprocessor.toc] command = "mdbook-toc" renderer = ["html"] @@ -30,10 +30,47 @@ renderer = ["html"] Finally, build your book as normal: -``` +```sh mdbook path/to/book ``` +## Custom TOC marker + +The default marker is: + +```md +<!-- toc --> +``` + +If you wish to use a different, such as the GitLab marker `[[_TOC_]]`, you must add the following settings to your `book.toml`. + +```toml +[preprocessor.toc] +marker = "[[_TOC_]]" +``` + +You can also use multi-line markers such as the GitHub marker, which is: + +```md +* auto-gen TOC; +{:toc} +``` + +Configure the string with a newline: + +```toml +[preprocessor.toc] +marker = "* auto-gen TOC;\n{:toc}" +``` + +or with multi-line strings: + +```toml +[preprocessor.toc] +marker = """* auto-gen TOC; +{:toc}""" +``` + ## License MPL. See [LICENSE](LICENSE). @@ -10,20 +10,40 @@ use pulldown_cmark_to_cmark::{cmark_with_options, Options as COptions}; pub struct Toc; +static DEFAULT_MARKER: &str = "<!-- toc -->\n"; + impl Preprocessor for Toc { fn name(&self) -> &str { "toc" } - fn run(&self, _ctx: &PreprocessorContext, mut book: Book) -> Result<Book> { + fn run(&self, ctx: &PreprocessorContext, mut book: Book) -> Result<Book> { let mut res = None; + let toc_marker = if let Some(cfg) = ctx.config.get_preprocessor(self.name()) { + if let Some(marker) = cfg.get("marker") { + match marker.as_str() { + Some(m) => m, + None => { + return Err(Error::msg(format!( + "Marker {:?} is not a valid string", + marker + ))) + } + } + } else { + DEFAULT_MARKER + } + } else { + DEFAULT_MARKER + }; + book.for_each_mut(|item: &mut BookItem| { if let Some(Err(_)) = res { return; } if let BookItem::Chapter(ref mut chapter) = *item { - res = Some(Toc::add_toc(chapter).map(|md| { + res = Some(Toc::add_toc(chapter, &toc_marker).map(|md| { chapter.content = md; })); } @@ -69,7 +89,7 @@ fn build_toc(toc: &[(u32, String, String)]) -> String { result } -fn add_toc(content: &str) -> Result<String> { +fn add_toc(content: &str, marker: &str) -> Result<String> { let mut buf = String::with_capacity(content.len()); let mut toc_found = false; @@ -84,17 +104,29 @@ fn add_toc(content: &str) -> Result<String> { opts.insert(Options::ENABLE_STRIKETHROUGH); opts.insert(Options::ENABLE_TASKLISTS); + let mark: Vec<Event> = Parser::new(marker).collect(); + let mut mark_start = -1; + let mut mark_loc = 0; + let mut c = -1; + for e in Parser::new_ext(&content, opts) { + c += 1; log::trace!("Event: {:?}", e); - - if let Event::Html(html) = e { - if &*html == "<!-- toc -->\n" { - toc_found = true; - } - continue; - } if !toc_found { - continue; + if e == mark[mark_loc] { + if mark_start == -1 { + mark_start = c; + } + mark_loc += 1; + if mark_loc >= mark.len() { + toc_found = true + } + } else if mark_loc > 0 { + mark_loc = 0; + mark_start = -1; + } else { + continue; + } } if let Event::Start(Heading(lvl)) = e { @@ -138,14 +170,23 @@ fn add_toc(content: &str) -> Result<String> { let toc = build_toc(&toc_content); let toc_events = Parser::new(&toc).collect::<Vec<_>>(); + let mut c = -1; let events = Parser::new_ext(&content, opts) .map(|e| { - if let Event::Html(html) = e.clone() { - if &*html == "<!-- toc -->\n" { - return toc_events.clone(); - } + c += 1; + if c > mark_start && c < mark_start + (mark.len() as i32) { + vec![] + } else if c == mark_start { + toc_events.clone() + } else { + vec![e] } - vec![e] + // if let Event::Html(html) = e.clone() { + // if &*html == marker { + // return toc_events.clone(); + // } + // } + // vec![e] }) .flatten(); @@ -156,14 +197,14 @@ fn add_toc(content: &str) -> Result<String> { } impl Toc { - fn add_toc(chapter: &mut Chapter) -> Result<String> { - add_toc(&chapter.content) + fn add_toc(chapter: &mut Chapter, marker: &str) -> Result<String> { + add_toc(&chapter.content, marker) } } #[cfg(test)] mod test { - use super::add_toc; + use super::{add_toc, DEFAULT_MARKER}; use pretty_assertions::assert_eq; #[test] @@ -207,7 +248,10 @@ mod test { ### Header 2.2.1"#; - assert_eq!(expected, add_toc(content).unwrap()); + assert_eq!( + expected, + add_toc(content, DEFAULT_MARKER).unwrap() + ); } #[test] @@ -240,7 +284,10 @@ mod test { ## Header 2.1"#; - assert_eq!(expected, add_toc(content).unwrap()); + assert_eq!( + expected, + add_toc(content, DEFAULT_MARKER).unwrap() + ); } #[test] @@ -262,7 +309,10 @@ mod test { |------|------| |Row 1|Row 2|"#; - assert_eq!(expected, add_toc(content).unwrap()); + assert_eq!( + expected, + add_toc(content, DEFAULT_MARKER).unwrap() + ); } #[test] @@ -311,7 +361,10 @@ mod test { # Another header `with inline` code"#; - assert_eq!(expected, add_toc(content).unwrap()); + assert_eq!( + expected, + add_toc(content, DEFAULT_MARKER).unwrap() + ); } #[test] @@ -358,7 +411,10 @@ mod test { ## User Preferences"#; - assert_eq!(expected, add_toc(content).unwrap()); + assert_eq!( + expected, + add_toc(content, DEFAULT_MARKER).unwrap() + ); } #[test] @@ -375,7 +431,7 @@ mod test { text"#; - let expected = r#"# Heading + let expected = r#"# Heading * [Level 1.1](#level-11) * [Level 1.1.1](#level-111) @@ -395,7 +451,55 @@ text"#; text"#; - assert_eq!(expected, add_toc(content).unwrap()); + assert_eq!( + expected, + add_toc(content, DEFAULT_MARKER).unwrap() + ); + } + + #[test] + fn add_toc_with_gitlab_marker() { + let marker = "[[_TOC_]]".to_owned(); + let content = r#"# Chapter + +[[_TOC_]] + +# Header 1 + +## Header 1.1 + +# Header 2 + +## Header 2.1 + +## Header 2.2 + +### Header 2.2.1 + +"#; + + let expected = r#"# Chapter + +* [Header 1](#header-1) + * [Header 1.1](#header-11) +* [Header 2](#header-2) + * [Header 2.1](#header-21) + * [Header 2.2](#header-22) + * [Header 2.2.1](#header-221) + +# Header 1 + +## Header 1.1 + +# Header 2 + +## Header 2.1 + +## Header 2.2 + +### Header 2.2.1"#; + + assert_eq!(expected, add_toc(content, &marker).unwrap()); } #[test] @@ -431,6 +535,52 @@ text"#; ## Duplicate"#; - assert_eq!(expected, add_toc(content).unwrap()); + assert_eq!(expected, add_toc(content, DEFAULT_MARKER).unwrap()); + } + + #[test] + fn add_toc_with_github_marker() { + let marker = "* auto-gen TOC:\n{:toc}".to_owned(); + let content = r#"# Chapter + +* auto-gen TOC: +{:toc} + +# Header 1 + +## Header 1.1 + +# Header 2 + +## Header 2.1 + +## Header 2.2 + +### Header 2.2.1 + +"#; + + let expected = r#"# Chapter + +* [Header 1](#header-1) + * [Header 1.1](#header-11) +* [Header 2](#header-2) + * [Header 2.1](#header-21) + * [Header 2.2](#header-22) + * [Header 2.2.1](#header-221) + +# Header 1 + +## Header 1.1 + +# Header 2 + +## Header 2.1 + +## Header 2.2 + +### Header 2.2.1"#; + + assert_eq!(expected, add_toc(content, &marker).unwrap()); } } |