]> git.proxmox.com Git - rustc.git/blame - vendor/mdbook/src/renderer/html_handlebars/helpers/navigation.rs
New upstream version 1.68.2+dfsg1
[rustc.git] / vendor / mdbook / src / renderer / html_handlebars / helpers / navigation.rs
CommitLineData
ea8adc8c 1use std::collections::BTreeMap;
9fa01778 2use std::path::Path;
7cac9316 3
9fa01778 4use handlebars::{Context, Handlebars, Helper, Output, RenderContext, RenderError, Renderable};
9fa01778 5
dc9dc135 6use crate::utils;
9c376795
FG
7use log::{debug, trace};
8use serde_json::json;
7cac9316 9
2c00a5a8 10type StringMap = BTreeMap<String, String>;
7cac9316 11
2c00a5a8
XL
12/// Target for `find_chapter`.
13enum Target {
14 Previous,
15 Next,
16}
17
18impl Target {
19 /// Returns target if found.
83c7162d
XL
20 fn find(
21 &self,
9fa01778
XL
22 base_path: &str,
23 current_path: &str,
83c7162d
XL
24 current_item: &StringMap,
25 previous_item: &StringMap,
26 ) -> Result<Option<StringMap>, RenderError> {
9fa01778
XL
27 match *self {
28 Target::Next => {
83c7162d
XL
29 let previous_path = previous_item
30 .get("path")
31 .ok_or_else(|| RenderError::new("No path found for chapter in JSON data"))?;
2c00a5a8
XL
32
33 if previous_path == base_path {
34 return Ok(Some(current_item.clone()));
35 }
83c7162d 36 }
2c00a5a8 37
9fa01778 38 Target::Previous => {
2c00a5a8
XL
39 if current_path == base_path {
40 return Ok(Some(previous_item.clone()));
41 }
42 }
43 }
44
45 Ok(None)
46 }
47}
7cac9316 48
9fa01778
XL
49fn find_chapter(
50 ctx: &Context,
f9f354fc 51 rc: &mut RenderContext<'_, '_>,
9fa01778
XL
52 target: Target,
53) -> Result<Option<StringMap>, RenderError> {
2c00a5a8 54 debug!("Get data from context");
ea8adc8c 55
416331ca
XL
56 let chapters = rc.evaluate(ctx, "@root/chapters").and_then(|c| {
57 serde_json::value::from_value::<Vec<StringMap>>(c.as_json().clone())
2c00a5a8
XL
58 .map_err(|_| RenderError::new("Could not decode the JSON data"))
59 })?;
7cac9316 60
9fa01778 61 let base_path = rc
416331ca
XL
62 .evaluate(ctx, "@root/path")?
63 .as_json()
83c7162d
XL
64 .as_str()
65 .ok_or_else(|| RenderError::new("Type error for `path`, string expected"))?
064997fb 66 .replace('\"', "");
7cac9316 67
e74abb32
XL
68 if !rc.evaluate(ctx, "@root/is_index")?.is_missing() {
69 // Special case for index.md which may be a synthetic page.
70 // Target::find won't match because there is no page with the path
71 // "index.md" (unless there really is an index.md in SUMMARY.md).
72 match target {
73 Target::Previous => return Ok(None),
74 Target::Next => match chapters
75 .iter()
76 .filter(|chapter| {
77 // Skip things like "spacer"
78 chapter.contains_key("path")
79 })
f035d41b 80 .nth(1)
e74abb32
XL
81 {
82 Some(chapter) => return Ok(Some(chapter.clone())),
83 None => return Ok(None),
84 },
85 }
86 }
87
2c00a5a8 88 let mut previous: Option<StringMap> = None;
7cac9316 89
2c00a5a8
XL
90 debug!("Search for chapter");
91
92 for item in chapters {
7cac9316
XL
93 match item.get("path") {
94 Some(path) if !path.is_empty() => {
2c00a5a8 95 if let Some(previous) = previous {
a2a8927a 96 if let Some(item) = target.find(&base_path, path, &item, &previous)? {
2c00a5a8 97 return Ok(Some(item));
7cac9316 98 }
7cac9316 99 }
7cac9316 100
2c00a5a8
XL
101 previous = Some(item.clone());
102 }
103 _ => continue,
7cac9316 104 }
7cac9316
XL
105 }
106
83c7162d 107 Ok(None)
7cac9316
XL
108}
109
2c00a5a8 110fn render(
dc9dc135 111 _h: &Helper<'_, '_>,
f9f354fc 112 r: &Handlebars<'_>,
9fa01778 113 ctx: &Context,
f9f354fc 114 rc: &mut RenderContext<'_, '_>,
dc9dc135 115 out: &mut dyn Output,
2c00a5a8
XL
116 chapter: &StringMap,
117) -> Result<(), RenderError> {
118 trace!("Creating BTreeMap to inject in context");
119
120 let mut context = BTreeMap::new();
9fa01778 121 let base_path = rc
416331ca
XL
122 .evaluate(ctx, "@root/path")?
123 .as_json()
9fa01778
XL
124 .as_str()
125 .ok_or_else(|| RenderError::new("Type error for `path`, string expected"))?
064997fb 126 .replace('\"', "");
9fa01778
XL
127
128 context.insert(
129 "path_to_root".to_owned(),
130 json!(utils::fs::path_to_root(&base_path)),
131 );
2c00a5a8 132
83c7162d
XL
133 chapter
134 .get("name")
135 .ok_or_else(|| RenderError::new("No title found for chapter in JSON data"))
136 .map(|name| context.insert("title".to_owned(), json!(name)))?;
137
138 chapter
139 .get("path")
140 .ok_or_else(|| RenderError::new("No path found for chapter in JSON data"))
141 .and_then(|p| {
142 Path::new(p)
143 .with_extension("html")
144 .to_str()
145 .ok_or_else(|| RenderError::new("Link could not be converted to str"))
064997fb 146 .map(|p| context.insert("link".to_owned(), json!(p.replace('\\', "/"))))
83c7162d 147 })?;
2c00a5a8
XL
148
149 trace!("Render template");
150
9c376795
FG
151 let t = _h
152 .template()
153 .ok_or_else(|| RenderError::new("Error with the handlebars template"))?;
154 let local_ctx = Context::wraps(&context)?;
155 let mut local_rc = rc.clone();
156 t.render(r, &local_ctx, &mut local_rc, out)
2c00a5a8 157}
7cac9316 158
9fa01778 159pub fn previous(
dc9dc135 160 _h: &Helper<'_, '_>,
f9f354fc 161 r: &Handlebars<'_>,
9fa01778 162 ctx: &Context,
f9f354fc 163 rc: &mut RenderContext<'_, '_>,
dc9dc135 164 out: &mut dyn Output,
9fa01778 165) -> Result<(), RenderError> {
2c00a5a8 166 trace!("previous (handlebars helper)");
7cac9316 167
9fa01778
XL
168 if let Some(previous) = find_chapter(ctx, rc, Target::Previous)? {
169 render(_h, r, ctx, rc, out, &previous)?;
2c00a5a8 170 }
7cac9316 171
2c00a5a8
XL
172 Ok(())
173}
7cac9316 174
9fa01778 175pub fn next(
dc9dc135 176 _h: &Helper<'_, '_>,
f9f354fc 177 r: &Handlebars<'_>,
9fa01778 178 ctx: &Context,
f9f354fc 179 rc: &mut RenderContext<'_, '_>,
dc9dc135 180 out: &mut dyn Output,
9fa01778 181) -> Result<(), RenderError> {
2c00a5a8 182 trace!("next (handlebars helper)");
7cac9316 183
9fa01778
XL
184 if let Some(next) = find_chapter(ctx, rc, Target::Next)? {
185 render(_h, r, ctx, rc, out, &next)?;
2c00a5a8 186 }
7cac9316 187
2c00a5a8
XL
188 Ok(())
189}
7cac9316 190
2c00a5a8
XL
191#[cfg(test)]
192mod tests {
83c7162d 193 use super::*;
2c00a5a8 194
dc9dc135 195 static TEMPLATE: &str =
83c7162d 196 "{{#previous}}{{title}}: {{link}}{{/previous}}|{{#next}}{{title}}: {{link}}{{/next}}";
2c00a5a8 197
83c7162d
XL
198 #[test]
199 fn test_next_previous() {
200 let data = json!({
dc9dc135
XL
201 "name": "two",
202 "path": "two.path",
203 "chapters": [
204 {
205 "name": "one",
206 "path": "one.path"
207 },
208 {
209 "name": "two",
210 "path": "two.path",
211 },
212 {
213 "name": "three",
214 "path": "three.path"
215 }
216 ]
217 });
2c00a5a8 218
83c7162d
XL
219 let mut h = Handlebars::new();
220 h.register_helper("previous", Box::new(previous));
221 h.register_helper("next", Box::new(next));
2c00a5a8 222
83c7162d
XL
223 assert_eq!(
224 h.render_template(TEMPLATE, &data).unwrap(),
225 "one: one.html|three: three.html"
226 );
227 }
2c00a5a8 228
83c7162d
XL
229 #[test]
230 fn test_first() {
231 let data = json!({
dc9dc135
XL
232 "name": "one",
233 "path": "one.path",
234 "chapters": [
235 {
236 "name": "one",
237 "path": "one.path"
238 },
239 {
240 "name": "two",
241 "path": "two.path",
242 },
243 {
244 "name": "three",
245 "path": "three.path"
246 }
247 ]
248 });
2c00a5a8 249
83c7162d
XL
250 let mut h = Handlebars::new();
251 h.register_helper("previous", Box::new(previous));
252 h.register_helper("next", Box::new(next));
253
254 assert_eq!(
255 h.render_template(TEMPLATE, &data).unwrap(),
256 "|two: two.html"
257 );
258 }
259 #[test]
260 fn test_last() {
261 let data = json!({
dc9dc135
XL
262 "name": "three",
263 "path": "three.path",
264 "chapters": [
265 {
266 "name": "one",
267 "path": "one.path"
268 },
269 {
270 "name": "two",
271 "path": "two.path",
272 },
273 {
274 "name": "three",
275 "path": "three.path"
276 }
277 ]
278 });
2c00a5a8 279
83c7162d
XL
280 let mut h = Handlebars::new();
281 h.register_helper("previous", Box::new(previous));
282 h.register_helper("next", Box::new(next));
2c00a5a8 283
83c7162d
XL
284 assert_eq!(
285 h.render_template(TEMPLATE, &data).unwrap(),
286 "two: two.html|"
287 );
288 }
7cac9316 289}