use super::BuildEvent;
use zlayer_tui::logger::{colorize, detect_color_support};
use zlayer_tui::palette::ansi;
#[derive(Debug, Clone)]
pub struct PlainLogger {
verbose: bool,
color: bool,
}
impl Default for PlainLogger {
fn default() -> Self {
Self::new(false)
}
}
impl PlainLogger {
#[must_use]
pub fn new(verbose: bool) -> Self {
Self {
verbose,
color: detect_color_support(),
}
}
#[must_use]
pub fn with_color(verbose: bool, color: bool) -> Self {
Self { verbose, color }
}
fn colorize(&self, text: &str, color: &str) -> String {
colorize(text, color, self.color)
}
pub fn handle_event(&self, event: &BuildEvent) {
match event {
BuildEvent::StageStarted {
index,
name,
base_image,
} => {
let stage_name = name.as_deref().unwrap_or("unnamed");
let header = format!("==> Stage {}: {} ({})", index + 1, stage_name, base_image);
println!("{}", self.colorize(&header, ansi::CYAN));
}
BuildEvent::InstructionStarted { instruction, .. } => {
let line = format!(" -> {instruction}");
println!("{}", self.colorize(&line, ansi::YELLOW));
}
BuildEvent::Output { line, is_stderr } if self.verbose => {
if *is_stderr {
eprintln!(" {}", self.colorize(line, ansi::DIM));
} else {
println!(" {line}");
}
}
BuildEvent::Output { .. } => {
}
BuildEvent::InstructionComplete { cached, .. } => {
if *cached && self.verbose {
println!(" {}", self.colorize("[cached]", ansi::CYAN));
}
}
BuildEvent::StageComplete { index } => {
if self.verbose {
let line = format!(" Stage {} complete", index + 1);
println!("{}", self.colorize(&line, ansi::GREEN));
}
}
BuildEvent::BuildComplete { image_id } => {
println!();
let success = format!("Build complete: {image_id}");
println!("{}", self.colorize(&success, ansi::GREEN));
}
BuildEvent::BuildFailed { error } => {
println!();
let failure = format!("Build failed: {error}");
eprintln!("{}", self.colorize(&failure, ansi::RED));
}
}
}
pub fn process_events<I>(&self, events: I)
where
I: IntoIterator<Item = BuildEvent>,
{
for event in events {
self.handle_event(&event);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_plain_logger_creation() {
let logger = PlainLogger::new(false);
assert!(!logger.verbose);
let verbose_logger = PlainLogger::new(true);
assert!(verbose_logger.verbose);
}
#[test]
fn test_with_color() {
let logger = PlainLogger::with_color(false, true);
assert!(logger.color);
let no_color_logger = PlainLogger::with_color(false, false);
assert!(!no_color_logger.color);
}
#[test]
fn test_colorize_enabled() {
let logger = PlainLogger::with_color(false, true);
let result = logger.colorize("test", ansi::GREEN);
assert!(result.contains("\x1b[32m"));
assert!(result.contains("\x1b[0m"));
assert!(result.contains("test"));
}
#[test]
fn test_colorize_disabled() {
let logger = PlainLogger::with_color(false, false);
let result = logger.colorize("test", ansi::GREEN);
assert_eq!(result, "test");
assert!(!result.contains("\x1b["));
}
#[test]
fn test_handle_event_does_not_panic() {
let logger = PlainLogger::with_color(true, false);
logger.handle_event(&BuildEvent::StageStarted {
index: 0,
name: Some("builder".to_string()),
base_image: "alpine".to_string(),
});
logger.handle_event(&BuildEvent::InstructionStarted {
stage: 0,
index: 0,
instruction: "RUN echo hello".to_string(),
});
logger.handle_event(&BuildEvent::Output {
line: "hello".to_string(),
is_stderr: false,
});
logger.handle_event(&BuildEvent::Output {
line: "warning".to_string(),
is_stderr: true,
});
logger.handle_event(&BuildEvent::InstructionComplete {
stage: 0,
index: 0,
cached: true,
});
logger.handle_event(&BuildEvent::StageComplete { index: 0 });
logger.handle_event(&BuildEvent::BuildComplete {
image_id: "sha256:abc".to_string(),
});
logger.handle_event(&BuildEvent::BuildFailed {
error: "test error".to_string(),
});
}
#[test]
fn test_default() {
let logger = PlainLogger::default();
assert!(!logger.verbose);
}
}