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