use std::io::Write;
use clap::Parser;
use clap::error::ErrorKind;
use crate::exit;
pub fn parse<P: Parser>(bin_name: &str, args: &[String]) -> Result<P, u8> {
let mut full: Vec<String> = Vec::with_capacity(args.len() + 1);
full.push(bin_name.to_owned());
full.extend(args.iter().cloned());
match P::try_parse_from(full) {
Ok(p) => Ok(p),
Err(e) => Err(report_clap_error(&e)),
}
}
#[must_use]
pub fn report_clap_error(e: &clap::Error) -> u8 {
match e.kind() {
ErrorKind::DisplayHelp | ErrorKind::DisplayVersion => {
let mut stdout = std::io::stdout().lock();
let _ = stdout.write_all(e.render().to_string().as_bytes());
return exit::OK;
}
_ => {}
}
let mut stderr = std::io::stderr().lock();
let _ = stderr.write_all(e.render().to_string().as_bytes());
map_clap_error_kind(e.kind())
}
#[must_use]
pub fn map_clap_error_kind(kind: ErrorKind) -> u8 {
match kind {
ErrorKind::InvalidValue | ErrorKind::ValueValidation => exit::DATAERR,
ErrorKind::Io => exit::NOINPUT,
ErrorKind::Format => exit::GENERAL_ERROR,
_ => exit::USAGE,
}
}
#[cfg(test)]
mod tests {
use super::*;
use clap::Parser;
#[derive(Parser, Debug)]
#[command(no_binary_name = false)]
struct DummyOpts {
#[arg(short, long)]
flag: bool,
#[arg(short = 'n', long, value_parser = clap::value_parser!(u32))]
count: Option<u32>,
}
#[test]
fn unknown_flag_is_usage() {
let res: Result<DummyOpts, _> = parse("mkit test", &["--bogus".to_string()]);
assert_eq!(res.unwrap_err(), exit::USAGE);
}
#[test]
fn invalid_value_is_dataerr() {
let res: Result<DummyOpts, _> = parse(
"mkit test",
&["--count".to_string(), "not-a-number".to_string()],
);
assert_eq!(res.unwrap_err(), exit::DATAERR);
}
#[test]
fn valid_parse_succeeds() {
let res: Result<DummyOpts, _> = parse(
"mkit test",
&["--flag".to_string(), "-n".to_string(), "42".to_string()],
);
let opts = res.expect("should parse");
assert!(opts.flag);
assert_eq!(opts.count, Some(42));
}
#[test]
fn no_args_parses_with_defaults() {
let res: Result<DummyOpts, _> = parse("mkit test", &[]);
let opts = res.expect("should parse with defaults");
assert!(!opts.flag);
assert_eq!(opts.count, None);
}
#[test]
fn mapping_table() {
assert_eq!(map_clap_error_kind(ErrorKind::InvalidValue), exit::DATAERR);
assert_eq!(
map_clap_error_kind(ErrorKind::ValueValidation),
exit::DATAERR
);
assert_eq!(map_clap_error_kind(ErrorKind::Io), exit::NOINPUT);
assert_eq!(
map_clap_error_kind(ErrorKind::MissingRequiredArgument),
exit::USAGE
);
assert_eq!(map_clap_error_kind(ErrorKind::UnknownArgument), exit::USAGE);
assert_eq!(
map_clap_error_kind(ErrorKind::InvalidSubcommand),
exit::USAGE
);
}
}