From 25f777f0947dc55325ae9a0edab8882958509bb3 Mon Sep 17 00:00:00 2001 From: Bryan Helmkamp Date: Fri, 20 Dec 2024 15:57:49 -0500 Subject: [PATCH 1/4] --- qlty-cli/src/ui/invocations.rs | 220 +++++++++++++++++++++++---------- 1 file changed, 158 insertions(+), 62 deletions(-) diff --git a/qlty-cli/src/ui/invocations.rs b/qlty-cli/src/ui/invocations.rs index bd3cc2a4..82a767fd 100644 --- a/qlty-cli/src/ui/invocations.rs +++ b/qlty-cli/src/ui/invocations.rs @@ -1,8 +1,11 @@ +use std::io::Write as _; + use anyhow::Result; use console::style; use num_format::{Locale, ToFormattedString as _}; use qlty_analysis::utils::fs::path_to_string; use qlty_check::{executor::InvocationStatus, Report}; +use tabwriter::TabWriter; pub fn print_invocations( writer: &mut dyn std::io::Write, @@ -19,7 +22,6 @@ pub fn print_invocations( } if verbose >= 1 { - writeln!(writer)?; writeln!( writer, "{}{}{}", @@ -32,88 +34,182 @@ pub fn print_invocations( writeln!(writer)?; } - let mut printed_summary = false; let cwd = std::env::current_dir().expect("Unable to identify current directory"); + let mut tw = TabWriter::new(vec![]); + + // Print a JOBS summary in verbose mode + if verbose >= 1 { + for invocation in &report.invocations { + let absolute_outfile_path = invocation.outfile_path(); + let outfile_path = pathdiff::diff_paths(absolute_outfile_path, &cwd).unwrap(); + + match invocation.status() { + InvocationStatus::Success => { + tw.write_all( + format!( + "{}\t{}\t{} {}\t{:.2}s\t{}\n", + invocation.invocation.plugin_name, + style("Success").green(), + invocation.invocation.targets_count, + if invocation.invocation.targets_count == 1 { + "target" + } else { + "targets" + }, + invocation.invocation.duration_secs, + style(path_to_string(outfile_path)).dim().underlined(), + ) + .as_bytes(), + ) + .unwrap(); + } + InvocationStatus::LintError => { + tw.write_all( + format!( + "{}\t{}\t{} {}\t{:.2}s\t{}\n", + invocation.invocation.plugin_name, + style("Error").red(), + invocation.invocation.targets_count, + if invocation.invocation.targets_count == 1 { + "target" + } else { + "targets" + }, + invocation.invocation.duration_secs, + style(path_to_string(outfile_path)).dim().underlined(), + ) + .as_bytes(), + ) + .unwrap(); + } + InvocationStatus::ParseError => { + tw.write_all( + format!( + "{}\t{}\t{} {}\t{:.2}s\t{}\n", + invocation.invocation.plugin_name, + style("Parse error").red(), + invocation.invocation.targets_count, + if invocation.invocation.targets_count == 1 { + "target" + } else { + "targets" + }, + invocation.invocation.duration_secs, + style(path_to_string(outfile_path)).dim().underlined(), + ) + .as_bytes(), + ) + .unwrap(); + } + } + } + + tw.flush().unwrap(); + let written = String::from_utf8(tw.into_inner().unwrap()).unwrap(); + + if !written.is_empty() { + writeln!(writer, "{}", written)?; + } + } + + let mut tw = TabWriter::new(vec![]); + let mut errors_count = 0; for invocation in &report.invocations { let absolute_outfile_path = invocation.outfile_path(); let outfile_path = pathdiff::diff_paths(absolute_outfile_path, &cwd).unwrap(); match invocation.status() { - InvocationStatus::Success => { - if verbose >= 1 { - writeln!( - writer, - "{} {} checked {} files in {:.2}s {}", - style("Success").green(), - invocation.invocation.plugin_name, - invocation.plan.workspace_entries.len(), - invocation.invocation.duration_secs, - style(path_to_string(outfile_path)).dim(), - )?; + InvocationStatus::Success => {} + InvocationStatus::LintError => { + errors_count += 1; - printed_summary = true; - } - } - InvocationStatus::LintError => match invocation.invocation.exit_code { - Some(code) => { - writeln!( - writer, - "{} {}: Exited with code {:?} {}", - style("Lint error").red(), - style(&invocation.invocation.plugin_name).red().bold(), - code, - style(path_to_string(outfile_path)).dim(), - )?; - - if invocation.invocation.stderr.is_empty() { - if !invocation.invocation.stdout.is_empty() { + match invocation.invocation.exit_code { + Some(code) => { + tw.write_all( + format!( + "{}\t{}\t{}\t{}\n", + invocation.invocation.plugin_name, + style("Error").red(), + format!( + "Exited with code {:?} in {:.2}s", + code, invocation.invocation.duration_secs + ), + style(path_to_string(outfile_path)).dim().underlined(), + ) + .as_bytes(), + ) + .unwrap(); + + if invocation.invocation.stderr.is_empty() { + if !invocation.invocation.stdout.is_empty() { + let text: String = + invocation.invocation.stdout.chars().take(2048).collect(); + + for line in text.lines() { + tw.write(format!(" {}", style(line).red()).as_bytes())?; + } + } + } else { let text: String = - invocation.invocation.stdout.chars().take(2048).collect(); + invocation.invocation.stderr.chars().take(2048).collect(); for line in text.lines() { - writeln!(writer, " {}", style(line).red())?; + tw.write(format!(" {}", style(line).red()).as_bytes())?; } } - } else { - let text: String = - invocation.invocation.stderr.chars().take(2048).collect(); - - for line in text.lines() { - writeln!(writer, " {}", style(line).red())?; - } } - - printed_summary = true; - } - None => { - writeln!( - writer, - "{} {}: Exited with unknown status {}", - style("Lint error").red(), - style(&invocation.invocation.plugin_name).red().bold(), - style(path_to_string(invocation.outfile_path())).dim(), - )?; - printed_summary = true; + None => { + tw.write_all( + format!( + "{}\t{}\t{}\t{}\n", + invocation.invocation.plugin_name, + style("Error").red(), + format!( + "Exited with unknown status in {:.2}s", + invocation.invocation.duration_secs + ), + style(path_to_string(outfile_path)).dim().underlined(), + ) + .as_bytes(), + ) + .unwrap(); + } } - }, + } InvocationStatus::ParseError => { - writeln!( - writer, - "{} {}: {} {}", - style("Parse error").red(), - invocation.invocation.plugin_name, - invocation.invocation.parser_error.as_ref().unwrap(), - style(path_to_string(outfile_path)).dim(), - )?; - - printed_summary = true; + errors_count += 1; + + tw.write_all( + format!( + "{}\t{}\t{}\t{}\n", + invocation.invocation.plugin_name, + style("Parse error").red(), + invocation.invocation.parser_error.as_ref().unwrap(), + style(path_to_string(outfile_path)).dim().underlined(), + ) + .as_bytes(), + ) + .unwrap(); } } } - if printed_summary { + tw.flush().unwrap(); + let written = String::from_utf8(tw.into_inner().unwrap()).unwrap(); + + if !written.is_empty() { + writeln!( + writer, + "{}{}{}", + style(" ERRORS: ").bold().reverse(), + style(errors_count.to_formatted_string(&Locale::en)) + .bold() + .reverse(), + style(" ").bold().reverse() + )?; writeln!(writer)?; + writeln!(writer, "{}", written)?; } Ok(()) From 3174ff29b9b53a4ab5f4fb557cd98045a814ce06 Mon Sep 17 00:00:00 2001 From: Bryan Helmkamp Date: Fri, 20 Dec 2024 17:03:24 -0500 Subject: [PATCH 2/4] --- qlty-cli/tests/cmd/check/missing_output_as_error.stdout | 5 +++-- qlty-cli/tests/cmd/check/skip_errored_plugins.stdout | 4 +++- .../check/skip_errored_plugins_reports_other_failures.stdout | 4 +++- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/qlty-cli/tests/cmd/check/missing_output_as_error.stdout b/qlty-cli/tests/cmd/check/missing_output_as_error.stdout index 45203eab..a04906a4 100644 --- a/qlty-cli/tests/cmd/check/missing_output_as_error.stdout +++ b/qlty-cli/tests/cmd/check/missing_output_as_error.stdout @@ -1,3 +1,4 @@ -Lint error exists: Exited with code 0 .qlty/out/invoke-[..].yaml -[..]The plugin crashed for some reason[..] + ERRORS: 1 +exists Error Exited with code 0 in 0.01s .qlty/out/invoke-350eEP.yaml + The plugin crashed for some reason diff --git a/qlty-cli/tests/cmd/check/skip_errored_plugins.stdout b/qlty-cli/tests/cmd/check/skip_errored_plugins.stdout index 6da2d804..846e95a5 100644 --- a/qlty-cli/tests/cmd/check/skip_errored_plugins.stdout +++ b/qlty-cli/tests/cmd/check/skip_errored_plugins.stdout @@ -1,2 +1,4 @@ -Lint error always_error: Exited with code 1 .qlty/out/invoke-[..].yaml + ERRORS: 1 + +always_error Error Exited with code 1 in 0.01s .qlty/out/invoke-Wt9ofg.yaml diff --git a/qlty-cli/tests/cmd/check/skip_errored_plugins_reports_other_failures.stdout b/qlty-cli/tests/cmd/check/skip_errored_plugins_reports_other_failures.stdout index 3a5747b2..e617d0f2 100644 --- a/qlty-cli/tests/cmd/check/skip_errored_plugins_reports_other_failures.stdout +++ b/qlty-cli/tests/cmd/check/skip_errored_plugins_reports_other_failures.stdout @@ -4,5 +4,7 @@ :0:0 0:0 high always_fail failed always_fail:fail -Lint error always_error: Exited with code 1 .qlty/out/invoke-[..].yaml + ERRORS: 1 + +always_error Error Exited with code 1 in 0.01s .qlty/out/invoke-zaTSXG.yaml From dbeea8e083375cad3ef45d579a8e2c3649b74a83 Mon Sep 17 00:00:00 2001 From: Bryan Helmkamp Date: Fri, 20 Dec 2024 17:05:36 -0500 Subject: [PATCH 3/4] --- qlty-cli/tests/cmd/check/missing_output_as_error.stdout | 2 +- qlty-cli/tests/cmd/check/skip_errored_plugins.stdout | 2 +- .../check/skip_errored_plugins_reports_other_failures.stdout | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/qlty-cli/tests/cmd/check/missing_output_as_error.stdout b/qlty-cli/tests/cmd/check/missing_output_as_error.stdout index a04906a4..c9f6b8d8 100644 --- a/qlty-cli/tests/cmd/check/missing_output_as_error.stdout +++ b/qlty-cli/tests/cmd/check/missing_output_as_error.stdout @@ -1,4 +1,4 @@ ERRORS: 1 -exists Error Exited with code 0 in 0.01s .qlty/out/invoke-350eEP.yaml +exists Error Exited with code 0 in [..].[..]s .qlty/out/invoke-[..].yaml The plugin crashed for some reason diff --git a/qlty-cli/tests/cmd/check/skip_errored_plugins.stdout b/qlty-cli/tests/cmd/check/skip_errored_plugins.stdout index 846e95a5..3f10a23c 100644 --- a/qlty-cli/tests/cmd/check/skip_errored_plugins.stdout +++ b/qlty-cli/tests/cmd/check/skip_errored_plugins.stdout @@ -1,4 +1,4 @@ ERRORS: 1 -always_error Error Exited with code 1 in 0.01s .qlty/out/invoke-Wt9ofg.yaml +always_error Error Exited with code 1 in [..].[..]s .qlty/out/invoke-[..].yaml diff --git a/qlty-cli/tests/cmd/check/skip_errored_plugins_reports_other_failures.stdout b/qlty-cli/tests/cmd/check/skip_errored_plugins_reports_other_failures.stdout index e617d0f2..e542d86c 100644 --- a/qlty-cli/tests/cmd/check/skip_errored_plugins_reports_other_failures.stdout +++ b/qlty-cli/tests/cmd/check/skip_errored_plugins_reports_other_failures.stdout @@ -6,5 +6,5 @@ ERRORS: 1 -always_error Error Exited with code 1 in 0.01s .qlty/out/invoke-zaTSXG.yaml +always_error Error Exited with code 1 in [..].[..]s .qlty/out/invoke-[..].yaml From f943e0e9f8da8c9b4979ae6a9c82461f220a0364 Mon Sep 17 00:00:00 2001 From: Bryan Helmkamp Date: Fri, 20 Dec 2024 19:46:37 -0500 Subject: [PATCH 4/4] --- qlty-cli/src/ui/invocations.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/qlty-cli/src/ui/invocations.rs b/qlty-cli/src/ui/invocations.rs index 82a767fd..2b302716 100644 --- a/qlty-cli/src/ui/invocations.rs +++ b/qlty-cli/src/ui/invocations.rs @@ -147,7 +147,7 @@ pub fn print_invocations( invocation.invocation.stdout.chars().take(2048).collect(); for line in text.lines() { - tw.write(format!(" {}", style(line).red()).as_bytes())?; + tw.write_all(format!("\t{}", style(line).red()).as_bytes())?; } } } else { @@ -155,20 +155,17 @@ pub fn print_invocations( invocation.invocation.stderr.chars().take(2048).collect(); for line in text.lines() { - tw.write(format!(" {}", style(line).red()).as_bytes())?; + tw.write_all(format!("\t{}", style(line).red()).as_bytes())?; } } } None => { tw.write_all( format!( - "{}\t{}\t{}\t{}\n", + "{}\t{}\tExited with unknown status in {:.2}s\t{}\n", invocation.invocation.plugin_name, style("Error").red(), - format!( - "Exited with unknown status in {:.2}s", - invocation.invocation.duration_secs - ), + invocation.invocation.duration_secs, style(path_to_string(outfile_path)).dim().underlined(), ) .as_bytes(),