concolor_clap/
lib.rs

1//! Mixin a clap argument for colored output selection
2//!
3//! ## Examples
4//!
5//! ```rust
6//! // ...
7//! #[derive(Debug, clap::Parser)]
8//! #[clap(color = concolor_clap::color_choice())]
9//! struct Cli {
10//!     #[command(flatten)]
11//!     color: concolor_clap::Color,
12//! }
13//! ```
14//!
15//! ## Features
16//!
17//! - `auto` (default): Automatically detect color support
18
19/// Get color choice for initializing the `clap::App`
20pub fn color_choice() -> clap::ColorChoice {
21    let color = concolor::get(concolor::Stream::Either);
22    if color.ansi_color() {
23        clap::ColorChoice::Always
24    } else {
25        clap::ColorChoice::Never
26    }
27}
28
29/// Mixin a clap argument for colored output selection
30#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, clap::Args)]
31#[command(about = None, long_about = None)]
32pub struct Color {
33    /// Controls when to use color.
34    #[arg(long, default_value_t = ColorChoice::Auto, value_name = "WHEN", value_enum, global = true)]
35    pub color: ColorChoice,
36}
37
38impl Color {
39    /// Set the user selection on `concolor`
40    #[cfg(feature = "api")]
41    pub fn apply(&self) {
42        concolor::set(self.to_control());
43    }
44
45    /// Get the user's selection
46    pub fn to_control(&self) -> concolor::ColorChoice {
47        match self.color {
48            ColorChoice::Auto => concolor::ColorChoice::Auto,
49            ColorChoice::Always => concolor::ColorChoice::Always,
50            ColorChoice::Never => concolor::ColorChoice::Never,
51        }
52    }
53}
54
55/// Argument value for when to color output
56#[derive(Copy, Clone, Debug, PartialEq, Eq, clap::ValueEnum)]
57pub enum ColorChoice {
58    Auto,
59    Always,
60    Never,
61}
62
63impl ColorChoice {
64    /// Report all `possible_values`
65    pub fn possible_values() -> impl Iterator<Item = clap::builder::PossibleValue> {
66        use clap::ValueEnum;
67        Self::value_variants()
68            .iter()
69            .filter_map(clap::ValueEnum::to_possible_value)
70    }
71}
72
73impl Default for ColorChoice {
74    fn default() -> Self {
75        Self::Auto
76    }
77}
78
79impl std::fmt::Display for ColorChoice {
80    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
81        use clap::ValueEnum;
82        self.to_possible_value()
83            .expect("no values are skipped")
84            .get_name()
85            .fmt(f)
86    }
87}
88
89impl std::str::FromStr for ColorChoice {
90    type Err = String;
91
92    fn from_str(s: &str) -> Result<Self, Self::Err> {
93        match s {
94            AUTO => Ok(Self::Auto),
95            ALWAYS => Ok(Self::Always),
96            NEVER => Ok(Self::Never),
97            other => Err(format!("Unknown color choice '{other}'")),
98        }
99    }
100}
101
102const AUTO: &str = "auto";
103const ALWAYS: &str = "always";
104const NEVER: &str = "never";
105
106#[cfg(test)]
107mod test {
108    use super::*;
109
110    #[test]
111    fn verify_app() {
112        #[derive(Debug, clap::Parser)]
113        struct Cli {
114            #[clap(flatten)]
115            color: Color,
116        }
117
118        use clap::CommandFactory;
119        Cli::command().debug_assert()
120    }
121}