Skip to content

Commit 38b3516

Browse files
committed
Implement links in section headers.
This project already had a transitive dependency on regex; let's use it. This isn't the most efficient solution, but it should be fine. It ends up doing five full scans of the text. There's probably an easier way but I'm mostly just trying to get this to work for now. This also implements the same algorithm that rustdoc does for generating the name for the link. Fixes #204
1 parent d609988 commit 38b3516

File tree

3 files changed

+41
-0
lines changed

3 files changed

+41
-0
lines changed

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ log = "0.3"
2424
env_logger = "0.3"
2525
toml = { version = "0.2", features = ["serde"] }
2626
open = "1.1"
27+
regex = "0.1.80"
2728

2829
# Watch feature
2930
notify = { version = "3.0", optional = true }

src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ extern crate serde;
7474
extern crate serde_json;
7575
extern crate handlebars;
7676
extern crate pulldown_cmark;
77+
extern crate regex;
7778

7879
#[macro_use] extern crate log;
7980
pub mod book;

src/renderer/html_handlebars/hbs_renderer.rs

+39
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ use renderer::Renderer;
33
use book::MDBook;
44
use book::bookitem::BookItem;
55
use {utils, theme};
6+
use regex::{Regex, Captures};
67

8+
use std::ascii::AsciiExt;
79
use std::path::{Path, PathBuf};
810
use std::fs::{self, File};
911
use std::error::Error;
@@ -91,6 +93,9 @@ impl Renderer for HtmlHandlebars {
9193
// Render the handlebars template with the data
9294
debug!("[*]: Render template");
9395
let rendered = try!(handlebars.render("index", &data));
96+
97+
// create links for headers
98+
let rendered = build_header_links(rendered);
9499

95100
// Write to file
96101
let filename = Path::new(&ch.path).with_extension("html");
@@ -208,3 +213,37 @@ fn make_data(book: &MDBook) -> Result<serde_json::Map<String, serde_json::Value>
208213
debug!("[*]: JSON constructed");
209214
Ok(data)
210215
}
216+
217+
fn build_header_links(mut html: String) -> String {
218+
for header in &["h1", "h2", "h3", "h4", "h5"] {
219+
let regex = Regex::new(&format!("<{h}>(.*?)</{h}>", h=header)).unwrap();
220+
221+
html = regex.replace_all(&html, |caps: &Captures| {
222+
let text = &caps[1];
223+
let mut id = text.to_string();
224+
let repl_sub = vec!["<em>", "</em>", "<code>", "</code>",
225+
"<strong>", "</strong>",
226+
"&lt;", "&gt;", "&amp;", "&#39;", "&quot;"];
227+
for sub in repl_sub {
228+
id = id.replace(sub, "");
229+
}
230+
let id = id.chars().filter_map(|c| {
231+
if c.is_alphanumeric() || c == '-' || c == '_' {
232+
if c.is_ascii() {
233+
Some(c.to_ascii_lowercase())
234+
} else {
235+
Some(c)
236+
}
237+
} else if c.is_whitespace() && c.is_ascii() {
238+
Some('-')
239+
} else {
240+
None
241+
}
242+
}).collect::<String>();
243+
244+
format!("<a class=\"header\" href=\"#{id}\" name=\"{id}\"><{h}>{text}</{h}></a>", h=header, id=id, text=text)
245+
});
246+
}
247+
248+
html
249+
}

0 commit comments

Comments
 (0)