zlayer_builder/tui/
logger.rs1use super::BuildEvent;
8use zlayer_tui::logger::{colorize, detect_color_support};
9use zlayer_tui::palette::ansi;
10
11#[derive(Debug, Clone)]
38pub struct PlainLogger {
39 verbose: bool,
41 color: bool,
43}
44
45impl Default for PlainLogger {
46 fn default() -> Self {
47 Self::new(false)
48 }
49}
50
51impl PlainLogger {
52 pub fn new(verbose: bool) -> Self {
59 Self {
60 verbose,
61 color: detect_color_support(),
62 }
63 }
64
65 pub fn with_color(verbose: bool, color: bool) -> Self {
67 Self { verbose, color }
68 }
69
70 fn colorize(&self, text: &str, color: &str) -> String {
75 colorize(text, color, self.color)
76 }
77
78 pub fn handle_event(&self, event: &BuildEvent) {
80 match event {
81 BuildEvent::StageStarted {
82 index,
83 name,
84 base_image,
85 } => {
86 let stage_name = name.as_deref().unwrap_or("unnamed");
87 let header = format!("==> Stage {}: {} ({})", index + 1, stage_name, base_image);
88 println!("{}", self.colorize(&header, ansi::CYAN));
89 }
90
91 BuildEvent::InstructionStarted { instruction, .. } => {
92 let line = format!(" -> {}", instruction);
93 println!("{}", self.colorize(&line, ansi::YELLOW));
94 }
95
96 BuildEvent::Output { line, is_stderr } if self.verbose => {
97 if *is_stderr {
98 eprintln!(" {}", self.colorize(line, ansi::DIM));
99 } else {
100 println!(" {}", line);
101 }
102 }
103
104 BuildEvent::Output { .. } => {
105 }
107
108 BuildEvent::InstructionComplete { cached, .. } => {
109 if *cached && self.verbose {
110 println!(" {}", self.colorize("[cached]", ansi::CYAN));
111 }
112 }
113
114 BuildEvent::StageComplete { index } => {
115 if self.verbose {
116 let line = format!(" Stage {} complete", index + 1);
117 println!("{}", self.colorize(&line, ansi::GREEN));
118 }
119 }
120
121 BuildEvent::BuildComplete { image_id } => {
122 println!();
123 let success = format!("Build complete: {}", image_id);
124 println!("{}", self.colorize(&success, ansi::GREEN));
125 }
126
127 BuildEvent::BuildFailed { error } => {
128 println!();
129 let failure = format!("Build failed: {}", error);
130 eprintln!("{}", self.colorize(&failure, ansi::RED));
131 }
132 }
133 }
134
135 pub fn process_events<I>(&self, events: I)
139 where
140 I: IntoIterator<Item = BuildEvent>,
141 {
142 for event in events {
143 self.handle_event(&event);
144 }
145 }
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151
152 #[test]
153 fn test_plain_logger_creation() {
154 let logger = PlainLogger::new(false);
155 assert!(!logger.verbose);
156
157 let verbose_logger = PlainLogger::new(true);
158 assert!(verbose_logger.verbose);
159 }
160
161 #[test]
162 fn test_with_color() {
163 let logger = PlainLogger::with_color(false, true);
164 assert!(logger.color);
165
166 let no_color_logger = PlainLogger::with_color(false, false);
167 assert!(!no_color_logger.color);
168 }
169
170 #[test]
171 fn test_colorize_enabled() {
172 let logger = PlainLogger::with_color(false, true);
173 let result = logger.colorize("test", ansi::GREEN);
174 assert!(result.contains("\x1b[32m"));
175 assert!(result.contains("\x1b[0m"));
176 assert!(result.contains("test"));
177 }
178
179 #[test]
180 fn test_colorize_disabled() {
181 let logger = PlainLogger::with_color(false, false);
182 let result = logger.colorize("test", ansi::GREEN);
183 assert_eq!(result, "test");
184 assert!(!result.contains("\x1b["));
185 }
186
187 #[test]
188 fn test_handle_event_does_not_panic() {
189 let logger = PlainLogger::with_color(true, false);
191
192 logger.handle_event(&BuildEvent::StageStarted {
194 index: 0,
195 name: Some("builder".to_string()),
196 base_image: "alpine".to_string(),
197 });
198
199 logger.handle_event(&BuildEvent::InstructionStarted {
200 stage: 0,
201 index: 0,
202 instruction: "RUN echo hello".to_string(),
203 });
204
205 logger.handle_event(&BuildEvent::Output {
206 line: "hello".to_string(),
207 is_stderr: false,
208 });
209
210 logger.handle_event(&BuildEvent::Output {
211 line: "warning".to_string(),
212 is_stderr: true,
213 });
214
215 logger.handle_event(&BuildEvent::InstructionComplete {
216 stage: 0,
217 index: 0,
218 cached: true,
219 });
220
221 logger.handle_event(&BuildEvent::StageComplete { index: 0 });
222
223 logger.handle_event(&BuildEvent::BuildComplete {
224 image_id: "sha256:abc".to_string(),
225 });
226
227 logger.handle_event(&BuildEvent::BuildFailed {
228 error: "test error".to_string(),
229 });
230 }
231
232 #[test]
233 fn test_default() {
234 let logger = PlainLogger::default();
235 assert!(!logger.verbose);
236 }
237}