use std::process;
use anstream::AutoStream;
use anstyle::{AnsiColor, Color, Style};
use clap::{Parser, ValueEnum};
use smart_config::{
ConfigRepository, ConfigSchema, DescribeConfig, Environment, ExampleConfig, Json, ParseErrors,
SerializerOptions, Yaml,
};
use smart_config_commands::{MarkdownOptions, ParamRef, Printer};
use crate::configs::{TestConfig, create_mock_repo};
#[path = "../../tests/integration/configs.rs"]
mod configs;
#[derive(Debug, Parser)]
enum Cli {
Print {
filter: Option<String>,
},
Docs {
filter: Option<String>,
},
Debug {
#[arg(long)]
bogus: bool,
filter: Option<String>,
},
Serialize {
#[arg(long)]
example: bool,
#[arg(long)]
diff: bool,
#[arg(long, value_enum, default_value_t = SerializationFormat::Yaml)]
format: SerializationFormat,
},
}
#[derive(Debug, Clone, Copy, ValueEnum)]
enum SerializationFormat {
Json,
Yaml,
Env,
}
const ERROR: Style = Style::new().fg_color(Some(Color::Ansi(AnsiColor::Red)));
fn path_filter(filter: Option<&String>) -> impl Fn(ParamRef<'_>) -> bool + '_ {
move |param_ref| {
filter.is_none_or(|needle| param_ref.all_paths().any(|(path, _)| path.contains(needle)))
}
}
fn main() -> anyhow::Result<()> {
let cli = Cli::parse();
let schema = ConfigSchema::new(&TestConfig::DESCRIPTION, "test");
match cli {
Cli::Print { filter } => {
Printer::stderr().print_help(&schema, path_filter(filter.as_ref()))?;
}
Cli::Docs { filter } => {
Printer::stdout().print_markdown_reference(
&schema,
&MarkdownOptions::default(),
path_filter(filter.as_ref()),
)?;
}
Cli::Debug { bogus, filter } => {
let repo = create_mock_repo(&schema, bogus);
let res = Printer::stderr().print_debug(&repo, path_filter(filter.as_ref()))?;
if let Err(err) = res {
let mut errors: Vec<_> = err.into_iter().collect();
errors.sort_unstable_by(|lhs, rhs| {
let cheap_ordering = lhs.path().cmp(rhs.path());
cheap_ordering
.then_with(|| lhs.inner().to_string().cmp(&rhs.inner().to_string()))
});
let err = errors
.into_iter()
.collect::<Result<(), ParseErrors>>()
.unwrap_err();
anstream::eprintln!(
"\n{ERROR}There were errors parsing configuration params:\n{err}{ERROR:#}"
);
process::exit(1);
}
}
Cli::Serialize {
example,
diff,
format,
} => {
let mut options = if diff {
SerializerOptions::diff_with_default()
} else {
SerializerOptions::default()
};
options = options.flat(matches!(format, SerializationFormat::Env));
let (json, original_config) = if example {
let example_config = TestConfig::example_config();
let json = options.serialize(&example_config);
(serde_json::json!({ "test": json }), example_config)
} else {
let repo = create_mock_repo(&schema, false);
let original_config: TestConfig = repo.single()?.parse()?;
(repo.canonicalize(&options)?.into(), original_config)
};
let mut buffer = vec![];
let restored_repo = match format {
SerializationFormat::Json => {
Printer::stderr().print_json(&json)?;
Printer::custom(AutoStream::never(&mut buffer)).print_json(&json)?;
let deserialized = serde_json::from_slice(&buffer)?;
let source = Json::new("deserialized.json", deserialized);
ConfigRepository::new(&schema).with(source)
}
SerializationFormat::Yaml => {
Printer::stderr().print_yaml(&json)?;
Printer::custom(AutoStream::never(&mut buffer)).print_yaml(&json)?;
let deserialized = serde_yaml::from_slice(&buffer)?;
let source = Yaml::new("deserialized.yaml", deserialized)?;
ConfigRepository::new(&schema).with(source)
}
SerializationFormat::Env => {
let env =
Environment::convert_flat_params(json.as_object().unwrap(), "APP_").into();
Printer::stderr().print_yaml(&env)?;
let env = env.as_object().unwrap().iter().map(|(name, value)| {
let value = match value {
serde_json::Value::String(s) => s.clone(),
_ => value.to_string(),
};
(name.as_str(), value)
});
let mut env = Environment::from_iter("APP_", env);
env.coerce_json()?;
ConfigRepository::new(&schema).with(env)
}
};
let restored_config: TestConfig = restored_repo.single()?.parse()?;
assert_eq!(original_config, restored_config);
}
}
Ok(())
}