1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
use mdbook::book::Chapter;
use memchr::Memchr;
use pulldown_cmark::{Event, Parser, Tag};
use std::fmt::{self, Display, Formatter};
#[derive(Debug, Clone, PartialEq)]
pub struct Link<'a> {
pub url: String,
pub offset: usize,
pub chapter: &'a Chapter,
}
impl<'a> Link<'a> {
pub fn line_number(&self) -> usize {
let content = &self.chapter.content;
if self.offset > content.len() {
panic!(
"Link has invalid offset. Got {} but chapter is only {} bytes long.",
self.offset,
self.chapter.content.len()
);
}
Memchr::new(b'\n', content[..self.offset].as_bytes()).count() + 1
}
}
impl<'a> Display for Link<'a> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(
f,
"\"{}\" in {}#{}",
self.url,
self.chapter.path.display(),
self.line_number()
)
}
}
pub fn collect_links(ch: &Chapter) -> Vec<Link> {
let mut links = Vec::new();
let mut parser = Parser::new(&ch.content);
while let Some(event) = parser.next() {
match event {
Event::Start(Tag::Link(dest, _)) | Event::Start(Tag::Image(dest, _)) => {
let link = Link {
url: dest.to_string(),
offset: parser.get_offset(),
chapter: ch,
};
trace!("Found {}", link);
links.push(link);
}
_ => {}
}
}
links
}
#[cfg(test)]
mod tests {
use super::*;
use mdbook::book::Chapter;
#[test]
fn find_links_in_chapter() {
let src = "[Reference other chapter](index.html) and [Google](https://google.com)";
let ch = Chapter::new("Foo", src.to_string(), "index.md", Vec::new());
let should_be = vec![
Link {
url: String::from("index.html"),
offset: 1,
chapter: &ch,
},
Link {
url: String::from("https://google.com"),
offset: 43,
chapter: &ch,
},
];
let got = collect_links(&ch);
assert_eq!(got, should_be);
}
}