syncable_cli/analyzer/hadolint/formatter/
tty.rs1use crate::analyzer::hadolint::formatter::Formatter;
7use crate::analyzer::hadolint::lint::LintResult;
8use crate::analyzer::hadolint::types::Severity;
9use std::io::Write;
10
11#[derive(Debug, Clone)]
13pub struct TtyFormatter {
14 pub colors: bool,
16 pub show_filename: bool,
18}
19
20impl Default for TtyFormatter {
21 fn default() -> Self {
22 Self {
23 colors: true,
24 show_filename: true,
25 }
26 }
27}
28
29impl TtyFormatter {
30 pub fn new() -> Self {
32 Self::default()
33 }
34
35 pub fn no_color() -> Self {
37 Self {
38 colors: false,
39 show_filename: true,
40 }
41 }
42
43 fn severity_color(&self, severity: Severity) -> &'static str {
44 if !self.colors {
45 return "";
46 }
47 match severity {
48 Severity::Error => "\x1b[1;31m", Severity::Warning => "\x1b[1;33m", Severity::Info => "\x1b[1;36m", Severity::Style => "\x1b[1;35m", Severity::Ignore => "\x1b[2m", }
54 }
55
56 fn reset(&self) -> &'static str {
57 if self.colors { "\x1b[0m" } else { "" }
58 }
59
60 fn dim(&self) -> &'static str {
61 if self.colors { "\x1b[2m" } else { "" }
62 }
63
64 fn bold(&self) -> &'static str {
65 if self.colors { "\x1b[1m" } else { "" }
66 }
67}
68
69impl Formatter for TtyFormatter {
70 fn format<W: Write>(
71 &self,
72 result: &LintResult,
73 filename: &str,
74 writer: &mut W,
75 ) -> std::io::Result<()> {
76 if result.failures.is_empty() {
77 return Ok(());
78 }
79
80 for failure in &result.failures {
81 let color = self.severity_color(failure.severity);
82 let reset = self.reset();
83 let dim = self.dim();
84 let bold = self.bold();
85
86 if self.show_filename {
88 write!(writer, "{}{}{}{}:{}", bold, filename, reset, dim, reset)?;
89 }
90
91 write!(writer, "{}{}{} ", dim, failure.line, reset)?;
92
93 let severity_str = match failure.severity {
95 Severity::Error => "error",
96 Severity::Warning => "warning",
97 Severity::Info => "info",
98 Severity::Style => "style",
99 Severity::Ignore => "ignore",
100 };
101
102 write!(writer, "{}{}{}", color, severity_str, reset)?;
103
104 write!(writer, " {}{}{}: ", dim, failure.code, reset)?;
106
107 writeln!(writer, "{}", failure.message)?;
109 }
110
111 let error_count = result
113 .failures
114 .iter()
115 .filter(|f| f.severity == Severity::Error)
116 .count();
117 let warning_count = result
118 .failures
119 .iter()
120 .filter(|f| f.severity == Severity::Warning)
121 .count();
122 let info_count = result
123 .failures
124 .iter()
125 .filter(|f| f.severity == Severity::Info)
126 .count();
127 let style_count = result
128 .failures
129 .iter()
130 .filter(|f| f.severity == Severity::Style)
131 .count();
132
133 writeln!(writer)?;
134
135 let mut parts = Vec::new();
136 if error_count > 0 {
137 parts.push(format!(
138 "{}{} error{}{}",
139 self.severity_color(Severity::Error),
140 error_count,
141 if error_count == 1 { "" } else { "s" },
142 self.reset()
143 ));
144 }
145 if warning_count > 0 {
146 parts.push(format!(
147 "{}{} warning{}{}",
148 self.severity_color(Severity::Warning),
149 warning_count,
150 if warning_count == 1 { "" } else { "s" },
151 self.reset()
152 ));
153 }
154 if info_count > 0 {
155 parts.push(format!(
156 "{}{} info{}",
157 self.severity_color(Severity::Info),
158 info_count,
159 self.reset()
160 ));
161 }
162 if style_count > 0 {
163 parts.push(format!(
164 "{}{} style{}",
165 self.severity_color(Severity::Style),
166 style_count,
167 self.reset()
168 ));
169 }
170
171 if !parts.is_empty() {
172 writeln!(writer, "{}", parts.join(", "))?;
173 }
174
175 Ok(())
176 }
177}
178
179#[cfg(test)]
180mod tests {
181 use super::*;
182 use crate::analyzer::hadolint::types::CheckFailure;
183
184 #[test]
185 fn test_tty_output_no_color() {
186 let mut result = LintResult::new();
187 result.failures.push(CheckFailure::new(
188 "DL3008",
189 Severity::Warning,
190 "Pin versions in apt get install",
191 5,
192 ));
193
194 let formatter = TtyFormatter::no_color();
195 let output = formatter.format_to_string(&result, "Dockerfile");
196
197 assert!(output.contains("Dockerfile"));
198 assert!(output.contains("5"));
199 assert!(output.contains("warning"));
200 assert!(output.contains("DL3008"));
201 assert!(output.contains("Pin versions"));
202 }
203}