Files
cuddle/cuddle/src/cli/subcommands/folder.rs
kjuulh 1d1ac49d0b
All checks were successful
continuous-integration/drone/push Build is passing
feat: prefer first variable
Signed-off-by: kjuulh <contact@kjuulh.io>
2024-03-09 21:08:16 +01:00

170 lines
5.6 KiB
Rust

use std::{collections::HashMap, path::PathBuf};
use anyhow::Context;
use clap::{Arg, ArgAction, ArgMatches, Command};
use serde_json::from_value;
use tera::Function;
use crate::{cli::CuddleCli, model::CuddleVariable};
pub fn build_command(root_cmd: Command) -> Command {
root_cmd.subcommand(
Command::new("folder")
.about("renders a template folder")
.args(&[
Arg::new("source")
.long("source")
.required(true)
.value_parser(clap::value_parser!(PathBuf)),
Arg::new("destination")
.long("destination")
.required(true)
.value_parser(clap::value_parser!(PathBuf)),
Arg::new("extra-var")
.long("extra-var")
.action(ArgAction::Append)
.required(false),
]),
)
}
pub struct FolderCommand {
variables: Vec<CuddleVariable>,
source: PathBuf,
destination: PathBuf,
}
impl FolderCommand {
pub fn from_matches(matches: &ArgMatches, cli: CuddleCli) -> anyhow::Result<Self> {
let source = matches
.get_one::<PathBuf>("source")
.expect("source")
.clone();
let destination = matches
.get_one::<PathBuf>("destination")
.expect("destination")
.clone();
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,
source,
destination,
})
}
pub fn execute(self) -> anyhow::Result<()> {
let _ = std::fs::remove_dir_all(&self.destination);
std::fs::create_dir_all(&self.destination).context("failed to create directory")?;
// Prepare context
let mut context = tera::Context::new();
for var in self.variables.iter().rev() {
context.insert(var.name.to_lowercase().replace([' ', '-'], "_"), &var.value)
}
let mut tera = tera::Tera::default();
tera.register_function("filter_by_prefix", filter_by_prefix(self.variables.clone()));
for entry in walkdir::WalkDir::new(&self.source) {
let entry = entry.context("entry was not found")?;
let entry_path = entry.path();
let rel_path = self
.destination
.join(entry_path.strip_prefix(&self.source)?);
if entry_path.is_file() {
// Load source template
let source = std::fs::read_to_string(entry_path)
.context("failed to read entry into memory")?;
let output = tera.render_str(&source, &context)?;
if let Some(parent) = rel_path.parent() {
std::fs::create_dir_all(parent).context("failed to create parent dir")?;
}
// Put template in final destination
std::fs::write(&rel_path, output).context(format!(
"failed to write to destination: {}",
&rel_path.display()
))?;
log::info!("finished writing template to: {}", &rel_path.display());
}
}
Ok(())
}
}
fn filter_by_prefix(variables: Vec<CuddleVariable>) -> impl Function {
Box::new(
move |args: &HashMap<String, tera::Value>| -> tera::Result<tera::Value> {
for var in &variables {
tracing::info!("variable: {} - {}", var.name, var.value);
}
let prefix = match args.get("prefix") {
Some(value) => match from_value::<Vec<String>>(value.clone()) {
Ok(prefix) => prefix,
Err(e) => {
tracing::error!("prefix was not a string: {}", e);
return Err("prefix was not a string".into());
}
},
None => return Err("prefix is required".into()),
};
let prefix = prefix.join("_");
let vars = variables
.iter()
.filter_map(|v| {
if v.name.starts_with(&prefix) {
Some(CuddleVariable::new(
v.name.trim_start_matches(&prefix).trim_start_matches('_'),
v.value.clone(),
))
} else {
None
}
})
.collect::<Vec<CuddleVariable>>();
let mut structure: HashMap<String, String> = HashMap::new();
for var in vars {
if !structure.contains_key(&var.name) {
tracing::info!("found: {} - {}", &var.name, &var.value);
structure.insert(var.name, var.value);
}
}
Ok(serde_json::to_value(structure).unwrap())
},
)
}
fn filter_by_name(args: &HashMap<String, tera::Value>) -> tera::Result<tera::Value> {
Ok(tera::Value::Null)
}