flag_rs/
parse_optimized.rs

1//! Memory-optimized flag parsing
2//!
3//! This module provides optimized parsing functions that minimize
4//! string allocations during command-line parsing.
5
6use crate::error::Result;
7use crate::flag::Flag;
8use std::collections::HashMap;
9use std::hash::BuildHasher;
10
11/// Optimized flag parsing that minimizes allocations
12///
13/// Returns a map of flag names to values where both are borrowed from the input args
14pub 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            // Everything after -- is treated as arguments
31            remaining.extend(args[i + 1..].iter().map(String::as_str));
32            break;
33        } else if let Some(long_flag) = arg.strip_prefix("--") {
34            // Handle long flags
35            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                // Flag with value: --flag=value
39                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                // Check if next arg is the value
45                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                // Unknown flag - might belong to subcommand
58                remaining.push(arg.as_str());
59            }
60        } else if let Some(short_flags) = arg.strip_prefix('-').filter(|s| !s.is_empty()) {
61            // Handle short flags
62            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 this is the last char and flag takes a value
69                    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                    // Unknown short flag
83                    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
97/// Find a flag by name in local or parent flags
98fn 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
111/// Find a flag by short name in local or parent flags
112fn 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
126/// Alternative parsing that returns borrowed strings where possible
127pub 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        // The parsed flags contain owned strings for compatibility
175        assert_eq!(parsed.get("flag1").map(String::as_str), Some("true"));
176        assert_eq!(remaining, vec!["value"]);
177    }
178}