tuning 0.4.0

ansible-like tool with a smaller scope, focused primarily on complementing dotfiles for cross-machine bliss
use std::collections::HashMap;

use lazy_static::lazy_static;
use regex::Regex;
use tera::{self, from_value, to_value, Context, Tera, Value};
use thiserror::Error as ThisError;
use which::which;

use super::facts::Facts;

lazy_static! {
    static ref DIR_EXPRESSION_RE: Regex = Regex::new(r"_dir\s*\}\}").unwrap();
}

#[derive(Debug, ThisError)]
pub(crate) enum Error {
    #[error("template error: {}", source)]
    Tera {
        #[from]
        source: tera::Error,
    },
}

pub(crate) type Result<T> = std::result::Result<T, Error>;

pub(crate) fn make_tera(facts: &Facts) -> Result<(Tera, Context)> {
    let context = Context::from_serialize(facts)?;

    let template_glob = facts
        .main_file
        .parent()
        .expect("main.toml file has no parent directory")
        .join("*.toml");
    let mut t = Tera::new(template_glob.as_str()).expect("unable to prepare template system");
    t.register_function("has_executable", template_function_has_executable);

    Ok((t, context))
}

fn template_function_has_executable(args: &HashMap<String, Value>) -> tera::Result<Value> {
    match args.get("exe") {
        Some(val) => match from_value::<String>(val.clone()) {
            Ok(v) => Ok(to_value(which(v).is_ok()).unwrap()),
            Err(_) => Err(tera::Error::from(r#""exe" must be a string"#)),
        },
        None => Err(tera::Error::from(r#"missing "exe" argument"#)),
    }
}

#[cfg(test)]
mod tests {
    use crate::facts::Facts;

    use super::*;

    #[test]
    fn make_tera_ok() {
        let facts = Facts::default();
        let got = make_tera(&facts);

        assert!(got.is_ok());
    }
}