use crate::doc::DocMeta;
use crate::doc_index::DocIndex;
use crate::permalink;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use tera::{Filter, Tera, Value};
pub type DocUrlLookup = Arc<dyn Fn(&str) -> Option<String> + Send + Sync>;
pub fn lookup_from_metas(docs: Arc<Vec<DocMeta>>) -> DocUrlLookup {
Arc::new(move |id_path: &str| {
let target = PathBuf::from(id_path);
docs.iter()
.find(|d| d.id_path == target)
.map(|d| permalink::to_url(&d.output_path))
})
}
pub fn lookup_from_index(index: Arc<DocIndex>) -> DocUrlLookup {
Arc::new(move |id_path: &str| index.output_path(Path::new(id_path)).map(permalink::to_url))
}
struct PermalinkFilter {
lookup: DocUrlLookup,
site_url: Option<String>,
base_path: String,
}
impl Filter for PermalinkFilter {
fn filter(&self, value: &Value, _args: &HashMap<String, Value>) -> tera::Result<Value> {
let url = lookup_doc_url(&self.lookup, value, "permalink")?;
let out = format!(
"{}{}{}",
self.site_url.as_deref().unwrap_or(""),
self.base_path,
url
);
Ok(Value::String(out))
}
fn is_safe(&self) -> bool {
true
}
}
struct LinkFilter {
lookup: DocUrlLookup,
base_path: String,
}
impl Filter for LinkFilter {
fn filter(&self, value: &Value, _args: &HashMap<String, Value>) -> tera::Result<Value> {
let url = lookup_doc_url(&self.lookup, value, "link")?;
let out = format!("{}{}", self.base_path, url);
Ok(Value::String(out))
}
fn is_safe(&self) -> bool {
true
}
}
struct RelativeUrlFilter {
base_path: String,
}
impl Filter for RelativeUrlFilter {
fn filter(&self, value: &Value, _args: &HashMap<String, Value>) -> tera::Result<Value> {
let p = value
.as_str()
.ok_or_else(|| tera::Error::msg("relative_url filter: input must be a string"))?;
let stripped = p.trim_start_matches('/');
let out = format!("{}/{}", self.base_path, stripped);
Ok(Value::String(out))
}
fn is_safe(&self) -> bool {
true
}
}
struct AbsoluteUrlFilter {
site_url: Option<String>,
base_path: String,
}
impl Filter for AbsoluteUrlFilter {
fn filter(&self, value: &Value, _args: &HashMap<String, Value>) -> tera::Result<Value> {
let p = value
.as_str()
.ok_or_else(|| tera::Error::msg("absolute_url filter: input must be a string"))?;
let stripped = p.trim_start_matches('/');
let out = format!(
"{}{}/{}",
self.site_url.as_deref().unwrap_or(""),
self.base_path,
stripped
);
Ok(Value::String(out))
}
fn is_safe(&self) -> bool {
true
}
}
pub fn register(env: &mut Tera, lookup: DocUrlLookup, site_url: Option<String>, base_path: String) {
env.register_filter(
"permalink",
PermalinkFilter {
lookup: lookup.clone(),
site_url: site_url.clone(),
base_path: base_path.clone(),
},
);
env.register_filter(
"link",
LinkFilter {
lookup,
base_path: base_path.clone(),
},
);
env.register_filter(
"relative_url",
RelativeUrlFilter {
base_path: base_path.clone(),
},
);
env.register_filter(
"absolute_url",
AbsoluteUrlFilter {
site_url,
base_path,
},
);
}
fn lookup_doc_url(lookup: &DocUrlLookup, value: &Value, filter_name: &str) -> tera::Result<String> {
let id_path_str = value.as_str().ok_or_else(|| {
tera::Error::msg(format!(
"{} filter: input must be a string id_path",
filter_name
))
})?;
lookup(id_path_str).ok_or_else(|| {
tera::Error::msg(format!(
"{} filter: no doc with id_path `{}`",
filter_name, id_path_str
))
})
}