use anyhow::{anyhow, Context, Result};
use serde::Serialize;
use serde_json::Value;
use crate::utils::template::no_autoescape_env;
use super::bundle::{require_builtin, CALEPIN};
use super::{dir_theme_name, validate_theme_dir, ThemeSelection, DEFAULT_THEME_NAME};
const PAGED_TEMPLATE: &str = "paged.typ.jinja";
#[derive(Debug, Clone)]
pub struct PagedTemplateContext {
pub input_path: String,
pub input_dir: String,
pub input_stem: String,
pub body: String,
pub page_meta: Value,
pub params: Value,
}
impl Default for PagedTemplateContext {
fn default() -> Self {
Self {
input_path: String::new(),
input_dir: String::new(),
input_stem: String::new(),
body: String::new(),
page_meta: Value::Null,
params: Value::Object(serde_json::Map::new()),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PagedSource {
pub source: String,
pub owns_body: bool,
}
#[derive(Serialize)]
struct PagedTemplateRenderContext<'a> {
theme: &'a str,
target: &'static str,
document: PagedDocumentContext<'a>,
params: &'a Value,
}
#[derive(Serialize)]
struct PagedDocumentContext<'a> {
path: &'a str,
dir: &'a str,
stem: &'a str,
body: &'a str,
meta: &'a Value,
}
pub fn paged_source(
selection: &ThemeSelection,
context: &PagedTemplateContext,
) -> Result<Option<PagedSource>> {
let render = |name: &str, source: String| {
let owns_body = source.contains("document.body");
render_paged_template(name, source, context).map(|source| PagedSource { source, owns_body })
};
match selection {
ThemeSelection::Disabled => Ok(None),
ThemeSelection::Default => render(DEFAULT_THEME_NAME, default_source()).map(Some),
ThemeSelection::Builtin(name) => {
let bundle = require_builtin(name)?;
let source = bundle
.file(PAGED_TEMPLATE)
.map(str::to_string)
.unwrap_or_else(default_source);
render(bundle.name, source).map(Some)
}
ThemeSelection::Dir(dir) => {
validate_theme_dir(dir)?;
let template_path = dir.join(PAGED_TEMPLATE);
if template_path.is_file() {
let source = std::fs::read_to_string(&template_path)
.with_context(|| format!("failed to read {}", template_path.display()))?;
let name = dir_theme_name(dir);
render(&name, source).map(Some)
} else {
render(DEFAULT_THEME_NAME, default_source()).map(Some)
}
}
}
}
fn default_source() -> String {
CALEPIN
.file(PAGED_TEMPLATE)
.map(str::to_string)
.expect("builtin calepin bundle ships paged.typ.jinja")
}
fn render_paged_template(
theme_name: &str,
source: String,
context: &PagedTemplateContext,
) -> Result<String> {
let mut env = no_autoescape_env();
env.add_template_owned(PAGED_TEMPLATE, source)
.map_err(|error| paged_template_error(theme_name, error))?;
let template = env
.get_template(PAGED_TEMPLATE)
.map_err(|error| paged_template_error(theme_name, error))?;
template
.render(PagedTemplateRenderContext {
theme: theme_name,
target: "paged",
document: PagedDocumentContext {
path: &context.input_path,
dir: &context.input_dir,
stem: &context.input_stem,
body: &context.body,
meta: &context.page_meta,
},
params: &context.params,
})
.map_err(|error| paged_template_error(theme_name, error))
}
fn paged_template_error(name: &str, error: minijinja::Error) -> anyhow::Error {
anyhow!("theme `{name}` paged.typ.jinja: {error}")
}