use std::collections::HashMap;
use std::str::FromStr;
#[derive(Clone, Debug, PartialEq)]
pub struct ParseResult {
flags: HashMap<String, bool>,
option_values: HashMap<String, Vec<String>>,
positionals: Vec<String>,
subcommand: Option<String>,
subcommand_result: Option<Box<ParseResult>>,
known_flags: Vec<String>,
known_options: Vec<String>,
}
#[must_use = "builder does nothing until .build() is called"]
#[derive(Clone, Debug, Default)]
pub struct ParseResultBuilder {
flags: HashMap<String, bool>,
option_values: HashMap<String, Vec<String>>,
positionals: Vec<String>,
subcommand: Option<String>,
subcommand_result: Option<Box<ParseResult>>,
}
impl ParseResultBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn flag(mut self, name: &str, value: bool) -> Self {
self.flags.insert(name.to_string(), value);
self
}
pub fn option(mut self, name: &str, value: &str) -> Self {
self.option_values.insert(name.to_string(), vec![value.to_string()]);
self
}
pub fn multi_option(mut self, name: &str, value: &str) -> Self {
self.option_values.entry(name.to_string()).or_default().push(value.to_string());
self
}
pub fn positional(mut self, value: &str) -> Self {
self.positionals.push(value.to_string());
self
}
pub fn subcommand(mut self, name: &str, result: ParseResult) -> Self {
self.subcommand = Some(name.to_string());
self.subcommand_result = Some(Box::new(result));
self
}
#[must_use]
pub fn build(self) -> ParseResult {
let known_flags: Vec<String> = self.flags.keys().cloned().collect();
let known_options: Vec<String> = self.option_values.keys().cloned().collect();
let mut result = ParseResult::new(
self.flags,
self.option_values,
self.positionals,
self.subcommand,
self.subcommand_result,
);
result.set_known_names(known_flags, known_options);
result
}
}
impl ParseResult {
pub(crate) fn new(
flags: HashMap<String, bool>,
option_values: HashMap<String, Vec<String>>,
positionals: Vec<String>,
subcommand: Option<String>,
subcommand_result: Option<Box<ParseResult>>,
) -> Self {
Self {
flags,
option_values,
positionals,
subcommand,
subcommand_result,
known_flags: Vec::new(),
known_options: Vec::new(),
}
}
pub(crate) fn set_known_names(&mut self, flags: Vec<String>, options: Vec<String>) {
self.known_flags = flags;
self.known_options = options;
}
fn has_schema(&self) -> bool {
!self.known_flags.is_empty() || !self.known_options.is_empty()
}
pub fn get_flag(&self, name: &str) -> bool {
debug_assert!(
!self.has_schema() || self.known_flags.iter().any(|f| f == name),
"get_flag(\"{name}\"): unknown flag. Known flags: {:?}",
self.known_flags
);
self.flags.get(name).copied().unwrap_or(false)
}
pub fn get_option(&self, name: &str) -> Option<&str> {
debug_assert!(
!self.has_schema() || self.known_options.iter().any(|o| o == name),
"get_option(\"{name}\"): unknown option. Known options: {:?}",
self.known_options
);
self.option_values.get(name)?.last().map(|s| s.as_str())
}
pub fn get_option_values(&self, name: &str) -> &[String] {
debug_assert!(
!self.has_schema() || self.known_options.iter().any(|o| o == name),
"get_option_values(\"{name}\"): unknown option. Known options: {:?}",
self.known_options
);
self.option_values.get(name).map_or(&[], |v| v.as_slice())
}
pub fn get_option_parsed<T: FromStr>(&self, name: &str) -> Option<Result<T, T::Err>> {
self.get_option(name).map(|v| v.parse::<T>())
}
pub fn get_option_values_parsed<T: FromStr>(&self, name: &str) -> Vec<Result<T, T::Err>> {
self.get_option_values(name).iter().map(|v| v.parse::<T>()).collect()
}
pub fn get_positionals(&self) -> &[String] {
&self.positionals
}
pub fn subcommand(&self) -> Option<&str> {
self.subcommand.as_deref()
}
pub fn subcommand_result(&self) -> Option<&ParseResult> {
self.subcommand_result.as_deref()
}
pub fn get_option_or_default<T: FromStr>(&self, name: &str, default: T) -> Result<T, OptionError>
where
T::Err: std::fmt::Display,
{
match self.get_option(name) {
Some(v) => v.parse::<T>().map_err(|e| OptionError::ParseFailed {
option: name.to_string(),
message: e.to_string(),
}),
None => Ok(default),
}
}
pub fn get_option_or<T: FromStr, F: FnOnce() -> T>(&self, name: &str, f: F) -> Result<T, OptionError>
where
T::Err: std::fmt::Display,
{
match self.get_option(name) {
Some(v) => v.parse::<T>().map_err(|e| OptionError::ParseFailed {
option: name.to_string(),
message: e.to_string(),
}),
None => Ok(f()),
}
}
pub fn get_option_required<T: FromStr>(&self, name: &str) -> Result<T, OptionError>
where
T::Err: std::fmt::Display,
{
match self.get_option(name) {
Some(v) => v.parse::<T>().map_err(|e| OptionError::ParseFailed {
option: name.to_string(),
message: e.to_string(),
}),
None => Err(OptionError::Missing {
option: name.to_string(),
}),
}
}
pub fn get_option_values_or_default<T: FromStr>(&self, name: &str, default: Vec<T>) -> Result<Vec<T>, OptionError>
where
T::Err: std::fmt::Display,
{
let raw = self.get_option_values(name);
if raw.is_empty() {
return Ok(default);
}
raw.iter().map(|v| v.parse::<T>()).collect::<Result<Vec<T>, _>>().map_err(|e| OptionError::ParseFailed {
option: name.to_string(),
message: e.to_string(),
})
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum OptionError {
Missing { option: String },
ParseFailed { option: String, message: String },
}
impl std::fmt::Display for OptionError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
OptionError::Missing { option } => {
write!(f, "option --{option} is required but was not provided")
}
OptionError::ParseFailed { option, message } => {
write!(f, "option --{option}: {message}")
}
}
}
}
impl std::error::Error for OptionError {}