#[cfg(feature = "help")]
use std::ffi::OsString;
#[cfg(feature = "help")]
use clap::ArgMatches;
use clap::{Arg, ArgAction, Command};
#[cfg(feature = "help")]
use crate::{ColorMode, OutputFormat};
#[cfg(feature = "help")]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) struct ParsedOutput {
pub(crate) format: OutputFormat,
pub(crate) color: ColorMode,
}
#[cfg(feature = "help")]
pub(crate) fn parsed_output<C: clap::CommandFactory>(raw: &[OsString]) -> ParsedOutput {
let command = C::command()
.arg_required_else_help(false)
.args_override_self(true)
.disable_help_flag(true)
.disable_version_flag(true)
.ignore_errors(true);
let Ok(matches) = command.try_get_matches_from(raw) else {
return ParsedOutput {
format: OutputFormat::Pretty,
color: ColorMode::Auto,
};
};
let format = matches
.try_get_one::<OutputFormat>("format")
.ok()
.flatten()
.copied()
.unwrap_or_default();
let color = matches
.try_get_one::<ColorMode>("color")
.ok()
.flatten()
.copied()
.unwrap_or_default();
let no_color = matches
.try_get_one::<bool>("no_color")
.ok()
.flatten()
.copied()
.unwrap_or(false);
ParsedOutput {
format,
color: crate::flags::resolve_color(color, no_color),
}
}
#[cfg(feature = "help")]
pub(crate) fn wants_help<C: clap::CommandFactory>(raw: &[OsString]) -> bool {
let declared = C::command();
let probe = lift_requirements(apply_defaults(declared.clone()));
match probe.try_get_matches_from(raw) {
Ok(matches) => names_help(&declared, &matches),
Err(error) => error.kind() == clap::error::ErrorKind::DisplayHelp,
}
}
#[cfg(feature = "help")]
fn lift_requirements(command: Command) -> Command {
command
.subcommand_negates_reqs(true)
.mut_subcommands(lift_requirements)
}
#[cfg(feature = "help")]
fn names_help(declared: &Command, matches: &ArgMatches) -> bool {
let Some((name, selected)) = matches.subcommand() else {
return false;
};
match declared.find_subcommand(name) {
Some(next) => names_help(next, selected),
None => name == "help",
}
}
#[cfg(feature = "app")]
pub(crate) fn requires_input<C: clap::CommandFactory>() -> bool {
let command = C::command();
command.is_arg_required_else_help_set() || command.is_subcommand_required_set()
}
#[cfg_attr(feature = "app", doc = "", doc = "[`App`]: crate::App")]
#[cfg_attr(not(feature = "app"), doc = "", doc = "[`App`]: crate#cargo-features")]
#[cfg_attr(
feature = "help",
doc = "",
doc = "[`help::try_emit`]: crate::help::try_emit"
)]
#[cfg_attr(
not(feature = "help"),
doc = "",
doc = "[`help::try_emit`]: crate#cargo-features"
)]
#[must_use]
pub fn apply_defaults(command: Command) -> Command {
assert_help_enabled(&command);
with_help(command.args_override_self(true).disable_version_flag(false))
}
const HELP_ABOUT: &str = "Print this message or the help of the given subcommand(s)";
#[cfg(feature = "surface-model")]
pub(crate) fn is_generated_help(command: &Command) -> bool {
command.get_name() == "help"
&& command
.get_about()
.is_some_and(|about| about.to_string() == HELP_ABOUT)
}
pub(crate) fn with_help(command: Command) -> Command {
with_help_below(command, false)
}
fn with_help_below(mut command: Command, inherited_long: bool) -> Command {
let long = inherited_long || has_long_help(&command);
let passed_down = inherited_long
|| command
.get_arguments()
.any(|arg| arg.is_global_set() && shows_long_help(arg));
let help_subcommand = (command.has_subcommands()
&& !command.is_disable_help_subcommand_set()
&& command.find_subcommand("help").is_none())
.then(|| help_subcommand(&command));
if !command.is_disable_help_flag_set()
&& command.get_arguments().all(|arg| arg.get_id() != "help")
{
command = command.disable_help_flag(true).arg(help_flag(long));
}
command = command.mut_subcommands(|subcommand| with_help_below(subcommand, passed_down));
match help_subcommand {
Some(help) => command.disable_help_subcommand(true).subcommand(help),
None => command,
}
}
fn help_flag(long: bool) -> Arg {
let flag = Arg::new("help")
.short('h')
.long("help")
.action(ArgAction::Help);
if long {
flag.help("Print help (see more with '--help')")
.long_help("Print help (see a summary with '-h')")
} else {
flag.help("Print help")
}
}
fn help_subcommand(parent: &Command) -> Command {
let help_help = Command::new("help")
.about(HELP_ABOUT)
.disable_help_flag(true)
.disable_version_flag(true);
Command::new("help")
.about(HELP_ABOUT)
.disable_help_flag(true)
.disable_version_flag(true)
.disable_help_subcommand(true)
.subcommands(parent.get_subcommands().map(copy_for_help))
.subcommand(help_help)
}
fn copy_for_help(command: &Command) -> Command {
let copy = Command::new(command.get_name().to_owned())
.hide(command.is_hide_set())
.disable_help_flag(true)
.disable_version_flag(true)
.disable_help_subcommand(true)
.subcommands(command.get_subcommands().map(copy_for_help));
match command.get_about() {
Some(about) => copy.about(about.clone()),
None => copy,
}
}
fn has_long_help(command: &Command) -> bool {
command.get_long_about().is_some()
|| command.get_before_long_help().is_some()
|| command.get_after_long_help().is_some()
|| command.get_arguments().any(shows_long_help)
}
fn shows_long_help(arg: &Arg) -> bool {
!arg.is_hide_set()
&& (arg.get_long_help().is_some()
|| arg.is_hide_long_help_set()
|| arg.is_hide_short_help_set()
|| (!arg.is_hide_possible_values_set()
&& arg
.get_possible_values()
.iter()
.any(|value| !value.is_hide_set() && value.get_help().is_some())))
}
pub fn verify<C: clap::CommandFactory>() {
C::command().debug_assert();
assert_help_enabled(&C::command());
}
pub(crate) fn assert_help_enabled(command: &Command) {
assert!(
!command.is_disable_help_flag_set(),
"disable_help_flag is forbidden; ctl-core owns -h/--help"
);
assert!(
!command.is_disable_version_flag_set(),
"disable_version_flag is forbidden; ctl-core owns -V/--version"
);
}
#[cfg(test)]
mod tests {
use clap::{Command, CommandFactory, Parser};
use super::{apply_defaults, assert_help_enabled};
#[derive(Parser)]
#[command(version, about = "toy", arg_required_else_help = true)]
struct Toy {
#[command(subcommand)]
command: ToyCmd,
}
#[derive(clap::Subcommand)]
enum ToyCmd {
Status,
}
#[test]
fn verify_toy() {
super::verify::<Toy>();
}
#[test]
fn derive_keeps_help_and_version() {
let mut command = apply_defaults(Toy::command());
assert!(command.is_args_override_self());
command.build();
let flags = command
.get_arguments()
.filter_map(|arg| arg.get_short().zip(arg.get_long()))
.collect::<Vec<_>>();
assert!(flags.contains(&('h', "help")), "{flags:?}");
assert!(flags.contains(&('V', "version")), "{flags:?}");
let status = command.find_subcommand("status").map(|status| {
status
.get_arguments()
.any(|arg| arg.get_short() == Some('h'))
});
assert_eq!(status, Some(true));
assert!(command.find_subcommand("help").is_some());
}
#[test]
fn defaults_apply_once() {
let once = apply_defaults(Toy::command());
let twice = super::with_help(once.clone());
assert_eq!(once.get_arguments().count(), twice.get_arguments().count());
assert_eq!(
once.get_subcommands().count(),
twice.get_subcommands().count()
);
}
#[test]
fn preserves_bare_invocation_policy() {
let command = apply_defaults(Toy::command().arg_required_else_help(false));
assert!(!command.is_arg_required_else_help_set());
}
#[test]
#[should_panic(expected = "disable_help_flag is forbidden")]
fn rejects_disabled_help() {
let command = Command::new("x").disable_help_flag(true);
assert_help_enabled(&command);
}
}