]> git.proxmox.com Git - rustc.git/blame - vendor/mdbook/src/renderer/html_handlebars/helpers/navigation.rs
New upstream version 1.64.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"))?
064997fb 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 })
f035d41b 78 .nth(1)
e74abb32
XL
79 {
80 Some(chapter) => return Ok(Some(chapter.clone())),
81 None => return Ok(None),
82 },
83 }
84 }
85
2c00a5a8 86 let mut previous: Option<StringMap> = None;
7cac9316 87
2c00a5a8
XL
88 debug!("Search for chapter");
89
90 for item in chapters {
7cac9316
XL
91 match item.get("path") {
92 Some(path) if !path.is_empty() => {
2c00a5a8 93 if let Some(previous) = previous {
a2a8927a 94 if let Some(item) = target.find(&base_path, path, &item, &previous)? {
2c00a5a8 95 return Ok(Some(item));
7cac9316 96 }
7cac9316 97 }
7cac9316 98
2c00a5a8
XL
99 previous = Some(item.clone());
100 }
101 _ => continue,
7cac9316 102 }
7cac9316
XL
103 }
104
83c7162d 105 Ok(None)
7cac9316
XL
106}
107
2c00a5a8 108fn render(
dc9dc135 109 _h: &Helper<'_, '_>,
f9f354fc 110 r: &Handlebars<'_>,
9fa01778 111 ctx: &Context,
f9f354fc 112 rc: &mut RenderContext<'_, '_>,
dc9dc135 113 out: &mut dyn Output,
2c00a5a8
XL
114 chapter: &StringMap,
115) -> Result<(), RenderError> {
116 trace!("Creating BTreeMap to inject in context");
117
118 let mut context = BTreeMap::new();
9fa01778 119 let base_path = rc
416331ca
XL
120 .evaluate(ctx, "@root/path")?
121 .as_json()
9fa01778
XL
122 .as_str()
123 .ok_or_else(|| RenderError::new("Type error for `path`, string expected"))?
064997fb 124 .replace('\"', "");
9fa01778
XL
125
126 context.insert(
127 "path_to_root".to_owned(),
128 json!(utils::fs::path_to_root(&base_path)),
129 );
2c00a5a8 130
83c7162d
XL
131 chapter
132 .get("name")
133 .ok_or_else(|| RenderError::new("No title found for chapter in JSON data"))
134 .map(|name| context.insert("title".to_owned(), json!(name)))?;
135
136 chapter
137 .get("path")
138 .ok_or_else(|| RenderError::new("No path found for chapter in JSON data"))
139 .and_then(|p| {
140 Path::new(p)
141 .with_extension("html")
142 .to_str()
143 .ok_or_else(|| RenderError::new("Link could not be converted to str"))
064997fb 144 .map(|p| context.insert("link".to_owned(), json!(p.replace('\\', "/"))))
83c7162d 145 })?;
2c00a5a8
XL
146
147 trace!("Render template");
148
149 _h.template()
150 .ok_or_else(|| RenderError::new("Error with the handlebars template"))
151 .and_then(|t| {
9fa01778 152 let local_ctx = Context::wraps(&context)?;
064997fb 153 let mut local_rc = rc.clone();
9fa01778 154 t.render(r, &local_ctx, &mut local_rc, out)
2c00a5a8 155 })?;
7cac9316 156
2c00a5a8
XL
157 Ok(())
158}
7cac9316 159
9fa01778 160pub fn previous(
dc9dc135 161 _h: &Helper<'_, '_>,
f9f354fc 162 r: &Handlebars<'_>,
9fa01778 163 ctx: &Context,
f9f354fc 164 rc: &mut RenderContext<'_, '_>,
dc9dc135 165 out: &mut dyn Output,
9fa01778 166) -> Result<(), RenderError> {
2c00a5a8 167 trace!("previous (handlebars helper)");
7cac9316 168
9fa01778
XL
169 if let Some(previous) = find_chapter(ctx, rc, Target::Previous)? {
170 render(_h, r, ctx, rc, out, &previous)?;
2c00a5a8 171 }
7cac9316 172
2c00a5a8
XL
173 Ok(())
174}
7cac9316 175
9fa01778 176pub fn next(
dc9dc135 177 _h: &Helper<'_, '_>,
f9f354fc 178 r: &Handlebars<'_>,
9fa01778 179 ctx: &Context,
f9f354fc 180 rc: &mut RenderContext<'_, '_>,
dc9dc135 181 out: &mut dyn Output,
9fa01778 182) -> Result<(), RenderError> {
2c00a5a8 183 trace!("next (handlebars helper)");
7cac9316 184
9fa01778
XL
185 if let Some(next) = find_chapter(ctx, rc, Target::Next)? {
186 render(_h, r, ctx, rc, out, &next)?;
2c00a5a8 187 }
7cac9316 188
2c00a5a8
XL
189 Ok(())
190}
7cac9316 191
2c00a5a8
XL
192#[cfg(test)]
193mod tests {
83c7162d 194 use super::*;
2c00a5a8 195
dc9dc135 196 static TEMPLATE: &str =
83c7162d 197 "{{#previous}}{{title}}: {{link}}{{/previous}}|{{#next}}{{title}}: {{link}}{{/next}}";
2c00a5a8 198
83c7162d
XL
199 #[test]
200 fn test_next_previous() {
201 let data = json!({
dc9dc135
XL
202 "name": "two",
203 "path": "two.path",
204 "chapters": [
205 {
206 "name": "one",
207 "path": "one.path"
208 },
209 {
210 "name": "two",
211 "path": "two.path",
212 },
213 {
214 "name": "three",
215 "path": "three.path"
216 }
217 ]
218 });
2c00a5a8 219
83c7162d
XL
220 let mut h = Handlebars::new();
221 h.register_helper("previous", Box::new(previous));
222 h.register_helper("next", Box::new(next));
2c00a5a8 223
83c7162d
XL
224 assert_eq!(
225 h.render_template(TEMPLATE, &data).unwrap(),
226 "one: one.html|three: three.html"
227 );
228 }
2c00a5a8 229
83c7162d
XL
230 #[test]
231 fn test_first() {
232 let data = json!({
dc9dc135
XL
233 "name": "one",
234 "path": "one.path",
235 "chapters": [
236 {
237 "name": "one",
238 "path": "one.path"
239 },
240 {
241 "name": "two",
242 "path": "two.path",
243 },
244 {
245 "name": "three",
246 "path": "three.path"
247 }
248 ]
249 });
2c00a5a8 250
83c7162d
XL
251 let mut h = Handlebars::new();
252 h.register_helper("previous", Box::new(previous));
253 h.register_helper("next", Box::new(next));
254
255 assert_eq!(
256 h.render_template(TEMPLATE, &data).unwrap(),
257 "|two: two.html"
258 );
259 }
260 #[test]
261 fn test_last() {
262 let data = json!({
dc9dc135
XL
263 "name": "three",
264 "path": "three.path",
265 "chapters": [
266 {
267 "name": "one",
268 "path": "one.path"
269 },
270 {
271 "name": "two",
272 "path": "two.path",
273 },
274 {
275 "name": "three",
276 "path": "three.path"
277 }
278 ]
279 });
2c00a5a8 280
83c7162d
XL
281 let mut h = Handlebars::new();
282 h.register_helper("previous", Box::new(previous));
283 h.register_helper("next", Box::new(next));
2c00a5a8 284
83c7162d
XL
285 assert_eq!(
286 h.render_template(TEMPLATE, &data).unwrap(),
287 "two: two.html|"
288 );
289 }
7cac9316 290}