All checks were successful
continuous-integration/drone/push Build is passing
Signed-off-by: kjuulh <contact@kjuulh.io>
170 lines
5.6 KiB
Rust
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)
|
|
}
|