clap_features/
clap_features.rs1use clap::{Arg, ArgAction, ArgGroup, Command, ValueHint, value_parser};
2use clap_tui::TuiApp;
3
4fn build_command() -> Command {
5 Command::new("clap-features")
6 .version("0.1.0")
7 .about("Manual compatibility and stress fixture for clap feature coverage")
8 .long_about(
9 "A broad clap command graph used to exercise clap features in clap-tui. \
10 This example is a diagnostic compatibility fixture rather than a learning-oriented CLI.",
11 )
12 .next_line_help(true)
13 .arg_required_else_help(true)
14 .subcommand_negates_reqs(true)
15 .allow_external_subcommands(true)
16 .external_subcommand_value_parser(value_parser!(String))
17 .group(
18 ArgGroup::new("required_mode_group")
19 .args(["required_mode_fast", "required_mode_safe"])
20 .required(true),
21 )
22 .arg(
23 Arg::new("global_counted_verbosity")
24 .short('v')
25 .long("global-counted-verbosity")
26 .help("Counted global flag")
27 .long_help("Repeat to increase the count, for example `-v`, `-vv`, or `-vvv`.")
28 .help_heading("Global")
29 .display_order(1)
30 .action(ArgAction::Count)
31 .global(true),
32 )
33 .arg(
34 Arg::new("global_conflicts_with_count")
35 .long("global-conflicts-with-count")
36 .help("Global flag that conflicts with --global-counted-verbosity")
37 .help_heading("Global")
38 .display_order(2)
39 .action(ArgAction::SetTrue)
40 .conflicts_with("global_counted_verbosity")
41 .global(true),
42 )
43 .arg(
44 Arg::new("global_optional_value_enum")
45 .long("global-optional-value-enum")
46 .visible_alias("global-optional-value-enum-alias")
47 .help("Global option with an optional value and default missing value")
48 .long_help(
49 "Optional-value flag. `--global-optional-value-enum` uses the default missing \
50 value, while `--global-optional-value-enum=never` sets an explicit value.",
51 )
52 .help_heading("Global")
53 .display_order(3)
54 .num_args(0..=1)
55 .require_equals(true)
56 .default_value("auto")
57 .default_missing_value("always")
58 .value_parser(["auto", "always", "never"])
59 .global(true),
60 )
61 .arg(
62 Arg::new("global_config_path")
63 .short('c')
64 .long("global-config-path")
65 .aliases(["config-path-alias"])
66 .visible_alias("cfg-path")
67 .help("Global file path option")
68 .help_heading("Global")
69 .display_order(4)
70 .value_name("FILE")
71 .value_hint(ValueHint::FilePath)
72 .global(true),
73 )
74 .arg(
75 Arg::new("global_defaulted_value_enum")
76 .long("global-defaulted-value-enum")
77 .visible_aliases(["global-visible-alias-a", "global-visible-alias-b"])
78 .help("Global option with a defaulted enumerated value")
79 .help_heading("Global")
80 .display_order(5)
81 .default_value("dev")
82 .value_parser(["dev", "stage", "prod"])
83 .global(true),
84 )
85 .arg(
86 Arg::new("required_mode_fast")
87 .long("required-mode-fast")
88 .help("First member of a required ArgGroup")
89 .help_heading("Groups")
90 .display_order(10)
91 .action(ArgAction::SetTrue)
92 .group("required_mode_group"),
93 )
94 .arg(
95 Arg::new("required_mode_safe")
96 .long("required-mode-safe")
97 .help("Second member of a required ArgGroup")
98 .help_heading("Groups")
99 .display_order(11)
100 .action(ArgAction::SetTrue)
101 .group("required_mode_group"),
102 )
103 .arg(
104 Arg::new("debug")
105 .long("debug")
106 .help("Boolean flag used by --conflicts-with-debug")
107 .help_heading("Relations")
108 .display_order(20)
109 .action(ArgAction::SetTrue),
110 )
111 .arg(
112 Arg::new("conflicts_with_debug")
113 .long("conflicts-with-debug")
114 .help("Boolean flag that conflicts with --debug")
115 .help_heading("Relations")
116 .display_order(21)
117 .action(ArgAction::SetTrue)
118 .conflicts_with("debug"),
119 )
120 .arg(
121 Arg::new("requires_config")
122 .long("requires-config")
123 .help("Boolean flag that requires --global-config-path")
124 .help_heading("Relations")
125 .display_order(22)
126 .action(ArgAction::SetTrue)
127 .requires("global_config_path"),
128 )
129 .arg(
130 Arg::new("allow_negative_integer")
131 .long("allow-negative-integer")
132 .help("Integer option that accepts negative numbers")
133 .help_heading("Input")
134 .display_order(30)
135 .default_value("0")
136 .allow_negative_numbers(true)
137 .value_parser(value_parser!(i32)),
138 )
139 .arg(
140 Arg::new("multiple_values")
141 .short('m')
142 .long("multiple-values")
143 .visible_alias("multiple-values-alias")
144 .help("Repeatable option that also accepts comma-delimited values")
145 .help_heading("Input")
146 .display_order(31)
147 .action(ArgAction::Append)
148 .num_args(1..)
149 .value_name("VALUE")
150 .value_delimiter(','),
151 )
152 .arg(
153 Arg::new("key_value_pair")
154 .long("key-value-pair")
155 .help("Option that captures two values per occurrence")
156 .help_heading("Input")
157 .display_order(32)
158 .action(ArgAction::Append)
159 .num_args(2)
160 .value_names(["KEY", "VALUE"]),
161 )
162 .arg(
163 Arg::new("terminated_paths")
164 .long("terminated-paths")
165 .help("Multi-value path list terminated by `;`")
166 .help_heading("Input")
167 .display_order(33)
168 .action(ArgAction::Append)
169 .num_args(1..)
170 .value_name("PATH")
171 .value_hint(ValueHint::AnyPath)
172 .value_terminator(";"),
173 )
174 .subcommand(
175 Command::new("required-args")
176 .about("Required positional arguments plus defaults and repeated values")
177 .display_order(1)
178 .arg(
179 Arg::new("required_path")
180 .help("Required positional path")
181 .required(true)
182 .index(1)
183 .value_hint(ValueHint::DirPath),
184 )
185 .arg(
186 Arg::new("defaulted_host")
187 .long("defaulted-host")
188 .help("Option with a default value")
189 .default_value("127.0.0.1"),
190 )
191 .arg(
192 Arg::new("defaulted_port")
193 .long("defaulted-port")
194 .help("Option with a default value parser")
195 .default_value("8080")
196 .value_parser(value_parser!(u16)),
197 )
198 .arg(
199 Arg::new("repeated_value_enum")
200 .long("repeated-value-enum")
201 .help("Repeated value enum input with comma-delimited values")
202 .action(ArgAction::Append)
203 .num_args(1..)
204 .value_delimiter(',')
205 .value_parser(["gzip", "brotli", "http2"]),
206 ),
207 )
208 .subcommand(
209 Command::new("repeated-values")
210 .about("Repeated positional values plus a trailing `last(true)` argument")
211 .short_flag('R')
212 .long_flag("repeated-values")
213 .visible_alias("repeated-values-alias")
214 .display_order(2)
215 .arg(
216 Arg::new("required_target")
217 .long("required-target")
218 .help("Required enumerated option")
219 .required(true)
220 .value_parser(["local", "s3", "gcs"]),
221 )
222 .arg(
223 Arg::new("repeated_paths")
224 .help("Required repeated positional values")
225 .required(true)
226 .index(1)
227 .action(ArgAction::Append)
228 .num_args(1..)
229 .value_hint(ValueHint::AnyPath),
230 )
231 .arg(
232 Arg::new("last_filters")
233 .help("Additional filter expressions after `--`")
234 .index(2)
235 .last(true)
236 .action(ArgAction::Append)
237 .num_args(1..)
238 .allow_hyphen_values(true),
239 ),
240 )
241 .subcommand(
242 Command::new("trailing-var-args")
243 .about("Trailing raw arguments, command hints, and repeated env pairs")
244 .visible_aliases(["spawn-alias", "run-raw-alias"])
245 .display_order(3)
246 .arg_required_else_help(true)
247 .arg(
248 Arg::new("optional_cwd")
249 .long("optional-cwd")
250 .help("Optional working directory")
251 .value_hint(ValueHint::DirPath),
252 )
253 .arg(
254 Arg::new("repeated_env_pair")
255 .long("repeated-env-pair")
256 .help("Repeated option with comma-delimited KEY=VALUE items")
257 .action(ArgAction::Append)
258 .num_args(1..)
259 .value_name("KEY=VALUE")
260 .value_delimiter(','),
261 )
262 .arg(
263 Arg::new("required_program")
264 .help("Required command name")
265 .required(true)
266 .index(1)
267 .value_hint(ValueHint::CommandName),
268 )
269 .arg(
270 Arg::new("raw_argv")
271 .help("Trailing raw command arguments")
272 .index(2)
273 .action(ArgAction::Append)
274 .num_args(1..)
275 .trailing_var_arg(true)
276 .allow_hyphen_values(true)
277 .value_hint(ValueHint::CommandWithArguments),
278 ),
279 )
280 .subcommand(
281 Command::new("args-conflict-with-subcommands")
282 .about("args_conflicts_with_subcommands and subcommand_negates_reqs coverage")
283 .display_order(4)
284 .arg_required_else_help(true)
285 .subcommand_negates_reqs(true)
286 .args_conflicts_with_subcommands(true)
287 .arg(
288 Arg::new("required_template")
289 .long("required-template")
290 .help("Required unless a subcommand is chosen")
291 .required(true)
292 .value_name("NAME"),
293 )
294 .subcommand(Command::new("plan").about("Render the workflow plan"))
295 .subcommand(Command::new("apply").about("Execute the workflow")),
296 )
297 .subcommand(
298 Command::new("nested-subcommands")
299 .about("Nested subcommands with subcommand_required")
300 .display_order(5)
301 .arg_required_else_help(true)
302 .subcommand_required(true)
303 .subcommand(Command::new("cache").about("Inspect cache state"))
304 .subcommand(
305 Command::new("users")
306 .about("Inspect user state")
307 .arg_required_else_help(true)
308 .subcommand_required(true)
309 .subcommand(
310 Command::new("list").about("List known users").arg(
311 Arg::new("user_status")
312 .long("user-status")
313 .help("Filter users by account state")
314 .value_parser(["active", "disabled", "pending"]),
315 ),
316 )
317 .subcommand(
318 Command::new("sessions")
319 .about("Inspect active user sessions")
320 .arg(
321 Arg::new("session_user")
322 .long("session-user")
323 .help("Only show sessions for this user")
324 .value_name("USER"),
325 ),
326 ),
327 ),
328 )
329 .subcommand(
330 Command::new("exclusive-flag")
331 .about("Exclusive flag coverage")
332 .visible_alias("exclusive-flag-alias")
333 .display_order(6)
334 .arg(
335 Arg::new("dump_defaults")
336 .long("dump-defaults")
337 .help("Exclusive flag that must be passed alone")
338 .action(ArgAction::SetTrue)
339 .exclusive(true),
340 )
341 .arg(
342 Arg::new("optional_item")
343 .help("Optional item to inspect")
344 .index(1)
345 .value_parser(["config", "cache", "state"]),
346 ),
347 )
348}
349
350fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
351 let app = TuiApp::from_command(build_command());
352 app.run_with_matches::<_, std::io::Error>(|matches| {
353 println!("{matches:#?}");
354 Ok(())
355 })?;
356 Ok(())
357}