Merge pull request #22 from badboy/regression-21/backslash-escape
This commit is contained in:
commit
0d17ed1801
6
.github/workflows/tests.yml
vendored
6
.github/workflows/tests.yml
vendored
|
@ -23,12 +23,6 @@ jobs:
|
||||||
toolchain: ${{ matrix.rust }}
|
toolchain: ${{ matrix.rust }}
|
||||||
override: true
|
override: true
|
||||||
|
|
||||||
- name: check
|
|
||||||
uses: actions-rs/cargo@v1
|
|
||||||
with:
|
|
||||||
command: check
|
|
||||||
args: --all
|
|
||||||
|
|
||||||
- name: tests
|
- name: tests
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
|
|
717
Cargo.lock
generated
717
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -10,13 +10,12 @@ edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
mdbook = "0.4.10"
|
mdbook = "0.4.10"
|
||||||
pulldown-cmark = "0.8.0"
|
pulldown-cmark = "0.9.1"
|
||||||
pulldown-cmark-to-cmark = "6.0.2"
|
|
||||||
env_logger = "0.8.4"
|
|
||||||
log = "0.4.11"
|
log = "0.4.11"
|
||||||
clap = "2.33.3"
|
clap = "2.33.3"
|
||||||
serde_json = "1.0.57"
|
serde_json = "1.0.57"
|
||||||
toml = "0.5.6"
|
toml = "0.5.6"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
pretty_assertions = "0.6.1"
|
pretty_assertions = "1.0.0"
|
||||||
|
env_logger = "0.9.0"
|
||||||
|
|
71
src/lib.rs
71
src/lib.rs
|
@ -8,7 +8,6 @@ use mdbook::errors::{Error, Result};
|
||||||
use mdbook::preprocess::{Preprocessor, PreprocessorContext};
|
use mdbook::preprocess::{Preprocessor, PreprocessorContext};
|
||||||
use pulldown_cmark::Tag::*;
|
use pulldown_cmark::Tag::*;
|
||||||
use pulldown_cmark::{Event, Options, Parser};
|
use pulldown_cmark::{Event, Options, Parser};
|
||||||
use pulldown_cmark_to_cmark::{cmark_with_options, Options as COptions};
|
|
||||||
use toml::value::Table;
|
use toml::value::Table;
|
||||||
|
|
||||||
pub struct Toc;
|
pub struct Toc;
|
||||||
|
@ -110,6 +109,7 @@ fn build_toc(toc: &[(u32, String, String)]) -> String {
|
||||||
let mut toc_iter = toc.iter().peekable();
|
let mut toc_iter = toc.iter().peekable();
|
||||||
|
|
||||||
// Start from the level of the first header.
|
// Start from the level of the first header.
|
||||||
|
let min_level = toc.iter().map(|(lvl, _, _)| *lvl).min().unwrap_or(1);
|
||||||
let mut last_lower = match toc_iter.peek() {
|
let mut last_lower = match toc_iter.peek() {
|
||||||
Some((lvl, _, _)) => *lvl,
|
Some((lvl, _, _)) => *lvl,
|
||||||
None => 0,
|
None => 0,
|
||||||
|
@ -127,7 +127,7 @@ fn build_toc(toc: &[(u32, String, String)]) -> String {
|
||||||
});
|
});
|
||||||
|
|
||||||
for (level, name, slug) in toc {
|
for (level, name, slug) in toc {
|
||||||
let width = 2 * (level - 1) as usize;
|
let width = 2 * (level - min_level) as usize;
|
||||||
writeln!(result, "{1:0$}* [{2}](#{3})", width, "", name, slug).unwrap();
|
writeln!(result, "{1:0$}* [{2}](#{3})", width, "", name, slug).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,7 +135,6 @@ fn build_toc(toc: &[(u32, String, String)]) -> String {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_toc(content: &str, cfg: &Config) -> Result<String> {
|
fn add_toc(content: &str, cfg: &Config) -> Result<String> {
|
||||||
let mut buf = String::with_capacity(content.len());
|
|
||||||
let mut toc_found = false;
|
let mut toc_found = false;
|
||||||
|
|
||||||
let mut toc_content = vec![];
|
let mut toc_content = vec![];
|
||||||
|
@ -150,40 +149,41 @@ fn add_toc(content: &str, cfg: &Config) -> Result<String> {
|
||||||
opts.insert(Options::ENABLE_TASKLISTS);
|
opts.insert(Options::ENABLE_TASKLISTS);
|
||||||
|
|
||||||
let mark: Vec<Event> = Parser::new(&cfg.marker).collect();
|
let mark: Vec<Event> = Parser::new(&cfg.marker).collect();
|
||||||
let mut mark_start = -1;
|
let mut mark_start = None;
|
||||||
|
let mut mark_end = 0..0;
|
||||||
let mut mark_loc = 0;
|
let mut mark_loc = 0;
|
||||||
let mut c = -1;
|
|
||||||
|
|
||||||
for e in Parser::new_ext(&content, opts) {
|
for (e, span) in Parser::new_ext(&content, opts).into_offset_iter() {
|
||||||
c += 1;
|
log::trace!("Event: {:?} (span: {:?})", e, span);
|
||||||
log::trace!("Event: {:?}", e);
|
|
||||||
if !toc_found {
|
if !toc_found {
|
||||||
log::trace!(
|
log::trace!(
|
||||||
"TOC not found yet. Location: {}, Start: {}",
|
"TOC not found yet. Location: {}, Start: {:?}",
|
||||||
mark_loc,
|
mark_loc,
|
||||||
mark_start
|
mark_start
|
||||||
);
|
);
|
||||||
if e == mark[mark_loc] {
|
if e == mark[mark_loc] {
|
||||||
if mark_start == -1 {
|
if mark_start.is_none() {
|
||||||
mark_start = c;
|
mark_start = Some(span.clone());
|
||||||
}
|
}
|
||||||
mark_loc += 1;
|
mark_loc += 1;
|
||||||
if mark_loc >= mark.len() {
|
if mark_loc >= mark.len() {
|
||||||
|
mark_end = span;
|
||||||
toc_found = true
|
toc_found = true
|
||||||
}
|
}
|
||||||
} else if mark_loc > 0 {
|
} else if mark_loc > 0 {
|
||||||
mark_loc = 0;
|
mark_loc = 0;
|
||||||
mark_start = -1;
|
mark_start = None;
|
||||||
} else {
|
} else {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Event::Start(Heading(lvl)) = e {
|
if let Event::Start(Heading(lvl, fragment, classes)) = e {
|
||||||
current_header_level = Some(lvl);
|
log::trace!("Header(lvl={lvl}, fragment={fragment:?}, classes={classes:?})");
|
||||||
|
current_header_level = Some(lvl as u32);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if let Event::End(Heading(_)) = e {
|
if let Event::End(Heading(..)) = e {
|
||||||
// Skip if this header is nested too deeply.
|
// Skip if this header is nested too deeply.
|
||||||
if let Some(level) = current_header_level.take() {
|
if let Some(level) = current_header_level.take() {
|
||||||
let header = current_header.clone();
|
let header = current_header.clone();
|
||||||
|
@ -219,29 +219,30 @@ fn add_toc(content: &str, cfg: &Config) -> Result<String> {
|
||||||
|
|
||||||
let toc = build_toc(&toc_content);
|
let toc = build_toc(&toc_content);
|
||||||
log::trace!("Built TOC: {:?}", toc);
|
log::trace!("Built TOC: {:?}", toc);
|
||||||
let toc_events = Parser::new(&toc).collect::<Vec<_>>();
|
log::trace!("toc_found={toc_found} mark_start={mark_start:?} mark_end={mark_end:?}");
|
||||||
|
|
||||||
let mut c = -1;
|
let content = if toc_found {
|
||||||
let events = Parser::new_ext(&content, opts)
|
let mark_start = mark_start.unwrap();
|
||||||
.map(|e| {
|
let content_before_toc = &content[0..mark_start.start];
|
||||||
c += 1;
|
let content_after_toc = &content[mark_end.end..];
|
||||||
if toc_found && c > mark_start && c < mark_start + (mark.len() as i32) {
|
log::trace!("content_before_toc={:?}", content_before_toc);
|
||||||
vec![]
|
log::trace!("content_after_toc={:?}", content_after_toc);
|
||||||
} else if toc_found && c == mark_start {
|
// Multiline markers might have consumed trailing newlines,
|
||||||
toc_events.clone()
|
// we ensure there's always one before the content.
|
||||||
|
let extra = if content_after_toc.as_bytes()[0] == b'\n' {
|
||||||
|
""
|
||||||
} else {
|
} else {
|
||||||
vec![e]
|
"\n"
|
||||||
}
|
|
||||||
})
|
|
||||||
.flatten();
|
|
||||||
|
|
||||||
let opts = COptions {
|
|
||||||
newlines_after_codeblock: 1,
|
|
||||||
..Default::default()
|
|
||||||
};
|
};
|
||||||
cmark_with_options(events, &mut buf, None, opts)
|
format!(
|
||||||
.map(|_| buf)
|
"{}{}{}{}",
|
||||||
.map_err(|err| Error::msg(format!("Markdown serialization failed: {}", err)))
|
content_before_toc, toc, extra, content_after_toc
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
content.to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Toc {
|
impl Toc {
|
||||||
|
|
|
@ -18,3 +18,4 @@
|
||||||
## Header 2.2
|
## Header 2.2
|
||||||
|
|
||||||
### Header 2.2.1
|
### Header 2.2.1
|
||||||
|
|
||||||
|
|
9
tests/backslash_escapes.in.md
Normal file
9
tests/backslash_escapes.in.md
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
\*not emphasized*
|
||||||
|
\<br/> not a tag
|
||||||
|
\[not a link](/foo)
|
||||||
|
\`not code`
|
||||||
|
\* not a list
|
||||||
|
\# not a heading
|
||||||
|
\[foo]: /url "not a reference"
|
||||||
|
\ö not a character entity
|
||||||
|
1\. not a list
|
9
tests/backslash_escapes.out.md
Normal file
9
tests/backslash_escapes.out.md
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
\*not emphasized*
|
||||||
|
\<br/> not a tag
|
||||||
|
\[not a link](/foo)
|
||||||
|
\`not code`
|
||||||
|
\* not a list
|
||||||
|
\# not a heading
|
||||||
|
\[foo]: /url "not a reference"
|
||||||
|
\ö not a character entity
|
||||||
|
1\. not a list
|
|
@ -13,5 +13,3 @@
|
||||||
##### Header 1.1.1.1.1
|
##### Header 1.1.1.1.1
|
||||||
|
|
||||||
# Another header `with inline` code
|
# Another header `with inline` code
|
||||||
|
|
||||||
|
|
||||||
|
|
11
tests/it.rs
11
tests/it.rs
|
@ -58,7 +58,7 @@ macro_rules! assert_toc {
|
||||||
let chapter = Chapter::from_content(content);
|
let chapter = Chapter::from_content(content);
|
||||||
let result = Toc::add_toc(&chapter, &config);
|
let result = Toc::add_toc(&chapter, &config);
|
||||||
match result {
|
match result {
|
||||||
Ok(result) => assert_eq!(expected.trim_end(), result),
|
Ok(result) => assert_eq!(expected, result),
|
||||||
Err(e) => panic!("{} failed. Error: {}", $name, e),
|
Err(e) => panic!("{} failed. Error: {}", $name, e),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -114,7 +114,7 @@ fn unique_slugs() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn add_toc_with_github_marker() {
|
fn add_toc_with_github_marker() {
|
||||||
let marker = "* auto-gen TOC:\n{:toc}".to_owned();
|
let marker = "* auto-gen TOC:\n{:toc}\n".to_owned();
|
||||||
assert_toc!("github_marker", with_marker(marker));
|
assert_toc!("github_marker", with_marker(marker));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,3 +150,10 @@ fn similar_heading_different_casing() {
|
||||||
fn tables_with_html() {
|
fn tables_with_html() {
|
||||||
assert_toc!("tables_with_html");
|
assert_toc!("tables_with_html");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn backslash_escapes() {
|
||||||
|
// Regression test #21
|
||||||
|
// Backslash-escaped elements should still be escaped.
|
||||||
|
assert_toc!("backslash_escapes");
|
||||||
|
}
|
||||||
|
|
|
@ -7,14 +7,9 @@
|
||||||
* [Level 1.2.1](#level-121)
|
* [Level 1.2.1](#level-121)
|
||||||
|
|
||||||
## Level 1.1
|
## Level 1.1
|
||||||
|
|
||||||
### Level 1.1.1
|
### Level 1.1.1
|
||||||
|
|
||||||
### Level 1.1.2
|
### Level 1.1.2
|
||||||
|
|
||||||
## Level 1.2
|
## Level 1.2
|
||||||
|
|
||||||
### Level 1.2.1
|
### Level 1.2.1
|
||||||
|
|
||||||
text
|
text
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
# Heading
|
# Heading
|
||||||
|
|
||||||
| Head 1 | Head 2 |
|
| Head 1 | Head 2 |
|
||||||
|------|------|
|
|--------|--------|
|
||||||
| Row 1 | Row 2 |
|
| Row 1 | Row 2 |
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# Heading
|
# Heading
|
||||||
|
|
||||||
| Head 1 | Head 2 |
|
| Head 1 | Head 2 |
|
||||||
|------|------|
|
|--------|--------|
|
||||||
| <span>Row 1</span> | Row 2 |
|
| <span>Row 1</span> | Row 2 |
|
||||||
|
|
|
@ -9,3 +9,6 @@
|
||||||
# Header 2
|
# Header 2
|
||||||
|
|
||||||
## Header 2.1
|
## Header 2.1
|
||||||
|
|
||||||
|
When code or an identifier must appear in a message or label, it should be
|
||||||
|
surrounded with backticks: `` `foo.bar` ``.
|
||||||
|
|
|
@ -12,3 +12,6 @@
|
||||||
# Header 2
|
# Header 2
|
||||||
|
|
||||||
## Header 2.1
|
## Header 2.1
|
||||||
|
|
||||||
|
When code or an identifier must appear in a message or label, it should be
|
||||||
|
surrounded with backticks: `` `foo.bar` ``.
|
||||||
|
|
Loading…
Reference in a new issue