use std::time::Duration;
use hsml::diagnostic::{
Diagnostic, Severity,
format::{
DiagnosticFormatter,
default::{DefaultColorFormatter, DefaultFormatter},
github::GithubFormatter,
gitlab::GitlabFormatter,
json::JsonFormatter,
},
};
pub type DimCodes = (&'static str, &'static str);
pub fn dim_codes(no_color: bool) -> DimCodes {
if no_color {
("", "")
} else {
("\x1b[2m", "\x1b[0m")
}
}
pub fn resolve_colors(matches: &clap::ArgMatches) -> (bool, DimCodes) {
let no_color = matches.get_flag("no_color") || std::env::var("NO_COLOR").is_ok();
(no_color, dim_codes(no_color))
}
pub struct FileDiagnostics {
pub diagnostics: Vec<Diagnostic>,
pub source: String,
}
pub fn render_diagnostics(results: &[FileDiagnostics], format: Option<&str>, no_color: bool) {
let all_diagnostics: Vec<&Diagnostic> =
results.iter().flat_map(|r| r.diagnostics.iter()).collect();
match format {
Some("json") => {
let owned: Vec<_> = all_diagnostics.into_iter().cloned().collect();
let output = JsonFormatter.format(&owned, None);
eprintln!("{output}");
}
Some("github") => {
let owned: Vec<_> = all_diagnostics.into_iter().cloned().collect();
let output = GithubFormatter.format(&owned, None);
eprint!("{output}");
}
Some("gitlab") => {
let owned: Vec<_> = all_diagnostics.into_iter().cloned().collect();
let output = GitlabFormatter.format(&owned, None);
eprintln!("{output}");
}
_ => {
let formatter: Box<dyn DiagnosticFormatter> = if no_color {
Box::new(DefaultFormatter)
} else {
Box::new(DefaultColorFormatter)
};
for result in results {
if !result.diagnostics.is_empty() {
eprint!(
"{}",
formatter.format(&result.diagnostics, Some(&result.source))
);
}
}
}
}
}
pub fn has_errors(results: &[FileDiagnostics]) -> bool {
results
.iter()
.flat_map(|r| r.diagnostics.iter())
.any(|d| d.severity == Severity::Error)
}
pub fn format_duration(duration: Duration) -> String {
let micros = duration.as_micros();
if micros < 1000 {
format!("{micros}µs")
} else {
format!("{}ms", duration.as_millis())
}
}
pub fn print_summary(
diagnostics: &[FileDiagnostics],
file_count: usize,
io_error_count: usize,
total_duration: Duration,
no_color: bool,
verb: &str,
) {
let dim = dim_codes(no_color);
let mut errors = 0;
let mut warnings = 0;
for fd in diagnostics {
for d in &fd.diagnostics {
match d.severity {
Severity::Error => errors += 1,
Severity::Warning => warnings += 1,
}
}
}
let timing = format_duration(total_duration);
let files = if file_count == 1 {
"1 file"
} else {
&format!("{file_count} files")
};
let has_failures = errors > 0 || io_error_count > 0;
let icon = if no_color {
if has_failures { "✗" } else { "✓" }
} else if has_failures {
"\x1b[31m✗\x1b[0m" } else {
"\x1b[32m✓\x1b[0m" };
let mut diag_parts = Vec::new();
if errors > 0 {
diag_parts.push(format!(
"{errors} error{}",
if errors == 1 { "" } else { "s" }
));
}
if warnings > 0 {
diag_parts.push(format!(
"{warnings} warning{}",
if warnings == 1 { "" } else { "s" }
));
}
let summary = if diag_parts.is_empty() {
format!("{files} {verb} in {timing}")
} else {
format!("{files} {verb} in {timing} ({})", diag_parts.join(", "))
};
println!("\n{icon} {}{summary}{}", dim.0, dim.1);
}