pub mod parser;
use crate::config::Schema;
pub trait FdlArgsTrait: Sized {
fn parse() -> Self {
let args: Vec<String> = std::env::args().collect();
match Self::try_parse_from(&args) {
Ok(t) => t,
Err(msg) => {
eprintln!("{msg}");
std::process::exit(2);
}
}
}
fn try_parse_from(args: &[String]) -> Result<Self, String>;
fn schema() -> Schema;
fn render_help() -> String;
}
pub fn parse_or_schema<T: FdlArgsTrait>() -> T {
let argv: Vec<String> = std::env::args().collect();
parse_or_schema_from::<T>(&argv)
}
pub fn parse_or_schema_from<T: FdlArgsTrait>(argv: &[String]) -> T {
if argv.iter().any(|a| a == "--fdl-schema") {
let schema = T::schema();
let json = serde_json::to_string_pretty(&schema)
.expect("Schema serializes cleanly by construction");
println!("{json}");
std::process::exit(0);
}
if argv.iter().any(|a| a == "--help" || a == "-h") {
println!("{}", T::render_help());
std::process::exit(0);
}
match T::try_parse_from(argv) {
Ok(t) => t,
Err(msg) => {
eprintln!("{msg}");
std::process::exit(2);
}
}
}
#[cfg(test)]
mod env_tests {
use std::sync::{Mutex, MutexGuard};
use crate::args::FdlArgsTrait;
use crate::FdlArgs;
static ENV_LOCK: Mutex<()> = Mutex::new(());
fn env_lock() -> MutexGuard<'static, ()> {
ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner())
}
fn mk_args(xs: &[&str]) -> Vec<String> {
xs.iter().map(|s| s.to_string()).collect()
}
struct EnvGuard(&'static str);
impl EnvGuard {
fn set(name: &'static str, value: &str) -> Self {
unsafe { std::env::set_var(name, value); }
EnvGuard(name)
}
}
impl Drop for EnvGuard {
fn drop(&mut self) {
unsafe { std::env::remove_var(self.0); }
}
}
#[derive(FdlArgs, Debug)]
struct OptArgs {
#[option(env = "FDL_TEST_PORT")]
port: Option<u16>,
}
#[test]
fn env_fills_absent_option() {
let _lock = env_lock();
let _g = EnvGuard::set("FDL_TEST_PORT", "8080");
let cli: OptArgs = OptArgs::try_parse_from(&mk_args(&["prog"])).unwrap();
assert_eq!(cli.port, Some(8080));
}
#[test]
fn argv_flag_beats_env() {
let _lock = env_lock();
let _g = EnvGuard::set("FDL_TEST_PORT", "8080");
let cli: OptArgs =
OptArgs::try_parse_from(&mk_args(&["prog", "--port", "9999"])).unwrap();
assert_eq!(cli.port, Some(9999));
}
#[test]
fn equals_form_beats_env() {
let _lock = env_lock();
let _g = EnvGuard::set("FDL_TEST_PORT", "8080");
let cli: OptArgs =
OptArgs::try_parse_from(&mk_args(&["prog", "--port=9999"])).unwrap();
assert_eq!(cli.port, Some(9999));
}
#[test]
fn empty_env_falls_through() {
let _lock = env_lock();
let _g = EnvGuard::set("FDL_TEST_PORT", "");
let cli: OptArgs = OptArgs::try_parse_from(&mk_args(&["prog"])).unwrap();
assert_eq!(cli.port, None);
}
#[derive(FdlArgs, Debug)]
struct ScalarArgs {
#[option(default = "3", env = "FDL_TEST_RETRIES")]
retries: u32,
}
#[test]
fn env_overrides_default_on_scalar() {
let _lock = env_lock();
let _g = EnvGuard::set("FDL_TEST_RETRIES", "7");
let cli: ScalarArgs = ScalarArgs::try_parse_from(&mk_args(&["prog"])).unwrap();
assert_eq!(cli.retries, 7);
}
#[test]
fn argv_beats_env_beats_default_on_scalar() {
let _lock = env_lock();
let _g = EnvGuard::set("FDL_TEST_RETRIES", "7");
let cli: ScalarArgs =
ScalarArgs::try_parse_from(&mk_args(&["prog", "--retries", "42"])).unwrap();
assert_eq!(cli.retries, 42);
}
#[derive(FdlArgs, Debug)]
struct ChoiceArgs {
#[option(choices = &["a", "b"], env = "FDL_TEST_CHOICE")]
pick: Option<String>,
}
#[test]
fn env_value_is_validated_against_choices() {
let _lock = env_lock();
let _g = EnvGuard::set("FDL_TEST_CHOICE", "z"); let err = ChoiceArgs::try_parse_from(&mk_args(&["prog"])).unwrap_err();
assert!(
err.contains("invalid value") && err.contains("z") && err.contains("allowed:"),
"env-sourced invalid choice should error like an argv one; got: {err}"
);
}
#[test]
fn env_valid_choice_accepted() {
let _lock = env_lock();
let _g = EnvGuard::set("FDL_TEST_CHOICE", "a");
let cli: ChoiceArgs = ChoiceArgs::try_parse_from(&mk_args(&["prog"])).unwrap();
assert_eq!(cli.pick.as_deref(), Some("a"));
}
#[derive(FdlArgs, Debug)]
struct ShortArgs {
#[option(short = 'p', env = "FDL_TEST_SHORT")]
port: Option<u16>,
}
#[test]
fn short_form_suppresses_env_fallback() {
let _lock = env_lock();
let _g = EnvGuard::set("FDL_TEST_SHORT", "8080");
let cli: ShortArgs =
ShortArgs::try_parse_from(&mk_args(&["prog", "-p", "9999"])).unwrap();
assert_eq!(cli.port, Some(9999));
}
}