Skip to content

Commit

Permalink
Use tab writer to improve output of invocations and errors (#1356)
Browse files Browse the repository at this point in the history
  • Loading branch information
brynary authored and noahd1 committed Dec 22, 2024
1 parent 6a04741 commit b117e6a
Show file tree
Hide file tree
Showing 4 changed files with 164 additions and 66 deletions.
217 changes: 155 additions & 62 deletions qlty-cli/src/ui/invocations.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -19,7 +22,6 @@ pub fn print_invocations(
}

if verbose >= 1 {
writeln!(writer)?;
writeln!(
writer,
"{}{}{}",
Expand All @@ -32,88 +34,179 @@ 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_all(format!("\t{}", 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_all(format!("\t{}", 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{}\tExited with unknown status in {:.2}s\t{}\n",
invocation.invocation.plugin_name,
style("Error").red(),
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(())
Expand Down
5 changes: 3 additions & 2 deletions qlty-cli/tests/cmd/check/missing_output_as_error.stdout
Original file line number Diff line number Diff line change
@@ -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 [..].[..]s .qlty/out/invoke-[..].yaml
The plugin crashed for some reason
4 changes: 3 additions & 1 deletion qlty-cli/tests/cmd/check/skip_errored_plugins.stdout
Original file line number Diff line number Diff line change
@@ -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 [..].[..]s .qlty/out/invoke-[..].yaml

Original file line number Diff line number Diff line change
Expand Up @@ -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 [..].[..]s .qlty/out/invoke-[..].yaml

0 comments on commit b117e6a

Please sign in to comment.