Initial commit

master
Dustin 2024-01-11 08:23:48 -06:00
commit bc1d8b187c
7 changed files with 1303 additions and 0 deletions

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
Cargo.lock -diff

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

1147
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

16
Cargo.toml Normal file
View File

@ -0,0 +1,16 @@
[package]
name = "tmpl"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
argparse = "0.2.2"
file-mode = { version = "0.1.2", features = ["serde"] }
serde = { version = "1.0.195", features = ["derive"] }
serde_yaml = "0.9.30"
tera = "1.19.1"
thiserror = "1.0.56"
tracing = { version = "0.1.40", features = ["log"] }
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }

1
rustfmt.toml Normal file
View File

@ -0,0 +1 @@
max_width = 79

120
src/main.rs Normal file
View File

@ -0,0 +1,120 @@
mod model;
use std::path::PathBuf;
use std::io::Read;
use argparse::{ArgumentParser, Store, StoreOption};
use serde::de::DeserializeOwned;
use serde_yaml::Value;
use tera::{Context, Tera};
use tracing::error;
use model::Instructions;
#[derive(Debug, thiserror::Error)]
enum LoadError {
#[error("{0}")]
Io(#[from] std::io::Error),
#[error("{0}")]
Yaml(#[from] serde_yaml::Error),
}
#[derive(Debug, thiserror::Error)]
enum ProcessInstructionsError {
#[error("Error loading templates: {0}")]
Tera(#[from] tera::Error),
}
#[derive(Default)]
struct Args {
instructions: String,
values: String,
templates: Option<String>,
destdir: Option<PathBuf>,
}
fn parse_args(args: &mut Args) {
let mut parser = ArgumentParser::new();
parser
.refer(&mut args.instructions)
.required()
.add_argument("instructions", Store, "Instructions");
parser
.refer(&mut args.values)
.required()
.add_argument("values", Store, "Values");
parser.refer(&mut args.templates).add_option(
&["--templates", "-t"],
StoreOption,
"Templates directory",
);
parser.refer(&mut args.destdir).add_option(
&["--destdir", "-d"],
StoreOption,
"Destination directory",
);
parser.parse_args_or_exit();
}
fn main() {
tracing_subscriber::fmt()
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
.with_writer(std::io::stderr)
.init();
let mut args: Args = Default::default();
parse_args(&mut args);
let instructions = match load_yaml(&args.instructions) {
Ok(i) => i,
Err(e) => {
error!(
"Could not load instructions from {}: {}",
args.instructions, e
);
std::process::exit(1);
}
};
let values = match load_yaml(&args.values) {
Ok(i) => i,
Err(e) => {
error!("Could not load values from {}: {}", args.values, e);
std::process::exit(1);
}
};
let templates = args.templates.as_deref().unwrap_or("templates");
if let Err(e) = process_instructions(templates, instructions, values) {
error!("Failed to process instructions: {}", e);
std::process::exit(1);
}
}
fn load_yaml<T: DeserializeOwned>(path: &str) -> Result<T, LoadError> {
let mut yaml = vec![];
if path == "-" {
std::io::stdin().read_to_end(&mut yaml)?;
} else {
let mut f = std::fs::File::open(path)?;
f.read_to_end(&mut yaml)?;
}
Ok(serde_yaml::from_slice(&yaml)?)
}
fn process_instructions(
templates: &str,
instructions: Instructions,
values: Value,
) -> Result<(), ProcessInstructionsError> {
let tera = Tera::new(&format!("{}/**", templates))?;
let ctx = Context::from_serialize(&values)?;
for i in instructions.render {
match tera.render(&i.template, &ctx) {
Ok(o) => {
print!("{}", o);
}
Err(e) => {
error!("Failed to render template {}: {}", i.template, e);
}
}
}
Ok(())
}

17
src/model.rs Normal file
View File

@ -0,0 +1,17 @@
use std::path::PathBuf;
use serde::Deserialize;
#[derive(Debug, Deserialize)]
pub struct RenderInstruction {
pub template: String,
pub dest: PathBuf,
pub owner: Option<String>,
pub group: Option<String>,
pub mode: Option<String>,
}
#[derive(Debug, Deserialize)]
pub struct Instructions {
pub render: Vec<RenderInstruction>,
}