Compare commits
11 commits
Author | SHA1 | Date | |
---|---|---|---|
0fbf3f2522 | |||
6b3d12e7f9 | |||
772411da73 | |||
9b6c3c9422 | |||
cd7dc90657 | |||
92a201981a | |||
cdc0c02e37 | |||
f873367a51 | |||
4f66db254c | |||
7fab543476 | |||
502e5a7615 |
8 changed files with 184 additions and 50 deletions
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "oink"
|
name = "oink"
|
||||||
version = "0.1.1"
|
version = "0.2.3"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
@ -9,7 +9,7 @@ edition = "2021"
|
||||||
pico-args = "0.5.0"
|
pico-args = "0.5.0"
|
||||||
termion = "2.0.1"
|
termion = "2.0.1"
|
||||||
toml = "0.7.6"
|
toml = "0.7.6"
|
||||||
upon = "0.7.1"
|
upon = "0.8.1"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
opt-level = "s"
|
opt-level = "s"
|
||||||
|
|
34
man/oink.1
Normal file
34
man/oink.1
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
.Dd $Mdocdate$
|
||||||
|
.Dt OINK 1
|
||||||
|
.Os
|
||||||
|
.Sh NAME
|
||||||
|
.Nm oink
|
||||||
|
.Nd a configuration file preprocessor
|
||||||
|
.Sh SYNOPSIS
|
||||||
|
.Nm
|
||||||
|
.Ar command
|
||||||
|
.Sh DESCRIPTION
|
||||||
|
.Nm
|
||||||
|
is a configuration file preprocessor focused on centralizing configuration changes.
|
||||||
|
.Ss COMMANDS
|
||||||
|
.Bl -tag -width Ds
|
||||||
|
.It apply
|
||||||
|
Copies the last generated files to their target paths.
|
||||||
|
.It build
|
||||||
|
Generates files from their templates.
|
||||||
|
.It full
|
||||||
|
Runs 'build', then 'apply'.
|
||||||
|
.El
|
||||||
|
.Sh EXIT STATUS
|
||||||
|
.Bl -tag -width Ds
|
||||||
|
.It 1
|
||||||
|
No command was given.
|
||||||
|
.It 2
|
||||||
|
The configuration file has no items in the target array.
|
||||||
|
.It 3
|
||||||
|
Failed to create the configuration directories.
|
||||||
|
.El
|
||||||
|
.Sh AUTHORS
|
||||||
|
.An -nosplit
|
||||||
|
.An Valerie Wolfe Aq Mt sleeplessval@gmail.com
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
//! configuration struct and implementations
|
//! configuration struct and implementations
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
collections::{ BTreeMap, HashMap },
|
collections::BTreeMap,
|
||||||
env::var,
|
env::var,
|
||||||
fs::{ create_dir, read_to_string, File },
|
fs::{ create_dir, read_to_string, File },
|
||||||
io::Error,
|
io::Error,
|
||||||
|
@ -15,9 +15,10 @@ use toml::{
|
||||||
Value
|
Value
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::error;
|
use crate::{ error, util };
|
||||||
|
|
||||||
pub type Context = BTreeMap<String, ContextValue>;
|
pub type Context = BTreeMap<String, ContextValue>;
|
||||||
|
pub type Table = toml::map::Map<String, Value>;
|
||||||
|
|
||||||
/// configuration struct
|
/// configuration struct
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
|
@ -55,26 +56,53 @@ impl Config {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// build tera context from "vars" and "colors" config sections
|
/// build context from "vars" and "colors" config sections
|
||||||
pub fn context(&self) -> Context {
|
pub fn context(&self, target: &Table) -> Context {
|
||||||
let mut output = Context::new();
|
let mut output = Context::new();
|
||||||
|
let mut def: Vec<ContextValue> = Vec::new();
|
||||||
|
|
||||||
let vars = self.inner.get("vars");
|
// pull global vars
|
||||||
if vars.is_some() {
|
if let Some(Value::Table(vars)) = self.inner.get("vars") {
|
||||||
let vars = vars.unwrap().as_table().unwrap();
|
|
||||||
for (key, value) in vars.iter() {
|
for (key, value) in vars.iter() {
|
||||||
output.insert(key.to_owned(), value.as_str().into());
|
let key = key.to_owned();
|
||||||
|
if let Some(value) = util::convert(value) {
|
||||||
|
output.insert(key.clone(), value);
|
||||||
|
def.push(ContextValue::String(key));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let colors = self.inner.get("colors");
|
|
||||||
if colors.is_some() {
|
|
||||||
let colors = colors.unwrap().as_table().unwrap();
|
|
||||||
let mut map = Context::new();
|
|
||||||
for (key, value) in colors.iter() {
|
|
||||||
map.insert(key.to_owned(), value.as_str().unwrap().into());
|
|
||||||
}
|
}
|
||||||
output.insert("colors".to_owned(), map.into());
|
|
||||||
|
// pull target values
|
||||||
|
for (key, value) in target.iter() {
|
||||||
|
if key.to_uppercase() == *key {
|
||||||
|
if let Some(value) = util::convert(value) {
|
||||||
|
let key = key.to_owned();
|
||||||
|
output.insert(key.clone(), value);
|
||||||
|
def.push(ContextValue::String(key));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// pull palette
|
||||||
|
let palette_name: Option<String> =
|
||||||
|
if let Some(Value::String(name)) = target.get("use_palette") { Some(name.clone()) }
|
||||||
|
else if let Some(Value::String(name)) = self.inner.get("use_palette") { Some(name.clone()) }
|
||||||
|
else { None };
|
||||||
|
if let Some(Value::Array(array)) = self.inner.get("palette") {
|
||||||
|
let palette = util::matches(array.to_owned(), palette_name.unwrap_or("default".to_string()));
|
||||||
|
if let Some(Value::Table(palette)) = palette {
|
||||||
|
let colors = Context::new();
|
||||||
|
for(key, value) in palette.iter() {
|
||||||
|
output.insert(key.to_owned(), value.as_str().unwrap().into());
|
||||||
|
}
|
||||||
|
let key = "palette".to_string();
|
||||||
|
output.insert(key.clone(), colors.into());
|
||||||
|
def.push(ContextValue::String(key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// insert set vars list
|
||||||
|
output.insert("def".to_string(), def.into());
|
||||||
|
|
||||||
output
|
output
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,11 @@ use std::{
|
||||||
process::exit
|
process::exit
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub fn no_command() {
|
||||||
|
crate::help_text();
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn no_targets() {
|
pub fn no_targets() {
|
||||||
println!("oink: configuration has no targets");
|
println!("oink: configuration has no targets");
|
||||||
exit(2);
|
exit(2);
|
||||||
|
|
7
src/filter.rs
Normal file
7
src/filter.rs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
|
||||||
|
use upon::Value;
|
||||||
|
|
||||||
|
pub fn has(list: Vec<Value>, key: String) -> bool {
|
||||||
|
return list.contains(&Value::String(key));
|
||||||
|
}
|
||||||
|
|
21
src/main.rs
21
src/main.rs
|
@ -1,10 +1,11 @@
|
||||||
use std::process::exit;
|
|
||||||
|
|
||||||
use pico_args::Arguments;
|
use pico_args::Arguments;
|
||||||
|
|
||||||
mod config;
|
mod config;
|
||||||
mod error;
|
mod error;
|
||||||
|
mod filter;
|
||||||
mod operation;
|
mod operation;
|
||||||
|
mod util;
|
||||||
|
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
|
|
||||||
|
@ -27,23 +28,19 @@ fn main() {
|
||||||
|
|
||||||
match operation.as_deref() {
|
match operation.as_deref() {
|
||||||
Some("apply")
|
Some("apply")
|
||||||
=> {
|
=> operation::apply(&targets),
|
||||||
operation::apply(&targets);
|
|
||||||
},
|
|
||||||
Some("build")
|
Some("build")
|
||||||
=> {
|
=> operation::build(&targets, template_dir, &config),
|
||||||
operation::build(&targets, template_dir, &config);
|
|
||||||
},
|
|
||||||
Some("full")
|
Some("full")
|
||||||
=> {
|
=> {
|
||||||
operation::build(&targets, template_dir, &config);
|
operation::build(&targets, template_dir, &config);
|
||||||
|
println!();
|
||||||
operation::apply(&targets);
|
operation::apply(&targets);
|
||||||
},
|
},
|
||||||
_
|
|
||||||
=> {
|
_ => error::no_command()
|
||||||
help_text();
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,13 +3,15 @@ use std::{
|
||||||
env::var,
|
env::var,
|
||||||
fs::{self, read_to_string, File },
|
fs::{self, read_to_string, File },
|
||||||
io::Write,
|
io::Write,
|
||||||
path::{ Path, PathBuf }
|
path::{ Path, PathBuf },
|
||||||
|
time::SystemTime
|
||||||
};
|
};
|
||||||
|
|
||||||
use termion::{
|
use termion::{
|
||||||
color::{ self, Fg },
|
color::{ self, Fg },
|
||||||
style::{
|
style::{
|
||||||
Bold as BOLD,
|
Bold as BOLD,
|
||||||
|
Faint as FAINT,
|
||||||
Italic as ITALIC,
|
Italic as ITALIC,
|
||||||
Reset as RESET
|
Reset as RESET
|
||||||
}
|
}
|
||||||
|
@ -17,16 +19,23 @@ use termion::{
|
||||||
use toml::{ map::Map, Value };
|
use toml::{ map::Map, Value };
|
||||||
use upon::Engine;
|
use upon::Engine;
|
||||||
|
|
||||||
use crate::config::Config;
|
use crate::{
|
||||||
|
config::Config,
|
||||||
|
filter,
|
||||||
|
util::time
|
||||||
|
};
|
||||||
|
|
||||||
static SUCCESS: Fg<color::Green> = Fg(color::Green);
|
static SUCCESS: Fg<color::Green> = Fg(color::Green);
|
||||||
static WARNING: Fg<color::Yellow> = Fg(color::Yellow);
|
static WARNING: Fg<color::Yellow> = Fg(color::Yellow);
|
||||||
static FAILURE: Fg<color::Red> = Fg(color::Red);
|
static FAILURE: Fg<color::Red> = Fg(color::Red);
|
||||||
|
|
||||||
pub fn apply(targets: &Vec<Map<String, Value>>) {
|
pub fn apply(targets: &Vec<Map<String, Value>>) {
|
||||||
|
let start = SystemTime::now();
|
||||||
|
|
||||||
let home = var("HOME").unwrap();
|
let home = var("HOME").unwrap();
|
||||||
println!("running apply:");
|
println!("running apply:");
|
||||||
for target in targets {
|
for target in targets {
|
||||||
|
let start = SystemTime::now();
|
||||||
// get path and name
|
// get path and name
|
||||||
let path = target.get("path");
|
let path = target.get("path");
|
||||||
let i_name = target.get("name");
|
let i_name = target.get("name");
|
||||||
|
@ -55,23 +64,31 @@ pub fn apply(targets: &Vec<Map<String, Value>>) {
|
||||||
|
|
||||||
// copy and print
|
// copy and print
|
||||||
let result = fs::copy(source, destination);
|
let result = fs::copy(source, destination);
|
||||||
|
let time = time(start);
|
||||||
if result.is_err() {
|
if result.is_err() {
|
||||||
println!(" {BOLD}{FAILURE}failed to copy{RESET}");
|
print!(" {BOLD}{FAILURE}failed to copy{RESET}");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
println!(" {BOLD}{SUCCESS}completed{RESET}");
|
print!(" {BOLD}{SUCCESS}completed{RESET}");
|
||||||
}
|
}
|
||||||
|
println!(" {FAINT}({time}){RESET}");
|
||||||
}
|
}
|
||||||
|
let time = time(start);
|
||||||
|
println!("{FAINT}(apply: {time}){RESET}");
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build(targets: &Vec<Map<String, Value>>, template_dir: String, config: &Config) {
|
pub fn build(targets: &Vec<Map<String, Value>>, template_dir: String, config: &Config) {
|
||||||
|
let start = SystemTime::now();
|
||||||
|
|
||||||
let home = var("HOME").unwrap();
|
let home = var("HOME").unwrap();
|
||||||
println!("running build:");
|
println!("running build:");
|
||||||
|
|
||||||
let engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
let context = config.context();
|
engine.add_filter("has", filter::has);
|
||||||
|
|
||||||
for target in targets {
|
for target in targets {
|
||||||
|
let start = SystemTime::now();
|
||||||
|
let context = config.context(target);
|
||||||
// get name property
|
// get name property
|
||||||
let i_name = target.get("name");
|
let i_name = target.get("name");
|
||||||
// handle empty names gracefully
|
// handle empty names gracefully
|
||||||
|
@ -85,25 +102,31 @@ pub fn build(targets: &Vec<Map<String, Value>>, template_dir: String, config: &C
|
||||||
println!(" building \"{name}\":");
|
println!(" building \"{name}\":");
|
||||||
|
|
||||||
// compile
|
// compile
|
||||||
println!(" {ITALIC}compiling{RESET}");
|
let compile_start = SystemTime::now();
|
||||||
|
print!(" {ITALIC}compiling{RESET}");
|
||||||
let mut path = PathBuf::from(&template_dir);
|
let mut path = PathBuf::from(&template_dir);
|
||||||
path.push(name);
|
if let Some(Value::String(base)) = target.get("base") { path.push(base); }
|
||||||
|
else { path.push(name); }
|
||||||
|
|
||||||
let content = read_to_string(path).unwrap();
|
let content = read_to_string(path).unwrap();
|
||||||
let template = engine.compile(&content);
|
let template = engine.compile(&content);
|
||||||
if template.is_err() {
|
let compile_time = time(compile_start);
|
||||||
let error = template.err().unwrap();
|
print!(" {FAINT}({compile_time}){RESET}");
|
||||||
println!(" {BOLD}{FAILURE}failed to compile template:{RESET}\n {FAILURE}{error}\n {BOLD}skipping{RESET}");
|
if let Err(error) = template {
|
||||||
|
println!("\n {BOLD}{FAILURE}failed to compile template:{RESET}\n {FAILURE}{error}\n {BOLD}skipping{RESET}");
|
||||||
continue;
|
continue;
|
||||||
}
|
} else { println!(); }
|
||||||
|
|
||||||
// render
|
// render
|
||||||
println!(" {ITALIC}rendering{RESET}");
|
let render_start = SystemTime::now();
|
||||||
let render = template.unwrap().render(&context).to_string();
|
print!(" {ITALIC}rendering{RESET}");
|
||||||
if render.is_err() {
|
let render = template.unwrap().render(&engine, &context).to_string();
|
||||||
let error = render.err().unwrap();
|
let render_time = time(render_start);
|
||||||
println!(" {BOLD}{FAILURE}failed to render template:{RESET}\n {FAILURE}{error}\n {BOLD}skipping{RESET}");
|
print!(" {FAINT}({render_time}){RESET}");
|
||||||
|
if let Err(error) = render {
|
||||||
|
println!("\n {BOLD}{FAILURE}failed to render template:{RESET}\n {FAILURE}{error}\n {BOLD}skipping{RESET}");
|
||||||
continue;
|
continue;
|
||||||
}
|
} else { println!(); }
|
||||||
|
|
||||||
// get rendered text and open destination file
|
// get rendered text and open destination file
|
||||||
let output = render.unwrap();
|
let output = render.unwrap();
|
||||||
|
@ -118,7 +141,12 @@ pub fn build(targets: &Vec<Map<String, Value>>, template_dir: String, config: &C
|
||||||
// write to destination file
|
// write to destination file
|
||||||
let written = write!(&mut file, "{output}");
|
let written = write!(&mut file, "{output}");
|
||||||
if written.is_err() { println!(" {FAILURE}failed to write to destination file at {path:?}{RESET}"); }
|
if written.is_err() { println!(" {FAILURE}failed to write to destination file at {path:?}{RESET}"); }
|
||||||
else { println!(" {BOLD}{SUCCESS}completed{RESET}"); }
|
else {
|
||||||
|
let time = time(start);
|
||||||
|
println!(" {BOLD}{SUCCESS}completed{RESET} {FAINT}({time}){RESET}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let time = time(start);
|
||||||
|
println!("{FAINT}(build: {time}){RESET}");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
35
src/util.rs
Normal file
35
src/util.rs
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
use std::time::SystemTime;
|
||||||
|
|
||||||
|
use upon::Value as ContextValue;
|
||||||
|
use toml::{ value::Array, Value };
|
||||||
|
|
||||||
|
pub fn matches(array: Array, to_match: String) -> Option<Value> {
|
||||||
|
array.iter().filter(|value| {
|
||||||
|
if let Value::Table(table) = value {
|
||||||
|
if let Some(Value::String(name)) = table.get("name") {
|
||||||
|
return *name == to_match;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}).map(|value| value.to_owned()).nth(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn convert(value: &Value) -> Option<ContextValue> {
|
||||||
|
match value.clone() {
|
||||||
|
Value::Boolean(bool) => Some(bool.into()),
|
||||||
|
Value::Float(float) => Some(float.into()),
|
||||||
|
Value::Integer(int) => Some(int.into()),
|
||||||
|
Value::String(string) => Some(string.into()),
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn time(start: SystemTime) -> String {
|
||||||
|
let now = SystemTime::now();
|
||||||
|
if let Ok(duration) = now.duration_since(start) {
|
||||||
|
let ms = duration.as_millis();
|
||||||
|
if ms > 0 { format!("{ms} ms") }
|
||||||
|
else { "< 1 ms".to_owned() }
|
||||||
|
} else { String::new() }
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue