grit_lib/parse_options_test_tool/
sub_cmd.rs1use 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;
10const SUBCOMMAND_OPTIONAL: u32 = 1 << 7;
11
12#[derive(Clone, Copy)]
13enum Subcmd {
14 One,
15 Two,
16}
17
18pub fn run_parse_subcommand(args: &[String]) -> Result<i32, ParseOptionsToolError> {
20 if args.len() < 2 {
21 return Err(fatal_need_subcommand());
22 }
23 let mut i = 1usize;
24 let mut test_flags: u32 = 0;
25 while i < args.len() {
26 let a = &args[i];
27 if !a.starts_with("--") {
28 break;
29 }
30 match a.as_str() {
31 "--keep-dashdash" => test_flags |= KEEP_DASHDASH,
32 "--stop-at-non-option" => test_flags |= STOP_AT_NON_OPTION,
33 "--keep-argv0" => test_flags |= KEEP_ARGV0,
34 "--keep-unknown-opt" => test_flags |= KEEP_UNKNOWN_OPT,
35 "--no-internal-help" => test_flags |= NO_INTERNAL_HELP,
36 "--subcommand-optional" => test_flags |= SUBCOMMAND_OPTIONAL,
37 _ => {
38 return Err(ParseOptionsToolError::Fatal(format!(
39 "error: unknown option `{a}'\n"
40 )));
41 }
42 }
43 i += 1;
44 }
45 if args.get(i).map(|s| s.as_str()) != Some("cmd") {
46 return Err(fatal_need_subcommand());
47 }
48
49 if (test_flags & STOP_AT_NON_OPTION) != 0 {
50 return Err(ParseOptionsToolError::Bug(
51 "BUG: parse-options.c:767: subcommands are incompatible with PARSE_OPT_STOP_AT_NON_OPTION\n"
52 .to_string(),
53 ));
54 }
55 if (test_flags & KEEP_UNKNOWN_OPT) != 0 && (test_flags & SUBCOMMAND_OPTIONAL) == 0 {
56 return Err(ParseOptionsToolError::Bug(
57 "BUG: parse-options.c:770: subcommands are incompatible with PARSE_OPT_KEEP_UNKNOWN_OPT unless in combination with PARSE_OPT_SUBCOMMAND_OPTIONAL\n"
58 .to_string(),
59 ));
60 }
61 if (test_flags & KEEP_DASHDASH) != 0 && (test_flags & SUBCOMMAND_OPTIONAL) == 0 {
62 return Err(ParseOptionsToolError::Bug(
63 "BUG: parse-options.c:772: subcommands are incompatible with PARSE_OPT_KEEP_DASHDASH unless in combination with PARSE_OPT_SUBCOMMAND_OPTIONAL\n"
64 .to_string(),
65 ));
66 }
67
68 parse_subcommand_inner(&args[i..], test_flags)
69}
70
71fn fatal_need_subcommand() -> ParseOptionsToolError {
72 ParseOptionsToolError::Fatal(
73 "error: need a subcommand\nusage: test-tool parse-subcommand [flag-options] cmd <subcommand>\n"
74 .to_string(),
75 )
76}
77
78fn parse_int(s: &str) -> Result<i32, ParseOptionsToolError> {
79 s.parse().map_err(|_| {
80 ParseOptionsToolError::Fatal("error: option `opt' expects a numerical value\n".to_string())
81 })
82}
83
84fn parse_subcommand_inner(argv: &[String], flags: u32) -> Result<i32, ParseOptionsToolError> {
85 if argv.is_empty() || argv[0] != "cmd" {
86 return Err(fatal_need_subcommand());
87 }
88
89 let keep_dashdash = flags & KEEP_DASHDASH != 0;
90 let keep_argv0 = flags & KEEP_ARGV0 != 0;
91 let keep_unknown = flags & KEEP_UNKNOWN_OPT != 0;
92 let subcommand_optional = flags & SUBCOMMAND_OPTIONAL != 0;
93 let internal_help = flags & NO_INTERNAL_HELP == 0;
94
95 let total_after_cmd = argv.len().saturating_sub(1);
96 let mut opt: i32 = 0;
97 let mut i = 1usize;
98
99 while i < argv.len() {
100 let arg = &argv[i];
101
102 if internal_help && total_after_cmd == 1 && arg == "-h" {
103 return Err(ParseOptionsToolError::Help);
104 }
105
106 if total_after_cmd == 1 && arg == "--git-completion-helper" {
107 println!("subcmd-one subcmd-two --opt= --no-opt");
108 return Ok(0);
109 }
110
111 if arg == "-" || !arg.starts_with('-') {
112 return match_dashless(arg, opt, &argv[i..], subcommand_optional, keep_argv0);
113 }
114
115 if arg == "--" {
116 if keep_dashdash {
117 return dispatch_subcmd_one(opt, &argv[i..], keep_argv0);
118 }
119 i += 1;
120 return finish_after_double_dash(opt, &argv[i..], subcommand_optional, keep_argv0);
121 }
122
123 if arg == "--end-of-options" {
124 if !keep_unknown {
125 i += 1;
126 return finish_after_double_dash(opt, &argv[i..], subcommand_optional, keep_argv0);
127 }
128 return dispatch_subcmd_one(opt, &argv[i..], keep_argv0);
129 }
130
131 if arg.starts_with("--") {
132 let name = arg.strip_prefix("--").unwrap_or(arg.as_str());
133 if internal_help && (name == "help" || name == "help-all") {
134 return Err(ParseOptionsToolError::Help);
135 }
136 if let Some(rest) = name.strip_prefix("opt=") {
137 opt = parse_int(rest)?;
138 i += 1;
139 continue;
140 }
141 if name == "opt" {
142 i += 1;
143 let v = argv.get(i).ok_or_else(|| {
144 ParseOptionsToolError::Fatal("error: option `opt' requires a value\n".to_string())
145 })?;
146 opt = parse_int(v)?;
147 i += 1;
148 continue;
149 }
150 if subcommand_optional && keep_unknown {
151 return dispatch_subcmd_one(opt, &argv[i..], keep_argv0);
152 }
153 return Err(ParseOptionsToolError::Fatal(format!(
154 "error: unknown option `{name}'\nusage: test-tool parse-subcommand [flag-options] cmd <subcommand>\n"
155 )));
156 }
157
158 let body = &arg[1..];
159 if let Some(rest) = body.strip_prefix('o') {
160 if rest.is_empty() {
161 i += 1;
162 let v = argv.get(i).ok_or_else(|| {
163 ParseOptionsToolError::Fatal("error: switch `o' requires a value\n".to_string())
164 })?;
165 opt = parse_int(v)?;
166 i += 1;
167 } else {
168 opt = parse_int(rest)?;
169 i += 1;
170 }
171 continue;
172 }
173 if internal_help && (body == "h" || body.starts_with('h')) {
174 return Err(ParseOptionsToolError::Help);
175 }
176 if subcommand_optional && keep_unknown {
177 return dispatch_subcmd_one(opt, &argv[i..], keep_argv0);
178 }
179 let c = body.chars().next().unwrap_or('?');
180 return Err(ParseOptionsToolError::Fatal(format!(
181 "error: unknown switch `{c}'\nusage: test-tool parse-subcommand [flag-options] cmd <subcommand>\n"
182 )));
183 }
184
185 if !subcommand_optional {
186 return Err(fatal_need_subcommand());
187 }
188 dispatch_subcmd_one(opt, &[], keep_argv0)
189}
190
191fn finish_after_double_dash(
192 opt: i32,
193 rest: &[String],
194 subcommand_optional: bool,
195 keep_argv0: bool,
196) -> Result<i32, ParseOptionsToolError> {
197 if subcommand_optional {
198 dispatch_subcmd_one(opt, rest, keep_argv0)
199 } else {
200 Err(fatal_need_subcommand())
201 }
202}
203
204fn match_dashless(
205 arg: &str,
206 opt: i32,
207 rest: &[String],
208 subcommand_optional: bool,
209 keep_argv0: bool,
210) -> Result<i32, ParseOptionsToolError> {
211 match arg {
212 "subcmd-one" => dispatch_subcmd(Subcmd::One, opt, rest, keep_argv0),
213 "subcmd-two" => dispatch_subcmd(Subcmd::Two, opt, rest, keep_argv0),
214 _ => {
215 if subcommand_optional {
216 dispatch_subcmd_one(opt, rest, keep_argv0)
217 } else {
218 Err(ParseOptionsToolError::Fatal(format!(
219 "error: unknown subcommand: `{arg}'\nusage: test-tool parse-subcommand [flag-options] cmd <subcommand>\n"
220 )))
221 }
222 }
223 }
224}
225
226fn dispatch_subcmd(
227 sub: Subcmd,
228 opt: i32,
229 rest: &[String],
230 keep_argv0: bool,
231) -> Result<i32, ParseOptionsToolError> {
232 println!("opt: {opt}");
233 match sub {
234 Subcmd::One => {
235 println!("fn: subcmd_one");
236 print_args_maybe_keep_argv0(rest, keep_argv0);
237 }
238 Subcmd::Two => {
239 println!("fn: subcmd_two");
240 print_args_maybe_keep_argv0(rest, keep_argv0);
241 }
242 }
243 Ok(0)
244}
245
246fn dispatch_subcmd_one(
247 opt: i32,
248 rest: &[String],
249 keep_argv0: bool,
250) -> Result<i32, ParseOptionsToolError> {
251 println!("opt: {opt}");
252 println!("fn: subcmd_one");
253 print_args_maybe_keep_argv0(rest, keep_argv0);
254 Ok(0)
255}
256
257fn print_args_maybe_keep_argv0(rest: &[String], keep_argv0: bool) {
258 if keep_argv0 {
259 println!("arg {:02}: cmd", 0);
260 for (idx, a) in rest.iter().enumerate() {
261 println!("arg {:02}: {a}", idx + 1);
262 }
263 } else {
264 for (idx, a) in rest.iter().enumerate() {
265 println!("arg {:02}: {a}", idx);
266 }
267 }
268}