From 22b545b98b71b0e58b0f3a855a199a5d834ad7e8 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 17 Jul 2023 10:37:26 -0500 Subject: [PATCH 1/2] feat(help): Explicit control over short/long help Fixes #4687 --- clap_builder/src/builder/action.rs | 62 ++++++++++++++++++++++++++++++ clap_builder/src/parser/parser.rs | 10 +++++ tests/builder/help.rs | 59 ++++++++++++++++++++++++++++ 3 files changed, 131 insertions(+) diff --git a/clap_builder/src/builder/action.rs b/clap_builder/src/builder/action.rs index 79ee9238e47..2def801f401 100644 --- a/clap_builder/src/builder/action.rs +++ b/clap_builder/src/builder/action.rs @@ -257,6 +257,58 @@ pub enum ArgAction { /// # } /// ``` Help, + /// When encountered, display [`Command::print_help`][super::Command::print_help] + /// + /// # Examples + /// + /// ```rust + /// # #[cfg(feature = "help")] { + /// # use clap_builder as clap; + /// # use clap::Command; + /// # use clap::Arg; + /// let cmd = Command::new("mycmd") + /// .arg( + /// Arg::new("special-help") + /// .short('?') + /// .action(clap::ArgAction::HelpShort) + /// ); + /// + /// // Existing help still exists + /// let err = cmd.clone().try_get_matches_from(["mycmd", "-h"]).unwrap_err(); + /// assert_eq!(err.kind(), clap::error::ErrorKind::DisplayHelp); + /// + /// // New help available + /// let err = cmd.try_get_matches_from(["mycmd", "-?"]).unwrap_err(); + /// assert_eq!(err.kind(), clap::error::ErrorKind::DisplayHelp); + /// # } + /// ``` + HelpShort, + /// When encountered, display [`Command::print_long_help`][super::Command::print_long_help] + /// + /// # Examples + /// + /// ```rust + /// # #[cfg(feature = "help")] { + /// # use clap_builder as clap; + /// # use clap::Command; + /// # use clap::Arg; + /// let cmd = Command::new("mycmd") + /// .arg( + /// Arg::new("special-help") + /// .short('?') + /// .action(clap::ArgAction::HelpLong) + /// ); + /// + /// // Existing help still exists + /// let err = cmd.clone().try_get_matches_from(["mycmd", "-h"]).unwrap_err(); + /// assert_eq!(err.kind(), clap::error::ErrorKind::DisplayHelp); + /// + /// // New help available + /// let err = cmd.try_get_matches_from(["mycmd", "-?"]).unwrap_err(); + /// assert_eq!(err.kind(), clap::error::ErrorKind::DisplayHelp); + /// # } + /// ``` + HelpLong, /// When encountered, display [`Command::version`][super::Command::version] /// /// Depending on the flag, [`Command::long_version`][super::Command::long_version] may be shown @@ -299,6 +351,8 @@ impl ArgAction { Self::SetFalse => false, Self::Count => false, Self::Help => false, + Self::HelpShort => false, + Self::HelpLong => false, Self::Version => false, } } @@ -311,6 +365,8 @@ impl ArgAction { Self::SetFalse => Some(std::ffi::OsStr::new("true")), Self::Count => Some(std::ffi::OsStr::new("0")), Self::Help => None, + Self::HelpShort => None, + Self::HelpLong => None, Self::Version => None, } } @@ -323,6 +379,8 @@ impl ArgAction { Self::SetFalse => Some(std::ffi::OsStr::new("false")), Self::Count => None, Self::Help => None, + Self::HelpShort => None, + Self::HelpLong => None, Self::Version => None, } } @@ -335,6 +393,8 @@ impl ArgAction { Self::SetFalse => Some(super::ValueParser::bool()), Self::Count => Some(crate::value_parser!(u8).into()), Self::Help => None, + Self::HelpShort => None, + Self::HelpLong => None, Self::Version => None, } } @@ -348,6 +408,8 @@ impl ArgAction { Self::SetFalse => None, Self::Count => Some(AnyValueId::of::()), Self::Help => None, + Self::HelpShort => None, + Self::HelpLong => None, Self::Version => None, } } diff --git a/clap_builder/src/parser/parser.rs b/clap_builder/src/parser/parser.rs index d2e198b2885..3e02d4c0bf7 100644 --- a/clap_builder/src/parser/parser.rs +++ b/clap_builder/src/parser/parser.rs @@ -1237,6 +1237,16 @@ impl<'cmd> Parser<'cmd> { debug!("Help: use_long={use_long}"); Err(self.help_err(use_long)) } + ArgAction::HelpShort => { + let use_long = false; + debug!("Help: use_long={use_long}"); + Err(self.help_err(use_long)) + } + ArgAction::HelpLong => { + let use_long = true; + debug!("Help: use_long={use_long}"); + Err(self.help_err(use_long)) + } ArgAction::Version => { let use_long = match ident { Some(Identifier::Long) => true, diff --git a/tests/builder/help.rs b/tests/builder/help.rs index 401bc432ff2..b4e6e358525 100644 --- a/tests/builder/help.rs +++ b/tests/builder/help.rs @@ -1063,6 +1063,65 @@ Options: utils::assert_output(cmd, "myapp --help", LONG_ABOUT, false); } +#[test] +fn explicit_short_long_help() { + static SHORT_ABOUT: &str = "\ +bar + +Usage: myapp [OPTIONS] [arg1] + +Arguments: + [arg1] some option + +Options: + -? + -h, --help + -V, --version Print version +"; + + static LONG_ABOUT: &str = "\ +something really really long, with +multiple lines of text +that should be displayed + +Usage: myapp [OPTIONS] [arg1] + +Arguments: + [arg1] + some option + +Options: + -? + + + -h, --help + + + -V, --version + Print version +"; + + let cmd = Command::new("myapp") + .disable_help_flag(true) + .version("1.0") + .author("foo") + .about("bar") + .long_about( + "something really really long, with\nmultiple lines of text\nthat should be displayed", + ) + .arg(Arg::new("arg1").help("some option")) + .arg(Arg::new("short").short('?').action(ArgAction::HelpShort)) + .arg( + Arg::new("long") + .short('h') + .long("help") + .action(ArgAction::HelpLong), + ); + utils::assert_output(cmd.clone(), "myapp -?", SHORT_ABOUT, false); + utils::assert_output(cmd.clone(), "myapp -h", LONG_ABOUT, false); + utils::assert_output(cmd, "myapp --help", LONG_ABOUT, false); +} + #[test] fn ripgrep_usage() { static RIPGREP_USAGE: &str = "\ From 36afe99bfae3c845bad8f2177e101a68731071b2 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 17 Jul 2023 10:40:42 -0500 Subject: [PATCH 2/2] fix(help): Skip `[OPTIONS]` if help/version action Our code for detecting when to skip this in the usage was never updated for actions. --- clap_builder/src/output/usage.rs | 15 +++++++++++++++ tests/builder/help.rs | 6 +++--- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/clap_builder/src/output/usage.rs b/clap_builder/src/output/usage.rs index 3f387a0d71e..884a64df960 100644 --- a/clap_builder/src/output/usage.rs +++ b/clap_builder/src/output/usage.rs @@ -4,6 +4,7 @@ #![cfg_attr(not(feature = "usage"), allow(dead_code))] // Internal +use crate::builder::ArgAction; use crate::builder::StyledStr; use crate::builder::Styles; use crate::builder::{ArgPredicate, Command}; @@ -210,6 +211,20 @@ impl<'cmd> Usage<'cmd> { debug!("Usage::needs_options_tag:iter Option is built-in"); continue; } + match f.get_action() { + ArgAction::Set + | ArgAction::Append + | ArgAction::SetTrue + | ArgAction::SetFalse + | ArgAction::Count => {} + ArgAction::Help + | ArgAction::HelpShort + | ArgAction::HelpLong + | ArgAction::Version => { + debug!("Usage::needs_options_tag:iter Option is built-in"); + continue; + } + } if f.is_hide_set() { debug!("Usage::needs_options_tag:iter Option is hidden"); diff --git a/tests/builder/help.rs b/tests/builder/help.rs index b4e6e358525..fda7a9aeb13 100644 --- a/tests/builder/help.rs +++ b/tests/builder/help.rs @@ -1068,7 +1068,7 @@ fn explicit_short_long_help() { static SHORT_ABOUT: &str = "\ bar -Usage: myapp [OPTIONS] [arg1] +Usage: myapp [arg1] Arguments: [arg1] some option @@ -1084,7 +1084,7 @@ something really really long, with multiple lines of text that should be displayed -Usage: myapp [OPTIONS] [arg1] +Usage: myapp [arg1] Arguments: [arg1] @@ -1351,7 +1351,7 @@ fn override_help_short() { } static OVERRIDE_HELP_LONG: &str = "\ -Usage: test [OPTIONS] +Usage: test Options: -h, --hell Print help