use crate::error::Result;
use crate::flag::Flag;
use std::collections::HashMap;
use std::hash::BuildHasher;
pub fn parse_flags_optimized<'a, S>(
args: &'a [String],
flags: &HashMap<String, Flag, S>,
parent_flags: Option<&HashMap<String, Flag, S>>,
) -> Result<(HashMap<String, String>, Vec<&'a str>)>
where
S: BuildHasher,
{
let mut parsed_flags = HashMap::new();
let mut remaining = Vec::new();
let mut i = 0;
while i < args.len() {
let arg = &args[i];
if arg == "--" {
remaining.extend(args[i + 1..].iter().map(String::as_str));
break;
} else if let Some(long_flag) = arg.strip_prefix("--") {
if long_flag == "help" {
parsed_flags.insert("help".to_string(), "true".to_string());
} else if let Some((name, value)) = long_flag.split_once('=') {
if let Some(flag) = find_flag(name, flags, parent_flags) {
flag.parse_value(value)?;
}
parsed_flags.insert(name.to_string(), value.to_string());
} else if let Some(flag) = find_flag(long_flag, flags, parent_flags) {
if flag.value_type != crate::flag::FlagType::Bool
&& i + 1 < args.len()
&& !args[i + 1].starts_with('-')
{
let value = &args[i + 1];
flag.parse_value(value)?;
parsed_flags.insert(long_flag.to_string(), value.clone());
i += 1;
} else {
parsed_flags.insert(long_flag.to_string(), "true".to_string());
}
} else {
remaining.push(arg.as_str());
}
} else if let Some(short_flags) = arg.strip_prefix('-').filter(|s| !s.is_empty()) {
let chars: Vec<char> = short_flags.chars().collect();
for (idx, ch) in chars.iter().enumerate() {
if *ch == 'h' {
parsed_flags.insert("help".to_string(), "true".to_string());
} else if let Some(flag) = find_flag_by_short(*ch, flags, parent_flags) {
if flag.value_type != crate::flag::FlagType::Bool
&& idx == chars.len() - 1
&& i + 1 < args.len()
&& !args[i + 1].starts_with('-')
{
let value = &args[i + 1];
flag.parse_value(value)?;
parsed_flags.insert(flag.name.clone(), value.clone());
i += 1;
} else {
parsed_flags.insert(flag.name.clone(), "true".to_string());
}
} else {
remaining.push(arg.as_str());
break;
}
}
} else {
remaining.push(arg.as_str());
}
i += 1;
}
Ok((parsed_flags, remaining))
}
fn find_flag<'a, S>(
name: &str,
flags: &'a HashMap<String, Flag, S>,
parent_flags: Option<&'a HashMap<String, Flag, S>>,
) -> Option<&'a Flag>
where
S: BuildHasher,
{
flags
.get(name)
.or_else(|| parent_flags.and_then(|pf| pf.get(name)))
}
fn find_flag_by_short<'a, S>(
short: char,
flags: &'a HashMap<String, Flag, S>,
parent_flags: Option<&'a HashMap<String, Flag, S>>,
) -> Option<&'a Flag>
where
S: BuildHasher,
{
flags
.values()
.find(|f| f.short == Some(short))
.or_else(|| parent_flags.and_then(|pf| pf.values().find(|f| f.short == Some(short))))
}
pub fn parse_flags_borrowed(args: &[String]) -> Vec<&str> {
args.iter().map(String::as_str).collect()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::flag::FlagType;
#[test]
fn test_optimized_parsing() {
let mut flags = HashMap::new();
flags.insert(
"verbose".to_string(),
Flag::new("verbose").short('v').value_type(FlagType::Bool),
);
flags.insert(
"output".to_string(),
Flag::new("output").short('o').value_type(FlagType::String),
);
let args = vec![
"-v".to_string(),
"--output".to_string(),
"file.txt".to_string(),
"arg1".to_string(),
];
let (parsed, remaining) = parse_flags_optimized(&args, &flags, None).unwrap();
assert_eq!(parsed.get("verbose").map(String::as_str), Some("true"));
assert_eq!(parsed.get("output").map(String::as_str), Some("file.txt"));
assert_eq!(remaining, vec!["arg1"]);
}
#[test]
fn test_no_allocations_for_flags() {
let mut flags = HashMap::new();
flags.insert(
"flag1".to_string(),
Flag::new("flag1").value_type(FlagType::Bool),
);
let args = vec!["--flag1".to_string(), "value".to_string()];
let (parsed, remaining) = parse_flags_optimized(&args, &flags, None).unwrap();
assert_eq!(parsed.get("flag1").map(String::as_str), Some("true"));
assert_eq!(remaining, vec!["value"]);
}
}