cuddle-v2/cuddle/src/cli/subcommands/render_template.rs
kjuulh 85cc1d46db
feat: make sure dir is there as well
Signed-off-by: kjuulh <contact@kjuulh.io>
2024-01-28 16:42:34 +01:00

154 lines
5.3 KiB
Rust

use std::{path::PathBuf, str::FromStr};
use anyhow::Context;
use clap::{Arg, ArgMatches, Command};
use crate::{cli::CuddleCli, model::CuddleVariable};
const DESTINATION: &str = "destination is the output path of the template once done, but default .tmpl is stripped and the normal file extension is used. this can be overwritten if a file path is entered instead. I.e. (/some/file/name.txt)";
const TEMPLATE_FILE: &str = "template-file is the input file path of the .tmpl file (or inferred) that you would like to render";
pub fn build_command(root_cmd: Command) -> Command {
root_cmd.subcommand(
Command::new("render_template")
.about("renders a jinja compatible template")
.args(&[
Arg::new("template-file")
.alias("template")
.short('t')
.long("template-file")
.required(true)
.action(clap::ArgAction::Set)
.long_help(TEMPLATE_FILE),
Arg::new("destination")
.alias("dest")
.short('d')
.long("destination")
.required(true)
.action(clap::ArgAction::Set)
.long_help(DESTINATION),
Arg::new("extra-var")
.long("extra-var")
.required(false)
.action(clap::ArgAction::Append),
]),
)
}
pub struct RenderTemplateCommand {
variables: Vec<CuddleVariable>,
template_file: PathBuf,
destination: PathBuf,
}
impl RenderTemplateCommand {
pub fn from_matches(matches: &ArgMatches, cli: CuddleCli) -> anyhow::Result<Self> {
let template_file = matches
.get_one::<String>("template-file")
.ok_or(anyhow::anyhow!("template-file was not found"))
.and_then(get_path_buf_and_check_exists)?;
let destination = matches
.get_one::<String>("destination")
.ok_or(anyhow::anyhow!("destination was not found"))
.and_then(get_path_buf_and_check_dir_exists)
.and_then(RenderTemplateCommand::transform_extension)
.context("failed to access dest directory")?;
let mut extra_vars: Vec<CuddleVariable> =
if let Some(extra_vars) = matches.get_many::<String>("extra-var") {
let mut vars = Vec::with_capacity(extra_vars.len());
for var in extra_vars.into_iter() {
let parts: Vec<&str> = var.split('=').collect();
if parts.len() != 2 {
return Err(anyhow::anyhow!("extra-var: is not set correctly: {}", var));
}
vars.push(CuddleVariable::new(parts[0], parts[1]));
}
vars
} else {
vec![]
};
extra_vars.append(&mut cli.variables.clone());
Ok(Self {
variables: extra_vars,
template_file,
destination,
})
}
pub fn execute(self) -> anyhow::Result<()> {
// Prepare context
let mut context = tera::Context::new();
for var in self.variables {
context.insert(var.name.to_lowercase().replace([' ', '-'], "_"), &var.value)
}
// Load source template
let source = std::fs::read_to_string(self.template_file)?;
let output = tera::Tera::one_off(source.as_str(), &context, false)?;
if let Some(parent) = self.destination.parent() {
std::fs::create_dir_all(parent)?;
}
// Put template in final destination
std::fs::write(&self.destination, output).context(format!(
"failed to write to destination: {}",
&self.destination.display(),
))?;
log::info!(
"finished writing template to: {}",
&self.destination.to_string_lossy()
);
Ok(())
}
fn transform_extension(template_path: PathBuf) -> anyhow::Result<PathBuf> {
if template_path.is_file() {
let ext = template_path.extension().ok_or(anyhow::anyhow!(
"destination path does not have an extension"
))?;
if ext.to_string_lossy().ends_with("tmpl") {
let template_dest = template_path
.to_str()
.and_then(|s| s.strip_suffix(".tmpl"))
.ok_or(anyhow::anyhow!("string does not end in .tmpl"))?;
return PathBuf::from_str(template_dest).map_err(|e| anyhow::anyhow!(e));
}
}
Ok(template_path)
}
}
fn get_path_buf_and_check_exists(raw_path: impl Into<String>) -> anyhow::Result<PathBuf> {
match PathBuf::from_str(&raw_path.into()) {
Ok(pb) => {
if pb.exists() {
Ok(pb)
} else {
Err(anyhow::anyhow!(
"path: {}, could not be found",
pb.to_string_lossy()
))
}
}
Err(e) => Err(anyhow::anyhow!(e)),
}
}
fn get_path_buf_and_check_dir_exists(raw_path: impl Into<String>) -> anyhow::Result<PathBuf> {
match PathBuf::from_str(&raw_path.into()) {
Ok(pb) => Ok(pb),
Err(e) => Err(anyhow::anyhow!(e)),
}
}