use std::{
collections::{BTreeMap, HashSet},
path::{Path, PathBuf},
time::Duration,
};
use crate::diagnostic::{Diagnostic, Severity};
use crate::{args, rules::rule::Rules};
use crate::{checker::CheckFileResult, config::Config};
pub fn display_settings(path: &Path, config: &Config, rules: &Rules) {
println!("Settings for file: {}", path.display());
println!(" {config:?}");
let rules_names = rules
.enabled
.iter()
.map(|r| r.name())
.collect::<Vec<&str>>()
.join(", ");
println!(
" Rules enabled: {}",
if rules_names.is_empty() {
"<none>"
} else {
&rules_names
}
);
}
fn display_diagnostics_human(result: &[CheckFileResult], args: &args::CheckArgs) {
let mut diags: Vec<&Diagnostic> = result.iter().flat_map(|x| &x.diagnostics).collect();
match args.sort {
args::CheckSort::Line => {
diags.sort_by_key(|diag| {
(
diag.path.as_path(),
diag.lines
.iter()
.map(|l| l.line_number)
.collect::<Vec<usize>>(),
)
});
}
args::CheckSort::Message => {
diags.sort_by_key(|diag| {
(
diag.lines.first().map_or("", |line| &line.message),
diag.path.as_path(),
diag.lines
.iter()
.map(|l| l.line_number)
.collect::<Vec<usize>>(),
)
});
}
args::CheckSort::Rule => {
diags.sort_by_key(|diag| {
(
diag.rule,
diag.path.as_path(),
diag.lines
.iter()
.map(|l| l.line_number)
.collect::<Vec<usize>>(),
)
});
}
}
for diag in diags {
println!("{diag}");
}
}
fn display_rule_stats(result: &[CheckFileResult]) {
let mut count_rule_errors = BTreeMap::<&str, usize>::new();
for rule in result.iter().flat_map(|x| &x.diagnostics).map(|r| r.rule) {
*count_rule_errors.entry(rule).or_insert(0) += 1;
}
let mut items: Vec<_> = count_rule_errors.iter().collect();
if items.is_empty() {
println!("No errors found.");
return;
}
items.sort_by(|a, b| b.1.cmp(a.1));
println!("Errors by rule:");
for (rule, count) in items {
println!(" {rule}: {count}");
}
}
fn display_file_stats(file_errors: &[(PathBuf, usize, usize, usize)]) {
for (filename, info, warnings, errors) in file_errors {
if errors + warnings + info == 0 {
println!("{}: all OK!", filename.display());
} else {
println!(
"{}: {} problems ({} errors, {} warnings, {} info)",
filename.display(),
errors + warnings + info,
errors,
warnings,
info,
);
}
}
}
fn display_diagnostics_json(result: &[CheckFileResult], _args: &args::CheckArgs) {
let diags: Vec<&Diagnostic> = result.iter().flat_map(|x| &x.diagnostics).collect();
println!("{}", serde_json::to_string(&diags).unwrap_or_default());
}
fn display_misspelled_words(result: &[CheckFileResult], _args: &args::CheckArgs) {
let hash_misspelled_words: HashSet<_> = result
.iter()
.flat_map(|x| &x.diagnostics)
.flat_map(|d| d.misspelled_words.iter())
.collect::<HashSet<_>>();
let mut misspelled_words = hash_misspelled_words.iter().copied().collect::<Vec<_>>();
misspelled_words.sort_unstable();
for word in misspelled_words {
println!("{word}");
}
}
pub fn display_result(
result: &[CheckFileResult],
args: &args::CheckArgs,
elapsed: &Duration,
) -> i32 {
let mut files_checked = 0;
let mut files_with_errors = 0;
let mut count_info = 0;
let mut count_warnings = 0;
let mut count_errors = 0;
let mut file_errors: Vec<(PathBuf, usize, usize, usize)> = Vec::new();
for file in result {
if args.show_settings && !args.quiet {
display_settings(file.path.as_path(), &file.config, &file.rules);
}
let mut count_file_info = 0;
let mut count_file_warnings = 0;
let mut count_file_errors = 0;
files_checked += 1;
if !file.diagnostics.is_empty() {
files_with_errors += 1;
for diag in &file.diagnostics {
match diag.severity {
Severity::Info => {
count_info += 1;
count_file_info += 1;
}
Severity::Warning => {
count_warnings += 1;
count_file_warnings += 1;
}
Severity::Error => {
count_errors += 1;
count_file_errors += 1;
}
}
}
}
if args.file_stats {
file_errors.push((
file.path.clone(),
count_file_info,
count_file_warnings,
count_file_errors,
));
}
}
if !args.quiet {
match args.output {
args::CheckOutputFormat::Human => {
if !args.no_errors {
display_diagnostics_human(result, args);
}
if args.rule_stats {
display_rule_stats(result);
}
if args.file_stats {
file_errors.sort();
display_file_stats(&file_errors);
}
}
args::CheckOutputFormat::Json => {
if !args.no_errors {
display_diagnostics_json(result, args);
}
}
args::CheckOutputFormat::Misspelled => {
if !args.no_errors {
display_misspelled_words(result, args);
}
}
}
}
if files_with_errors == 0 {
if !args.quiet && args.output == args::CheckOutputFormat::Human {
if files_checked > 0 {
println!("{files_checked} files checked: all OK! [{elapsed:?}]");
} else {
println!("No files checked [{elapsed:?}]");
}
}
0
} else {
if !args.quiet && args.output == args::CheckOutputFormat::Human {
println!(
"{files_checked} files checked: \
{} problems \
in {files_with_errors} files \
({count_errors} errors, \
{count_warnings} warnings, \
{count_info} info) \
[{elapsed:?}]",
count_errors + count_warnings + count_info
);
}
if args.output == args::CheckOutputFormat::Misspelled {
return 0;
}
1
}
}