1
Fork 0

Allow custom ToC markers through configuration

This commit is contained in:
Rene Leveille 2020-07-21 17:40:39 -04:00 committed by Jan-Erik Rediger
parent f8f09d441e
commit 6c96ceea1f
2 changed files with 219 additions and 32 deletions

View file

@ -6,7 +6,7 @@ A preprocessor for [mdbook][] to add inline Table of Contents support.
It turns this: It turns this:
``` ```md
<!-- toc --> <!-- 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: If you want to use only this preprocessor, install the tool:
``` ```sh
cargo install mdbook-toc cargo install mdbook-toc
``` ```
Add it as a preprocessor to your `book.toml`: Add it as a preprocessor to your `book.toml`:
``` ```toml
[preprocessor.toc] [preprocessor.toc]
command = "mdbook-toc" command = "mdbook-toc"
renderer = ["html"] renderer = ["html"]
@ -30,10 +30,47 @@ renderer = ["html"]
Finally, build your book as normal: Finally, build your book as normal:
``` ```sh
mdbook path/to/book 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 ## License
MPL. See [LICENSE](LICENSE). MPL. See [LICENSE](LICENSE).

View file

@ -10,20 +10,40 @@ use pulldown_cmark_to_cmark::{cmark_with_options, Options as COptions};
pub struct Toc; pub struct Toc;
static DEFAULT_MARKER: &str = "<!-- toc -->\n";
impl Preprocessor for Toc { impl Preprocessor for Toc {
fn name(&self) -> &str { fn name(&self) -> &str {
"toc" "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 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| { book.for_each_mut(|item: &mut BookItem| {
if let Some(Err(_)) = res { if let Some(Err(_)) = res {
return; return;
} }
if let BookItem::Chapter(ref mut chapter) = *item { 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; chapter.content = md;
})); }));
} }
@ -69,7 +89,7 @@ fn build_toc(toc: &[(u32, String, String)]) -> String {
result 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 buf = String::with_capacity(content.len());
let mut toc_found = false; let mut toc_found = false;
@ -84,18 +104,30 @@ fn add_toc(content: &str) -> Result<String> {
opts.insert(Options::ENABLE_STRIKETHROUGH); opts.insert(Options::ENABLE_STRIKETHROUGH);
opts.insert(Options::ENABLE_TASKLISTS); opts.insert(Options::ENABLE_TASKLISTS);
for e in Parser::new_ext(&content, opts) { let mark: Vec<Event> = Parser::new(marker).collect();
log::trace!("Event: {:?}", e); let mut mark_start = -1;
let mut mark_loc = 0;
let mut c = -1;
if let Event::Html(html) = e { for e in Parser::new_ext(&content, opts) {
if &*html == "<!-- toc -->\n" { c += 1;
toc_found = true; log::trace!("Event: {:?}", e);
}
continue;
}
if !toc_found { if !toc_found {
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; continue;
} }
}
if let Event::Start(Heading(lvl)) = e { if let Event::Start(Heading(lvl)) = e {
current_header_level = Some(lvl); current_header_level = Some(lvl);
@ -138,14 +170,23 @@ fn add_toc(content: &str) -> Result<String> {
let toc = build_toc(&toc_content); let toc = build_toc(&toc_content);
let toc_events = Parser::new(&toc).collect::<Vec<_>>(); let toc_events = Parser::new(&toc).collect::<Vec<_>>();
let mut c = -1;
let events = Parser::new_ext(&content, opts) let events = Parser::new_ext(&content, opts)
.map(|e| { .map(|e| {
if let Event::Html(html) = e.clone() { c += 1;
if &*html == "<!-- toc -->\n" { if c > mark_start && c < mark_start + (mark.len() as i32) {
return toc_events.clone(); 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(); .flatten();
@ -156,14 +197,14 @@ fn add_toc(content: &str) -> Result<String> {
} }
impl Toc { impl Toc {
fn add_toc(chapter: &mut Chapter) -> Result<String> { fn add_toc(chapter: &mut Chapter, marker: &str) -> Result<String> {
add_toc(&chapter.content) add_toc(&chapter.content, marker)
} }
} }
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::add_toc; use super::{add_toc, DEFAULT_MARKER};
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
#[test] #[test]
@ -207,7 +248,10 @@ mod test {
### Header 2.2.1"#; ### Header 2.2.1"#;
assert_eq!(expected, add_toc(content).unwrap()); assert_eq!(
expected,
add_toc(content, DEFAULT_MARKER).unwrap()
);
} }
#[test] #[test]
@ -240,7 +284,10 @@ mod test {
## Header 2.1"#; ## Header 2.1"#;
assert_eq!(expected, add_toc(content).unwrap()); assert_eq!(
expected,
add_toc(content, DEFAULT_MARKER).unwrap()
);
} }
#[test] #[test]
@ -262,7 +309,10 @@ mod test {
|------|------| |------|------|
|Row 1|Row 2|"#; |Row 1|Row 2|"#;
assert_eq!(expected, add_toc(content).unwrap()); assert_eq!(
expected,
add_toc(content, DEFAULT_MARKER).unwrap()
);
} }
#[test] #[test]
@ -311,7 +361,10 @@ mod test {
# Another header `with inline` code"#; # Another header `with inline` code"#;
assert_eq!(expected, add_toc(content).unwrap()); assert_eq!(
expected,
add_toc(content, DEFAULT_MARKER).unwrap()
);
} }
#[test] #[test]
@ -358,7 +411,10 @@ mod test {
## User Preferences"#; ## User Preferences"#;
assert_eq!(expected, add_toc(content).unwrap()); assert_eq!(
expected,
add_toc(content, DEFAULT_MARKER).unwrap()
);
} }
#[test] #[test]
@ -395,7 +451,55 @@ text"#;
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] #[test]
@ -431,6 +535,52 @@ text"#;
## Duplicate"#; ## 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());
} }
} }