flag_rs/
parse_optimized.rs1use crate::error::Result;
7use crate::flag::Flag;
8use std::collections::HashMap;
9use std::hash::BuildHasher;
10
11pub fn parse_flags_optimized<'a, S>(
15 args: &'a [String],
16 flags: &HashMap<String, Flag, S>,
17 parent_flags: Option<&HashMap<String, Flag, S>>,
18) -> Result<(HashMap<String, String>, Vec<&'a str>)>
19where
20 S: BuildHasher,
21{
22 let mut parsed_flags = HashMap::new();
23 let mut remaining = Vec::new();
24 let mut i = 0;
25
26 while i < args.len() {
27 let arg = &args[i];
28
29 if arg == "--" {
30 remaining.extend(args[i + 1..].iter().map(String::as_str));
32 break;
33 } else if let Some(long_flag) = arg.strip_prefix("--") {
34 if long_flag == "help" {
36 parsed_flags.insert("help".to_string(), "true".to_string());
37 } else if let Some((name, value)) = long_flag.split_once('=') {
38 if let Some(flag) = find_flag(name, flags, parent_flags) {
40 flag.parse_value(value)?;
41 }
42 parsed_flags.insert(name.to_string(), value.to_string());
43 } else if let Some(flag) = find_flag(long_flag, flags, parent_flags) {
44 if flag.value_type != crate::flag::FlagType::Bool
46 && i + 1 < args.len()
47 && !args[i + 1].starts_with('-')
48 {
49 let value = &args[i + 1];
50 flag.parse_value(value)?;
51 parsed_flags.insert(long_flag.to_string(), value.to_string());
52 i += 1;
53 } else {
54 parsed_flags.insert(long_flag.to_string(), "true".to_string());
55 }
56 } else {
57 remaining.push(arg.as_str());
59 }
60 } else if let Some(short_flags) = arg.strip_prefix('-').filter(|s| !s.is_empty()) {
61 let chars: Vec<char> = short_flags.chars().collect();
63
64 for (idx, ch) in chars.iter().enumerate() {
65 if *ch == 'h' {
66 parsed_flags.insert("help".to_string(), "true".to_string());
67 } else if let Some(flag) = find_flag_by_short(*ch, flags, parent_flags) {
68 if flag.value_type != crate::flag::FlagType::Bool
70 && idx == chars.len() - 1
71 && i + 1 < args.len()
72 && !args[i + 1].starts_with('-')
73 {
74 let value = &args[i + 1];
75 flag.parse_value(value)?;
76 parsed_flags.insert(flag.name.clone(), value.to_string());
77 i += 1;
78 } else {
79 parsed_flags.insert(flag.name.clone(), "true".to_string());
80 }
81 } else {
82 remaining.push(arg.as_str());
84 break;
85 }
86 }
87 } else {
88 remaining.push(arg.as_str());
89 }
90
91 i += 1;
92 }
93
94 Ok((parsed_flags, remaining))
95}
96
97fn find_flag<'a, S>(
99 name: &str,
100 flags: &'a HashMap<String, Flag, S>,
101 parent_flags: Option<&'a HashMap<String, Flag, S>>,
102) -> Option<&'a Flag>
103where
104 S: BuildHasher,
105{
106 flags
107 .get(name)
108 .or_else(|| parent_flags.and_then(|pf| pf.get(name)))
109}
110
111fn find_flag_by_short<'a, S>(
113 short: char,
114 flags: &'a HashMap<String, Flag, S>,
115 parent_flags: Option<&'a HashMap<String, Flag, S>>,
116) -> Option<&'a Flag>
117where
118 S: BuildHasher,
119{
120 flags
121 .values()
122 .find(|f| f.short == Some(short))
123 .or_else(|| parent_flags.and_then(|pf| pf.values().find(|f| f.short == Some(short))))
124}
125
126pub fn parse_flags_borrowed(args: &[String]) -> Vec<&str> {
128 args.iter().map(String::as_str).collect()
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134 use crate::flag::FlagType;
135
136 #[test]
137 fn test_optimized_parsing() {
138 let mut flags = HashMap::new();
139 flags.insert(
140 "verbose".to_string(),
141 Flag::new("verbose").short('v').value_type(FlagType::Bool),
142 );
143 flags.insert(
144 "output".to_string(),
145 Flag::new("output").short('o').value_type(FlagType::String),
146 );
147
148 let args = vec![
149 "-v".to_string(),
150 "--output".to_string(),
151 "file.txt".to_string(),
152 "arg1".to_string(),
153 ];
154
155 let (parsed, remaining) = parse_flags_optimized(&args, &flags, None).unwrap();
156
157 assert_eq!(parsed.get("verbose").map(String::as_str), Some("true"));
158 assert_eq!(parsed.get("output").map(String::as_str), Some("file.txt"));
159 assert_eq!(remaining, vec!["arg1"]);
160 }
161
162 #[test]
163 fn test_no_allocations_for_flags() {
164 let mut flags = HashMap::new();
165 flags.insert(
166 "flag1".to_string(),
167 Flag::new("flag1").value_type(FlagType::Bool),
168 );
169
170 let args = vec!["--flag1".to_string(), "value".to_string()];
171
172 let (parsed, remaining) = parse_flags_optimized(&args, &flags, None).unwrap();
173
174 assert_eq!(parsed.get("flag1").map(String::as_str), Some("true"));
176 assert_eq!(remaining, vec!["value"]);
177 }
178}