151 lines
5.1 KiB
Rust
151 lines
5.1 KiB
Rust
use std::{path::PathBuf, str::FromStr};
|
|
|
|
use clap::{Arg, ArgMatches, Command};
|
|
|
|
use crate::{cli::CuddleCli, model::CuddleVariable};
|
|
|
|
pub fn build_command<'a>(root_cmd: Command<'a>) -> Command<'a> {
|
|
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 is the input file path of the .tmpl file (or inferred) that you would like to render"),
|
|
Arg::new("destination")
|
|
.alias("dest")
|
|
.short('d')
|
|
.long("destination")
|
|
.required(true)
|
|
.action(clap::ArgAction::Set)
|
|
.long_help("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)"),
|
|
Arg::new("extra-var")
|
|
.long("extra-var")
|
|
.required(false)
|
|
.action(clap::ArgAction::Set),
|
|
]))
|
|
}
|
|
|
|
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)?;
|
|
|
|
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].into(), parts[1].into()));
|
|
}
|
|
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(" ", "_").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)?;
|
|
|
|
// Put template in final destination
|
|
std::fs::write(&self.destination, output)?;
|
|
|
|
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: &String) -> anyhow::Result<PathBuf> {
|
|
match PathBuf::from_str(&raw_path) {
|
|
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: &String) -> anyhow::Result<PathBuf> {
|
|
match PathBuf::from_str(&raw_path) {
|
|
Ok(pb) => {
|
|
if pb.is_dir() && pb.exists() {
|
|
Ok(pb)
|
|
} else if pb.is_file() {
|
|
Ok(pb)
|
|
} else {
|
|
Ok(pb)
|
|
}
|
|
}
|
|
Err(e) => Err(anyhow::anyhow!(e)),
|
|
}
|
|
}
|