1use crate::Verbosity;
2
3#[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 pub fn is_present(&self) -> bool {
59 self.verbose != 0 || self.quiet != 0 || self.no_warnings
60 }
61
62 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}