diff --git a/README.md b/README.md index c5109dcb9f7..09e8f0c27e7 100644 --- a/README.md +++ b/README.md @@ -444,7 +444,7 @@ Disabling optional features can decrease the binary size of `clap` and decrease * **std**: _Not Currently Used._ Placeholder for supporting `no_std` environments in a backwards compatible manner. * **derive**: Enables the custom derive (i.e. `#[derive(Parser)]`). Without this you must use one of the other methods of creating a `clap` CLI listed above. (builds dependency `clap_derive`) * **cargo**: Turns on macros that read values from `CARGO_*` environment variables. -* **color**: Turns on colored error messages. (builds dependency `termcolor`) +* **color**: Turns on colored error messages. (builds dependency `atty`, `termcolor`) * **env**: Turns on the usage of environment variables during parsing. * **suggestions**: Turns on the `Did you mean '--myoption'?` feature for when users make typos. (builds dependency `strsim`) * **unicode**: Turns on support for unicode characters in arguments and help messages. (builds dependency `textwrap`, `unicase`) diff --git a/clap_derive/src/lib.rs b/clap_derive/src/lib.rs index 23fa383ae60..110c87a5b80 100644 --- a/clap_derive/src/lib.rs +++ b/clap_derive/src/lib.rs @@ -16,7 +16,7 @@ #![doc(html_root_url = "https://docs.rs/clap_derive/3.0.0-beta.4")] //! This crate is custom derive for clap. It should not be used -//! directly. See [clap documentation](clap) +//! directly. See [clap documentation](http://docs.rs/clap) //! for the usage of `#[derive(Parser)]`. #![forbid(unsafe_code)] diff --git a/src/build/app/mod.rs b/src/build/app/mod.rs index 6ba45b095f8..51d0713ec42 100644 --- a/src/build/app/mod.rs +++ b/src/build/app/mod.rs @@ -28,7 +28,7 @@ use crate::{ mkeymap::MKeyMap, output::{fmt::Colorizer, Help, HelpWriter, Usage}, parse::{ArgMatcher, ArgMatches, Input, Parser}, - util::{safe_exit, termcolor::ColorChoice, Id, Key, USAGE_CODE}, + util::{color::ColorChoice, safe_exit, Id, Key, USAGE_CODE}, Result as ClapResult, INTERNAL_ERROR_MSG, }; @@ -90,6 +90,7 @@ pub struct App<'help> { pub(crate) template: Option<&'help str>, pub(crate) settings: AppFlags, pub(crate) g_settings: AppFlags, + pub(crate) color: ColorChoice, pub(crate) args: MKeyMap<'help>, pub(crate) subcommands: Vec>, pub(crate) replacers: HashMap<&'help str, &'help [&'help str]>, @@ -985,7 +986,7 @@ impl<'help> App<'help> { /// ```no_run /// # use clap::{App, AppSettings}; /// App::new("myprog") - /// .unset_global_setting(AppSettings::ColorAuto) + /// .unset_global_setting(AppSettings::UnifiedHelpMessage) /// # ; /// ``` /// [global]: App::global_setting() @@ -996,6 +997,28 @@ impl<'help> App<'help> { self } + /// Sets the behaviour of colored output during runtime. + /// + /// **NOTE:** This choice is propagated to all child subcommands. + /// + /// **NOTE:** Default behaviour is [`ColorChoice::Auto`]. + /// + /// # Examples + /// + /// ```no_run + /// # use clap::{App, ColorChoice}; + /// App::new("myprog") + /// .color(ColorChoice::Never) + /// .get_matches(); + /// ``` + /// [`ColorChoice::Auto`]: crate::ColorChoice::Auto + #[cfg(feature = "color")] + #[inline] + pub fn color(mut self, color: ColorChoice) -> Self { + self.color = color; + self + } + /// Sets the terminal width at which to wrap help messages. Defaults to /// `100`. Using `0` will ignore terminal widths and use source formatting. /// @@ -1788,7 +1811,7 @@ impl<'help> App<'help> { /// [`--help` (long)]: Arg::long_about() pub fn print_help(&mut self) -> io::Result<()> { self._build(); - let color = self.color(); + let color = self.get_color(); let p = Parser::new(self); let mut c = Colorizer::new(false, color); @@ -1815,7 +1838,7 @@ impl<'help> App<'help> { /// [`--help` (long)]: Arg::long_about() pub fn print_long_help(&mut self) -> io::Result<()> { self._build(); - let color = self.color(); + let color = self.get_color(); let p = Parser::new(self); let mut c = Colorizer::new(false, color); @@ -2656,20 +2679,8 @@ impl<'help> App<'help> { } #[inline] - // Should we color the output? - pub(crate) fn color(&self) -> ColorChoice { - debug!("App::color: Color setting..."); - - if self.is_set(AppSettings::ColorNever) { - debug!("Never"); - ColorChoice::Never - } else if self.is_set(AppSettings::ColorAlways) { - debug!("Always"); - ColorChoice::Always - } else { - debug!("Auto"); - ColorChoice::Auto - } + pub(crate) fn get_color(&self) -> ColorChoice { + self.color } #[inline] diff --git a/src/build/app/settings.rs b/src/build/app/settings.rs index c462626d708..aebd9b6b5e6 100644 --- a/src/build/app/settings.rs +++ b/src/build/app/settings.rs @@ -26,10 +26,6 @@ bitflags! { const NO_POS_VALUES = 1 << 17; const NEXT_LINE_HELP = 1 << 18; const DERIVE_DISP_ORDER = 1 << 19; - const COLORED_HELP = 1 << 20; - const COLOR_ALWAYS = 1 << 21; - const COLOR_AUTO = 1 << 22; - const COLOR_NEVER = 1 << 23; const DONT_DELIM_TRAIL = 1 << 24; const ALLOW_NEG_NUMS = 1 << 25; const DISABLE_HELP_SC = 1 << 27; @@ -59,7 +55,7 @@ pub struct AppFlags(Flags); impl Default for AppFlags { fn default() -> Self { - AppFlags(Flags::COLOR_AUTO) + Self::empty() } } @@ -80,12 +76,6 @@ impl_settings! { AppSettings, AppFlags, => Flags::ALLOW_NEG_NUMS, AllowMissingPositional("allowmissingpositional") => Flags::ALLOW_MISSING_POS, - ColorAlways("coloralways") - => Flags::COLOR_ALWAYS, - ColorAuto("colorauto") - => Flags::COLOR_AUTO, - ColorNever("colornever") - => Flags::COLOR_NEVER, DontDelimitTrailingValues("dontdelimittrailingvalues") => Flags::DONT_DELIM_TRAIL, DontCollapseArgsInUsage("dontcollapseargsinusage") @@ -491,62 +481,6 @@ pub enum AppSettings { /// ``` SubcommandPrecedenceOverArg, - /// Enables colored output only when the output is going to a terminal or TTY. - /// - /// **NOTE:** This is the default behavior of `clap`. - /// - /// **NOTE:** Must be compiled with the `color` cargo feature. - /// - /// # Platform Specific - /// - /// This setting only applies to Unix, Linux, and OSX (i.e. non-Windows platforms). - /// - /// # Examples - /// - /// ```no_run - /// # use clap::{App, Arg, AppSettings}; - /// App::new("myprog") - /// .setting(AppSettings::ColorAuto) - /// .get_matches(); - /// ``` - ColorAuto, - - /// Enables colored output regardless of whether or not the output is going to a terminal/TTY. - /// - /// **NOTE:** Must be compiled with the `color` cargo feature. - /// - /// # Platform Specific - /// - /// This setting only applies to Unix, Linux, and OSX (i.e. non-Windows platforms). - /// - /// # Examples - /// - /// ```no_run - /// # use clap::{App, Arg, AppSettings}; - /// App::new("myprog") - /// .setting(AppSettings::ColorAlways) - /// .get_matches(); - /// ``` - ColorAlways, - - /// Disables colored output no matter if the output is going to a terminal/TTY, or not. - /// - /// **NOTE:** Must be compiled with the `color` cargo feature - /// - /// # Platform Specific - /// - /// This setting only applies to Unix, Linux, and OSX (i.e. non-Windows platforms) - /// - /// # Examples - /// - /// ```no_run - /// # use clap::{App, Arg, AppSettings}; - /// App::new("myprog") - /// .setting(AppSettings::ColorNever) - /// .get_matches(); - /// ``` - ColorNever, - /// Disables the automatic collapsing of positional args into `[ARGS]` inside the usage string /// /// # Examples @@ -1086,18 +1020,6 @@ mod test { "allownegativenumbers".parse::().unwrap(), AppSettings::AllowNegativeNumbers ); - assert_eq!( - "colorauto".parse::().unwrap(), - AppSettings::ColorAuto - ); - assert_eq!( - "coloralways".parse::().unwrap(), - AppSettings::ColorAlways - ); - assert_eq!( - "colornever".parse::().unwrap(), - AppSettings::ColorNever - ); assert_eq!( "disablehelpsubcommand".parse::().unwrap(), AppSettings::DisableHelpSubcommand diff --git a/src/lib.rs b/src/lib.rs index 0c44c350c90..24899e30c80 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,6 +29,7 @@ pub use crate::{ }, parse::errors::{Error, ErrorKind, Result}, parse::{ArgMatches, Indices, OsValues, Values}, + util::color::ColorChoice, }; pub use crate::derive::{ArgEnum, Args, FromArgMatches, IntoApp, Parser, Subcommand}; diff --git a/src/output/fmt.rs b/src/output/fmt.rs index 6c898b92026..267feb18053 100644 --- a/src/output/fmt.rs +++ b/src/output/fmt.rs @@ -1,7 +1,4 @@ -#[cfg(not(feature = "color"))] -use crate::util::termcolor::{Color, ColorChoice}; -#[cfg(feature = "color")] -use termcolor::{Color, ColorChoice}; +use crate::util::color::{Color, ColorChoice}; use std::{ fmt::{self, Display, Formatter}, @@ -63,12 +60,12 @@ impl Colorizer { impl Colorizer { #[cfg(feature = "color")] pub(crate) fn print(&self) -> io::Result<()> { - use termcolor::{BufferWriter, ColorSpec, WriteColor}; + use termcolor::{BufferWriter, ColorChoice as DepColorChoice, ColorSpec, WriteColor}; let color_when = match self.color_when { - always @ ColorChoice::Always => always, - choice if is_a_tty(self.use_stderr) => choice, - _ => ColorChoice::Never, + ColorChoice::Always => DepColorChoice::Always, + ColorChoice::Auto if is_a_tty(self.use_stderr) => DepColorChoice::Auto, + _ => DepColorChoice::Never, }; let writer = if self.use_stderr { diff --git a/src/parse/errors.rs b/src/parse/errors.rs index bc476ecb6c6..50e8ca59509 100644 --- a/src/parse/errors.rs +++ b/src/parse/errors.rs @@ -12,7 +12,7 @@ use crate::{ build::Arg, output::fmt::Colorizer, parse::features::suggestions, - util::{safe_exit, termcolor::ColorChoice, SUCCESS_CODE, USAGE_CODE}, + util::{color::ColorChoice, safe_exit, SUCCESS_CODE, USAGE_CODE}, App, AppSettings, }; @@ -546,7 +546,7 @@ impl Error { other: Option, usage: String, ) -> Self { - let mut c = Colorizer::new(true, app.color()); + let mut c = Colorizer::new(true, app.get_color()); let arg = arg.to_string(); start_error(&mut c, "The argument '"); @@ -582,7 +582,7 @@ impl Error { } pub(crate) fn empty_value(app: &App, arg: &Arg, usage: String) -> Self { - let mut c = Colorizer::new(true, app.color()); + let mut c = Colorizer::new(true, app.get_color()); let arg = arg.to_string(); start_error(&mut c, "The argument '"); @@ -601,7 +601,7 @@ impl Error { } pub(crate) fn no_equals(app: &App, arg: String, usage: String) -> Self { - let mut c = Colorizer::new(true, app.color()); + let mut c = Colorizer::new(true, app.get_color()); start_error(&mut c, "Equal sign is needed when assigning values to '"); c.warning(&arg); @@ -629,7 +629,7 @@ impl Error { where G: AsRef + Display, { - let mut c = Colorizer::new(true, app.color()); + let mut c = Colorizer::new(true, app.get_color()); let suffix = suggestions::did_you_mean(&bad_val, good_vals.iter()).pop(); let mut sorted: Vec = good_vals @@ -690,7 +690,7 @@ impl Error { name: String, usage: String, ) -> Self { - let mut c = Colorizer::new(true, app.color()); + let mut c = Colorizer::new(true, app.get_color()); start_error(&mut c, "The subcommand '"); c.warning(subcmd.clone()); @@ -716,7 +716,7 @@ impl Error { } pub(crate) fn unrecognized_subcommand(app: &App, subcmd: String, name: String) -> Self { - let mut c = Colorizer::new(true, app.color()); + let mut c = Colorizer::new(true, app.get_color()); start_error(&mut c, " The subcommand '"); c.warning(subcmd.clone()); @@ -739,7 +739,7 @@ impl Error { required: Vec, usage: String, ) -> Self { - let mut c = Colorizer::new(true, app.color()); + let mut c = Colorizer::new(true, app.get_color()); start_error( &mut c, @@ -766,7 +766,7 @@ impl Error { } pub(crate) fn missing_subcommand(app: &App, name: String, usage: String) -> Self { - let mut c = Colorizer::new(true, app.color()); + let mut c = Colorizer::new(true, app.get_color()); start_error(&mut c, "'"); c.warning(name); @@ -784,7 +784,7 @@ impl Error { } pub(crate) fn invalid_utf8(app: &App, usage: String) -> Self { - let mut c = Colorizer::new(true, app.color()); + let mut c = Colorizer::new(true, app.get_color()); start_error( &mut c, @@ -809,7 +809,7 @@ impl Error { curr_occurs: usize, usage: String, ) -> Self { - let mut c = Colorizer::new(true, app.color()); + let mut c = Colorizer::new(true, app.get_color()); let verb = Error::singular_or_plural(curr_occurs); start_error(&mut c, "The argument '"); @@ -836,7 +836,7 @@ impl Error { } pub(crate) fn too_many_values(app: &App, val: String, arg: String, usage: String) -> Self { - let mut c = Colorizer::new(true, app.color()); + let mut c = Colorizer::new(true, app.get_color()); start_error(&mut c, "The value '"); c.warning(val.clone()); @@ -862,7 +862,7 @@ impl Error { curr_vals: usize, usage: String, ) -> Self { - let mut c = Colorizer::new(true, app.color()); + let mut c = Colorizer::new(true, app.get_color()); let verb = Error::singular_or_plural(curr_vals); start_error(&mut c, "The argument '"); @@ -896,7 +896,7 @@ impl Error { info, source, backtrace, - } = Self::value_validation_with_color(arg, val, err, app.color()); + } = Self::value_validation_with_color(arg, val, err, app.get_color()); try_help(app, &mut message); Self { message, @@ -947,7 +947,7 @@ impl Error { curr_vals: usize, usage: String, ) -> Self { - let mut c = Colorizer::new(true, app.color()); + let mut c = Colorizer::new(true, app.get_color()); let verb = Error::singular_or_plural(curr_vals); start_error(&mut c, "The argument '"); @@ -970,7 +970,7 @@ impl Error { } pub(crate) fn unexpected_multiple_usage(app: &App, arg: &Arg, usage: String) -> Self { - let mut c = Colorizer::new(true, app.color()); + let mut c = Colorizer::new(true, app.get_color()); let arg = arg.to_string(); start_error(&mut c, "The argument '"); @@ -994,7 +994,7 @@ impl Error { did_you_mean: Option<(String, Option)>, usage: String, ) -> Self { - let mut c = Colorizer::new(true, app.color()); + let mut c = Colorizer::new(true, app.get_color()); start_error(&mut c, "Found argument '"); c.warning(arg.clone()); @@ -1039,7 +1039,7 @@ impl Error { } pub(crate) fn unnecessary_double_dash(app: &App, arg: String, usage: String) -> Self { - let mut c = Colorizer::new(true, app.color()); + let mut c = Colorizer::new(true, app.get_color()); start_error(&mut c, "Found argument '"); c.warning(arg.clone()); diff --git a/src/parse/parser.rs b/src/parse/parser.rs index 143dc418fe3..aa5b26554ed 100644 --- a/src/parse/parser.rs +++ b/src/parse/parser.rs @@ -1874,7 +1874,7 @@ impl<'help, 'app> Parser<'help, 'app> { } pub(crate) fn write_help_err(&self) -> ClapResult { - let mut c = Colorizer::new(true, self.app.color()); + let mut c = Colorizer::new(true, self.app.get_color()); Help::new(HelpWriter::Buffer(&mut c), self, false).write_help()?; Ok(c) } @@ -1886,7 +1886,7 @@ impl<'help, 'app> Parser<'help, 'app> { ); use_long = use_long && self.use_long_help(); - let mut c = Colorizer::new(false, self.app.color()); + let mut c = Colorizer::new(false, self.app.get_color()); match Help::new(HelpWriter::Buffer(&mut c), self, use_long).write_help() { Err(e) => e.into(), @@ -1898,7 +1898,7 @@ impl<'help, 'app> Parser<'help, 'app> { debug!("Parser::version_err"); let msg = self.app._render_version(use_long); - let mut c = Colorizer::new(false, self.app.color()); + let mut c = Colorizer::new(false, self.app.get_color()); c.none(msg); ClapError::new(c, ErrorKind::DisplayVersion) } diff --git a/src/util/color.rs b/src/util/color.rs new file mode 100644 index 00000000000..825b98760b9 --- /dev/null +++ b/src/util/color.rs @@ -0,0 +1,73 @@ +/// Represents the color preferences for program output +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum ColorChoice { + /// Enables colored output only when the output is going to a terminal or TTY. + /// + /// **NOTE:** This is the default behavior of `clap`. + /// + /// # Platform Specific + /// + /// This setting only applies to Unix, Linux, and macOS (i.e. non-Windows platforms). + /// + /// # Examples + /// + #[cfg_attr(not(feature = "color"), doc = " ```ignore")] + #[cfg_attr(feature = "color", doc = " ```no_run")] + /// # use clap::{App, ColorChoice}; + /// App::new("myprog") + /// .color(ColorChoice::Auto) + /// .get_matches(); + /// ``` + Auto, + + /// Enables colored output regardless of whether or not the output is going to a terminal/TTY. + /// + /// # Platform Specific + /// + /// This setting only applies to Unix, Linux, and macOS (i.e. non-Windows platforms). + /// + /// # Examples + /// + #[cfg_attr(not(feature = "color"), doc = " ```ignore")] + #[cfg_attr(feature = "color", doc = " ```no_run")] + /// # use clap::{App, ColorChoice}; + /// App::new("myprog") + /// .color(ColorChoice::Always) + /// .get_matches(); + /// ``` + Always, + + /// Disables colored output no matter if the output is going to a terminal/TTY, or not. + /// + /// # Platform Specific + /// + /// This setting only applies to Unix, Linux, and macOS (i.e. non-Windows platforms) + /// + /// # Examples + /// + #[cfg_attr(not(feature = "color"), doc = " ```ignore")] + #[cfg_attr(feature = "color", doc = " ```no_run")] + /// # use clap::{App, ColorChoice}; + /// App::new("myprog") + /// .color(ColorChoice::Never) + /// .get_matches(); + /// ``` + Never, +} + +impl Default for ColorChoice { + fn default() -> Self { + Self::Auto + } +} + +#[cfg(feature = "color")] +pub(crate) use termcolor::Color; + +#[cfg(not(feature = "color"))] +#[derive(Debug)] +pub(crate) enum Color { + Green, + Yellow, + Red, +} diff --git a/src/util/mod.rs b/src/util/mod.rs index 957a0ae3595..ed35765ff6a 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -12,11 +12,7 @@ pub use self::fnv::Key; pub(crate) use self::str_to_bool::str_to_bool; pub(crate) use self::{graph::ChildGraph, id::Id}; -#[cfg(feature = "color")] -pub(crate) use termcolor; - -#[cfg(not(feature = "color"))] -pub(crate) mod termcolor; +pub(crate) mod color; pub(crate) const SUCCESS_CODE: i32 = 0; // While sysexists.h defines EX_USAGE as 64, this doesn't seem to be used much in practice but @@ -39,5 +35,6 @@ pub(crate) fn safe_exit(code: i32) -> ! { pub(crate) fn eq_ignore_case(left: &str, right: &str) -> bool { left.eq_ignore_ascii_case(right) } + #[cfg(feature = "unicode")] pub(crate) use unicase::eq as eq_ignore_case; diff --git a/src/util/termcolor.rs b/src/util/termcolor.rs deleted file mode 100644 index 97ac63f82de..00000000000 --- a/src/util/termcolor.rs +++ /dev/null @@ -1,13 +0,0 @@ -#[derive(Debug, Copy, Clone, PartialEq)] -pub(crate) enum ColorChoice { - Auto, - Always, - Never, -} - -#[derive(Debug)] -pub(crate) enum Color { - Green, - Yellow, - Red, -} diff --git a/tests/help.rs b/tests/help.rs index dbc3244879e..ebb12f60739 100644 --- a/tests/help.rs +++ b/tests/help.rs @@ -2263,13 +2263,11 @@ fn option_usage_order() { #[test] fn about_in_subcommands_list() { - let app = App::new("about-in-subcommands-list") - .setting(AppSettings::ColorNever) - .subcommand( - App::new("sub") - .long_about("long about sub") - .about("short about sub"), - ); + let app = App::new("about-in-subcommands-list").subcommand( + App::new("sub") + .long_about("long about sub") + .about("short about sub"), + ); assert!(utils::compare_output( app, diff --git a/tests/macros.rs b/tests/macros.rs index 1bf90ebc281..5a90d070246 100644 --- a/tests/macros.rs +++ b/tests/macros.rs @@ -32,7 +32,7 @@ fn basic() { (version: "0.1") (about: "tests clap library") (author: "Kevin K. ") - (@global_setting ColorNever) + (@global_setting UnifiedHelpMessage) (@arg opt: -o --option +takes_value ... "tests options") (@arg positional: index(1) "tests positionals") (@arg flag: -f --flag ... +global "tests flags")