use crate::ParserOptions;
use crate::help::print_help;
use crate::into_result::IntoActionResult;
use crate::messages::{invalid_arg, missing_arg, parse_value};
use crate::parse_result::ParseResult;
use std::collections::VecDeque;
use std::error::Error;
use std::str::FromStr;
use std::sync::RwLock;
pub static NULL_PTR: &str = "";
pub enum ArgAction<'a> {
Bool(&'a mut bool),
RwLockBool(&'a RwLock<bool>),
Value(
Box<dyn FnMut(&str, &str, &str, &mut VecDeque<String>) -> Result<(), Box<dyn Error>> + 'a>,
),
}
pub struct Arg<'a> {
pub short: Option<&'a str>,
pub long: &'a str,
pub error_name: &'a str,
pub action: ArgAction<'a>,
pub essential: bool,
}
impl<'a> Arg<'a> {
pub fn bool(s: Option<&'a str>, l: &'a str, target: &'a mut bool) -> Self {
Self {
short: s,
long: l,
error_name: NULL_PTR,
action: ArgAction::Bool(target),
essential: false,
}
}
pub fn rw_bool(s: Option<&'a str>, l: &'a str, target: &'a RwLock<bool>) -> Self {
Self {
short: s,
long: l,
error_name: NULL_PTR,
action: ArgAction::RwLockBool(target),
essential: false,
}
}
pub fn set<T>(s: Option<&'a str>, l: &'a str, value: T, target: &'a mut T) -> Self
where
T: Clone + 'a,
{
let closure =
move |_sub: &str, _val_name: &str, _arg: &str, _args: &mut VecDeque<String>| {
*target = value.clone();
Ok(())
};
Self {
short: s,
long: l,
error_name: NULL_PTR,
action: ArgAction::Value(Box::new(closure)),
essential: false,
}
}
pub fn value<T>(s: Option<&'a str>, l: &'a str, err: &'a str, target: &'a mut T) -> Self
where
T: FromStr + 'a,
T::Err: Error + 'static,
{
let closure = move |sub: &str, val_name: &str, arg: &str, args: &mut VecDeque<String>| {
let next = args.front().map(|s| s.as_str());
let extracted = parse_value(sub, val_name, arg, next).map_err(|e| {
Box::new(std::io::Error::new(std::io::ErrorKind::InvalidInput, e)) as Box<dyn Error>
})?;
if !arg.contains('=') {
args.pop_front();
}
*target = extracted
.parse()
.map_err(|e| Box::new(e) as Box<dyn Error>)?;
Ok(())
};
Self {
short: s,
long: l,
error_name: err,
action: ArgAction::Value(Box::new(closure)),
essential: false,
}
}
pub fn rw_set<T>(s: Option<&'a str>, l: &'a str, value: T, target: &'a RwLock<T>) -> Self
where
T: Clone + 'a,
{
let closure =
move |_sub: &str, _val_name: &str, _arg: &str, _args: &mut VecDeque<String>| {
*target.write().unwrap() = value.clone();
Ok(())
};
Self {
short: s,
long: l,
error_name: NULL_PTR,
action: ArgAction::Value(Box::new(closure)),
essential: false,
}
}
pub fn rw_value<T>(s: Option<&'a str>, l: &'a str, err: &'a str, target: &'a RwLock<T>) -> Self
where
T: FromStr + 'a,
T::Err: Error + 'static,
{
let closure = move |sub: &str, val_name: &str, arg: &str, args: &mut VecDeque<String>| {
let next = args.front().map(|s| s.as_str());
let extracted = parse_value(sub, val_name, arg, next).map_err(|e| {
Box::new(std::io::Error::new(std::io::ErrorKind::InvalidInput, e)) as Box<dyn Error>
})?;
if !arg.contains('=') {
args.pop_front();
}
*target.write().unwrap() = extracted
.parse()
.map_err(|e| Box::new(e) as Box<dyn Error>)?;
Ok(())
};
Self {
short: s,
long: l,
error_name: err,
action: ArgAction::Value(Box::new(closure)),
essential: false,
}
}
pub fn option<T>(
s: Option<&'a str>,
l: &'a str,
err: &'a str,
target: &'a mut Option<T>,
) -> Self
where
T: FromStr + 'a,
T::Err: Error + 'static,
{
let closure = move |sub: &str, val_name: &str, arg: &str, args: &mut VecDeque<String>| {
let next = args.front().map(|s| s.as_str());
let extracted = parse_value(sub, val_name, arg, next).map_err(|e| {
Box::new(std::io::Error::new(std::io::ErrorKind::InvalidInput, e)) as Box<dyn Error>
})?;
if !arg.contains('=') {
args.pop_front();
}
*target = Some(
extracted
.parse()
.map_err(|e| Box::new(e) as Box<dyn Error>)?,
);
Ok(())
};
Self {
short: s,
long: l,
error_name: err,
action: ArgAction::Value(Box::new(closure)),
essential: false,
}
}
pub fn essential(mut self) -> Self {
self.essential = true;
self
}
pub fn collect_list(
s: Option<&'a str>,
l: &'a str,
err: &'a str,
target: &'a mut Vec<String>,
) -> Self {
let closure = move |sub: &str, val_name: &str, arg: &str, args: &mut VecDeque<String>| {
let next = args.front().map(|s| s.as_str());
let first = parse_value(sub, val_name, arg, next).map_err(|e| {
Box::new(std::io::Error::new(std::io::ErrorKind::InvalidInput, e)) as Box<dyn Error>
})?;
if !arg.contains('=') {
args.pop_front();
}
target.push(first);
while let Some(next_arg) = args.front() {
if next_arg.starts_with('-') {
break;
}
target.push(args.pop_front().unwrap());
}
Ok(())
};
Self {
short: s,
long: l,
error_name: err,
action: ArgAction::Value(Box::new(closure)),
essential: false,
}
}
pub fn action<F, R>(s: Option<&'a str>, l: &'a str, mut callback: F) -> Self
where
F: FnMut() -> R + 'a,
R: IntoActionResult,
{
let closure = move |_sub: &str, _err: &str, _arg: &str, _args: &mut VecDeque<String>| {
callback().into_result()
};
Self {
short: s,
long: l,
error_name: NULL_PTR,
action: ArgAction::Value(Box::new(closure)),
essential: false,
}
}
}
pub fn parse_into_vars<'a>(
rules: &mut [Arg<'a>],
mut args: VecDeque<String>,
opts: ParserOptions<'a>,
) -> ParseResult {
let mut help_requested = false;
let mut version_requested = false;
let mut help_all_requested = false;
if !opts.ignore_help {
if let Some(arg) = args.front() {
if arg == "--help" || arg == "-h" {
help_requested = true;
} else if arg == "--version" || arg == "-V" {
version_requested = true;
} else if arg == "--help-all" {
help_all_requested = true;
}
}
if help_requested || version_requested || help_all_requested {
let props = opts.help_rules.iter().find_map(|r| r.properties.as_ref());
if version_requested {
println!("{}", props.map(|p| p.version).unwrap_or("0.1.0"));
} else if help_all_requested {
print_help(opts.subcommand, props, rules, opts.help_rules, true);
} else {
print_help(opts.subcommand, props, rules, opts.help_rules, false);
}
return ParseResult {
res: Ok(()),
help_requested: true,
};
}
}
let is_empty = args.is_empty();
let mut essential_met = false;
let mut positional_only = false;
let mut remaining = VecDeque::new();
let mut arg_indices = Vec::new();
let mut current_idx = 0;
let has_essentials = rules.iter().any(|r| r.essential);
let mut result = (|| -> Result<(), Box<dyn Error>> {
while let Some(arg) = args.pop_front() {
current_idx += 1;
if !positional_only && arg == "--" {
positional_only = true;
continue;
}
if positional_only {
remaining.push_back(arg);
arg_indices.push(current_idx);
continue;
}
let rule = rules.iter_mut().find(|r| {
r.long
.split('|')
.any(|l| arg == l || arg.starts_with(&format!("{}=", l)))
|| r.short.map_or(false, |s| arg == s)
});
if let Some(r) = rule {
if r.essential {
essential_met = true;
}
match &mut r.action {
ArgAction::Bool(val) => **val = true,
ArgAction::RwLockBool(lock) => *lock.write().unwrap() = true,
ArgAction::Value(callback) => {
callback(opts.subcommand, r.error_name, &arg, &mut args)?
}
}
} else {
remaining.push_back(arg.clone());
arg_indices.push(current_idx);
if arg.starts_with('-') && arg != "-" {
return Err(invalid_arg(opts.subcommand, &arg));
}
}
}
Ok(())
})();
if result.is_ok() && opts.strict {
if let Some(level) = opts.strict_level {
for (i, &pos) in arg_indices.iter().enumerate() {
if pos <= level {
if let Some(arg) = remaining.get(i) {
result = Err(invalid_arg(opts.subcommand, arg));
break;
}
}
}
} else if let Some(arg) = remaining.front() {
result = Err(invalid_arg(opts.subcommand, arg));
}
}
if result.is_ok() && opts.require_args && is_empty {
result = Err(missing_arg(opts.subcommand, false));
}
if result.is_ok() && has_essentials && !essential_met && !is_empty {
result = Err(missing_arg(opts.subcommand, true));
}
if opts.passthrough && result.is_err() {
result = Ok(());
}
if let Some(target) = opts.collect_args {
target.extend(remaining.drain(..));
}
ParseResult {
res: result,
help_requested: false,
}
}