initial implementation of functionality
This commit is contained in:
commit
3b7ea51e04
7 changed files with 283 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
/target
|
||||||
|
Cargo.lock
|
12
Cargo.toml
Normal file
12
Cargo.toml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
[package]
|
||||||
|
name = "oink"
|
||||||
|
version = "0.0.1"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
#copy_dir = "0.1.3"
|
||||||
|
pico-args = "0.5.0"
|
||||||
|
tera = "1.19.0"
|
||||||
|
toml = "0.7.6"
|
25
README.md
Normal file
25
README.md
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
|
||||||
|
# oink 🐷
|
||||||
|
|
||||||
|
A configuration file preprocessor written in Rust.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```sh
|
||||||
|
|
||||||
|
# process config files
|
||||||
|
oink build
|
||||||
|
# apply processed configs
|
||||||
|
oink apply
|
||||||
|
|
||||||
|
# build and apply
|
||||||
|
oink full
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## Libraries
|
||||||
|
|
||||||
|
- [pico-args](https://crates.io/crates/pico-args) — argument parsing
|
||||||
|
- [tera](https://crates.io/crates/tera) — template replacement
|
||||||
|
- [toml](https://crates.io/crates/toml) — configuration parsing
|
||||||
|
|
83
src/config.rs
Normal file
83
src/config.rs
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
//! configuration struct and implementations
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
env::var,
|
||||||
|
fs::read_to_string,
|
||||||
|
path::PathBuf
|
||||||
|
};
|
||||||
|
|
||||||
|
use tera::Context;
|
||||||
|
use toml::{
|
||||||
|
map::Map,
|
||||||
|
Value
|
||||||
|
};
|
||||||
|
|
||||||
|
/// configuration struct
|
||||||
|
pub struct Config {
|
||||||
|
pub dir: String,
|
||||||
|
inner: Map<String, Value>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
/// create a new Config using the file at '$HOME/.config/oink/oink.toml'
|
||||||
|
pub fn new() -> Config {
|
||||||
|
// get base config dir
|
||||||
|
let home = var("HOME").unwrap();
|
||||||
|
let mut dir = PathBuf::from(&home);
|
||||||
|
dir.push(".config/oink");
|
||||||
|
|
||||||
|
// read toml file
|
||||||
|
let mut toml_path = dir.clone();
|
||||||
|
toml_path.push("oink.toml");
|
||||||
|
let raw = read_to_string(toml_path).unwrap();
|
||||||
|
let toml_conf: Value = toml::from_str(&raw).unwrap();
|
||||||
|
let inner = toml_conf.as_table().unwrap();
|
||||||
|
|
||||||
|
// return struct
|
||||||
|
Config {
|
||||||
|
dir: dir.to_string_lossy().to_string(),
|
||||||
|
inner: inner.to_owned()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// build tera context from "vars" and "colors" config sections
|
||||||
|
pub fn context(&self) -> Context {
|
||||||
|
let mut output = Context::new();
|
||||||
|
|
||||||
|
let vars = self.inner.get("vars");
|
||||||
|
if vars.is_some() {
|
||||||
|
let vars = vars.unwrap().as_table().unwrap();
|
||||||
|
for (key, value) in vars.iter() {
|
||||||
|
output.insert(key, value.as_str().unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let colors = self.inner.get("colors");
|
||||||
|
if colors.is_some() {
|
||||||
|
let colors = colors.unwrap().as_table().unwrap();
|
||||||
|
let mut map = HashMap::<&str, &str>::new();
|
||||||
|
for (key, value) in colors.iter() {
|
||||||
|
map.insert(key, value.as_str().unwrap());
|
||||||
|
}
|
||||||
|
output.insert("colors", &map);
|
||||||
|
}
|
||||||
|
|
||||||
|
output
|
||||||
|
}
|
||||||
|
|
||||||
|
/// build array of targets from "target" array
|
||||||
|
pub fn targets(&self) -> Vec<Map<String, Value>> {
|
||||||
|
let mut output = Vec::new();
|
||||||
|
|
||||||
|
let target_array: Option<&Value> = self.inner.get("target");
|
||||||
|
if target_array.is_some() {
|
||||||
|
let target_array = target_array.unwrap().as_array().unwrap().to_owned();
|
||||||
|
for target in target_array {
|
||||||
|
output.push(target.as_table().unwrap().to_owned());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
output
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
7
src/error.rs
Normal file
7
src/error.rs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
use std::process::exit;
|
||||||
|
|
||||||
|
pub fn no_targets() {
|
||||||
|
println!("oink: configuration has no targets");
|
||||||
|
exit(2);
|
||||||
|
}
|
||||||
|
|
65
src/main.rs
Normal file
65
src/main.rs
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
use std::process::exit;
|
||||||
|
|
||||||
|
use pico_args::Arguments;
|
||||||
|
use tera::Tera;
|
||||||
|
|
||||||
|
mod config;
|
||||||
|
mod error;
|
||||||
|
mod operation;
|
||||||
|
use crate::config::Config;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// get args
|
||||||
|
let mut args = Arguments::from_env();
|
||||||
|
|
||||||
|
let flag_help = args.contains(["-h", "--help"]);
|
||||||
|
let operation = args.subcommand().unwrap();
|
||||||
|
if flag_help && operation.is_none() { help_text(); }
|
||||||
|
|
||||||
|
// init configuration
|
||||||
|
let config = Config::new();
|
||||||
|
|
||||||
|
// tera init
|
||||||
|
let context = config.context();
|
||||||
|
let template_dir = format!("{}/templates/*", &(config.dir));
|
||||||
|
let mut tera = Tera::new(&template_dir).unwrap();
|
||||||
|
|
||||||
|
let targets = config.targets();
|
||||||
|
if targets.len() == 0 { error::no_targets(); }
|
||||||
|
|
||||||
|
match operation.as_deref() {
|
||||||
|
Some("apply")
|
||||||
|
=> {
|
||||||
|
operation::apply(&targets);
|
||||||
|
},
|
||||||
|
Some("build")
|
||||||
|
=> {
|
||||||
|
operation::build(&targets, &mut tera, &context);
|
||||||
|
},
|
||||||
|
Some("full")
|
||||||
|
=> {
|
||||||
|
operation::build(&targets, &mut tera, &context);
|
||||||
|
operation::apply(&targets);
|
||||||
|
},
|
||||||
|
_
|
||||||
|
=> {
|
||||||
|
help_text();
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn help_text() {
|
||||||
|
let version = env!("CARGO_PKG_VERSION");
|
||||||
|
println!("oink v{version}
|
||||||
|
Valerie Wolfe <sleeplessval@gmail.com>
|
||||||
|
A configuration preprocessor written in Rust.
|
||||||
|
|
||||||
|
usage: oink [operation]
|
||||||
|
|
||||||
|
oink operations:
|
||||||
|
apply Copies the last built files to their destination paths.
|
||||||
|
build Runs oink preprocessing on configuration files.
|
||||||
|
full Runs 'build' and 'apply'");
|
||||||
|
}
|
||||||
|
|
89
src/operation.rs
Normal file
89
src/operation.rs
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
//! higher-level operation functions
|
||||||
|
use std::{
|
||||||
|
env::var,
|
||||||
|
fs::{ self, File },
|
||||||
|
io::Write,
|
||||||
|
path::Path
|
||||||
|
};
|
||||||
|
|
||||||
|
use toml::{ map::Map, Value };
|
||||||
|
use tera::{ Context, Tera };
|
||||||
|
|
||||||
|
pub fn apply(targets: &Vec<Map<String, Value>>) {
|
||||||
|
let home = var("HOME").unwrap();
|
||||||
|
println!("running apply:");
|
||||||
|
for target in targets {
|
||||||
|
// get path and name
|
||||||
|
let path = target.get("path");
|
||||||
|
let i_name = target.get("name");
|
||||||
|
|
||||||
|
// handle empty properties gracefully
|
||||||
|
if path.is_none() {
|
||||||
|
if i_name.is_some() {
|
||||||
|
let name = i_name.unwrap().as_str().unwrap();
|
||||||
|
println!(" \"{name}\" is missing its path property; skipping");
|
||||||
|
} else { println!(" skipping empty target"); }
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if i_name.is_none() {
|
||||||
|
println!(" target missing name; skipping");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// print applying text
|
||||||
|
let name = i_name.unwrap().as_str().unwrap();
|
||||||
|
println!(" applying \"{name}\":");
|
||||||
|
|
||||||
|
// set up paths
|
||||||
|
let destination = Path::new(path.unwrap().as_str().unwrap());
|
||||||
|
let source_path = format!("{home}/.config/oink/generated/{name}");
|
||||||
|
let source = Path::new(&source_path);
|
||||||
|
|
||||||
|
// copy and print
|
||||||
|
let result = fs::copy(source, destination);
|
||||||
|
if result.is_err() { println!(" failed to copy!"); }
|
||||||
|
else { println!(" completed successfully"); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build(targets: &Vec<Map<String, Value>>, tera: &mut Tera, context: &Context) {
|
||||||
|
let home = var("HOME").unwrap();
|
||||||
|
println!("running build:");
|
||||||
|
for target in targets {
|
||||||
|
// get name property
|
||||||
|
let i_name = target.get("name");
|
||||||
|
// handle empty names gracefully
|
||||||
|
if i_name.is_none() {
|
||||||
|
println!(" target missing name; skipping");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// print building text
|
||||||
|
let name = i_name.unwrap().as_str().unwrap();
|
||||||
|
println!(" building \"{name}\":");
|
||||||
|
|
||||||
|
// render template
|
||||||
|
let render = tera.render(name, context);
|
||||||
|
// handle rendering errors gracefully
|
||||||
|
if render.is_err() {
|
||||||
|
println!(" failed to render template");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get rendered text and open destination file
|
||||||
|
let output = render.unwrap();
|
||||||
|
let destination = format!("{home}/.config/oink/generated/{name}");
|
||||||
|
let path = Path::new(&destination);
|
||||||
|
let i_file = File::create(path);
|
||||||
|
if i_file.is_err() {
|
||||||
|
println!(" failed to create destination file at {path:?}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let mut file = i_file.unwrap();
|
||||||
|
// write to destination file
|
||||||
|
let written = write!(&mut file, "{output}");
|
||||||
|
if written.is_err() { println!(" failed to write to destination file at {path:?}"); }
|
||||||
|
else { println!(" completed successfully"); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue