From bec19b930a7dd427bab06e87f90dc0bdaf056d6b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 3 Nov 2025 14:06:32 +0000 Subject: [PATCH 1/3] Initial plan From 5590c4315b4bf747cb102034ed1d70bd15fd26d4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 3 Nov 2025 14:17:55 +0000 Subject: [PATCH 2/3] refactor: extract verbosity filtering logic into VerbosityFilter struct Co-authored-by: josecelano <58816+josecelano@users.noreply.github.com> --- src/presentation/user_output.rs | 191 ++++++++++++++++++++++++++++++-- 1 file changed, 181 insertions(+), 10 deletions(-) diff --git a/src/presentation/user_output.rs b/src/presentation/user_output.rs index 09334f4e..c61f6adf 100644 --- a/src/presentation/user_output.rs +++ b/src/presentation/user_output.rs @@ -70,6 +70,61 @@ pub enum VerbosityLevel { Debug, } +/// Determines what messages should be displayed based on verbosity level +/// +/// This struct encapsulates verbosity filtering logic, making it testable +/// independently from output formatting. +struct VerbosityFilter { + level: VerbosityLevel, +} + +impl VerbosityFilter { + /// Create a new verbosity filter with the specified level + fn new(level: VerbosityLevel) -> Self { + Self { level } + } + + /// Check if messages at the given level should be shown + fn should_show(&self, required_level: VerbosityLevel) -> bool { + self.level >= required_level + } + + /// Progress messages require Normal level + fn should_show_progress(&self) -> bool { + self.should_show(VerbosityLevel::Normal) + } + + /// Success messages require Normal level + fn should_show_success(&self) -> bool { + self.should_show(VerbosityLevel::Normal) + } + + /// Warning messages require Normal level + fn should_show_warnings(&self) -> bool { + self.should_show(VerbosityLevel::Normal) + } + + /// Errors are always shown regardless of verbosity level + fn should_show_errors(&self) -> bool { + true + } + + /// Blank lines require Normal level + fn should_show_blank_lines(&self) -> bool { + self.should_show(VerbosityLevel::Normal) + } + + /// Steps require Normal level + fn should_show_steps(&self) -> bool { + self.should_show(VerbosityLevel::Normal) + } + + /// Info blocks require Normal level + fn should_show_info_blocks(&self) -> bool { + self.should_show(VerbosityLevel::Normal) + } +} + /// Handles user-facing output separate from internal logging /// /// Uses dual channels following Unix conventions and modern CLI best practices: @@ -100,7 +155,7 @@ pub enum VerbosityLevel { /// output.result("Processing complete"); /// ``` pub struct UserOutput { - verbosity: VerbosityLevel, + verbosity_filter: VerbosityFilter, stdout_writer: Box, stderr_writer: Box, } @@ -118,7 +173,7 @@ impl UserOutput { #[must_use] pub fn new(verbosity: VerbosityLevel) -> Self { Self { - verbosity, + verbosity_filter: VerbosityFilter::new(verbosity), stdout_writer: Box::new(std::io::stdout()), stderr_writer: Box::new(std::io::stderr()), } @@ -151,7 +206,7 @@ impl UserOutput { stderr_writer: Box, ) -> Self { Self { - verbosity, + verbosity_filter: VerbosityFilter::new(verbosity), stdout_writer, stderr_writer, } @@ -172,7 +227,7 @@ impl UserOutput { /// // Output to stderr: ⏳ Destroying environment... /// ``` pub fn progress(&mut self, message: &str) { - if self.verbosity >= VerbosityLevel::Normal { + if self.verbosity_filter.should_show_progress() { writeln!(self.stderr_writer, "⏳ {message}").ok(); } } @@ -191,7 +246,7 @@ impl UserOutput { /// // Output to stderr: ✅ Environment destroyed successfully /// ``` pub fn success(&mut self, message: &str) { - if self.verbosity >= VerbosityLevel::Normal { + if self.verbosity_filter.should_show_success() { writeln!(self.stderr_writer, "✅ {message}").ok(); } } @@ -208,7 +263,7 @@ impl UserOutput { /// // Output to stderr: ⚠️ Infrastructure may already be destroyed /// ``` pub fn warn(&mut self, message: &str) { - if self.verbosity >= VerbosityLevel::Normal { + if self.verbosity_filter.should_show_warnings() { writeln!(self.stderr_writer, "⚠️ {message}").ok(); } } @@ -227,7 +282,9 @@ impl UserOutput { /// // Output to stderr: ❌ Failed to destroy environment /// ``` pub fn error(&mut self, message: &str) { - writeln!(self.stderr_writer, "❌ {message}").ok(); + if self.verbosity_filter.should_show_errors() { + writeln!(self.stderr_writer, "❌ {message}").ok(); + } } /// Output final results to stdout for piping/redirection @@ -280,7 +337,7 @@ impl UserOutput { /// output.progress("Starting next steps..."); /// ``` pub fn blank_line(&mut self) { - if self.verbosity >= VerbosityLevel::Normal { + if self.verbosity_filter.should_show_blank_lines() { writeln!(self.stderr_writer).ok(); } } @@ -307,7 +364,7 @@ impl UserOutput { /// // 3. Run the deploy command /// ``` pub fn steps(&mut self, title: &str, steps: &[&str]) { - if self.verbosity >= VerbosityLevel::Normal { + if self.verbosity_filter.should_show_steps() { writeln!(self.stderr_writer, "{title}").ok(); for (idx, step) in steps.iter().enumerate() { writeln!(self.stderr_writer, "{}. {}", idx + 1, step).ok(); @@ -337,7 +394,7 @@ impl UserOutput { /// // - key_path: path/to/key /// ``` pub fn info_block(&mut self, title: &str, lines: &[&str]) { - if self.verbosity >= VerbosityLevel::Normal { + if self.verbosity_filter.should_show_info_blocks() { writeln!(self.stderr_writer, "{title}").ok(); for line in lines { writeln!(self.stderr_writer, "{line}").ok(); @@ -649,4 +706,118 @@ mod tests { let stderr_content = String::from_utf8(stderr_buf.lock().unwrap().clone()).unwrap(); assert_eq!(stderr_content, ""); } + + // VerbosityFilter tests + mod verbosity_filter { + use super::super::*; + + #[test] + fn it_should_show_progress_at_normal_level() { + let filter = VerbosityFilter::new(VerbosityLevel::Normal); + assert!(filter.should_show_progress()); + } + + #[test] + fn it_should_not_show_progress_at_quiet_level() { + let filter = VerbosityFilter::new(VerbosityLevel::Quiet); + assert!(!filter.should_show_progress()); + } + + #[test] + fn it_should_show_progress_at_verbose_level() { + let filter = VerbosityFilter::new(VerbosityLevel::Verbose); + assert!(filter.should_show_progress()); + } + + #[test] + fn it_should_always_show_errors_regardless_of_level() { + assert!(VerbosityFilter::new(VerbosityLevel::Quiet).should_show_errors()); + assert!(VerbosityFilter::new(VerbosityLevel::Normal).should_show_errors()); + assert!(VerbosityFilter::new(VerbosityLevel::Verbose).should_show_errors()); + assert!(VerbosityFilter::new(VerbosityLevel::VeryVerbose).should_show_errors()); + assert!(VerbosityFilter::new(VerbosityLevel::Debug).should_show_errors()); + } + + #[test] + fn it_should_show_success_at_normal_level() { + let filter = VerbosityFilter::new(VerbosityLevel::Normal); + assert!(filter.should_show_success()); + } + + #[test] + fn it_should_not_show_success_at_quiet_level() { + let filter = VerbosityFilter::new(VerbosityLevel::Quiet); + assert!(!filter.should_show_success()); + } + + #[test] + fn it_should_show_warnings_at_normal_level() { + let filter = VerbosityFilter::new(VerbosityLevel::Normal); + assert!(filter.should_show_warnings()); + } + + #[test] + fn it_should_not_show_warnings_at_quiet_level() { + let filter = VerbosityFilter::new(VerbosityLevel::Quiet); + assert!(!filter.should_show_warnings()); + } + + #[test] + fn it_should_show_blank_lines_at_normal_level() { + let filter = VerbosityFilter::new(VerbosityLevel::Normal); + assert!(filter.should_show_blank_lines()); + } + + #[test] + fn it_should_not_show_blank_lines_at_quiet_level() { + let filter = VerbosityFilter::new(VerbosityLevel::Quiet); + assert!(!filter.should_show_blank_lines()); + } + + #[test] + fn it_should_show_steps_at_normal_level() { + let filter = VerbosityFilter::new(VerbosityLevel::Normal); + assert!(filter.should_show_steps()); + } + + #[test] + fn it_should_not_show_steps_at_quiet_level() { + let filter = VerbosityFilter::new(VerbosityLevel::Quiet); + assert!(!filter.should_show_steps()); + } + + #[test] + fn it_should_show_info_blocks_at_normal_level() { + let filter = VerbosityFilter::new(VerbosityLevel::Normal); + assert!(filter.should_show_info_blocks()); + } + + #[test] + fn it_should_not_show_info_blocks_at_quiet_level() { + let filter = VerbosityFilter::new(VerbosityLevel::Quiet); + assert!(!filter.should_show_info_blocks()); + } + + #[test] + fn it_should_show_when_level_meets_requirement() { + let filter = VerbosityFilter::new(VerbosityLevel::Normal); + assert!(filter.should_show(VerbosityLevel::Quiet)); + assert!(filter.should_show(VerbosityLevel::Normal)); + assert!(!filter.should_show(VerbosityLevel::Verbose)); + } + + #[test] + fn it_should_handle_all_verbosity_levels_in_should_show() { + let quiet_filter = VerbosityFilter::new(VerbosityLevel::Quiet); + assert!(quiet_filter.should_show(VerbosityLevel::Quiet)); + assert!(!quiet_filter.should_show(VerbosityLevel::Normal)); + + let debug_filter = VerbosityFilter::new(VerbosityLevel::Debug); + assert!(debug_filter.should_show(VerbosityLevel::Quiet)); + assert!(debug_filter.should_show(VerbosityLevel::Normal)); + assert!(debug_filter.should_show(VerbosityLevel::Verbose)); + assert!(debug_filter.should_show(VerbosityLevel::VeryVerbose)); + assert!(debug_filter.should_show(VerbosityLevel::Debug)); + } + } } From 046869f0cf8c2ea3bcf471ec897d868b7ac0ec32 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 3 Nov 2025 14:35:41 +0000 Subject: [PATCH 3/3] fix: add clippy allow for unused_self in should_show_errors Co-authored-by: josecelano <58816+josecelano@users.noreply.github.com> --- src/presentation/user_output.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/presentation/user_output.rs b/src/presentation/user_output.rs index c61f6adf..49c304f1 100644 --- a/src/presentation/user_output.rs +++ b/src/presentation/user_output.rs @@ -105,6 +105,7 @@ impl VerbosityFilter { } /// Errors are always shown regardless of verbosity level + #[allow(clippy::unused_self)] fn should_show_errors(&self) -> bool { true }