#![forbid(unsafe_code)]
use std::process::ExitCode;
use clap::{Parser, Subcommand};
use dynamic_config::{explain, snapshot, Format, LoadSpec, Source};
#[derive(Parser)]
#[command(name = "dynamic-config", version, about)]
struct Cli {
#[command(subcommand)]
command: Command,
}
#[derive(Subcommand)]
enum Command {
Explain {
path: String,
#[arg(short, long = "file", required = true)]
files: Vec<String>,
#[arg(short, long)]
key: String,
#[arg(short, long)]
env: Option<String>,
#[arg(long)]
profile_env: Option<String>,
#[arg(long = "env-file")]
env_files: Vec<String>,
#[arg(long)]
show_values: bool,
},
Completions {
shell: clap_complete::Shell,
},
Man,
Diff {
old: String,
new: String,
#[arg(short, long)]
key: String,
},
}
fn main() -> ExitCode {
match run(Cli::parse()) {
Ok(()) => ExitCode::SUCCESS,
Err(error) => {
eprintln!("{error}");
ExitCode::FAILURE
}
}
}
fn run(cli: Cli) -> Result<(), dynamic_config::Error> {
match cli.command {
Command::Explain {
path,
files,
key,
env,
profile_env,
env_files,
show_values,
} => {
let sources = sources(&files)?;
let env_files: Vec<&str> = env_files.iter().map(String::as_str).collect();
let mut spec = LoadSpec::new(&key, &sources).with_env_files(&env_files);
if let Some(prefix) = &env {
spec = spec.with_env(prefix);
}
if let Some(variable) = &profile_env {
spec = spec.with_profile_env(variable);
}
let mut explanation = explain(&spec, &path)?;
if !show_values {
explanation = explanation.redacted();
}
print!("{explanation}");
Ok(())
}
Command::Completions { shell } => {
clap_complete::generate(
shell,
&mut <Cli as clap::CommandFactory>::command(),
"dynamic-config",
&mut std::io::stdout(),
);
Ok(())
}
Command::Man => {
clap_mangen::Man::new(<Cli as clap::CommandFactory>::command())
.render(&mut std::io::stdout())
.map_err(dynamic_config::Error::invalid)?;
Ok(())
}
Command::Diff { old, new, key } => {
let before = one_document(&old, &key)?;
let after = one_document(&new, &key)?;
for change in before.diff(&after) {
println!("{change}");
}
Ok(())
}
}
}
fn sources(files: &[String]) -> Result<Vec<Source<'_>>, dynamic_config::Error> {
files
.iter()
.map(|file| {
Format::from_path(std::path::Path::new(file)).map(|format| Source::file(file, format))
})
.collect()
}
fn one_document(file: &str, key: &str) -> Result<dynamic_config::Snapshot, dynamic_config::Error> {
let format = Format::from_path(std::path::Path::new(file))?;
let sources = [Source::file(file, format)];
snapshot(&LoadSpec::new(key, &sources))
}