mod cli;
mod format;
mod logger;
use clap::Parser;
use cli::{
CrawkArgs, CrawkCommands, CyclesMode, DepsArgs, DepsOutputFormat, ListArgs, ListOutputFormat,
UseArgs, UseOutputFormat, WhyArgs, WhyOutputFormat,
};
use crawk::{AnalysisOptions, Analyzer, DependencyGraphOptions, version};
use logger::configure_tracing;
use std::path::Path;
use tracing::info;
fn main() -> anyhow::Result<()> {
let command = CrawkArgs::parse();
configure_tracing(&command)?;
let crate_root = command.crate_root()?;
info!("Running {} v{}", version::NAME, version::VERSION);
info!("Crate root: {}", crate_root.display());
let crate_root = crate_root.canonicalize()?;
match command.command {
CrawkCommands::Use(ref args) => handle_use_command(&crate_root, args)?,
CrawkCommands::List(ref args) => handle_list_command(&crate_root, args)?,
CrawkCommands::Deps(ref args) => handle_deps_command(&crate_root, args)?,
CrawkCommands::Why(ref args) => handle_why_command(&crate_root, args)?,
}
Ok(())
}
fn handle_deps_command(crate_root: &Path, args: &DepsArgs) -> anyhow::Result<()> {
let mut analyzer = Analyzer::new(crate_root)?;
let mut graph_opts = DependencyGraphOptions::default();
graph_opts.include_tests = args.include_tests;
graph_opts.depth = args.depth;
graph_opts.show_apis = args.show_apis;
let graph = analyzer.dependency_graph(&graph_opts)?;
let output = if args.orphans {
let orphans = graph.orphans();
if orphans.is_empty() {
String::new()
} else {
info!("Found {} orphan module(s).", orphans.len());
format::orphans::render_orphans(&orphans)
}
} else if let Some(cycles_mode) = args.cycles.as_ref() {
let cycles = graph.cycles();
if cycles.is_empty() {
String::new()
} else {
info!("Found {} dependency cycle(s).", cycles.len());
if *cycles_mode == CyclesMode::Highlight && args.format != DepsOutputFormat::Dot {
eprintln!(
"warning: --cycles highlight has no effect with {} format, showing cycles only",
args.format
);
}
match (&args.format, cycles_mode) {
(DepsOutputFormat::Plain, _) => format::cycles::render_cycles_plain(&cycles),
(DepsOutputFormat::Grouped, _) => format::cycles::render_cycles_grouped(&cycles),
(DepsOutputFormat::Dot, CyclesMode::Detect) => {
format::cycles::render_cycles_dot(&cycles)
}
(DepsOutputFormat::Dot, CyclesMode::Highlight) => {
format::cycles::render_cycles_dot_highlight(&cycles, graph.edges())
}
}
}
} else {
match args.format {
DepsOutputFormat::Plain => format::deps_cmd::render_plain(graph.edges()),
DepsOutputFormat::Grouped => format::deps_cmd::render_grouped(graph.edges()),
DepsOutputFormat::Dot => format::deps_cmd::render_dot(graph.edges()),
}
};
if output.is_empty() {
if args.orphans {
eprintln!("No orphan modules found.");
} else if args.cycles.is_some() {
eprintln!("No dependency cycles found.");
} else {
info!("No inter-module dependencies found.");
}
} else {
print!("{output}");
}
Ok(())
}
fn handle_list_command(crate_root: &Path, args: &ListArgs) -> anyhow::Result<()> {
let mut analyzer = Analyzer::new(crate_root)?;
let (mut modules, is_all_targets) = if let Some(ref module_path) = args.module_path {
info!("Listing modules from: {module_path}");
let mods = analyzer.list_modules(module_path, args.include_tests)?;
(mods, false)
} else {
info!("Listing all targets");
let mods = analyzer.list_all_modules(args.include_tests)?;
(mods, true)
};
let multi_target = args.display.show_targets || {
is_all_targets && {
let distinct_targets = modules
.iter()
.map(crawk::ModuleInfo::target)
.collect::<std::collections::HashSet<_>>()
.len();
distinct_targets > 1
}
};
if let Some(depth) = args.depth {
modules.retain(|m| m.path().matches("::").count() < depth);
}
if let Some(ref filter) = args.filter {
modules.retain(|m| m.path().contains(filter.as_str()));
}
if modules.is_empty() {
info!("No modules found.");
} else {
let display_opts = format::list_cmd::ListDisplayOptions {
show_source: args.display.show_source,
show_visibility: args.display.show_visibility,
multi_target,
};
let output = match args.format {
ListOutputFormat::Plain => {
format::list_cmd::render_list_plain(&modules, &display_opts, crate_root)
}
ListOutputFormat::Table => {
format::list_cmd::render_list_table(&modules, &display_opts, crate_root)
}
};
print!("{output}");
}
Ok(())
}
fn handle_why_command(crate_root: &Path, args: &WhyArgs) -> anyhow::Result<()> {
let mut analyzer = Analyzer::new(crate_root)?;
let options = AnalysisOptions {
recursive: args.recursive,
include_tests: args.include_tests,
expand_groups: true,
resolve_globs: false,
};
let refs = analyzer.explain_dependency(&args.source, &args.target, &options)?;
if refs.is_empty() {
info!("No references from '{}' to '{}'.", args.source, args.target);
} else {
let output = match args.format {
WhyOutputFormat::Plain => format::why_cmd::render_plain(&refs),
WhyOutputFormat::Grouped => format::why_cmd::render_grouped(&refs),
};
print!("{output}");
}
Ok(())
}
fn handle_use_command(crate_root: &Path, args: &UseArgs) -> anyhow::Result<()> {
let mut analyzer = Analyzer::new(crate_root)?;
let options = AnalysisOptions {
recursive: args.recursive,
include_tests: args.include_tests,
expand_groups: args.expand,
resolve_globs: args.resolve_globs,
};
let result = analyzer.analyze_module(&args.module_path, &options)?;
info!("Module file: {}", result.source_file().display());
if result.is_empty() {
info!("No internal crate use statements found.");
} else {
let output = match args.format {
UseOutputFormat::Plain => format::use_cmd::render_flat(&result, args.depth),
UseOutputFormat::Grouped => format::use_cmd::render_grouped(&result, args.depth),
};
print!("{output}");
}
Ok(())
}