use super::template_cfg::TemplateCfg;
use super::transform_values::TransformsValues;
use super::variable_cfg::VariableCfg;
use crate::Result;
use crate::Variables;
use crate::files;
use crate::graph::Graph;
use crate::scripts::Script;
use crate::source_file::SourceFile;
use crate::source_loc::SourceLoc;
use handlebars_misc_helpers::new_hbs;
use std::collections::HashMap;
use std::collections::HashSet;
use tracing::{Level, debug, instrument, span, warn};
#[derive(Debug, Clone)]
pub struct TemplateLayer {
order: usize,
loc: SourceLoc,
cfg: TemplateCfg,
}
impl TransformsValues for TemplateLayer {
fn transforms_values<F>(&self, render: &F) -> Result<Self>
where
F: Fn(&str) -> String,
{
let cfg = self.cfg.transforms_values(render)?;
Ok(TemplateLayer {
order: self.order,
loc: self.loc.clone(),
cfg,
})
}
}
#[derive(Debug, Clone)]
pub struct TemplateComposite {
layers: Vec<TemplateLayer>,
}
impl TemplateComposite {
pub fn from_src(
variables: &Variables,
offline: bool,
src: &SourceLoc,
) -> Result<TemplateComposite> {
let mut templates = HashMap::new();
deep_download(variables, offline, src, &mut templates)?;
let layers = templates
.find_edges_ordered_by_depth(src)
.into_iter()
.enumerate()
.map(|(i, k)| {
let v = templates.get(&k).expect("should exist").clone();
TemplateLayer {
order: i,
loc: k,
cfg: v,
}
})
.collect::<Vec<_>>();
debug!(?layers);
Ok(TemplateComposite { layers })
}
pub fn find_variablecfgs(&self) -> Result<Vec<VariableCfg>> {
let mut back = vec![];
let mut names = HashSet::new();
for layer in &self.layers {
let _span_ = span!(Level::DEBUG, "find_variablecfgs", layer = ?layer).entered();
for variable in layer.cfg.variables.clone() {
if !names.contains(&variable.name) {
names.insert(variable.name.clone());
back.push(variable.clone());
}
}
}
Ok(back)
}
pub fn find_sourcefiles(&self) -> Result<Vec<SourceFile>> {
let mut back = vec![];
for layer in &self.layers {
let _span_ = span!(Level::DEBUG, "find_sourcefiles", layer = ?layer).entered();
let ignores = &layer.cfg.find_ignores()?;
let template_dir = if layer.cfg.use_template_dir {
"template"
} else {
""
};
let path = layer.loc.as_local_path()?.join(template_dir);
debug!(ignores = ?ignores);
for childpath in files::find_childpaths(path, ignores) {
back.push(SourceFile::from((childpath, layer.order)));
}
}
Ok(back)
}
pub fn find_scripts(&self) -> Result<Vec<(&SourceLoc, Vec<Script>)>> {
self.layers
.iter()
.map(|t| t.cfg.find_scripts().map(|l| (&t.loc, l)))
.collect()
}
}
impl Graph for HashMap<SourceLoc, TemplateCfg> {
type K = SourceLoc;
type V = TemplateCfg;
fn find_node(&self, k: &Self::K) -> Option<&Self::V> {
self.get(k)
}
fn find_edges_direct(&self, v: &Self::V) -> Vec<Self::K> {
v.find_sourcelocs()
.expect("TODO find_sourcelocs without error")
}
}
#[instrument(skip(variables, templates))]
fn deep_download(
variables: &Variables,
offline: bool,
src: &SourceLoc,
templates: &mut HashMap<SourceLoc, TemplateCfg>,
) -> Result<()> {
if !templates.contains_key(src) {
let template_base_path = &src.download(offline)?;
let template_cfg = TemplateCfg::from_template_folder(template_base_path)?;
let mut variables_children = variables.clone();
variables_children.insert("ffizer_src_uri", src.uri.raw.clone())?;
variables_children.insert("ffizer_src_rev", src.rev.clone())?;
let template_cfg_for_imports =
render_imports_only(&template_cfg, &variables_children, false)?;
let children = template_cfg_for_imports.find_sourcelocs()?;
templates.insert(src.clone(), template_cfg_for_imports);
for child in children {
deep_download(&variables_children, offline, &child, templates)?;
}
}
Ok(())
}
impl TransformsValues for TemplateComposite {
fn transforms_values<F>(&self, render: &F) -> Result<Self>
where
F: Fn(&str) -> String,
{
let layers = self.layers.transforms_values(render)?;
Ok(TemplateComposite { layers })
}
}
pub(crate) fn render_composite(
template_composite: &TemplateComposite,
variables: &Variables,
log_warning: bool,
) -> Result<TemplateComposite> {
let handlebars = new_hbs();
let render = |v: &str| {
let r = handlebars.render_template(v, variables);
match r {
Ok(s) => s,
Err(e) => {
if log_warning {
warn!(input = ?v, error = ?e, "failed to convert")
}
v.into()
}
}
};
template_composite.transforms_values(&render)
}
fn render_imports_only(
template_cfg: &TemplateCfg,
input_variables: &Variables,
log_warning: bool,
) -> Result<TemplateCfg> {
let handlebars = new_hbs();
let render = |v: &str| {
let r = handlebars.render_template(v, input_variables);
match r {
Ok(s) => s,
Err(e) => {
if log_warning {
warn!(input = ?v, error = ?e, "failed to convert")
}
v.into()
}
}
};
let variables_cfg = template_cfg.variables.clone();
let ignores = template_cfg.ignores.clone();
let imports = template_cfg.imports.transforms_values(&render)?;
let scripts = template_cfg.scripts.clone();
Ok(TemplateCfg {
variables: variables_cfg,
ignores,
imports,
scripts,
use_template_dir: template_cfg.use_template_dir,
})
}