scarb_ui/args/
verbosity.rs

1use crate::Verbosity;
2
3/// [`clap`] structured arguments that provide Scarb UI verbosity selection.
4#[derive(clap::Args, Debug, Clone, Default)]
5#[command(about = None, long_about = None)]
6pub struct VerbositySpec {
7    #[arg(
8    long,
9    short = 'v',
10    action = clap::ArgAction::Count,
11    global = true,
12    help = "Increase logging verbosity.",
13    )]
14    verbose: u8,
15
16    #[arg(
17    long,
18    short = 'q',
19    action = clap::ArgAction::Count,
20    global = true,
21    help = "Decrease logging verbosity.",
22    conflicts_with = "verbose",
23    )]
24    quiet: u8,
25
26    #[arg(
27    long = "no-warnings",
28    action = clap::ArgAction::SetTrue,
29    global = true,
30    help = "Decrease logging verbosity, hiding warnings but still showing errors.",
31    conflicts_with_all = &["verbose", "quiet"]
32    )]
33    no_warnings: bool,
34
35    #[arg(
36        long,
37        global = true,
38        help = "Set UI verbosity level by name.",
39        env = "SCARB_UI_VERBOSITY"
40    )]
41    verbosity: Option<Verbosity>,
42}
43
44impl Verbosity {
45    fn level_value(level: Self) -> i8 {
46        match level {
47            Self::Quiet => -2,
48            Self::NoWarnings => -1,
49            Self::Normal => 0,
50            Self::Verbose => 1,
51        }
52    }
53}
54
55impl VerbositySpec {
56    /// Whether any verbosity flags (either `--verbose`, `--quiet`, or `--no-warnings`)
57    /// are present on the command line.
58    pub fn is_present(&self) -> bool {
59        self.verbose != 0 || self.quiet != 0 || self.no_warnings
60    }
61
62    /// Convert the verbosity specification to a [`tracing_core::LevelFilter`].
63    pub fn as_trace(&self) -> String {
64        let level = match self.integer_verbosity() {
65            i8::MIN..=-2 => tracing_core::LevelFilter::OFF,
66            -1 => tracing_core::LevelFilter::ERROR,
67            0 => tracing_core::LevelFilter::ERROR,
68            1 => tracing_core::LevelFilter::WARN,
69            2 => tracing_core::LevelFilter::INFO,
70            3 => tracing_core::LevelFilter::DEBUG,
71            4..=i8::MAX => tracing_core::LevelFilter::TRACE,
72        };
73        format!("scarb={level}")
74    }
75
76    fn integer_verbosity(&self) -> i8 {
77        let int_level = if self.no_warnings {
78            -1
79        } else {
80            (self.verbose as i8) - (if self.quiet > 0 { 1 } else { 0 }) - self.quiet as i8
81        };
82
83        if self.is_present() {
84            int_level
85        } else {
86            self.verbosity
87                .map(Verbosity::level_value)
88                .unwrap_or(int_level)
89        }
90    }
91}
92
93impl From<VerbositySpec> for Verbosity {
94    fn from(spec: VerbositySpec) -> Self {
95        match spec.integer_verbosity() {
96            v if v < -1 => Verbosity::Quiet,
97            -1 => Verbosity::NoWarnings,
98            0 => Verbosity::Normal,
99            _ => Verbosity::Verbose,
100        }
101    }
102}
103
104#[cfg(test)]
105mod tests {
106    use test_case::test_case;
107
108    use crate::Verbosity;
109    use crate::args::VerbositySpec;
110
111    #[test_case(Verbosity::Quiet)]
112    #[test_case(Verbosity::NoWarnings)]
113    #[test_case(Verbosity::Normal)]
114    #[test_case(Verbosity::Verbose)]
115    fn verbosity_serialization_identity(level: Verbosity) {
116        assert_eq!(
117            Verbosity::from(VerbositySpec {
118                verbose: 0,
119                quiet: 0,
120                verbosity: Some(level),
121                no_warnings: false
122            }),
123            level
124        );
125    }
126
127    #[test_case(2, 0, false, Verbosity::Quiet, tracing_core::LevelFilter::OFF)]
128    #[test_case(1, 0, false, Verbosity::Quiet, tracing_core::LevelFilter::OFF)]
129    #[test_case(0, 0, false, Verbosity::Normal, tracing_core::LevelFilter::ERROR)]
130    #[test_case(0, 0, true, Verbosity::NoWarnings, tracing_core::LevelFilter::ERROR)]
131    #[test_case(0, 1, false, Verbosity::Verbose, tracing_core::LevelFilter::WARN)]
132    #[test_case(0, 2, false, Verbosity::Verbose, tracing_core::LevelFilter::INFO)]
133    #[test_case(0, 3, false, Verbosity::Verbose, tracing_core::LevelFilter::DEBUG)]
134    #[test_case(0, 4, false, Verbosity::Verbose, tracing_core::LevelFilter::TRACE)]
135    #[test_case(0, 5, false, Verbosity::Verbose, tracing_core::LevelFilter::TRACE)]
136    fn verbosity_levels(
137        quiet: u8,
138        verbose: u8,
139        no_warnings: bool,
140        level: Verbosity,
141        trace: tracing_core::LevelFilter,
142    ) {
143        let spec = VerbositySpec {
144            verbose,
145            quiet,
146            verbosity: None,
147            no_warnings,
148        };
149        assert_eq!(spec.as_trace(), format!("scarb={trace}"));
150        assert_eq!(Verbosity::from(spec), level);
151    }
152}