use std::collections::HashMap;
use std::path::{Path, PathBuf};
use anyhow::Result;
use tera::{Function, Tera, Value};
use crate::platform;
pub fn new_engine() -> Tera {
let mut t = Tera::default();
t.register_function("is_windows", os_fn(platform::is_windows));
t.register_function("is_linux", os_fn(platform::is_linux));
t.register_function("is_mac", os_fn(platform::is_mac));
t
}
fn os_fn(check: fn() -> bool) -> impl Function {
move |_args: &HashMap<String, Value>| -> tera::Result<Value> { Ok(Value::Bool(check())) }
}
pub fn render(tera: &mut Tera, template: &str, ctx: &tera::Context) -> Result<String> {
Ok(tera.render_str(template, ctx)?)
}
#[derive(Debug, Clone)]
pub struct FileParts {
pub path: PathBuf,
pub dir: String,
pub name: String,
pub stem: String,
pub ext: String,
}
impl FileParts {
pub fn from_path(p: &Path) -> Self {
let path = p.to_path_buf();
let dir = p.parent().map(path_string).unwrap_or_default();
let name = p
.file_name()
.map(|s| s.to_string_lossy().to_string())
.unwrap_or_default();
let stem = p
.file_stem()
.map(|s| s.to_string_lossy().to_string())
.unwrap_or_default();
let ext = p
.extension()
.map(|s| s.to_string_lossy().to_string())
.unwrap_or_default();
Self {
path,
dir,
name,
stem,
ext,
}
}
}
fn path_string(p: &Path) -> String {
let s = p.to_string_lossy();
s.strip_prefix(r"\\?\").unwrap_or(&s).to_string()
}
pub fn build_context(
file: &FileParts,
editor_cmd_parts: Option<&FileParts>,
cwd: &str,
group: &str,
rule_name: &str,
vars: &std::collections::BTreeMap<String, toml::Value>,
) -> tera::Context {
let mut ctx = tera::Context::new();
ctx.insert("file_path", &path_string(&file.path));
ctx.insert("file_dir", &file.dir);
ctx.insert("file_name", &file.name);
ctx.insert("file_stem", &file.stem);
ctx.insert("file_ext", &file.ext);
if let Some(ed) = editor_cmd_parts {
ctx.insert("editor_path", &path_string(&ed.path));
ctx.insert("editor_dir", &ed.dir);
ctx.insert("editor_name", &ed.name);
ctx.insert("editor_stem", &ed.stem);
ctx.insert("editor_ext", &ed.ext);
} else {
ctx.insert("editor_path", "");
ctx.insert("editor_dir", "");
ctx.insert("editor_name", "");
ctx.insert("editor_stem", "");
ctx.insert("editor_ext", "");
}
ctx.insert("cwd", cwd);
ctx.insert("group", group);
ctx.insert("rule", rule_name);
let env_map: HashMap<String, String> = std::env::vars().collect();
ctx.insert("env", &env_map);
let vars_map: HashMap<String, toml::Value> = vars.clone().into_iter().collect();
ctx.insert("vars", &vars_map);
ctx
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::BTreeMap;
use std::path::PathBuf;
#[test]
fn file_parts_rust_path_semantics() {
let p = PathBuf::from("/tmp/foo.rs");
let fp = FileParts::from_path(&p);
assert_eq!(fp.name, "foo.rs");
assert_eq!(fp.stem, "foo");
assert_eq!(fp.ext, "rs"); }
#[test]
fn file_parts_handles_double_extension() {
let p = PathBuf::from("/tmp/foo.tar.gz");
let fp = FileParts::from_path(&p);
assert_eq!(fp.stem, "foo.tar"); assert_eq!(fp.ext, "gz");
}
#[test]
fn file_parts_handles_no_extension() {
let p = PathBuf::from("/tmp/Makefile");
let fp = FileParts::from_path(&p);
assert_eq!(fp.name, "Makefile");
assert_eq!(fp.stem, "Makefile");
assert_eq!(fp.ext, ""); }
#[test]
fn os_function_returns_correct_bool() {
let mut tera = new_engine();
let ctx = tera::Context::new();
let rendered = tera
.render_str("{% if is_windows() %}W{% else %}nW{% endif %}", &ctx)
.unwrap();
if cfg!(target_os = "windows") {
assert_eq!(rendered, "W");
} else {
assert_eq!(rendered, "nW");
}
}
#[test]
fn renders_file_path_and_vars() {
let file = FileParts::from_path(Path::new("/tmp/hello.md"));
let mut vars = BTreeMap::new();
vars.insert("greeting".into(), toml::Value::String("hi".into()));
let ctx = build_context(&file, None, "/cwd", "default", "default", &vars);
let mut tera = new_engine();
let out = render(
&mut tera,
"{{ file_stem }}/{{ file_ext }} -> {{ vars.greeting }}",
&ctx,
)
.unwrap();
assert_eq!(out, "hello/md -> hi");
}
#[test]
fn renders_env_var() {
unsafe { std::env::set_var("TODOKE_TEST_VAR", "test_value") };
let file = FileParts::from_path(Path::new("/tmp/x"));
let ctx = build_context(&file, None, "/cwd", "g", "r", &BTreeMap::new());
let mut tera = new_engine();
let out = render(&mut tera, "{{ env.TODOKE_TEST_VAR }}", &ctx).unwrap();
assert_eq!(out, "test_value");
}
}