]> git.proxmox.com Git - rustc.git/blame - vendor/mdbook/src/renderer/html_handlebars/helpers/toc.rs
New upstream version 1.62.1+dfsg1
[rustc.git] / vendor / mdbook / src / renderer / html_handlebars / helpers / toc.rs
CommitLineData
ea8adc8c 1use std::collections::BTreeMap;
9fa01778 2use std::path::Path;
cc61c64b 3
dc9dc135 4use crate::utils;
04454e1e 5use crate::utils::bracket_escape;
9fa01778
XL
6
7use handlebars::{Context, Handlebars, Helper, HelperDef, Output, RenderContext, RenderError};
cc61c64b
XL
8
9// Handlebars helper to construct TOC
10#[derive(Clone, Copy)]
2c00a5a8 11pub struct RenderToc {
83c7162d 12 pub no_section_label: bool,
2c00a5a8 13}
cc61c64b
XL
14
15impl HelperDef for RenderToc {
9fa01778
XL
16 fn call<'reg: 'rc, 'rc>(
17 &self,
dc9dc135 18 _h: &Helper<'reg, 'rc>,
f9f354fc 19 _r: &'reg Handlebars<'_>,
dc9dc135 20 ctx: &'rc Context,
f9f354fc 21 rc: &mut RenderContext<'reg, 'rc>,
dc9dc135 22 out: &mut dyn Output,
9fa01778 23 ) -> Result<(), RenderError> {
cc61c64b
XL
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
416331ca
XL
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())
2c00a5a8
XL
29 .map_err(|_| RenderError::new("Could not decode the JSON data"))
30 })?;
e74abb32 31 let current_path = rc
416331ca
XL
32 .evaluate(ctx, "@root/path")?
33 .as_json()
83c7162d 34 .as_str()
f035d41b 35 .ok_or_else(|| RenderError::new("Type error for `path`, string expected"))?
83c7162d 36 .replace("\"", "");
cc61c64b 37
e74abb32
XL
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()
f035d41b 49 .ok_or_else(|| RenderError::new("Type error for `fold_enable`, bool expected"))?;
e74abb32
XL
50
51 let fold_level = rc
52 .evaluate(ctx, "@root/fold_level")?
53 .as_json()
54 .as_u64()
f035d41b 55 .ok_or_else(|| RenderError::new("Type error for `fold_level`, u64 expected"))?;
e74abb32 56
9fa01778 57 out.write("<ol class=\"chapter\">")?;
cc61c64b
XL
58
59 let mut current_level = 1;
60
ea8adc8c 61 for item in chapters {
cc61c64b
XL
62 // Spacer
63 if item.get("spacer").is_some() {
9fa01778 64 out.write("<li class=\"spacer\"></li>")?;
cc61c64b
XL
65 continue;
66 }
67
e74abb32
XL
68 let (section, level) = if let Some(s) = item.get("section") {
69 (s.as_str(), s.matches('.').count())
cc61c64b 70 } else {
e74abb32
XL
71 ("", 1)
72 };
73
f035d41b
XL
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.
e74abb32
XL
78 true
79 } else {
80 // Levels that are larger than this would be folded.
81 level - 1 < fold_level as usize
f035d41b 82 };
cc61c64b
XL
83
84 if level > current_level {
85 while level > current_level {
9fa01778
XL
86 out.write("<li>")?;
87 out.write("<ol class=\"section\">")?;
cc61c64b
XL
88 current_level += 1;
89 }
e74abb32 90 write_li_open_tag(out, is_expanded, false)?;
cc61c64b
XL
91 } else if level < current_level {
92 while level < current_level {
9fa01778
XL
93 out.write("</ol>")?;
94 out.write("</li>")?;
cc61c64b
XL
95 current_level -= 1;
96 }
e74abb32 97 write_li_open_tag(out, is_expanded, false)?;
cc61c64b 98 } else {
e74abb32 99 write_li_open_tag(out, is_expanded, item.get("section").is_none())?;
cc61c64b 100 }
f035d41b
XL
101
102 // Part title
103 if let Some(title) = item.get("part") {
3dfed10e 104 out.write("<li class=\"part-title\">")?;
04454e1e 105 out.write(&bracket_escape(title))?;
f035d41b
XL
106 out.write("</li>")?;
107 continue;
108 }
cc61c64b
XL
109
110 // Link
1b1a35ee
XL
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\"")?;
cc61c64b 131 }
1b1a35ee
XL
132
133 out.write(">")?;
134 true
cc61c64b 135 } else {
1b1a35ee 136 out.write("<div>")?;
cc61c64b
XL
137 false
138 };
139
2c00a5a8
XL
140 if !self.no_section_label {
141 // Section does not necessarily exist
142 if let Some(section) = item.get("section") {
9fa01778 143 out.write("<strong aria-hidden=\"true\">")?;
a2a8927a 144 out.write(section)?;
9fa01778 145 out.write("</strong> ")?;
2c00a5a8 146 }
cc61c64b
XL
147 }
148
149 if let Some(name) = item.get("name") {
04454e1e 150 out.write(&bracket_escape(name))?
cc61c64b
XL
151 }
152
153 if path_exists {
9fa01778 154 out.write("</a>")?;
1b1a35ee
XL
155 } else {
156 out.write("</div>")?;
cc61c64b
XL
157 }
158
e74abb32
XL
159 // Render expand/collapse toggle
160 if let Some(flag) = item.get("has_sub_items") {
161 let has_sub_items = flag.parse::<bool>().unwrap_or_default();
162 if fold_enable && has_sub_items {
163 out.write("<a class=\"toggle\"><div>❱</div></a>")?;
164 }
165 }
9fa01778 166 out.write("</li>")?;
cc61c64b
XL
167 }
168 while current_level > 1 {
9fa01778
XL
169 out.write("</ol>")?;
170 out.write("</li>")?;
cc61c64b
XL
171 current_level -= 1;
172 }
173
9fa01778 174 out.write("</ol>")?;
cc61c64b
XL
175 Ok(())
176 }
177}
e74abb32
XL
178
179fn write_li_open_tag(
180 out: &mut dyn Output,
181 is_expanded: bool,
182 is_affix: bool,
183) -> Result<(), std::io::Error> {
f9f354fc 184 let mut li = String::from("<li class=\"chapter-item ");
e74abb32
XL
185 if is_expanded {
186 li.push_str("expanded ");
187 }
188 if is_affix {
189 li.push_str("affix ");
190 }
191 li.push_str("\">");
192 out.write(&li)
193}