Skip to main content

grit_lib/parse_options_test_tool/
flags_cmd.rs

1//! `test-tool parse-options-flags` — mirrors `cmd__parse_options_flags` / `parse_options_flags__cmd`.
2
3use super::parse_options_cmd::ParseOptionsToolError;
4
5const KEEP_DASHDASH: u32 = 1 << 0;
6const STOP_AT_NON_OPTION: u32 = 1 << 1;
7const KEEP_ARGV0: u32 = 1 << 2;
8const KEEP_UNKNOWN_OPT: u32 = 1 << 3;
9const NO_INTERNAL_HELP: u32 = 1 << 4;
10
11/// `test-tool parse-options-flags` — mirrors `cmd__parse_options_flags`.
12pub fn run_parse_options_flags(args: &[String]) -> Result<i32, ParseOptionsToolError> {
13    if args.len() < 2 {
14        return Err(ParseOptionsToolError::Fatal(
15            "error: 'cmd' is mandatory\nusage: test-tool parse-options-flags [flag-options] cmd [options]\n"
16                .to_string(),
17        ));
18    }
19    let mut i = 1usize;
20    let mut test_flags: u32 = 0;
21    while i < args.len() {
22        let a = &args[i];
23        if !a.starts_with("--") {
24            break;
25        }
26        match a.as_str() {
27            "--keep-dashdash" => test_flags |= KEEP_DASHDASH,
28            "--stop-at-non-option" => test_flags |= STOP_AT_NON_OPTION,
29            "--keep-argv0" => test_flags |= KEEP_ARGV0,
30            "--keep-unknown-opt" => test_flags |= KEEP_UNKNOWN_OPT,
31            "--no-internal-help" => test_flags |= NO_INTERNAL_HELP,
32            "--subcommand-optional" => test_flags |= 1 << 7,
33            _ => {
34                return Err(ParseOptionsToolError::Fatal(format!(
35                    "error: unknown option `{a}`\n"
36                )));
37            }
38        }
39        i += 1;
40    }
41    if args.get(i).map(|s| s.as_str()) != Some("cmd") {
42        return Err(ParseOptionsToolError::Fatal(
43            "error: 'cmd' is mandatory\nusage: test-tool parse-options-flags [flag-options] cmd [options]\n"
44                .to_string(),
45        ));
46    }
47    parse_flags_cmd_inner(&args[i..], test_flags)
48}
49
50fn parse_int_opt(s: &str) -> Result<i32, ParseOptionsToolError> {
51    s.parse().map_err(|_| {
52        ParseOptionsToolError::Fatal("error: option `opt' expects a numerical value\n".to_string())
53    })
54}
55
56fn parse_flags_cmd_inner(argv: &[String], flags: u32) -> Result<i32, ParseOptionsToolError> {
57    if argv.is_empty() || argv[0] != "cmd" {
58        return Err(ParseOptionsToolError::Fatal(
59            "error: 'cmd' is mandatory\nusage: test-tool parse-options-flags [flag-options] cmd [options]\n"
60                .to_string(),
61        ));
62    }
63
64    let keep_dashdash = flags & KEEP_DASHDASH != 0;
65    let stop_at_non = flags & STOP_AT_NON_OPTION != 0;
66    let keep_argv0 = flags & KEEP_ARGV0 != 0;
67    let keep_unknown = flags & KEEP_UNKNOWN_OPT != 0;
68    let no_internal_help = flags & NO_INTERNAL_HELP != 0;
69    let internal_help = !no_internal_help;
70
71    let total_after_cmd = argv.len().saturating_sub(1);
72    let mut opt: i32 = 0;
73    let mut i = 1usize;
74    let mut out: Vec<String> = Vec::new();
75    if keep_argv0 {
76        out.push("cmd".to_string());
77    }
78
79    while i < argv.len() {
80        let arg = &argv[i];
81
82        if internal_help && total_after_cmd == 1 && arg == "-h" {
83            return Err(ParseOptionsToolError::Help);
84        }
85
86        if arg == "-" || !arg.starts_with('-') {
87            if stop_at_non {
88                out.extend(argv[i..].iter().cloned());
89                break;
90            }
91            out.push(arg.clone());
92            i += 1;
93            continue;
94        }
95
96        if arg == "--" {
97            if keep_dashdash {
98                out.push("--".to_string());
99                i += 1;
100                out.extend(argv[i..].iter().cloned());
101            } else {
102                i += 1;
103                out.extend(argv[i..].iter().cloned());
104            }
105            break;
106        }
107
108        if arg == "--end-of-options" {
109            if !keep_unknown {
110                i += 1;
111                out.extend(argv[i..].iter().cloned());
112            } else {
113                out.push(arg.clone());
114                i += 1;
115                out.extend(argv[i..].iter().cloned());
116            }
117            break;
118        }
119
120        if arg.starts_with("--") {
121            let name = arg.strip_prefix("--").unwrap_or(arg.as_str());
122            if internal_help && (name == "help" || name == "help-all") {
123                return Err(ParseOptionsToolError::Help);
124            }
125            if let Some(rest) = name.strip_prefix("opt=") {
126                opt = parse_int_opt(rest)?;
127                i += 1;
128                continue;
129            }
130            if name == "opt" {
131                i += 1;
132                let v = argv.get(i).ok_or_else(|| {
133                    ParseOptionsToolError::Fatal(
134                        "error: option `opt' requires a value\n".to_string(),
135                    )
136                })?;
137                opt = parse_int_opt(v)?;
138                i += 1;
139                continue;
140            }
141            if keep_unknown {
142                out.push(arg.clone());
143                i += 1;
144                continue;
145            }
146            let key = name.split('=').next().unwrap_or(name);
147            return Err(ParseOptionsToolError::Fatal(format!(
148                "error: unknown option `{key}`\nusage: <...> cmd [options]\n"
149            )));
150        }
151
152        // short options: -o, -oN, -h, -u2, ...
153        let body = &arg[1..];
154        if let Some(rest) = body.strip_prefix('o') {
155            if rest.is_empty() {
156                i += 1;
157                let v = argv.get(i).ok_or_else(|| {
158                    ParseOptionsToolError::Fatal("error: switch `o' requires a value\n".to_string())
159                })?;
160                opt = parse_int_opt(v)?;
161                i += 1;
162            } else {
163                opt = parse_int_opt(rest)?;
164                i += 1;
165            }
166            continue;
167        }
168        if (body == "h" || (internal_help && body.starts_with('h'))) && internal_help {
169            return Err(ParseOptionsToolError::Help);
170        }
171        if no_internal_help && (body == "h" || body.starts_with('h')) {
172            if keep_unknown {
173                out.push(arg.clone());
174                i += 1;
175                continue;
176            }
177            return Err(ParseOptionsToolError::Fatal(
178                "error: unknown switch `h'\nusage: <...> cmd [options]\n".to_string(),
179            ));
180        }
181        if keep_unknown {
182            out.push(arg.clone());
183            i += 1;
184            continue;
185        }
186        let c = body.chars().next().unwrap_or('?');
187        return Err(ParseOptionsToolError::Fatal(format!(
188            "error: unknown switch `{c}`\nusage: <...> cmd [options]\n"
189        )));
190    }
191
192    println!("opt: {opt}");
193    for (idx, a) in out.iter().enumerate() {
194        println!("arg {:02}: {a}", idx);
195    }
196    Ok(0)
197}