1use std::{
2 borrow::Cow,
3 io,
4 result::Result as StdResult,
5 sync::{Mutex, MutexGuard, PoisonError},
6 time::Duration,
7};
8
9use indicatif::{ProgressBar, ProgressStyle};
10use once_cell::sync::OnceCell;
11use serde::{
12 de::{self, Deserializer},
13 Deserialize, Serialize,
14};
15use strum::{Display, EnumString};
16
17use crate::{error::Result, Ctx};
18
19#[cfg_attr(feature = "api_tests", allow(dead_code))]
20static GLOBAL_PRINTER: OnceCell<Mutex<self::_printer::Printer>> = OnceCell::new();
21#[cfg_attr(feature = "api_tests", allow(dead_code))]
22static GLOBAL_EPRINTER: OnceCell<Mutex<self::_printer::Printer>> = OnceCell::new();
23
24pub use self::_printer::{eprinter, printer, Printer};
25
26#[allow(missing_debug_implementations)]
28pub struct Pb(Option<ProgressBar>);
29
30impl Pb {
31 pub fn new(ctx: &Ctx) -> Self {
32 if !ctx.disable_pb && crate::log::log_level() <= &crate::log::LogLevel::Info {
33 let pb = ProgressBar::new_spinner();
34 pb.enable_steady_tick(Duration::from_millis(120));
35 pb.set_style(
36 ProgressStyle::default_bar()
37 .template("{spinner:.green} {msg}")
38 .expect("Invalid progress bar template"),
39 );
40 Pb(Some(pb))
41 } else {
42 Pb(None)
43 }
44 }
45
46 pub fn set_message(&self, msg: impl Into<Cow<'static, str>>) {
47 if let Some(pb) = &self.0 {
48 pb.set_message(msg);
49 }
50 }
51
52 pub fn finish_and_clear(&self) {
53 if let Some(pb) = &self.0 {
54 pb.finish_and_clear()
55 }
56 }
57}
58
59impl Drop for Pb {
60 fn drop(&mut self) { self.finish_and_clear() }
61}
62
63#[derive(EnumString, Display, Deserialize, Copy, Clone, Debug, PartialEq, Eq, clap::ValueEnum)]
64#[strum(ascii_case_insensitive, serialize_all = "lowercase")]
65pub enum OutputFormat {
66 Table,
67 Json,
68}
69
70impl Default for OutputFormat {
71 fn default() -> Self { OutputFormat::Table }
72}
73
74#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, clap::ValueEnum, Display, EnumString)]
75#[strum(ascii_case_insensitive, serialize_all = "lowercase")]
76pub enum ColorChoice {
77 Always,
78 Ansi,
79 Auto,
80 Never,
81}
82
83impl Default for ColorChoice {
84 fn default() -> Self { ColorChoice::Auto }
85}
86
87impl<'de> Deserialize<'de> for ColorChoice {
88 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> StdResult<Self, D::Error> {
89 use std::str::FromStr;
90 let s = <&str>::deserialize(deserializer)?;
91 ColorChoice::from_str(s).map_err(de::Error::custom)
92 }
93}
94
95#[derive(Clone, Copy, Debug, Eq, PartialEq)]
96#[non_exhaustive]
97#[allow(dead_code)]
98pub enum Color {
99 Black,
100 Blue,
101 Green,
102 Red,
103 Cyan,
104 Magenta,
105 Yellow,
106 White,
107}
108
109pub trait Output {
110 fn print_json(&self, _ctx: &Ctx) -> Result<()> {
111 cli_eprint!("--format=");
112 cli_eprint!(@Yellow, "json");
113 cli_eprintln!(" is not supported by this object");
114
115 Ok(())
116 }
117
118 fn print_table(&self, _ctx: &Ctx) -> Result<()> {
119 cli_eprint!("--format=");
120 cli_eprint!(@Yellow, "table");
121 cli_eprintln!(" is not supported by this object");
122
123 Ok(())
124 }
125}
126
127#[cfg(all(feature = "color", not(feature = "api_tests")))]
128mod _printer {
129 use atty::Stream;
130 use termcolor::{
131 Color as TermColorColor, ColorChoice as TermColorChoice, ColorSpec, StandardStream,
132 WriteColor,
133 };
134
135 use super::*;
136
137 fn detect_tty(stream: Stream) -> TermColorChoice {
138 if atty::is(stream) {
139 TermColorChoice::Auto
140 } else {
141 TermColorChoice::Never
142 }
143 }
144
145 #[allow(missing_debug_implementations)]
146 pub struct Printer(StandardStream);
147
148 pub fn printer() -> MutexGuard<'static, Printer> {
149 GLOBAL_PRINTER
150 .get_or_init(|| Mutex::new(Printer(StandardStream::stdout(TermColorChoice::Auto))))
151 .lock()
152 .unwrap_or_else(PoisonError::into_inner)
153 }
154
155 pub fn eprinter() -> MutexGuard<'static, Printer> {
156 GLOBAL_EPRINTER
157 .get_or_init(|| Mutex::new(Printer(StandardStream::stderr(TermColorChoice::Auto))))
158 .lock()
159 .unwrap_or_else(PoisonError::into_inner)
160 }
161
162 impl Printer {
163 pub fn init(color: ColorChoice) {
164 use TermColorChoice::*;
165 let (choice, echoice) = match color {
166 ColorChoice::Always => (Always, Always),
167 ColorChoice::Auto => (detect_tty(Stream::Stdout), detect_tty(Stream::Stderr)),
168 ColorChoice::Ansi => (AlwaysAnsi, AlwaysAnsi),
169 ColorChoice::Never => (Never, Never),
170 };
171
172 printer().set_stream(StandardStream::stdout(choice));
173 eprinter().set_stream(StandardStream::stderr(echoice));
174 }
175
176 fn set_stream(&mut self, stream: StandardStream) { self.0 = stream; }
177
178 pub fn set_color(&mut self, color: Color) {
179 let _ = self
180 .0
181 .set_color(ColorSpec::new().set_fg(Some(color.into_termcolor())));
182 }
183
184 pub fn reset(&mut self) { let _ = self.0.reset(); }
185 }
186
187 impl io::Write for Printer {
188 fn write(&mut self, buf: &[u8]) -> io::Result<usize> { self.0.write(buf) }
189
190 fn flush(&mut self) -> io::Result<()> { self.0.flush() }
191 }
192
193 impl Color {
194 fn into_termcolor(self) -> TermColorColor {
195 match self {
196 Color::Black => TermColorColor::Black,
197 Color::Blue => TermColorColor::Blue,
198 Color::Green => TermColorColor::Green,
199 Color::Red => TermColorColor::Red,
200 Color::Cyan => TermColorColor::Cyan,
201 Color::Magenta => TermColorColor::Magenta,
202 Color::Yellow => TermColorColor::Yellow,
203 Color::White => TermColorColor::White,
204 }
205 }
206 }
207}
208
209#[cfg(all(not(feature = "color"), not(feature = "api_tests")))]
210mod _printer {
211 use super::*;
212
213 enum StandardStream {
214 Stdout(io::Stdout),
215 Stderr(io::Stderr),
216 }
217
218 #[allow(missing_debug_implementations)]
219 pub struct Printer(StandardStream);
220
221 pub fn printer() -> MutexGuard<'static, Printer> {
222 GLOBAL_PRINTER
223 .get_or_init(|| Mutex::new(Printer(StandardStream::Stdout(io::stdout()))))
224 .lock()
225 .unwrap_or_else(PoisonError::into_inner)
226 }
227
228 pub fn eprinter() -> MutexGuard<'static, Printer> {
229 GLOBAL_EPRINTER
230 .get_or_init(|| Mutex::new(Printer(StandardStream::Stderr(io::stderr()))))
231 .lock()
232 .unwrap_or_else(PoisonError::into_inner)
233 }
234
235 impl Printer {
236 pub fn init(_color: ColorChoice) {
237 let _a = printer();
238 let _a = eprinter();
239 }
240
241 pub fn set_color(&mut self, _color: Color) {}
242
243 pub fn reset(&mut self) {}
244 }
245
246 impl io::Write for Printer {
247 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
248 match self.0 {
249 StandardStream::Stdout(ref mut s) => s.write(buf),
250 StandardStream::Stderr(ref mut s) => s.write(buf),
251 }
252 }
253
254 fn flush(&mut self) -> io::Result<()> {
255 match self.0 {
256 StandardStream::Stdout(ref mut s) => s.flush(),
257 StandardStream::Stderr(ref mut s) => s.flush(),
258 }
259 }
260 }
261}
262
263#[cfg(feature = "api_tests")]
264mod _printer {
265 use std::borrow::Cow;
266
267 use super::*;
268
269 static GLOBAL_TEST_PRINTER: OnceCell<Mutex<self::_printer::Printer>> = OnceCell::new();
270 static GLOBAL_TEST_EPRINTER: OnceCell<Mutex<self::_printer::Printer>> = OnceCell::new();
271
272 #[allow(missing_debug_implementations)]
273 pub struct Printer(pub Vec<u8>);
274
275 pub fn printer() -> MutexGuard<'static, Printer> {
276 GLOBAL_TEST_PRINTER
277 .get_or_init(|| Mutex::new(Printer(Vec::new())))
278 .lock()
279 .unwrap_or_else(PoisonError::into_inner)
280 }
281
282 pub fn eprinter() -> MutexGuard<'static, Printer> {
283 GLOBAL_TEST_EPRINTER
284 .get_or_init(|| Mutex::new(Printer(Vec::new())))
285 .lock()
286 .unwrap_or_else(PoisonError::into_inner)
287 }
288
289 impl Printer {
290 pub fn init(_color: ColorChoice) {
291 let _a = printer();
292 let _a = eprinter();
293 }
294
295 pub fn set_color(&mut self, _color: Color) {}
296
297 pub fn reset(&mut self) {}
298
299 pub fn clear(&mut self) { self.0.clear() }
300
301 pub fn as_string(&self) -> Cow<'_, str> { String::from_utf8_lossy(&self.0) }
302 }
303
304 impl io::Write for Printer {
305 fn write(&mut self, buf: &[u8]) -> io::Result<usize> { self.0.write(buf) }
306
307 fn flush(&mut self) -> io::Result<()> { Ok(()) }
308 }
309}