1pub mod diagnostics;
7
8use serde::{Deserialize, Serialize};
9use std::io::IsTerminal;
10use std::str::FromStr;
11
12#[derive(
14 Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize, Serialize, schemars::JsonSchema,
15)]
16#[serde(rename_all = "lowercase")]
17pub enum ColorMode {
18 #[default]
20 Auto,
21 Always,
23 Never,
25}
26
27impl FromStr for ColorMode {
28 type Err = String;
29 fn from_str(s: &str) -> Result<Self, Self::Err> {
30 match s {
31 "auto" => Ok(Self::Auto),
32 "always" => Ok(Self::Always),
33 "never" => Ok(Self::Never),
34 _ => Err(format!(
35 "unknown color mode `{s}`; expected auto, always, or never"
36 )),
37 }
38 }
39}
40
41#[derive(Debug, Clone, Deserialize, Serialize, Default, schemars::JsonSchema)]
51#[serde(default)]
52pub struct PrettyConfig {
53 pub enabled: Option<bool>,
55 pub colors: Option<ColorMode>,
57 pub highlight: Option<bool>,
59}
60
61impl PrettyConfig {
62 pub fn enabled(&self) -> bool {
65 self.enabled
66 .unwrap_or_else(|| std::io::stdout().is_terminal())
67 }
68
69 pub fn use_colors(&self) -> bool {
72 if std::env::var("NO_COLOR").is_ok() {
74 return false;
75 }
76
77 match self.colors.unwrap_or_default() {
78 ColorMode::Always => true,
79 ColorMode::Never => false,
80 ColorMode::Auto => std::io::stdout().is_terminal(),
81 }
82 }
83
84 pub fn highlight(&self) -> bool {
86 self.highlight.unwrap_or(true)
87 }
88}
89
90pub trait OutputFormatter: Serialize + schemars::JsonSchema {
95 fn format_text(&self) -> String;
97
98 fn format_pretty(&self) -> String {
101 self.format_text()
102 }
103}
104
105pub fn progress_bar(ratio: f64, width: usize) -> String {
110 let ratio = ratio.clamp(0.0, 1.0);
111 let filled = ((ratio * width as f64).round() as usize).min(width);
112 format!("{}{}", "█".repeat(filled), "░".repeat(width - filled))
113}
114
115pub fn progress_bar_good(ratio: f64, width: usize) -> String {
117 use nu_ansi_term::Color;
118 let color = if ratio >= 0.67 {
119 Color::Green
120 } else if ratio >= 0.34 {
121 Color::Yellow
122 } else {
123 Color::Red
124 };
125 color.paint(progress_bar(ratio, width)).to_string()
126}
127
128pub fn progress_bar_bad(ratio: f64, width: usize) -> String {
130 progress_bar_good(1.0 - ratio, width)
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136
137 #[derive(Serialize, schemars::JsonSchema)]
138 #[allow(dead_code)]
139 struct TestOutput {
140 name: String,
141 count: usize,
142 }
143
144 impl OutputFormatter for TestOutput {
145 fn format_text(&self) -> String {
146 format!("{}: {}", self.name, self.count)
147 }
148 }
149
150 #[test]
151 fn test_pretty_config_use_colors() {
152 let config = PrettyConfig {
154 colors: Some(ColorMode::Always),
155 ..Default::default()
156 };
157 assert!(config.use_colors());
158
159 let config = PrettyConfig {
161 colors: Some(ColorMode::Never),
162 ..Default::default()
163 };
164 assert!(!config.use_colors());
165 }
166
167 #[test]
168 fn test_pretty_config_highlight() {
169 let config = PrettyConfig::default();
171 assert!(config.highlight());
172
173 let config = PrettyConfig {
175 highlight: Some(false),
176 ..Default::default()
177 };
178 assert!(!config.highlight());
179 }
180}