]> git.proxmox.com Git - rustc.git/blob - vendor/mdbook/src/renderer/html_handlebars/helpers/toc.rs
New upstream version 1.48.0~beta.8+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 crate::utils;
5
6 use handlebars::{Context, Handlebars, Helper, HelperDef, Output, RenderContext, RenderError};
7 use pulldown_cmark::{html, Event, Parser};
8
9 // Handlebars helper to construct TOC
10 #[derive(Clone, Copy)]
11 pub struct RenderToc {
12 pub no_section_label: bool,
13 }
14
15 impl HelperDef for RenderToc {
16 fn call<'reg: 'rc, 'rc>(
17 &self,
18 _h: &Helper<'reg, 'rc>,
19 _r: &'reg Handlebars<'_>,
20 ctx: &'rc Context,
21 rc: &mut RenderContext<'reg, 'rc>,
22 out: &mut dyn Output,
23 ) -> Result<(), RenderError> {
24 // get value from context data
25 // rc.get_path() is current json parent path, you should always use it like this
26 // param is the key of value you want to display
27 let chapters = rc.evaluate(ctx, "@root/chapters").and_then(|c| {
28 serde_json::value::from_value::<Vec<BTreeMap<String, String>>>(c.as_json().clone())
29 .map_err(|_| RenderError::new("Could not decode the JSON data"))
30 })?;
31 let current_path = rc
32 .evaluate(ctx, "@root/path")?
33 .as_json()
34 .as_str()
35 .ok_or_else(|| RenderError::new("Type error for `path`, string expected"))?
36 .replace("\"", "");
37
38 let current_section = rc
39 .evaluate(ctx, "@root/section")?
40 .as_json()
41 .as_str()
42 .map(str::to_owned)
43 .unwrap_or_default();
44
45 let fold_enable = rc
46 .evaluate(ctx, "@root/fold_enable")?
47 .as_json()
48 .as_bool()
49 .ok_or_else(|| RenderError::new("Type error for `fold_enable`, bool expected"))?;
50
51 let fold_level = rc
52 .evaluate(ctx, "@root/fold_level")?
53 .as_json()
54 .as_u64()
55 .ok_or_else(|| RenderError::new("Type error for `fold_level`, u64 expected"))?;
56
57 out.write("<ol class=\"chapter\">")?;
58
59 let mut current_level = 1;
60
61 for item in chapters {
62 // Spacer
63 if item.get("spacer").is_some() {
64 out.write("<li class=\"spacer\"></li>")?;
65 continue;
66 }
67
68 let (section, level) = if let Some(s) = item.get("section") {
69 (s.as_str(), s.matches('.').count())
70 } else {
71 ("", 1)
72 };
73
74 let is_expanded =
75 if !fold_enable || (!section.is_empty() && current_section.starts_with(section)) {
76 // Expand if folding is disabled, or if the section is an
77 // ancestor or the current section itself.
78 true
79 } else {
80 // Levels that are larger than this would be folded.
81 level - 1 < fold_level as usize
82 };
83
84 if level > current_level {
85 while level > current_level {
86 out.write("<li>")?;
87 out.write("<ol class=\"section\">")?;
88 current_level += 1;
89 }
90 write_li_open_tag(out, is_expanded, false)?;
91 } else if level < current_level {
92 while level < current_level {
93 out.write("</ol>")?;
94 out.write("</li>")?;
95 current_level -= 1;
96 }
97 write_li_open_tag(out, is_expanded, false)?;
98 } else {
99 write_li_open_tag(out, is_expanded, item.get("section").is_none())?;
100 }
101
102 // Part title
103 if let Some(title) = item.get("part") {
104 out.write("<li class=\"part-title\">")?;
105 out.write(title)?;
106 out.write("</li>")?;
107 continue;
108 }
109
110 // Link
111 let path_exists = if let Some(path) =
112 item.get("path")
113 .and_then(|p| if p.is_empty() { None } else { Some(p) })
114 {
115 out.write("<a href=\"")?;
116
117 let tmp = Path::new(item.get("path").expect("Error: path should be Some(_)"))
118 .with_extension("html")
119 .to_str()
120 .unwrap()
121 // Hack for windows who tends to use `\` as separator instead of `/`
122 .replace("\\", "/");
123
124 // Add link
125 out.write(&utils::fs::path_to_root(&current_path))?;
126 out.write(&tmp)?;
127 out.write("\"")?;
128
129 if path == &current_path {
130 out.write(" class=\"active\"")?;
131 }
132
133 out.write(">")?;
134 true
135 } else {
136 out.write("<div>")?;
137 false
138 };
139
140 if !self.no_section_label {
141 // Section does not necessarily exist
142 if let Some(section) = item.get("section") {
143 out.write("<strong aria-hidden=\"true\">")?;
144 out.write(&section)?;
145 out.write("</strong> ")?;
146 }
147 }
148
149 if let Some(name) = item.get("name") {
150 // Render only inline code blocks
151
152 // filter all events that are not inline code blocks
153 let parser = Parser::new(name).filter(|event| match *event {
154 Event::Code(_) | Event::Html(_) | Event::Text(_) => true,
155 _ => false,
156 });
157
158 // render markdown to html
159 let mut markdown_parsed_name = String::with_capacity(name.len() * 3 / 2);
160 html::push_html(&mut markdown_parsed_name, parser);
161
162 // write to the handlebars template
163 out.write(&markdown_parsed_name)?;
164 }
165
166 if path_exists {
167 out.write("</a>")?;
168 } else {
169 out.write("</div>")?;
170 }
171
172 // Render expand/collapse toggle
173 if let Some(flag) = item.get("has_sub_items") {
174 let has_sub_items = flag.parse::<bool>().unwrap_or_default();
175 if fold_enable && has_sub_items {
176 out.write("<a class=\"toggle\"><div>❱</div></a>")?;
177 }
178 }
179 out.write("</li>")?;
180 }
181 while current_level > 1 {
182 out.write("</ol>")?;
183 out.write("</li>")?;
184 current_level -= 1;
185 }
186
187 out.write("</ol>")?;
188 Ok(())
189 }
190 }
191
192 fn write_li_open_tag(
193 out: &mut dyn Output,
194 is_expanded: bool,
195 is_affix: bool,
196 ) -> Result<(), std::io::Error> {
197 let mut li = String::from("<li class=\"chapter-item ");
198 if is_expanded {
199 li.push_str("expanded ");
200 }
201 if is_affix {
202 li.push_str("affix ");
203 }
204 li.push_str("\">");
205 out.write(&li)
206 }