use std::collections::HashMap;
pub fn parse_positional_args(args: &[String]) -> (Option<String>, HashMap<String, String>) {
if args.is_empty() {
return (None, HashMap::new());
}
let mut params = HashMap::new();
let mut first_positional = None;
let mut i = 0;
while i < args.len() {
let arg = &args[i];
if let Some((key, value)) = arg.split_once(':') {
let final_value = if value.starts_with('"') && !value.ends_with('"') {
let mut full_value = value.to_string();
i += 1;
while i < args.len() {
let next_part = &args[i];
full_value.push(' ');
full_value.push_str(next_part);
if next_part.ends_with('"') {
break;
}
i += 1;
}
if full_value.starts_with('"') && full_value.ends_with('"') {
full_value[1..full_value.len() - 1].to_string()
} else {
full_value
}
} else if value.starts_with('"') && value.ends_with('"') && value.len() > 1 {
value[1..value.len() - 1].to_string()
} else {
value.to_string()
};
params.insert(key.to_string(), final_value);
} else if first_positional.is_none() {
first_positional = Some(arg.clone());
} else {
eprintln!("Warning: Ignoring extra positional argument: {arg}");
}
i += 1;
}
(first_positional, params)
}
pub fn get_required_string(
positional: Option<String>,
params: &HashMap<String, String>,
key: &str,
error_msg: &str,
) -> Result<String, String> {
positional
.or_else(|| params.get(key).cloned())
.ok_or_else(|| error_msg.to_string())
}
pub fn get_usize_param(params: &HashMap<String, String>, key: &str, default: usize) -> usize {
params
.get(key)
.and_then(|s| s.parse::<usize>().ok())
.unwrap_or(default)
}
pub fn get_string_param(params: &HashMap<String, String>, key: &str) -> Option<String> {
params.get(key).cloned()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_simple_positional() {
let args = vec!["search_term".to_string()];
let (pos, params) = parse_positional_args(&args);
assert_eq!(pos, Some("search_term".to_string()));
assert!(params.is_empty());
}
#[test]
fn test_parse_key_value_pairs() {
let args = vec!["limit:10".to_string(), "kind:function".to_string()];
let (pos, params) = parse_positional_args(&args);
assert_eq!(pos, None);
assert_eq!(params.get("limit"), Some(&"10".to_string()));
assert_eq!(params.get("kind"), Some(&"function".to_string()));
}
#[test]
fn test_parse_mixed() {
let args = vec!["main".to_string(), "limit:5".to_string()];
let (pos, params) = parse_positional_args(&args);
assert_eq!(pos, Some("main".to_string()));
assert_eq!(params.get("limit"), Some(&"5".to_string()));
}
#[test]
fn test_parse_quoted_value() {
let args = vec!["query:\"test value\"".to_string()];
let (pos, params) = parse_positional_args(&args);
assert_eq!(pos, None);
assert_eq!(params.get("query"), Some(&"test value".to_string()));
}
#[test]
fn test_parse_split_quoted_value() {
let args = vec![
"query:\"error".to_string(),
"handling".to_string(),
"in".to_string(),
"parser\"".to_string(),
"limit:3".to_string(),
];
let (pos, params) = parse_positional_args(&args);
assert_eq!(pos, None);
assert_eq!(
params.get("query"),
Some(&"error handling in parser".to_string())
);
assert_eq!(params.get("limit"), Some(&"3".to_string()));
}
}