]> git.proxmox.com Git - rustc.git/blob - vendor/mdbook/src/renderer/html_handlebars/helpers/toc.rs
New upstream version 1.34.2+dfsg1
[rustc.git] / vendor / mdbook / src / renderer / html_handlebars / helpers / toc.rs
1 use std::collections::BTreeMap;
2 use std::path::Path;
3
4 use utils;
5
6 use handlebars::{Context, Handlebars, Helper, HelperDef, Output, RenderContext, RenderError};
7 use pulldown_cmark::{html, Event, Parser, Tag};
8 use serde_json;
9
10 // Handlebars helper to construct TOC
11 #[derive(Clone, Copy)]
12 pub struct RenderToc {
13 pub no_section_label: bool,
14 }
15
16 impl HelperDef for RenderToc {
17 fn call<'reg: 'rc, 'rc>(
18 &self,
19 _h: &Helper,
20 _: &Handlebars,
21 ctx: &Context,
22 rc: &mut RenderContext,
23 out: &mut Output,
24 ) -> Result<(), RenderError> {
25 // get value from context data
26 // rc.get_path() is current json parent path, you should always use it like this
27 // param is the key of value you want to display
28 let chapters = rc.evaluate_absolute(ctx, "chapters", true).and_then(|c| {
29 serde_json::value::from_value::<Vec<BTreeMap<String, String>>>(c.clone())
30 .map_err(|_| RenderError::new("Could not decode the JSON data"))
31 })?;
32 let current = rc
33 .evaluate_absolute(ctx, "path", true)?
34 .as_str()
35 .ok_or_else(|| RenderError::new("Type error for `path`, string expected"))?
36 .replace("\"", "");
37
38 out.write("<ol class=\"chapter\">")?;
39
40 let mut current_level = 1;
41
42 for item in chapters {
43 // Spacer
44 if item.get("spacer").is_some() {
45 out.write("<li class=\"spacer\"></li>")?;
46 continue;
47 }
48
49 let level = if let Some(s) = item.get("section") {
50 s.matches('.').count()
51 } else {
52 1
53 };
54
55 if level > current_level {
56 while level > current_level {
57 out.write("<li>")?;
58 out.write("<ol class=\"section\">")?;
59 current_level += 1;
60 }
61 out.write("<li>")?;
62 } else if level < current_level {
63 while level < current_level {
64 out.write("</ol>")?;
65 out.write("</li>")?;
66 current_level -= 1;
67 }
68 out.write("<li>")?;
69 } else {
70 out.write("<li")?;
71 if item.get("section").is_none() {
72 out.write(" class=\"affix\"")?;
73 }
74 out.write(">")?;
75 }
76
77 // Link
78 let path_exists = if let Some(path) = item.get("path") {
79 if !path.is_empty() {
80 out.write("<a href=\"")?;
81
82 let tmp = Path::new(item.get("path").expect("Error: path should be Some(_)"))
83 .with_extension("html")
84 .to_str()
85 .unwrap()
86 // Hack for windows who tends to use `\` as separator instead of `/`
87 .replace("\\", "/");
88
89 // Add link
90 out.write(&utils::fs::path_to_root(&current))?;
91 out.write(&tmp)?;
92 out.write("\"")?;
93
94 if path == &current {
95 out.write(" class=\"active\"")?;
96 }
97
98 out.write(">")?;
99 true
100 } else {
101 false
102 }
103 } else {
104 false
105 };
106
107 if !self.no_section_label {
108 // Section does not necessarily exist
109 if let Some(section) = item.get("section") {
110 out.write("<strong aria-hidden=\"true\">")?;
111 out.write(&section)?;
112 out.write("</strong> ")?;
113 }
114 }
115
116 if let Some(name) = item.get("name") {
117 // Render only inline code blocks
118
119 // filter all events that are not inline code blocks
120 let parser = Parser::new(name).filter(|event| match *event {
121 Event::Start(Tag::Code)
122 | Event::End(Tag::Code)
123 | Event::InlineHtml(_)
124 | Event::Text(_) => true,
125 _ => false,
126 });
127
128 // render markdown to html
129 let mut markdown_parsed_name = String::with_capacity(name.len() * 3 / 2);
130 html::push_html(&mut markdown_parsed_name, parser);
131
132 // write to the handlebars template
133 out.write(&markdown_parsed_name)?;
134 }
135
136 if path_exists {
137 out.write("</a>")?;
138 }
139
140 out.write("</li>")?;
141 }
142 while current_level > 1 {
143 out.write("</ol>")?;
144 out.write("</li>")?;
145 current_level -= 1;
146 }
147
148 out.write("</ol>")?;
149 Ok(())
150 }
151 }