feat(notgraph): wire full CLI pipeline
This commit is contained in:
@@ -1,3 +1,90 @@
|
|||||||
fn main() {
|
use notgraph_lib::{analysis, crate_graph, emit, module_graph, symbols};
|
||||||
println!("notgraph");
|
use anyhow::{Context, Result};
|
||||||
|
use clap::Parser;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
#[derive(Parser)]
|
||||||
|
#[command(name = "notgraph", about = "Workspace module graph and symbol analysis")]
|
||||||
|
struct Cli {
|
||||||
|
#[arg(long, default_value = "docs/graph")]
|
||||||
|
output: PathBuf,
|
||||||
|
|
||||||
|
#[arg(long)]
|
||||||
|
fail_on_cycles: bool,
|
||||||
|
|
||||||
|
#[arg(long, default_value_t = 10)]
|
||||||
|
top: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
let cli = Cli::parse();
|
||||||
|
let manifest = find_workspace_manifest()?;
|
||||||
|
let workspace_root = manifest.parent().unwrap().to_path_buf();
|
||||||
|
|
||||||
|
let crate_g = crate_graph::build(&manifest)
|
||||||
|
.context("failed to build crate graph")?;
|
||||||
|
|
||||||
|
let meta = cargo_metadata::MetadataCommand::new()
|
||||||
|
.manifest_path(&manifest)
|
||||||
|
.no_deps()
|
||||||
|
.exec()?;
|
||||||
|
|
||||||
|
let mut module_graphs = Vec::new();
|
||||||
|
let mut symbol_tables = Vec::new();
|
||||||
|
|
||||||
|
for pkg in &meta.packages {
|
||||||
|
if !meta.workspace_members.contains(&pkg.id) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let src_dir = PathBuf::from(pkg.manifest_path.as_str())
|
||||||
|
.parent().unwrap().join("src");
|
||||||
|
if !src_dir.exists() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
module_graphs.push(
|
||||||
|
module_graph::build(pkg.name.clone(), &src_dir)
|
||||||
|
.with_context(|| format!("module_graph failed for {}", pkg.name))?
|
||||||
|
);
|
||||||
|
symbol_tables.push(
|
||||||
|
symbols::build(pkg.name.clone(), &src_dir)
|
||||||
|
.with_context(|| format!("symbols failed for {}", pkg.name))?
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let stats = analysis::analyse(&crate_g, &module_graphs, cli.top);
|
||||||
|
|
||||||
|
let output_dir = if cli.output.is_absolute() {
|
||||||
|
cli.output.clone()
|
||||||
|
} else {
|
||||||
|
workspace_root.join(&cli.output)
|
||||||
|
};
|
||||||
|
|
||||||
|
emit::write_all(&output_dir, &stats, &symbol_tables)
|
||||||
|
.context("failed to write reports")?;
|
||||||
|
|
||||||
|
println!("notgraph: reports written to {}", output_dir.display());
|
||||||
|
|
||||||
|
if cli.fail_on_cycles && !stats.cycles.is_empty() {
|
||||||
|
eprintln!("notgraph: {} cycle(s) detected", stats.cycles.len());
|
||||||
|
for cycle in &stats.cycles {
|
||||||
|
eprintln!(" {}", cycle.join(" -> "));
|
||||||
|
}
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_workspace_manifest() -> Result<PathBuf> {
|
||||||
|
let mut dir = std::env::current_dir()?;
|
||||||
|
loop {
|
||||||
|
let candidate = dir.join("Cargo.toml");
|
||||||
|
if candidate.exists() {
|
||||||
|
let content = std::fs::read_to_string(&candidate)?;
|
||||||
|
if content.contains("[workspace]") {
|
||||||
|
return Ok(candidate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
anyhow::ensure!(dir.pop(), "could not find workspace Cargo.toml");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ pub fn build(krate: CrateName, src_dir: &Path) -> Result<ModuleGraph> {
|
|||||||
for entry in WalkDir::new(src_dir)
|
for entry in WalkDir::new(src_dir)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|e| e.ok())
|
.filter_map(|e| e.ok())
|
||||||
.filter(|e| e.path().extension().map_or(false, |x| x == "rs"))
|
.filter(|e| e.path().extension().is_some_and(|x| x == "rs"))
|
||||||
{
|
{
|
||||||
let path = entry.path();
|
let path = entry.path();
|
||||||
let content = std::fs::read_to_string(path)?;
|
let content = std::fs::read_to_string(path)?;
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ pub fn build(krate: CrateName, src_dir: &Path) -> Result<SymbolTable> {
|
|||||||
for entry in WalkDir::new(src_dir)
|
for entry in WalkDir::new(src_dir)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|e| e.ok())
|
.filter_map(|e| e.ok())
|
||||||
.filter(|e| e.path().extension().map_or(false, |x| x == "rs"))
|
.filter(|e| e.path().extension().is_some_and(|x| x == "rs"))
|
||||||
{
|
{
|
||||||
let path = entry.path();
|
let path = entry.path();
|
||||||
let content = std::fs::read_to_string(path)?;
|
let content = std::fs::read_to_string(path)?;
|
||||||
|
|||||||
Reference in New Issue
Block a user