Skip to main content

clap_features/
clap_features.rs

1use 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}