use std::path::PathBuf;
use anyhow::{Result, bail};
use clap::{Parser, Subcommand};
use config_forge::{
Format, ValueFormat, check_convert_path, convert_path, delete_path_value, diff_paths,
get_path_value, inspect_path, merge_paths, render_diff, render_query_value, set_path_value,
validate_path,
};
#[derive(Debug, Parser)]
#[command(name = "config-forge")]
#[command(version, about = config_forge::describe())]
struct Cli {
#[command(subcommand)]
command: Option<Command>,
}
#[derive(Debug, Subcommand)]
enum Command {
Convert {
input: PathBuf,
#[arg(short, long)]
output: Option<PathBuf>,
#[arg(long, value_parser = parse_format)]
from: Option<Format>,
#[arg(long, value_parser = parse_format)]
to: Option<Format>,
#[arg(long)]
check: bool,
#[arg(long)]
overwrite: bool,
},
Inspect {
input: PathBuf,
#[arg(long, value_parser = parse_format)]
from: Option<Format>,
},
Validate {
input: PathBuf,
#[arg(long, value_parser = parse_format)]
from: Option<Format>,
},
Get {
input: PathBuf,
path: String,
#[arg(long, value_parser = parse_format)]
from: Option<Format>,
#[arg(long, value_parser = parse_format)]
to: Option<Format>,
},
Set {
input: PathBuf,
path: String,
value: String,
#[arg(short, long)]
output: PathBuf,
#[arg(long, value_parser = parse_format)]
from: Option<Format>,
#[arg(long, value_parser = parse_format)]
to: Option<Format>,
#[arg(long, default_value = "string", value_parser = parse_value_format)]
value_format: ValueFormat,
#[arg(long)]
overwrite: bool,
},
Delete {
input: PathBuf,
path: String,
#[arg(short, long)]
output: PathBuf,
#[arg(long, value_parser = parse_format)]
from: Option<Format>,
#[arg(long, value_parser = parse_format)]
to: Option<Format>,
#[arg(long)]
overwrite: bool,
},
Merge {
base: PathBuf,
override_file: PathBuf,
#[arg(short, long)]
output: PathBuf,
#[arg(long, value_parser = parse_format)]
base_format: Option<Format>,
#[arg(long, value_parser = parse_format)]
override_format: Option<Format>,
#[arg(long, value_parser = parse_format)]
to: Option<Format>,
#[arg(long)]
overwrite: bool,
},
Diff {
old: PathBuf,
new: PathBuf,
#[arg(long, value_parser = parse_format)]
old_format: Option<Format>,
#[arg(long, value_parser = parse_format)]
new_format: Option<Format>,
},
}
fn main() -> Result<()> {
let cli = Cli::parse();
match cli.command {
Some(Command::Convert {
input,
output,
from,
to,
check,
overwrite,
}) => convert(input, output, from, to, check, overwrite),
Some(Command::Inspect { input, from }) => inspect(input, from),
Some(Command::Validate { input, from }) => validate(input, from),
Some(Command::Get {
input,
path,
from,
to,
}) => get(input, &path, from, to),
Some(Command::Set {
input,
path,
value,
output,
from,
to,
value_format,
overwrite,
}) => set(
input,
&path,
&value,
output,
from,
to,
value_format,
overwrite,
),
Some(Command::Delete {
input,
path,
output,
from,
to,
overwrite,
}) => delete(input, &path, output, from, to, overwrite),
Some(Command::Merge {
base,
override_file,
output,
base_format,
override_format,
to,
overwrite,
}) => merge(
base,
override_file,
output,
base_format,
override_format,
to,
overwrite,
),
Some(Command::Diff {
old,
new,
old_format,
new_format,
}) => diff(old, new, old_format, new_format),
None => {
println!("{} {}", config_forge::NAME, config_forge::VERSION);
println!("{}", config_forge::describe());
Ok(())
}
}
}
fn convert(
input: PathBuf,
output: Option<PathBuf>,
from: Option<Format>,
to: Option<Format>,
check: bool,
overwrite: bool,
) -> Result<()> {
if check {
let output_format = check_convert_path(&input, from, to)?;
println!("ok: conversion to {} is valid", output_format.name());
return Ok(());
}
if output.is_none() && to.is_none() {
bail!("--to is required when --output is not provided");
}
let rendered = convert_path(&input, output.as_ref(), from, to, overwrite)?;
if output.is_none() {
print!("{rendered}");
}
Ok(())
}
fn inspect(input: PathBuf, from: Option<Format>) -> Result<()> {
let info = inspect_path(&input, from)?;
println!("path: {}", input.display());
println!("format: {}", info.format.name());
println!("root: {}", info.root_kind);
println!("size: {} bytes", info.size_bytes);
Ok(())
}
fn validate(input: PathBuf, from: Option<Format>) -> Result<()> {
let format = validate_path(&input, from)?;
let file_name = input
.file_name()
.and_then(|value| value.to_str())
.unwrap_or_else(|| input.to_str().unwrap_or("<input>"));
println!("ok: {file_name} is valid {}", format.name());
Ok(())
}
fn get(input: PathBuf, path: &str, from: Option<Format>, to: Option<Format>) -> Result<()> {
let value = get_path_value(&input, path, from)?;
let rendered = render_query_value(&value, to)?;
print!("{rendered}");
Ok(())
}
fn set(
input: PathBuf,
path: &str,
value: &str,
output: PathBuf,
from: Option<Format>,
to: Option<Format>,
value_format: ValueFormat,
overwrite: bool,
) -> Result<()> {
set_path_value(
&input,
&output,
path,
value,
value_format,
from,
to,
overwrite,
)?;
Ok(())
}
fn delete(
input: PathBuf,
path: &str,
output: PathBuf,
from: Option<Format>,
to: Option<Format>,
overwrite: bool,
) -> Result<()> {
delete_path_value(&input, &output, path, from, to, overwrite)?;
Ok(())
}
fn merge(
base: PathBuf,
override_file: PathBuf,
output: PathBuf,
base_format: Option<Format>,
override_format: Option<Format>,
to: Option<Format>,
overwrite: bool,
) -> Result<()> {
merge_paths(
&base,
&override_file,
&output,
base_format,
override_format,
to,
overwrite,
)?;
Ok(())
}
fn diff(
old: PathBuf,
new: PathBuf,
old_format: Option<Format>,
new_format: Option<Format>,
) -> Result<()> {
let entries = diff_paths(&old, &new, old_format, new_format)?;
print!("{}", render_diff(&entries));
Ok(())
}
fn parse_format(value: &str) -> Result<Format> {
value.parse()
}
fn parse_value_format(value: &str) -> Result<ValueFormat> {
value.parse()
}