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(
145 "error: option `opt' requires a value\n".to_string(),
146 )
147 })?;
148 opt = parse_int(v)?;
149 i += 1;
150 continue;
151 }
152 if subcommand_optional && keep_unknown {
153 return dispatch_subcmd_one(opt, &argv[i..], keep_argv0);
154 }
155 return Err(ParseOptionsToolError::Fatal(format!(
156 "error: unknown option `{name}'\nusage: test-tool parse-subcommand [flag-options] cmd <subcommand>\n"
157 )));
158 }
159
160 let body = &arg[1..];
161 if let Some(rest) = body.strip_prefix('o') {
162 if rest.is_empty() {
163 i += 1;
164 let v = argv.get(i).ok_or_else(|| {
165 ParseOptionsToolError::Fatal("error: switch `o' requires a value\n".to_string())
166 })?;
167 opt = parse_int(v)?;
168 i += 1;
169 } else {
170 opt = parse_int(rest)?;
171 i += 1;
172 }
173 continue;
174 }
175 if internal_help && (body == "h" || body.starts_with('h')) {
176 return Err(ParseOptionsToolError::Help);
177 }
178 if subcommand_optional && keep_unknown {
179 return dispatch_subcmd_one(opt, &argv[i..], keep_argv0);
180 }
181 let c = body.chars().next().unwrap_or('?');
182 return Err(ParseOptionsToolError::Fatal(format!(
183 "error: unknown switch `{c}'\nusage: test-tool parse-subcommand [flag-options] cmd <subcommand>\n"
184 )));
185 }
186
187 if !subcommand_optional {
188 return Err(fatal_need_subcommand());
189 }
190 dispatch_subcmd_one(opt, &[], keep_argv0)
191}
192
193fn finish_after_double_dash(
194 opt: i32,
195 rest: &[String],
196 subcommand_optional: bool,
197 keep_argv0: bool,
198) -> Result<i32, ParseOptionsToolError> {
199 if subcommand_optional {
200 dispatch_subcmd_one(opt, rest, keep_argv0)
201 } else {
202 Err(fatal_need_subcommand())
203 }
204}
205
206fn match_dashless(
207 arg: &str,
208 opt: i32,
209 rest: &[String],
210 subcommand_optional: bool,
211 keep_argv0: bool,
212) -> Result<i32, ParseOptionsToolError> {
213 match arg {
214 "subcmd-one" => dispatch_subcmd(Subcmd::One, opt, rest, keep_argv0),
215 "subcmd-two" => dispatch_subcmd(Subcmd::Two, opt, rest, keep_argv0),
216 _ => {
217 if subcommand_optional {
218 dispatch_subcmd_one(opt, rest, keep_argv0)
219 } else {
220 Err(ParseOptionsToolError::Fatal(format!(
221 "error: unknown subcommand: `{arg}'\nusage: test-tool parse-subcommand [flag-options] cmd <subcommand>\n"
222 )))
223 }
224 }
225 }
226}
227
228fn dispatch_subcmd(
229 sub: Subcmd,
230 opt: i32,
231 rest: &[String],
232 keep_argv0: bool,
233) -> Result<i32, ParseOptionsToolError> {
234 println!("opt: {opt}");
235 match sub {
236 Subcmd::One => {
237 println!("fn: subcmd_one");
238 print_args_maybe_keep_argv0(rest, keep_argv0);
239 }
240 Subcmd::Two => {
241 println!("fn: subcmd_two");
242 print_args_maybe_keep_argv0(rest, keep_argv0);
243 }
244 }
245 Ok(0)
246}
247
248fn dispatch_subcmd_one(
249 opt: i32,
250 rest: &[String],
251 keep_argv0: bool,
252) -> Result<i32, ParseOptionsToolError> {
253 println!("opt: {opt}");
254 println!("fn: subcmd_one");
255 print_args_maybe_keep_argv0(rest, keep_argv0);
256 Ok(0)
257}
258
259fn print_args_maybe_keep_argv0(rest: &[String], keep_argv0: bool) {
260 if keep_argv0 {
261 println!("arg {:02}: cmd", 0);
262 for (idx, a) in rest.iter().enumerate() {
263 println!("arg {:02}: {a}", idx + 1);
264 }
265 } else {
266 for (idx, a) in rest.iter().enumerate() {
267 println!("arg {:02}: {a}", idx);
268 }
269 }
270}