Skip to content

Commit f593f45

Browse files
committed
extract hbs render and rendering postprocessing to wrapper
1 parent bb3398b commit f593f45

File tree

3 files changed

+246
-209
lines changed

3 files changed

+246
-209
lines changed

src/renderer/html_handlebars/hbs_renderer.rs

Lines changed: 12 additions & 209 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::book::{Book, BookItem};
22
use crate::config::{Config, HtmlConfig, Playpen};
33
use crate::errors::*;
4+
use crate::renderer::html_handlebars::hbs_wrapper::{HbsWrapper, HbsConfig};
45
use crate::renderer::html_handlebars::helpers;
56
use crate::renderer::{RenderContext, Renderer};
67
use crate::theme::{self, playpen_editor, Theme};
@@ -11,9 +12,9 @@ use std::collections::HashMap;
1112
use std::fs;
1213
use std::path::{Path, PathBuf};
1314

14-
use handlebars::Handlebars;
1515
use regex::{Captures, Regex};
1616

17+
1718
#[derive(Default)]
1819
pub struct HtmlHandlebars;
1920

@@ -76,9 +77,7 @@ impl HtmlHandlebars {
7677

7778
// Render the handlebars template with the data
7879
debug!("Render template");
79-
let rendered = ctx.handlebars.render("index", &ctx.data)?;
80-
81-
let rendered = self.post_process(rendered, &ctx.html_config.playpen);
80+
let rendered = ctx.handlebars.render("index", &ctx.data, &ctx.html_config.playpen)?;
8281

8382
// Write to file
8483
debug!("Creating {}", filepath.display());
@@ -87,8 +86,7 @@ impl HtmlHandlebars {
8786
if ctx.is_index {
8887
ctx.data.insert("path".to_owned(), json!("index.md"));
8988
ctx.data.insert("path_to_root".to_owned(), json!(""));
90-
let rendered_index = ctx.handlebars.render("index", &ctx.data)?;
91-
let rendered_index = self.post_process(rendered_index, &ctx.html_config.playpen);
89+
let rendered_index = ctx.handlebars.render("index", &ctx.data, &ctx.html_config.playpen)?;
9290
debug!("Creating index.html from {}", path);
9391
utils::fs::write_file(&ctx.destination, "index.html", rendered_index.as_bytes())?;
9492
}
@@ -97,15 +95,6 @@ impl HtmlHandlebars {
9795
Ok(())
9896
}
9997

100-
#[cfg_attr(feature = "cargo-clippy", allow(clippy::let_and_return))]
101-
fn post_process(&self, rendered: String, playpen_config: &Playpen) -> String {
102-
let rendered = build_header_links(&rendered);
103-
let rendered = fix_code_blocks(&rendered);
104-
let rendered = add_playpen_pre(&rendered, playpen_config);
105-
106-
rendered
107-
}
108-
10998
fn copy_static_files(
11099
&self,
111100
destination: &Path,
@@ -204,18 +193,6 @@ impl HtmlHandlebars {
204193
);
205194
}
206195

207-
fn register_hbs_helpers(&self, handlebars: &mut Handlebars, html_config: &HtmlConfig) {
208-
handlebars.register_helper(
209-
"toc",
210-
Box::new(helpers::toc::RenderToc {
211-
no_section_label: html_config.no_section_label,
212-
}),
213-
);
214-
handlebars.register_helper("previous", Box::new(helpers::navigation::previous));
215-
handlebars.register_helper("next", Box::new(helpers::navigation::next));
216-
handlebars.register_helper("theme_option", Box::new(helpers::theme::theme_option));
217-
}
218-
219196
/// Copy across any additional CSS and JavaScript files which the book
220197
/// has been configured to use.
221198
fn copy_additional_css_and_js(
@@ -285,8 +262,6 @@ impl Renderer for HtmlHandlebars {
285262
let book = &ctx.book;
286263

287264
trace!("render");
288-
let mut handlebars = Handlebars::new();
289-
290265
let theme_dir = match html_config.theme {
291266
Some(ref theme) => theme.to_path_buf(),
292267
None => ctx.root.join("theme"),
@@ -303,15 +278,12 @@ impl Renderer for HtmlHandlebars {
303278
}
304279

305280
let theme = theme::Theme::new(theme_dir);
306-
307-
debug!("Register the index handlebars template");
308-
handlebars.register_template_string("index", String::from_utf8(theme.index.clone())?)?;
309-
310-
debug!("Register the header handlebars template");
311-
handlebars.register_partial("header", String::from_utf8(theme.header.clone())?)?;
312-
313-
debug!("Register handlebars helpers");
314-
self.register_hbs_helpers(&mut handlebars, &html_config);
281+
let config = HbsConfig {
282+
index_template: String::from_utf8(theme.index.clone())?,
283+
header_template: String::from_utf8(theme.header.clone())?,
284+
no_section_label: html_config.no_section_label,
285+
};
286+
let handlebars = HbsWrapper::with_config(config)?;
315287

316288
let mut data = make_data(&ctx.root, &book, &ctx.config, &html_config)?;
317289

@@ -342,9 +314,7 @@ impl Renderer for HtmlHandlebars {
342314

343315
// Render the handlebars template with the data
344316
debug!("Render template");
345-
let rendered = handlebars.render("index", &data)?;
346-
347-
let rendered = self.post_process(rendered, &html_config.playpen);
317+
let rendered = handlebars.render("index", &data, &html_config.playpen)?;
348318

349319
utils::fs::write_file(&destination, "print.html", rendered.as_bytes())?;
350320
debug!("Creating print.html ✓");
@@ -499,177 +469,10 @@ fn make_data(
499469
Ok(data)
500470
}
501471

502-
/// Goes through the rendered HTML, making sure all header tags have
503-
/// an anchor respectively so people can link to sections directly.
504-
fn build_header_links(html: &str) -> String {
505-
let regex = Regex::new(r"<h(\d)>(.*?)</h\d>").unwrap();
506-
let mut id_counter = HashMap::new();
507-
508-
regex
509-
.replace_all(html, |caps: &Captures<'_>| {
510-
let level = caps[1]
511-
.parse()
512-
.expect("Regex should ensure we only ever get numbers here");
513-
514-
insert_link_into_header(level, &caps[2], &mut id_counter)
515-
})
516-
.into_owned()
517-
}
518-
519-
/// Insert a sinle link into a header, making sure each link gets its own
520-
/// unique ID by appending an auto-incremented number (if necessary).
521-
fn insert_link_into_header(
522-
level: usize,
523-
content: &str,
524-
id_counter: &mut HashMap<String, usize>,
525-
) -> String {
526-
let raw_id = utils::id_from_content(content);
527-
528-
let id_count = id_counter.entry(raw_id.clone()).or_insert(0);
529-
530-
let id = match *id_count {
531-
0 => raw_id,
532-
other => format!("{}-{}", raw_id, other),
533-
};
534-
535-
*id_count += 1;
536-
537-
format!(
538-
r##"<h{level}><a class="header" href="#{id}" id="{id}">{text}</a></h{level}>"##,
539-
level = level,
540-
id = id,
541-
text = content
542-
)
543-
}
544-
545-
// The rust book uses annotations for rustdoc to test code snippets,
546-
// like the following:
547-
// ```rust,should_panic
548-
// fn main() {
549-
// // Code here
550-
// }
551-
// ```
552-
// This function replaces all commas by spaces in the code block classes
553-
fn fix_code_blocks(html: &str) -> String {
554-
let regex = Regex::new(r##"<code([^>]+)class="([^"]+)"([^>]*)>"##).unwrap();
555-
regex
556-
.replace_all(html, |caps: &Captures<'_>| {
557-
let before = &caps[1];
558-
let classes = &caps[2].replace(",", " ");
559-
let after = &caps[3];
560-
561-
format!(
562-
r#"<code{before}class="{classes}"{after}>"#,
563-
before = before,
564-
classes = classes,
565-
after = after
566-
)
567-
})
568-
.into_owned()
569-
}
570-
571-
fn add_playpen_pre(html: &str, playpen_config: &Playpen) -> String {
572-
let regex = Regex::new(r##"((?s)<code[^>]?class="([^"]+)".*?>(.*?)</code>)"##).unwrap();
573-
regex
574-
.replace_all(html, |caps: &Captures<'_>| {
575-
let text = &caps[1];
576-
let classes = &caps[2];
577-
let code = &caps[3];
578-
579-
if (classes.contains("language-rust")
580-
&& !classes.contains("ignore")
581-
&& !classes.contains("noplaypen"))
582-
|| classes.contains("mdbook-runnable")
583-
{
584-
// wrap the contents in an external pre block
585-
if playpen_config.editable && classes.contains("editable")
586-
|| text.contains("fn main")
587-
|| text.contains("quick_main!")
588-
{
589-
format!("<pre class=\"playpen\">{}</pre>", text)
590-
} else {
591-
// we need to inject our own main
592-
let (attrs, code) = partition_source(code);
593-
594-
format!(
595-
"<pre class=\"playpen\"><code class=\"{}\">\n# \
596-
#![allow(unused_variables)]\n{}#fn main() {{\n{}#}}</code></pre>",
597-
classes, attrs, code
598-
)
599-
}
600-
} else {
601-
// not language-rust, so no-op
602-
text.to_owned()
603-
}
604-
})
605-
.into_owned()
606-
}
607-
608-
fn partition_source(s: &str) -> (String, String) {
609-
let mut after_header = false;
610-
let mut before = String::new();
611-
let mut after = String::new();
612-
613-
for line in s.lines() {
614-
let trimline = line.trim();
615-
let header = trimline.chars().all(char::is_whitespace) || trimline.starts_with("#![");
616-
if !header || after_header {
617-
after_header = true;
618-
after.push_str(line);
619-
after.push_str("\n");
620-
} else {
621-
before.push_str(line);
622-
before.push_str("\n");
623-
}
624-
}
625-
626-
(before, after)
627-
}
628-
629472
struct RenderItemContext<'a> {
630-
handlebars: &'a Handlebars,
473+
handlebars: &'a HbsWrapper,
631474
destination: PathBuf,
632475
data: serde_json::Map<String, serde_json::Value>,
633476
is_index: bool,
634477
html_config: HtmlConfig,
635478
}
636-
637-
#[cfg(test)]
638-
mod tests {
639-
use super::*;
640-
641-
#[test]
642-
fn original_build_header_links() {
643-
let inputs = vec![
644-
(
645-
"blah blah <h1>Foo</h1>",
646-
r##"blah blah <h1><a class="header" href="#foo" id="foo">Foo</a></h1>"##,
647-
),
648-
(
649-
"<h1>Foo</h1>",
650-
r##"<h1><a class="header" href="#foo" id="foo">Foo</a></h1>"##,
651-
),
652-
(
653-
"<h3>Foo^bar</h3>",
654-
r##"<h3><a class="header" href="#foobar" id="foobar">Foo^bar</a></h3>"##,
655-
),
656-
(
657-
"<h4></h4>",
658-
r##"<h4><a class="header" href="#" id=""></a></h4>"##,
659-
),
660-
(
661-
"<h4><em>Hï</em></h4>",
662-
r##"<h4><a class="header" href="#hï" id="hï"><em>Hï</em></a></h4>"##,
663-
),
664-
(
665-
"<h1>Foo</h1><h3>Foo</h3>",
666-
r##"<h1><a class="header" href="#foo" id="foo">Foo</a></h1><h3><a class="header" href="#foo-1" id="foo-1">Foo</a></h3>"##,
667-
),
668-
];
669-
670-
for (src, should_be) in inputs {
671-
let got = build_header_links(&src);
672-
assert_eq!(got, should_be);
673-
}
674-
}
675-
}

0 commit comments

Comments
 (0)