use clap::ArgMatches;
use crate::domain::model::check::{CheckViolation, Severity};
use crate::domain::usecases::check::{
run_check, run_fix, CheckReport, CheckedEntry, DrSource, FixDrSource, FixIssueSource, FixMode,
FixReport, IssueSource,
};
use crate::infra::driving::cli::check_renderer::{render, render_with_hint};
use super::super::errors::{die1, CliError};
use super::super::theme;
use super::super::Context;
#[derive(serde::Serialize)]
pub(in super::super) struct CheckEntry {
pub kind: String,
pub path: String,
pub ok: bool,
pub violations: Vec<CheckViolationView>,
}
#[derive(serde::Serialize)]
pub(in super::super) struct CheckViolationView {
#[serde(with = "crate::infra::serde_support::severity")]
pub severity: Severity,
pub message: String,
}
impl From<&CheckViolation> for CheckViolationView {
fn from(v: &CheckViolation) -> Self {
CheckViolationView {
severity: v.severity,
message: render(&v.kind),
}
}
}
impl From<&CheckedEntry> for CheckEntry {
fn from(e: &CheckedEntry) -> Self {
CheckEntry {
kind: e.kind.clone(),
path: e.path.display().to_string(),
ok: !e.violations.has_errors(),
violations: e.violations.iter().map(CheckViolationView::from).collect(),
}
}
}
pub(in super::super) fn execute_global_check(matches: &ArgMatches, ctx: &Context<'_>) {
let config = ctx.config();
let output_fmt = ctx.output_fmt;
let verbose = matches.get_flag("verbose");
let fix = matches.get_flag("fix");
let dry_run = matches.get_flag("dry-run");
let known_refs = ctx.load_known_refs().unwrap_or_else(|e| {
die1(
CliError::new(format!("loading workspace refs: {e}")),
output_fmt,
);
});
let issue_repo = ctx.issue_repository();
let issue_defects = ctx.issue_defect_scanner();
let dr_repos: Vec<_> = config
.decision_kinds
.iter()
.map(|k| (k.kind.clone(), ctx.decision_record_repository(k)))
.collect();
let dr_defects: Vec<_> = config
.decision_kinds
.iter()
.map(|k| ctx.decision_record_defect_scanner(k))
.collect();
let dr_descriptors: Vec<_> = config
.decision_kinds
.iter()
.map(|k| config.tag_descriptors_for(&k.kind))
.collect();
let issue_descriptors = config.tag_descriptors_for("issues");
if fix {
let fix_dr_sources: Vec<FixDrSource<'_>> = dr_repos
.iter()
.zip(dr_descriptors.iter())
.map(|((k, r), td)| FixDrSource {
kind: k.as_str(),
repo: r,
tag_descriptors: td,
})
.collect();
let mode = if dry_run {
FixMode::DryRun
} else {
FixMode::Apply
};
let report = run_fix(
FixIssueSource {
repo: &issue_repo,
statuses: ctx.issues_statuses,
tag_descriptors: &issue_descriptors,
},
&fix_dr_sources,
&known_refs,
mode,
)
.unwrap_or_else(|e| {
die1(CliError::new(format!("fix: {e}")), output_fmt);
});
render_fix(&report, mode);
}
let dr_sources: Vec<DrSource<'_>> = dr_repos
.iter()
.zip(dr_defects.iter())
.zip(dr_descriptors.iter())
.map(|(((k, r), d), td)| DrSource {
kind: k.as_str(),
repo: r,
defect_scanner: d,
tag_descriptors: td,
})
.collect();
let report = run_check(
Some(IssueSource {
repo: &issue_repo,
defect_scanner: &issue_defects,
content_reader: &issue_repo,
statuses: ctx.issues_statuses,
tag_descriptors: &issue_descriptors,
}),
&dr_sources,
&known_refs,
)
.unwrap_or_else(|e| {
die1(CliError::new(format!("check: {e}")), output_fmt);
});
render_schema_warning(ctx);
if output_fmt.is_structured() {
let entries: Vec<CheckEntry> = report.entries.iter().map(CheckEntry::from).collect();
super::super::render_structured(&entries, output_fmt);
} else {
render_human(&report, verbose);
}
if report.has_errors() {
std::process::exit(1);
}
}
fn render_schema_warning(ctx: &Context<'_>) {
use crate::infra::driven::fs::config::CURRENT_SCHEMA_VERSION;
let config = ctx.config();
if config.schema_version < CURRENT_SCHEMA_VERSION && !ctx.output_fmt.is_structured() {
println!(
"{}",
theme::check_warn(
"[config]",
&format!(
"cartulary.toml has no version field (schema version 0). \
Run 'cartu migrate' to upgrade to version {CURRENT_SCHEMA_VERSION}."
)
)
);
}
}
fn render_fix(report: &FixReport, mode: FixMode) {
let verb = if matches!(mode, FixMode::DryRun) {
"would fix"
} else {
"fixed"
};
for item in &report.items {
println!("{verb} [{}]: {}", item.rule_id, item.description);
}
}
fn render_human(report: &CheckReport, verbose: bool) {
render_human_entries(&report.entries, verbose);
}
pub(in super::super) fn render_human_entries(entries: &[CheckedEntry], verbose: bool) {
for entry in entries {
if entry.violations.is_empty() {
if verbose {
let path = format!("[{}] {}", entry.kind, entry.path.display());
println!("{}", theme::check_ok(&path));
}
continue;
}
for violation in &entry.violations {
let path = format!("[{}] {}", entry.kind, entry.path.display());
match violation.severity {
Severity::Error => {
println!(
"{}",
theme::check_error(&path, &render_with_hint(violation))
);
}
Severity::Warning => {
println!("{}", theme::check_warn(&path, &render_with_hint(violation)));
}
}
}
}
}