154 lines
5.3 KiB
Rust
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)),
|
|
}
|
|
}
|