use std::ffi::{OsStr, OsString};
use ready_set_sdk::OutputMode;
use ready_set_sdk::context::{ColorMode, LogLevel};
#[derive(Debug)]
pub enum ParsedArgs {
Help,
Version,
List {
all: bool,
globals: GlobalFlags,
},
Subcommand {
name: String,
args: Vec<OsString>,
globals: GlobalFlags,
},
Empty {
globals: GlobalFlags,
},
}
#[derive(Debug, Clone, Default)]
pub struct GlobalFlags {
pub output: Option<OutputMode>,
pub log: Option<LogLevel>,
pub color: Option<ColorMode>,
}
#[must_use]
pub fn parse(mut args: impl Iterator<Item = OsString>) -> ParsedArgs {
drop(args.next());
let mut globals = GlobalFlags::default();
let mut list_seen = false;
let mut list_all = false;
while let Some(arg) = args.next() {
if arg == OsStr::new("--help") || arg == OsStr::new("-h") {
return ParsedArgs::Help;
}
if arg == OsStr::new("--version") || arg == OsStr::new("-V") {
return ParsedArgs::Version;
}
if arg == OsStr::new("--list") {
list_seen = true;
continue;
}
if list_seen && arg == OsStr::new("--all") {
list_all = true;
continue;
}
if arg == OsStr::new("--quiet") {
globals.log = Some(LogLevel::Quiet);
continue;
}
if arg == OsStr::new("--verbose") {
globals.log = Some(LogLevel::Verbose);
continue;
}
if arg == OsStr::new("--json") {
globals.output = Some(OutputMode::Json);
continue;
}
if arg == OsStr::new("--color") {
if let Some(value) = args.next() {
globals.color = Some(match value.to_string_lossy().as_ref() {
"always" => ColorMode::Always,
"never" => ColorMode::Never,
_ => ColorMode::Auto,
});
}
continue;
}
if list_seen {
}
let name = arg.to_string_lossy().into_owned();
let rest: Vec<OsString> = args.collect();
return ParsedArgs::Subcommand {
name,
args: rest,
globals,
};
}
if list_seen {
return ParsedArgs::List {
all: list_all,
globals,
};
}
ParsedArgs::Empty { globals }
}
#[cfg(test)]
#[allow(clippy::panic, clippy::missing_panics_doc)]
mod tests {
use super::*;
fn parse_str(args: &[&str]) -> ParsedArgs {
parse(args.iter().map(OsString::from))
}
#[test]
fn parses_help() {
assert!(matches!(
parse_str(&["ready-set", "--help"]),
ParsedArgs::Help
));
assert!(matches!(parse_str(&["ready-set", "-h"]), ParsedArgs::Help));
}
#[test]
fn parses_version() {
assert!(matches!(
parse_str(&["ready-set", "--version"]),
ParsedArgs::Version
));
assert!(matches!(
parse_str(&["ready-set", "-V"]),
ParsedArgs::Version
));
}
#[test]
fn parses_list_with_all() {
let ParsedArgs::List { all, .. } = parse_str(&["ready-set", "--list", "--all"]) else {
panic!("expected List variant");
};
assert!(all);
}
#[test]
fn list_carries_global_flags() {
let ParsedArgs::List { globals, .. } = parse_str(&["ready-set", "--json", "--list"]) else {
panic!("expected List variant");
};
assert_eq!(globals.output, Some(OutputMode::Json));
}
#[test]
fn parses_subcommand_with_passthrough() {
let ParsedArgs::Subcommand { name, args, .. } =
parse_str(&["ready-set", "scan", "--json", "--path", "/tmp"])
else {
panic!("expected Subcommand variant");
};
assert_eq!(name, "scan");
assert_eq!(
args,
vec![
OsString::from("--json"),
OsString::from("--path"),
OsString::from("/tmp"),
]
);
}
#[test]
fn captures_global_flags_before_subcommand() {
let ParsedArgs::Subcommand { name, globals, .. } =
parse_str(&["ready-set", "--json", "--verbose", "go"])
else {
panic!("expected Subcommand variant");
};
assert_eq!(name, "go");
assert_eq!(globals.output, Some(OutputMode::Json));
assert_eq!(globals.log, Some(LogLevel::Verbose));
}
}