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 {
58 "\x1b[0m"
59 } else {
60 ""
61 }
62 }
63
64 fn dim(&self) -> &'static str {
65 if self.colors {
66 "\x1b[2m"
67 } else {
68 ""
69 }
70 }
71
72 fn bold(&self) -> &'static str {
73 if self.colors {
74 "\x1b[1m"
75 } else {
76 ""
77 }
78 }
79}
80
81impl Formatter for TtyFormatter {
82 fn format<W: Write>(&self, result: &LintResult, filename: &str, writer: &mut W) -> std::io::Result<()> {
83 if result.failures.is_empty() {
84 return Ok(());
85 }
86
87 for failure in &result.failures {
88 let color = self.severity_color(failure.severity);
89 let reset = self.reset();
90 let dim = self.dim();
91 let bold = self.bold();
92
93 if self.show_filename {
95 write!(
96 writer,
97 "{}{}{}{}:{}",
98 bold, filename, reset, dim, reset
99 )?;
100 }
101
102 write!(
103 writer,
104 "{}{}{} ",
105 dim,
106 failure.line,
107 reset
108 )?;
109
110 let severity_str = match failure.severity {
112 Severity::Error => "error",
113 Severity::Warning => "warning",
114 Severity::Info => "info",
115 Severity::Style => "style",
116 Severity::Ignore => "ignore",
117 };
118
119 write!(
120 writer,
121 "{}{}{}",
122 color, severity_str, reset
123 )?;
124
125 write!(
127 writer,
128 " {}{}{}: ",
129 dim, failure.code, reset
130 )?;
131
132 writeln!(writer, "{}", failure.message)?;
134 }
135
136 let error_count = result.failures.iter().filter(|f| f.severity == Severity::Error).count();
138 let warning_count = result.failures.iter().filter(|f| f.severity == Severity::Warning).count();
139 let info_count = result.failures.iter().filter(|f| f.severity == Severity::Info).count();
140 let style_count = result.failures.iter().filter(|f| f.severity == Severity::Style).count();
141
142 writeln!(writer)?;
143
144 let mut parts = Vec::new();
145 if error_count > 0 {
146 parts.push(format!(
147 "{}{} error{}{}",
148 self.severity_color(Severity::Error),
149 error_count,
150 if error_count == 1 { "" } else { "s" },
151 self.reset()
152 ));
153 }
154 if warning_count > 0 {
155 parts.push(format!(
156 "{}{} warning{}{}",
157 self.severity_color(Severity::Warning),
158 warning_count,
159 if warning_count == 1 { "" } else { "s" },
160 self.reset()
161 ));
162 }
163 if info_count > 0 {
164 parts.push(format!(
165 "{}{} info{}",
166 self.severity_color(Severity::Info),
167 info_count,
168 self.reset()
169 ));
170 }
171 if style_count > 0 {
172 parts.push(format!(
173 "{}{} style{}",
174 self.severity_color(Severity::Style),
175 style_count,
176 self.reset()
177 ));
178 }
179
180 if !parts.is_empty() {
181 writeln!(writer, "{}", parts.join(", "))?;
182 }
183
184 Ok(())
185 }
186}
187
188#[cfg(test)]
189mod tests {
190 use super::*;
191 use crate::analyzer::hadolint::types::CheckFailure;
192
193 #[test]
194 fn test_tty_output_no_color() {
195 let mut result = LintResult::new();
196 result.failures.push(CheckFailure::new(
197 "DL3008",
198 Severity::Warning,
199 "Pin versions in apt get install",
200 5,
201 ));
202
203 let formatter = TtyFormatter::no_color();
204 let output = formatter.format_to_string(&result, "Dockerfile");
205
206 assert!(output.contains("Dockerfile"));
207 assert!(output.contains("5"));
208 assert!(output.contains("warning"));
209 assert!(output.contains("DL3008"));
210 assert!(output.contains("Pin versions"));
211 }
212}