diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d1cbdae452..a739c5bcb2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -268,6 +268,7 @@ Behavior Changes - Allow resetting most builder methods - Can now pass runtime generated data to `Command`, `Arg`, `ArgGroup`, `PossibleValue`, etc without managing lifetimes with the `string` feature flag (#2150, #4223) - *(error)* `Error::apply` for changing the formatter for dropping binary size (#4111) +- *(error)* New default `error-context` feature flag that can be turned off for smaller binaries - *(help)* Show `PossibleValue::help` in long help (`--help`) (#3312) - *(help)* New `{tab}` variable for `Command::help_template` (#4161) diff --git a/Cargo.toml b/Cargo.toml index b18b4ca1f61..2071d2707f0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,6 +57,9 @@ pre-release-replacements = [ default = [ "std", "color", + "help", + "usage", + "error-context", "suggestions", ] debug = ["clap_derive/debug", "dep:backtrace"] # Enables debug messages @@ -65,7 +68,10 @@ unstable-doc = ["derive", "cargo", "wrap_help", "env", "unicode", "string", "uns # Used in default std = [] # support for no_std in a backwards-compatible way color = ["dep:atty", "dep:termcolor"] -suggestions = ["dep:strsim"] +help = [] +usage = [] +error-context = [] +suggestions = ["dep:strsim", "error-context"] # Optional deprecated = ["clap_derive?/deprecated"] # Guided experience to prepare for next breaking release (at different stages of development, this may become default) @@ -151,6 +157,7 @@ path = "examples/multicall-hostname.rs" [[example]] name = "repl" path = "examples/repl.rs" +required-features = ["help"] [[example]] name = "01_quick" diff --git a/clap_bench/Cargo.toml b/clap_bench/Cargo.toml index 49f307827cd..ce158b2fee0 100644 --- a/clap_bench/Cargo.toml +++ b/clap_bench/Cargo.toml @@ -11,7 +11,7 @@ publish = false release = false [dev-dependencies] -clap = { path = "../", version = "4.0.0-alpha.0", default-features = false, features = ["std"] } +clap = { path = "../", version = "4.0.0-alpha.0", default-features = false, features = ["std", "help"] } criterion = "0.3.2" lazy_static = "1" diff --git a/clap_complete/Cargo.toml b/clap_complete/Cargo.toml index 50fe07a8af2..838bdbd8adf 100644 --- a/clap_complete/Cargo.toml +++ b/clap_complete/Cargo.toml @@ -52,7 +52,7 @@ unicode-xid = { version = "0.2.2", optional = true } snapbox = { version = "0.3", features = ["diff"] } # Cutting out `filesystem` feature trycmd = { version = "0.13", default-features = false, features = ["color-auto", "diff", "examples"] } -clap = { path = "../", version = "4.0.0-alpha.0", default-features = false, features = ["std", "derive"] } +clap = { path = "../", version = "4.0.0-alpha.0", default-features = false, features = ["std", "derive", "help"] } [[example]] name = "dynamic" diff --git a/clap_complete_fig/Cargo.toml b/clap_complete_fig/Cargo.toml index dffb1227143..c32ade4c08b 100644 --- a/clap_complete_fig/Cargo.toml +++ b/clap_complete_fig/Cargo.toml @@ -44,3 +44,4 @@ clap_complete = { path = "../clap_complete", version = "4.0.0-alpha.0" } [dev-dependencies] snapbox = { version = "0.3", features = ["diff"] } +clap = { path = "../", version = "4.0.0-alpha.0", default-features = false, features = ["std", "help"] } diff --git a/clap_mangen/Cargo.toml b/clap_mangen/Cargo.toml index bd73c601966..bed0cafd1bf 100644 --- a/clap_mangen/Cargo.toml +++ b/clap_mangen/Cargo.toml @@ -45,7 +45,7 @@ clap = { path = "../", version = "4.0.0-alpha.0", default-features = false, feat [dev-dependencies] snapbox = { version = "0.3", features = ["diff"] } -clap = { path = "../", version = "4.0.0-alpha.0", default-features = false, features = ["std"] } +clap = { path = "../", version = "4.0.0-alpha.0", default-features = false, features = ["std", "help"] } [features] default = [] diff --git a/src/_features.rs b/src/_features.rs index 7c12ef1510c..12d7e536b03 100644 --- a/src/_features.rs +++ b/src/_features.rs @@ -6,6 +6,9 @@ //! //! * **std**: _Not Currently Used._ Placeholder for supporting `no_std` environments in a backwards compatible manner. //! * **color**: Turns on colored error messages. +//! * **help**: Auto-generate help output +//! * **usage**: Auto-generate usage +//! * **error-context**: Include contextual information for errors (which arg failed, etc) //! * **suggestions**: Turns on the `Did you mean '--myoption'?` feature for when users make typos. //! //! #### Optional features diff --git a/src/builder/action.rs b/src/builder/action.rs index 5a4a3c493de..7cd3e017908 100644 --- a/src/builder/action.rs +++ b/src/builder/action.rs @@ -2,7 +2,8 @@ /// /// # Examples /// -/// ```rust +#[cfg_attr(not(feature = "help"), doc = " ```ignore")] +#[cfg_attr(feature = "help", doc = " ```")] /// # use clap::Command; /// # use clap::Arg; /// let cmd = Command::new("mycmd") @@ -211,7 +212,8 @@ pub enum ArgAction { /// /// # Examples /// - /// ```rust + #[cfg_attr(not(feature = "help"), doc = " ```ignore")] + #[cfg_attr(feature = "help", doc = " ```")] /// # use clap::Command; /// # use clap::Arg; /// let cmd = Command::new("mycmd") diff --git a/src/builder/arg.rs b/src/builder/arg.rs index 73ead55d881..dce3794b206 100644 --- a/src/builder/arg.rs +++ b/src/builder/arg.rs @@ -1107,7 +1107,8 @@ impl Arg { /// # ; /// ``` /// - /// ```rust + #[cfg_attr(not(feature = "help"), doc = " ```ignore")] + #[cfg_attr(feature = "help", doc = " ```")] /// # use clap::{Command, Arg}; /// let m = Command::new("prog") /// .arg(Arg::new("config") @@ -1168,7 +1169,8 @@ impl Arg { /// .value_names(["fast", "slow"]); /// ``` /// - /// ```rust + #[cfg_attr(not(feature = "help"), doc = " ```ignore")] + #[cfg_attr(feature = "help", doc = " ```")] /// # use clap::{Command, Arg}; /// let m = Command::new("prog") /// .arg(Arg::new("io") @@ -1991,7 +1993,8 @@ impl Arg { /// Setting `help` displays a short message to the side of the argument when the user passes /// `-h` or `--help` (by default). /// - /// ```rust + #[cfg_attr(not(feature = "help"), doc = " ```ignore")] + #[cfg_attr(feature = "help", doc = " ```")] /// # use clap::{Command, Arg}; /// let m = Command::new("prog") /// .arg(Arg::new("cfg") @@ -2040,7 +2043,8 @@ impl Arg { /// Setting `help` displays a short message to the side of the argument when the user passes /// `-h` or `--help` (by default). /// - /// ```rust + #[cfg_attr(not(feature = "help"), doc = " ```ignore")] + #[cfg_attr(feature = "help", doc = " ```")] /// # use clap::{Command, Arg}; /// let m = Command::new("prog") /// .arg(Arg::new("cfg") @@ -2096,7 +2100,8 @@ impl Arg { /// /// # Examples /// - /// ```rust + #[cfg_attr(not(feature = "help"), doc = " ```ignore")] + #[cfg_attr(feature = "help", doc = " ```")] /// # use clap::{Command, Arg, ArgAction}; /// let m = Command::new("prog") /// .arg(Arg::new("a") // Typically args are grouped alphabetically by name. @@ -2162,7 +2167,8 @@ impl Arg { /// /// # Examples /// - /// ```rust + #[cfg_attr(not(feature = "help"), doc = " ```ignore")] + #[cfg_attr(feature = "help", doc = " ```")] /// # use clap::{Command, Arg, ArgAction}; /// let m = Command::new("prog") /// .arg(Arg::new("opt") @@ -2212,7 +2218,8 @@ impl Arg { /// /// Setting `Hidden` will hide the argument when displaying help text /// - /// ```rust + #[cfg_attr(not(feature = "help"), doc = " ```ignore")] + #[cfg_attr(feature = "help", doc = " ```")] /// # use clap::{Command, Arg}; /// let m = Command::new("prog") /// .arg(Arg::new("cfg") @@ -2385,7 +2392,8 @@ impl Arg { /// /// Setting `hide_short_help(true)` will hide the argument when displaying short help text /// - /// ```rust + #[cfg_attr(not(feature = "help"), doc = " ```ignore")] + #[cfg_attr(feature = "help", doc = " ```")] /// # use clap::{Command, Arg}; /// let m = Command::new("prog") /// .arg(Arg::new("cfg") @@ -2411,7 +2419,8 @@ impl Arg { /// /// However, when --help is called /// - /// ```rust + #[cfg_attr(not(feature = "help"), doc = " ```ignore")] + #[cfg_attr(feature = "help", doc = " ```")] /// # use clap::{Command, Arg}; /// let m = Command::new("prog") /// .arg(Arg::new("cfg") @@ -2456,7 +2465,8 @@ impl Arg { /// /// Setting `hide_long_help(true)` will hide the argument when displaying long help text /// - /// ```rust + #[cfg_attr(not(feature = "help"), doc = " ```ignore")] + #[cfg_attr(feature = "help", doc = " ```")] /// # use clap::{Command, Arg}; /// let m = Command::new("prog") /// .arg(Arg::new("cfg") @@ -2482,7 +2492,8 @@ impl Arg { /// /// However, when -h is called /// - /// ```rust + #[cfg_attr(not(feature = "help"), doc = " ```ignore")] + #[cfg_attr(feature = "help", doc = " ```")] /// # use clap::{Command, Arg}; /// let m = Command::new("prog") /// .arg(Arg::new("cfg") @@ -4074,10 +4085,6 @@ impl Arg { } } - pub(crate) fn longest_filter(&self) -> bool { - self.is_takes_value_set() || self.long.is_some() || self.short.is_none() - } - // Used for positionals when printing pub(crate) fn name_no_brackets(&self) -> String { debug!("Arg::name_no_brackets:{}", self.get_id()); @@ -4200,6 +4207,7 @@ impl Arg { self.is_multiple_values_set() || matches!(*self.get_action(), ArgAction::Append) } + #[cfg(feature = "help")] pub(crate) fn get_display_order(&self) -> usize { self.disp_ord.unwrap_or(999) } diff --git a/src/builder/command.rs b/src/builder/command.rs index a400f13ed0d..def9139452b 100644 --- a/src/builder/command.rs +++ b/src/builder/command.rs @@ -1,3 +1,5 @@ +#![cfg_attr(not(feature = "usage"), allow(unused_mut))] + // Std use std::env; use std::ffi::OsString; @@ -19,7 +21,7 @@ use crate::error::ErrorKind; use crate::error::Result as ClapResult; use crate::mkeymap::MKeyMap; use crate::output::fmt::Stream; -use crate::output::{fmt::Colorizer, Help, Usage}; +use crate::output::{fmt::Colorizer, write_help, Usage}; use crate::parser::{ArgMatcher, ArgMatches, Parser}; use crate::util::ChildGraph; use crate::util::FlatMap; @@ -90,6 +92,7 @@ pub struct Command { disp_ord: Option, term_w: Option, max_w: Option, + #[cfg(feature = "help")] template: Option, settings: AppFlags, g_settings: AppFlags, @@ -723,7 +726,7 @@ impl Command { let mut styled = StyledStr::new(); let usage = Usage::new(self); - Help::new(&mut styled, self, &usage, false).write_help(); + write_help(&mut styled, self, &usage, false); let c = Colorizer::new(Stream::Stdout, color).with_content(styled); c.print() @@ -750,7 +753,7 @@ impl Command { let mut styled = StyledStr::new(); let usage = Usage::new(self); - Help::new(&mut styled, self, &usage, true).write_help(); + write_help(&mut styled, self, &usage, true); let c = Colorizer::new(Stream::Stdout, color).with_content(styled); c.print() @@ -777,7 +780,7 @@ impl Command { let mut styled = StyledStr::new(); let usage = Usage::new(self); - Help::new(&mut styled, self, &usage, false).write_help(); + write_help(&mut styled, self, &usage, false); write!(w, "{}", styled)?; w.flush() } @@ -803,7 +806,7 @@ impl Command { let mut styled = StyledStr::new(); let usage = Usage::new(self); - Help::new(&mut styled, self, &usage, true).write_help(); + write_help(&mut styled, self, &usage, true); write!(w, "{}", styled)?; w.flush() } @@ -867,10 +870,10 @@ impl Command { /// println!("{}", cmd.render_usage()); /// ``` pub fn render_usage(&mut self) -> String { - self.render_usage_().to_string() + self.render_usage_().unwrap_or_default().to_string() } - pub(crate) fn render_usage_(&mut self) -> StyledStr { + pub(crate) fn render_usage_(&mut self) -> Option { // If there are global arguments, or settings we need to propagate them down to subcommands // before parsing incase we run into a subcommand self._build_self(false); @@ -1727,6 +1730,7 @@ impl Command { /// [`Command::before_help`]: Command::before_help() /// [`Command::before_long_help`]: Command::before_long_help() #[must_use] + #[cfg(feature = "help")] pub fn help_template(mut self, s: impl IntoResettable) -> Self { self.template = s.into_resettable().into_option(); self @@ -2536,7 +2540,8 @@ impl Command { /// /// # Examples /// - /// ```rust + #[cfg_attr(not(feature = "help"), doc = " ```ignore")] + #[cfg_attr(feature = "help", doc = " ```")] /// # use clap::{Command, }; /// let m = Command::new("cust-ord") /// .subcommand(Command::new("alpha") // typically subcommands are grouped @@ -3139,6 +3144,7 @@ impl Command { /// # Reflection impl Command { #[inline] + #[cfg(feature = "usage")] pub(crate) fn get_usage_name(&self) -> Option<&str> { self.usage_name.as_deref() } @@ -3661,14 +3667,17 @@ impl Command { self.help_str.as_ref() } + #[cfg(feature = "help")] pub(crate) fn get_help_template(&self) -> Option<&StyledStr> { self.template.as_ref() } + #[cfg(feature = "help")] pub(crate) fn get_term_width(&self) -> Option { self.term_w } + #[cfg(feature = "help")] pub(crate) fn get_max_term_width(&self) -> Option { self.max_w } @@ -3753,6 +3762,11 @@ impl Command { self.settings.insert(AppSettings::DisableHelpFlag.into()); self.settings.insert(AppSettings::DisableVersionFlag.into()); } + if !cfg!(feature = "help") && self.get_override_help().is_none() { + self.settings.insert(AppSettings::DisableHelpFlag.into()); + self.settings + .insert(AppSettings::DisableHelpSubcommand.into()); + } if self.is_set(AppSettings::ArgsNegateSubcommands) { self.settings .insert(AppSettings::SubcommandsNegateReqs.into()); @@ -3840,6 +3854,7 @@ impl Command { use std::fmt::Write; let mut mid_string = String::from(" "); + #[cfg(feature = "usage")] if !self.is_subcommand_negates_reqs_set() && !self.is_args_conflicts_with_subcommands_set() { let reqs = Usage::new(self).get_required_usage_from(&[], None, true); // maybe Some(m) @@ -3925,6 +3940,7 @@ impl Command { if !self.is_set(AppSettings::BinNameBuilt) { let mut mid_string = String::from(" "); + #[cfg(feature = "usage")] if !self.is_subcommand_negates_reqs_set() && !self.is_args_conflicts_with_subcommands_set() { @@ -4279,22 +4295,11 @@ impl<'a, T> Captures<'a> for T {} // Internal Query Methods impl Command { /// Iterate through the *flags* & *options* arguments. + #[cfg(any(feature = "usage", feature = "help"))] pub(crate) fn get_non_positionals(&self) -> impl Iterator { self.get_arguments().filter(|a| !a.is_positional()) } - /// Iterate through the *positionals* that don't have custom heading. - pub(crate) fn get_positionals_with_no_heading(&self) -> impl Iterator { - self.get_positionals() - .filter(|a| a.get_help_heading().is_none()) - } - - /// Iterate through the *flags* & *options* that don't have custom heading. - pub(crate) fn get_non_positionals_with_no_heading(&self) -> impl Iterator { - self.get_non_positionals() - .filter(|a| a.get_help_heading().is_none()) - } - pub(crate) fn find(&self, arg_id: &Id) -> Option<&Arg> { self.args.args().find(|a| a.id == *arg_id) } @@ -4319,6 +4324,7 @@ impl Command { self.get_positionals().next().is_some() } + #[cfg(any(feature = "usage", feature = "help"))] pub(crate) fn has_visible_subcommands(&self) -> bool { self.subcommands .iter() @@ -4473,6 +4479,7 @@ impl Command { .map(|sc| sc.get_name()) } + #[cfg(feature = "help")] pub(crate) fn get_display_order(&self) -> usize { self.disp_ord.unwrap_or(999) } @@ -4488,7 +4495,7 @@ impl Command { let usage = Usage::new(self); let mut styled = StyledStr::new(); - Help::new(&mut styled, self, &usage, use_long).write_help(); + write_help(&mut styled, self, &usage, use_long); styled } @@ -4565,6 +4572,7 @@ impl Default for Command { disp_ord: Default::default(), term_w: Default::default(), max_w: Default::default(), + #[cfg(feature = "help")] template: Default::default(), settings: Default::default(), g_settings: Default::default(), diff --git a/src/builder/debug_asserts.rs b/src/builder/debug_asserts.rs index 92ac824ec31..e40d9369e97 100644 --- a/src/builder/debug_asserts.rs +++ b/src/builder/debug_asserts.rs @@ -337,6 +337,7 @@ pub(crate) fn assert_app(cmd: &Command) { _verify_positionals(cmd); + #[cfg(feature = "help")] if let Some(help_template) = cmd.get_help_template() { assert!( !help_template.to_string().contains("{flags}"), diff --git a/src/builder/possible_value.rs b/src/builder/possible_value.rs index 5fbca9f9ed4..03964fe118b 100644 --- a/src/builder/possible_value.rs +++ b/src/builder/possible_value.rs @@ -1,5 +1,3 @@ -use std::{borrow::Cow, iter}; - use crate::builder::IntoResettable; use crate::builder::Str; use crate::builder::StyledStr; @@ -157,6 +155,7 @@ impl PossibleValue { /// Get the help specified for this argument, if any and the argument /// value is not hidden #[inline] + #[cfg(feature = "help")] pub(crate) fn get_visible_help(&self) -> Option<&StyledStr> { if !self.hide { self.get_help() @@ -178,7 +177,8 @@ impl PossibleValue { /// Get the name if argument value is not hidden, `None` otherwise, /// but wrapped in quotes if it contains whitespace - pub(crate) fn get_visible_quoted_name(&self) -> Option> { + #[cfg(feature = "help")] + pub(crate) fn get_visible_quoted_name(&self) -> Option> { if !self.hide { Some(if self.name.contains(char::is_whitespace) { format!("{:?}", self.name).into() @@ -194,7 +194,7 @@ impl PossibleValue { /// /// Namely the name and all aliases. pub fn get_name_and_aliases(&self) -> impl Iterator + '_ { - iter::once(self.get_name()).chain(self.aliases.iter().map(|s| s.as_str())) + std::iter::once(self.get_name()).chain(self.aliases.iter().map(|s| s.as_str())) } /// Tests if the value is valid for this argument value diff --git a/src/builder/styled_str.rs b/src/builder/styled_str.rs index 503bf333747..5f2969e0405 100644 --- a/src/builder/styled_str.rs +++ b/src/builder/styled_str.rs @@ -1,18 +1,27 @@ -use crate::output::display_width; -use crate::output::textwrap; - /// Terminal-styling container #[derive(Clone, Default, Debug, PartialEq, Eq)] pub struct StyledStr { + #[cfg(feature = "color")] pieces: Vec<(Option