1#[cfg(any(feature = "view", feature = "help"))]
4use std::ffi::OsString;
5
6use clap::{ColorChoice, Command};
7
8#[cfg(any(feature = "view", feature = "help"))]
9use crate::{ColorMode, OutputFormat};
10
11#[cfg(any(feature = "view", feature = "help"))]
14#[derive(Clone, Copy, Debug, Eq, PartialEq)]
15pub(crate) struct ParsedOutput {
16 pub(crate) format: OutputFormat,
17 pub(crate) color: ColorMode,
18}
19
20#[cfg(any(feature = "view", feature = "help"))]
25pub(crate) fn parsed_output<C: clap::CommandFactory>(raw: &[OsString]) -> ParsedOutput {
26 let command = C::command()
27 .arg_required_else_help(false)
28 .args_override_self(true)
29 .disable_help_flag(true)
30 .disable_version_flag(true)
31 .ignore_errors(true)
32 .color(ColorChoice::Never);
33 let Ok(matches) = command.try_get_matches_from(raw) else {
34 return ParsedOutput {
35 format: OutputFormat::Pretty,
36 color: ColorMode::Auto,
37 };
38 };
39 let format = matches
40 .try_get_one::<OutputFormat>("format")
41 .ok()
42 .flatten()
43 .copied()
44 .unwrap_or_default();
45 let color = matches
46 .try_get_one::<ColorMode>("color")
47 .ok()
48 .flatten()
49 .copied()
50 .unwrap_or_default();
51 let no_color = matches
52 .try_get_one::<bool>("no_color")
53 .ok()
54 .flatten()
55 .copied()
56 .unwrap_or(false);
57 ParsedOutput {
58 format,
59 color: crate::flags::resolve_color(color, no_color),
60 }
61}
62
63#[cfg(feature = "help")]
65pub(crate) fn wants_help<C: clap::CommandFactory>(raw: &[OsString]) -> bool {
66 let command = apply_defaults(C::command()).color(ColorChoice::Never);
67 matches!(
68 command.try_get_matches_from(raw),
69 Err(error) if error.kind() == clap::error::ErrorKind::DisplayHelp
70 )
71}
72
73#[cfg(feature = "help")]
75pub(crate) fn requires_input<C: clap::CommandFactory>() -> bool {
76 let command = C::command();
77 command.is_arg_required_else_help_set() || command.is_subcommand_required_set()
78}
79
80#[must_use]
86pub fn apply_defaults(command: Command) -> Command {
87 assert_help_enabled(&command);
88 command
89 .args_override_self(true)
90 .color(ColorChoice::Auto)
91 .disable_help_flag(false)
92 .disable_version_flag(false)
93}
94
95pub fn verify<C: clap::CommandFactory>() {
101 C::command().debug_assert();
102 assert_help_enabled(&C::command());
103}
104
105pub fn assert_help_enabled(command: &Command) {
109 assert!(
110 !command.is_disable_help_flag_set(),
111 "disable_help_flag is forbidden; ctl-core owns -h/--help"
112 );
113 assert!(
114 !command.is_disable_version_flag_set(),
115 "disable_version_flag is forbidden; ctl-core owns -V/--version"
116 );
117}
118
119#[cfg(test)]
120mod tests {
121 use clap::{Command, CommandFactory, Parser};
122
123 use super::{apply_defaults, assert_help_enabled};
124
125 #[derive(Parser)]
126 #[command(version, about = "toy", arg_required_else_help = true)]
127 struct Toy {
128 #[command(subcommand)]
129 command: ToyCmd,
130 }
131
132 #[derive(clap::Subcommand)]
133 enum ToyCmd {
134 Status,
135 }
136
137 #[test]
138 fn verify_toy() {
139 super::verify::<Toy>();
140 }
141
142 #[test]
143 fn derive_keeps_help_and_version() {
144 let mut command = apply_defaults(Toy::command());
145 assert_help_enabled(&command);
146 assert!(command.is_args_override_self());
147 let help = command.render_long_help().to_string();
148 assert!(help.contains("-h, --help"));
149 assert!(help.contains("-V, --version"));
150 }
151
152 #[test]
153 fn preserves_bare_invocation_policy() {
154 let command = apply_defaults(Toy::command().arg_required_else_help(false));
155 assert!(!command.is_arg_required_else_help_set());
156 }
157
158 #[test]
159 #[should_panic(expected = "disable_help_flag is forbidden")]
160 fn rejects_disabled_help() {
161 let command = Command::new("x").disable_help_flag(true);
162 assert_help_enabled(&command);
163 }
164}