mod churn;
mod complexity;
mod coupling;
mod duplication;
mod hotspot;
mod lcom;
mod loc;
mod section;
use std::io::{self, Write};
use std::path::Path;
use anyhow::Result;
use serde_json::json;
use crate::cli::MetricKind;
use crate::core::config::load_from_project;
use crate::core::term::write_through_pager;
use crate::core::HealPaths;
use crate::observers::run_all;
use section::{all_sections, SectionCtx};
pub fn run(
project: &Path,
json_output: bool,
metric: Option<MetricKind>,
workspace: Option<&Path>,
no_pager: bool,
) -> Result<()> {
let paths = HealPaths::new(project);
let cfg_exists = paths.config().exists();
let cfg = if cfg_exists {
Some(load_from_project(project)?)
} else {
None
};
let reports = cfg.as_ref().map(|c| run_all(project, c, metric, workspace));
let sections = all_sections();
if json_output {
println!(
"{}",
serde_json::to_string_pretty(&build_json(
cfg_exists,
cfg.as_ref(),
reports.as_ref(),
metric,
workspace,
§ions,
))?,
);
return Ok(());
}
if !cfg_exists {
println!("HEAL is not initialized in this project. Run `heal init` first.");
return Ok(());
}
let cfg = cfg.expect("cfg_exists branch implies cfg loaded");
let reports = reports.expect("cfg present implies reports built");
write_through_pager(no_pager, |w, colorize| {
let ctx = SectionCtx {
cfg: &cfg,
reports: &reports,
colorize,
};
write_header(w, project, &paths, workspace)?;
for s in §ions {
if !matches_metric(metric, s.metric()) {
continue;
}
s.render_text(&ctx, w)?;
}
Ok(())
})
}
fn write_header(
w: &mut dyn Write,
project: &Path,
paths: &HealPaths,
workspace: Option<&Path>,
) -> io::Result<()> {
writeln!(w, "HEAL metrics (project: {})", project.display())?;
writeln!(w, " config: {}", paths.config().display())?;
if let Some(ws) = workspace {
writeln!(w, " workspace: {}", ws.display())?;
}
Ok(())
}
fn matches_metric(filter: Option<MetricKind>, section: MetricKind) -> bool {
filter.is_none_or(|f| f == section)
}
fn build_json(
cfg_exists: bool,
cfg: Option<&crate::core::config::Config>,
reports: Option<&crate::observers::ObserverReports>,
metric: Option<MetricKind>,
workspace: Option<&Path>,
sections: &[Box<dyn section::MetricSection>],
) -> serde_json::Value {
let mut payload = serde_json::Map::new();
payload.insert("initialized".into(), json!(cfg_exists));
if let Some(m) = metric {
payload.insert("metric".into(), json!(m.json_key()));
}
if let Some(ws) = workspace {
payload.insert("workspace".into(), json!(ws.display().to_string()));
}
if let (Some(cfg), Some(reports)) = (cfg, reports) {
let ctx = SectionCtx {
cfg,
reports,
colorize: false,
};
if metric.is_none() {
for s in sections {
payload.insert(s.metric().json_key().into(), s.raw_json(&ctx));
}
} else if let Some(m) = metric {
if let Some(s) = sections.iter().find(|s| s.metric() == m) {
let (top_n, worst) = s.worst_json(&ctx);
payload.insert("top_n".into(), json!(top_n));
payload.insert("worst".into(), worst);
}
}
}
serde_json::Value::Object(payload)
}