2015-09-05 17:26:17 +02:00
|
|
|
use std::path::Path;
|
2017-06-03 14:06:09 +08:00
|
|
|
use std::collections::BTreeMap;
|
2015-07-31 18:34:43 +02:00
|
|
|
|
2016-11-03 01:58:42 +01:00
|
|
|
use serde_json;
|
2017-10-03 17:25:23 +05:45
|
|
|
use handlebars::{Context, Handlebars, Helper, RenderContext, RenderError, Renderable};
|
2015-07-31 18:34:43 +02:00
|
|
|
|
2018-07-11 23:33:44 +10:00
|
|
|
use utils;
|
|
|
|
|
|
2017-11-18 12:17:26 +01:00
|
|
|
type StringMap = BTreeMap<String, String>;
|
2015-07-31 18:34:43 +02:00
|
|
|
|
2017-11-18 12:17:26 +01:00
|
|
|
/// Target for `find_chapter`.
|
|
|
|
|
enum Target {
|
|
|
|
|
Previous,
|
|
|
|
|
Next,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Target {
|
|
|
|
|
/// Returns target if found.
|
2018-03-14 23:28:32 +08:00
|
|
|
fn find(
|
|
|
|
|
&self,
|
|
|
|
|
base_path: &String,
|
|
|
|
|
current_path: &String,
|
|
|
|
|
current_item: &StringMap,
|
|
|
|
|
previous_item: &StringMap,
|
|
|
|
|
) -> Result<Option<StringMap>, RenderError> {
|
2017-11-18 12:17:26 +01:00
|
|
|
match self {
|
|
|
|
|
&Target::Next => {
|
2018-03-14 23:28:32 +08:00
|
|
|
let previous_path = previous_item
|
|
|
|
|
.get("path")
|
|
|
|
|
.ok_or_else(|| RenderError::new("No path found for chapter in JSON data"))?;
|
2017-11-18 12:17:26 +01:00
|
|
|
|
|
|
|
|
if previous_path == base_path {
|
|
|
|
|
return Ok(Some(current_item.clone()));
|
|
|
|
|
}
|
2018-03-14 23:28:32 +08:00
|
|
|
}
|
2017-11-18 12:17:26 +01:00
|
|
|
|
|
|
|
|
&Target::Previous => {
|
|
|
|
|
if current_path == base_path {
|
|
|
|
|
return Ok(Some(previous_item.clone()));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(None)
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-07-31 18:34:43 +02:00
|
|
|
|
2018-03-14 23:28:32 +08:00
|
|
|
fn find_chapter(rc: &mut RenderContext, target: Target) -> Result<Option<StringMap>, RenderError> {
|
2018-01-23 01:28:37 +08:00
|
|
|
debug!("Get data from context");
|
2017-11-18 12:17:26 +01:00
|
|
|
|
2018-03-14 23:28:32 +08:00
|
|
|
let chapters = rc.evaluate_absolute("chapters", true).and_then(|c| {
|
2017-11-18 12:17:26 +01:00
|
|
|
serde_json::value::from_value::<Vec<StringMap>>(c.clone())
|
2017-10-03 17:25:23 +05:45
|
|
|
.map_err(|_| RenderError::new("Could not decode the JSON data"))
|
|
|
|
|
})?;
|
2017-06-03 14:06:09 +08:00
|
|
|
|
2018-03-14 23:28:32 +08:00
|
|
|
let base_path = rc.evaluate_absolute("path", true)?
|
|
|
|
|
.as_str()
|
|
|
|
|
.ok_or_else(|| RenderError::new("Type error for `path`, string expected"))?
|
|
|
|
|
.replace("\"", "");
|
2017-11-18 12:17:26 +01:00
|
|
|
|
|
|
|
|
let mut previous: Option<StringMap> = None;
|
2015-07-31 18:34:43 +02:00
|
|
|
|
2018-01-23 01:28:37 +08:00
|
|
|
debug!("Search for chapter");
|
2015-07-31 18:34:43 +02:00
|
|
|
|
2017-06-03 14:06:09 +08:00
|
|
|
for item in chapters {
|
2015-08-03 22:09:26 +02:00
|
|
|
match item.get("path") {
|
2015-09-16 22:46:23 -04:00
|
|
|
Some(path) if !path.is_empty() => {
|
2017-11-18 12:17:26 +01:00
|
|
|
if let Some(previous) = previous {
|
|
|
|
|
if let Some(item) = target.find(&base_path, &path, &item, &previous)? {
|
|
|
|
|
return Ok(Some(item));
|
2015-08-03 22:09:26 +02:00
|
|
|
}
|
|
|
|
|
}
|
2017-11-18 12:17:26 +01:00
|
|
|
|
|
|
|
|
previous = Some(item.clone());
|
2017-10-03 17:25:23 +05:45
|
|
|
}
|
2015-08-03 22:09:26 +02:00
|
|
|
_ => continue,
|
2015-07-31 18:34:43 +02:00
|
|
|
}
|
|
|
|
|
}
|
2015-08-11 22:55:51 +02:00
|
|
|
|
2018-03-14 23:28:32 +08:00
|
|
|
Ok(None)
|
2017-11-18 12:17:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn render(
|
|
|
|
|
_h: &Helper,
|
|
|
|
|
r: &Handlebars,
|
|
|
|
|
rc: &mut RenderContext,
|
|
|
|
|
chapter: &StringMap,
|
|
|
|
|
) -> Result<(), RenderError> {
|
2018-01-23 01:28:37 +08:00
|
|
|
trace!("Creating BTreeMap to inject in context");
|
2017-11-18 12:17:26 +01:00
|
|
|
|
|
|
|
|
let mut context = BTreeMap::new();
|
2018-07-11 23:33:44 +10:00
|
|
|
let base_path = rc.evaluate_absolute("path", false)?
|
|
|
|
|
.as_str()
|
|
|
|
|
.ok_or_else(|| RenderError::new("Type error for `path`, string expected"))?
|
|
|
|
|
.replace("\"", "");
|
|
|
|
|
|
|
|
|
|
context.insert("path_to_root".to_owned(),
|
|
|
|
|
json!(utils::fs::path_to_root(&base_path)));
|
2017-11-18 12:17:26 +01:00
|
|
|
|
2018-03-14 23:28:32 +08:00
|
|
|
chapter
|
|
|
|
|
.get("name")
|
|
|
|
|
.ok_or_else(|| RenderError::new("No title found for chapter in JSON data"))
|
|
|
|
|
.map(|name| context.insert("title".to_owned(), json!(name)))?;
|
|
|
|
|
|
|
|
|
|
chapter
|
|
|
|
|
.get("path")
|
|
|
|
|
.ok_or_else(|| RenderError::new("No path found for chapter in JSON data"))
|
|
|
|
|
.and_then(|p| {
|
|
|
|
|
Path::new(p)
|
|
|
|
|
.with_extension("html")
|
|
|
|
|
.to_str()
|
|
|
|
|
.ok_or_else(|| RenderError::new("Link could not be converted to str"))
|
|
|
|
|
.map(|p| context.insert("link".to_owned(), json!(p.replace("\\", "/"))))
|
|
|
|
|
})?;
|
2017-11-18 12:17:26 +01:00
|
|
|
|
2018-01-23 01:28:37 +08:00
|
|
|
trace!("Render template");
|
2017-11-18 12:17:26 +01:00
|
|
|
|
|
|
|
|
_h.template()
|
|
|
|
|
.ok_or_else(|| RenderError::new("Error with the handlebars template"))
|
|
|
|
|
.and_then(|t| {
|
|
|
|
|
let mut local_rc = rc.with_context(Context::wraps(&context)?);
|
|
|
|
|
t.render(r, &mut local_rc)
|
|
|
|
|
})?;
|
|
|
|
|
|
2015-07-31 18:34:43 +02:00
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-18 12:17:26 +01:00
|
|
|
pub fn previous(_h: &Helper, r: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> {
|
2018-01-23 01:28:37 +08:00
|
|
|
trace!("previous (handlebars helper)");
|
2015-07-31 18:34:43 +02:00
|
|
|
|
2017-11-18 12:17:26 +01:00
|
|
|
if let Some(previous) = find_chapter(rc, Target::Previous)? {
|
|
|
|
|
render(_h, r, rc, &previous)?;
|
|
|
|
|
}
|
2015-07-31 18:34:43 +02:00
|
|
|
|
2017-11-18 12:17:26 +01:00
|
|
|
Ok(())
|
|
|
|
|
}
|
2015-07-31 18:34:43 +02:00
|
|
|
|
2017-02-15 22:34:37 -05:00
|
|
|
pub fn next(_h: &Helper, r: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> {
|
2018-01-23 01:28:37 +08:00
|
|
|
trace!("next (handlebars helper)");
|
2015-08-04 12:51:07 +02:00
|
|
|
|
2017-11-18 12:17:26 +01:00
|
|
|
if let Some(next) = find_chapter(rc, Target::Next)? {
|
|
|
|
|
render(_h, r, rc, &next)?;
|
|
|
|
|
}
|
2015-07-31 18:34:43 +02:00
|
|
|
|
2017-11-18 12:17:26 +01:00
|
|
|
Ok(())
|
|
|
|
|
}
|
2015-08-04 12:51:07 +02:00
|
|
|
|
2017-11-18 12:17:26 +01:00
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
2018-03-14 23:28:32 +08:00
|
|
|
use super::*;
|
2017-11-18 12:17:26 +01:00
|
|
|
|
2018-03-14 23:28:32 +08:00
|
|
|
static TEMPLATE: &'static str =
|
|
|
|
|
"{{#previous}}{{title}}: {{link}}{{/previous}}|{{#next}}{{title}}: {{link}}{{/next}}";
|
2017-11-18 12:17:26 +01:00
|
|
|
|
2018-03-14 23:28:32 +08:00
|
|
|
#[test]
|
|
|
|
|
fn test_next_previous() {
|
|
|
|
|
let data = json!({
|
2017-11-18 12:17:26 +01:00
|
|
|
"name": "two",
|
|
|
|
|
"path": "two.path",
|
|
|
|
|
"chapters": [
|
|
|
|
|
{
|
|
|
|
|
"name": "one",
|
|
|
|
|
"path": "one.path"
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"name": "two",
|
|
|
|
|
"path": "two.path",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"name": "three",
|
|
|
|
|
"path": "three.path"
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
});
|
|
|
|
|
|
2018-03-14 23:28:32 +08:00
|
|
|
let mut h = Handlebars::new();
|
|
|
|
|
h.register_helper("previous", Box::new(previous));
|
|
|
|
|
h.register_helper("next", Box::new(next));
|
2017-11-18 12:17:26 +01:00
|
|
|
|
2018-03-14 23:28:32 +08:00
|
|
|
assert_eq!(
|
|
|
|
|
h.render_template(TEMPLATE, &data).unwrap(),
|
|
|
|
|
"one: one.html|three: three.html"
|
|
|
|
|
);
|
|
|
|
|
}
|
2017-11-18 12:17:26 +01:00
|
|
|
|
2018-03-14 23:28:32 +08:00
|
|
|
#[test]
|
|
|
|
|
fn test_first() {
|
|
|
|
|
let data = json!({
|
2017-11-18 12:17:26 +01:00
|
|
|
"name": "one",
|
|
|
|
|
"path": "one.path",
|
|
|
|
|
"chapters": [
|
|
|
|
|
{
|
|
|
|
|
"name": "one",
|
|
|
|
|
"path": "one.path"
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"name": "two",
|
|
|
|
|
"path": "two.path",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"name": "three",
|
|
|
|
|
"path": "three.path"
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
});
|
|
|
|
|
|
2018-03-14 23:28:32 +08:00
|
|
|
let mut h = Handlebars::new();
|
|
|
|
|
h.register_helper("previous", Box::new(previous));
|
|
|
|
|
h.register_helper("next", Box::new(next));
|
|
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
h.render_template(TEMPLATE, &data).unwrap(),
|
|
|
|
|
"|two: two.html"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_last() {
|
|
|
|
|
let data = json!({
|
2017-11-18 12:17:26 +01:00
|
|
|
"name": "three",
|
|
|
|
|
"path": "three.path",
|
|
|
|
|
"chapters": [
|
|
|
|
|
{
|
|
|
|
|
"name": "one",
|
|
|
|
|
"path": "one.path"
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"name": "two",
|
|
|
|
|
"path": "two.path",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"name": "three",
|
|
|
|
|
"path": "three.path"
|
2017-10-03 17:25:23 +05:45
|
|
|
}
|
2017-11-18 12:17:26 +01:00
|
|
|
]
|
|
|
|
|
});
|
2015-08-11 22:55:51 +02:00
|
|
|
|
2018-03-14 23:28:32 +08:00
|
|
|
let mut h = Handlebars::new();
|
|
|
|
|
h.register_helper("previous", Box::new(previous));
|
|
|
|
|
h.register_helper("next", Box::new(next));
|
2017-11-18 12:17:26 +01:00
|
|
|
|
2018-03-14 23:28:32 +08:00
|
|
|
assert_eq!(
|
|
|
|
|
h.render_template(TEMPLATE, &data).unwrap(),
|
|
|
|
|
"two: two.html|"
|
|
|
|
|
);
|
|
|
|
|
}
|
2015-07-31 18:34:43 +02:00
|
|
|
}
|