#![doc = include_str!("../README.md")]
use anyhow::{Context, Result};
use clap::{ArgAction, Parser};
use configlation::{Config, Contents, Input, InputKind, Output};
use dialoguer::theme::ColorfulTheme;
use indicatif::{ProgressBar, ProgressStyle};
use std::{
collections::HashMap, fmt::Write, fs, path::PathBuf, thread::sleep,
time::Duration,
};
use tinytemplate::TinyTemplate;
use tracing::{debug, instrument, trace, warn};
#[derive(Debug, Parser)]
struct Cli {
#[clap(short, long, action = ArgAction::Count, global = true )]
verbose: u8,
#[clap(short, long, default_value = "./configlation.toml")]
config: PathBuf,
#[clap(short, long, default_value = ".")]
destination: PathBuf,
#[clap(long)]
reconfigure: bool,
}
#[instrument(ret, err)]
fn main() -> Result<()> {
let cli = Cli::parse();
jacklog::from_level!(2 + cli.verbose)?;
debug!(?cli);
let cfg: Config = configlation::read_toml(&cli.config)?;
debug!(?cfg);
let values_dest = cli.destination.join("saved.configlation.toml");
let mut values: HashMap<String, String> =
configlation::read_toml(&values_dest).unwrap_or_default();
debug!(?values);
eprintln!("{}", console::style(cfg.title).bold());
let inputs_len = cfg.inputs.len();
for (
n,
Input {
name,
kind,
default,
description,
},
) in cfg.inputs.into_iter().enumerate()
{
let theme = ColorfulTheme::default();
let mut dia = dialoguer::Input::<String>::with_theme(&theme)
.with_prompt(format!(
"{} {}: {name}{}",
console::style(format!("{}/{inputs_len}", n + 1)).dim(),
description.unwrap_or_default(),
console::style(if matches!(kind, InputKind::Secret) {
" <redacted>"
} else {
""
})
.dim(),
));
if let Some(v) = values.get(&name) {
if !cli.reconfigure {
continue;
}
dia = dia.default(v.into());
} else if let Some(default) = default {
dia = dia.default(default);
}
if matches!(kind, InputKind::Secret) {
trace!(?name, ?kind, "redacting post completion text");
dia = dia
.with_post_completion_text("<redacted>")
.show_default(false);
}
let val = dia.interact_text()?;
if let Some(v) = values.insert(name.clone(), val.clone()) {
if v != val {
warn!("overwrote {v} with {val} for input {name}");
}
}
}
debug!(?values);
let bar = ProgressBar::new(cfg.outputs.len() as u64)
.with_style(
ProgressStyle::default_bar()
.template("{wide_bar} {pos}/{len} {msg:21}")?,
)
.with_message("writing outputs");
for output in cfg.outputs {
trace!(?output);
match output {
Output::File {
path,
contents,
} => {
let mut tt = TinyTemplate::new();
tt.add_formatter("quoted", |v, o| {
match v {
serde_json::Value::Null => {},
serde_json::Value::Bool(b) => {
write!(o, "{b}")?;
},
serde_json::Value::Number(number) => {
write!(o, "{number:?}")?;
},
serde_json::Value::String(s) => {
write!(o, "{s:?}")?;
},
serde_json::Value::Array(values) => todo!(),
serde_json::Value::Object(map) => todo!(),
}
Ok(())
});
let template = match contents {
Contents::String(s) => s,
Contents::List(v) => v.join("\n"),
};
tt.add_template("default", &template)?;
let dest = if path.is_absolute() {
path
} else {
cli.destination.join(path)
};
fs::write(dest, tt.render("default", &values)?)?;
},
}
bar.inc(1);
}
bar.finish_with_message("all outputs complete!");
configlation::write_toml(&values_dest, &values, 400)?;
eprintln!("all configuration updated");
Ok(())
}