refactor: deepen modules, consolidate inference, delete dead code
- Extract build_graph/load_config/create_renderer in presentation (393→~250 lines) - Move module inference into ModuleName::from_path(), delete 3 scattered copies - Move resolve_relationships/filter_external_imports into CodeGraph - Add LanguageExtractor trait in tree-sitter adapter - Add CodeGraph::elements_by_module(), replace 6 identical grouping loops - Delete dead RenderDiagrams query
This commit is contained in:
@@ -8,7 +8,7 @@ use archlens_application::queries::AnalyzeCodebase;
|
||||
use archlens_ascii::AsciiRenderer;
|
||||
use archlens_cargo_workspace::CargoWorkspaceAnalyzer;
|
||||
use archlens_domain::{
|
||||
CodeGraph, DiagramLevel,
|
||||
CodeGraph, DiagramLevel, ModuleName,
|
||||
ports::{ConfigLoader, OutputWriter, ProjectAnalyzer},
|
||||
};
|
||||
use archlens_file_writer::FileOutputWriter;
|
||||
@@ -30,21 +30,43 @@ pub fn run(args: Cli) -> Result<()> {
|
||||
}
|
||||
init_tracing(args.verbose);
|
||||
|
||||
let config_loader = match &args.config {
|
||||
Some(path) => TomlConfigLoader::from_path(std::path::Path::new(path))?,
|
||||
let level = parse_level(&args.level);
|
||||
let graph = build_graph(&args, level)?;
|
||||
let renderer = create_renderer(&args.format, level)?;
|
||||
let ext = format_extension(&args.format);
|
||||
|
||||
if args.check {
|
||||
return check_freshness(&args.output, &graph, &*renderer);
|
||||
}
|
||||
|
||||
if args.split_by_module {
|
||||
write_split(&graph, &*renderer, &args.output, ext)?;
|
||||
} else {
|
||||
write_single(&graph, &*renderer, &args.output)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn load_config(args: &Cli) -> Result<TomlConfigLoader> {
|
||||
match &args.config {
|
||||
Some(path) => Ok(TomlConfigLoader::from_path(std::path::Path::new(path))?),
|
||||
None => {
|
||||
let default_path = args.path.join("archlens.toml");
|
||||
if default_path.exists() {
|
||||
TomlConfigLoader::from_path(&default_path)?
|
||||
Ok(TomlConfigLoader::from_path(&default_path)?)
|
||||
} else {
|
||||
TomlConfigLoader::default()
|
||||
Ok(TomlConfigLoader::default())
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn build_graph(args: &Cli, level: DiagramLevel) -> Result<CodeGraph> {
|
||||
let config_loader = load_config(args)?;
|
||||
let mut analysis_config = config_loader.load_analysis_config()?;
|
||||
let level = parse_level(&args.level);
|
||||
analysis_config = analysis_config.with_level(level);
|
||||
|
||||
if let Some(ref scope) = args.scope {
|
||||
analysis_config = analysis_config.with_scope(scope.clone());
|
||||
}
|
||||
@@ -54,79 +76,80 @@ pub fn run(args: Cli) -> Result<()> {
|
||||
analysis_config = analysis_config.with_excludes(excludes);
|
||||
}
|
||||
|
||||
let graph = if level == DiagramLevel::Project {
|
||||
let project_analyzer = CargoWorkspaceAnalyzer::new();
|
||||
project_analyzer.analyze(&args.path)?
|
||||
} else {
|
||||
let discovery = WalkdirDiscovery::new();
|
||||
let analyzer = TreeSitterAnalyzer::new();
|
||||
let analyze = AnalyzeCodebase::new(discovery, analyzer);
|
||||
let result = analyze.execute(&args.path, &analysis_config)?;
|
||||
if level == DiagramLevel::Project {
|
||||
return Ok(CargoWorkspaceAnalyzer::new().analyze(&args.path)?);
|
||||
}
|
||||
|
||||
if !result.warnings().is_empty() {
|
||||
for warning in result.warnings() {
|
||||
eprintln!(
|
||||
"WARNING: {}:{} {}",
|
||||
warning.file_path().as_str(),
|
||||
warning.line(),
|
||||
warning.message()
|
||||
);
|
||||
}
|
||||
if args.strict {
|
||||
bail!(
|
||||
"analysis produced {} warning(s) in strict mode",
|
||||
result.warnings().len()
|
||||
);
|
||||
}
|
||||
let discovery = WalkdirDiscovery::new();
|
||||
let analyzer = TreeSitterAnalyzer::new();
|
||||
let analyze = AnalyzeCodebase::new(discovery, analyzer);
|
||||
let result = analyze.execute(&args.path, &analysis_config)?;
|
||||
|
||||
if !result.warnings().is_empty() {
|
||||
for warning in result.warnings() {
|
||||
eprintln!(
|
||||
"WARNING: {}:{} {}",
|
||||
warning.file_path().as_str(),
|
||||
warning.line(),
|
||||
warning.message()
|
||||
);
|
||||
}
|
||||
|
||||
let mut graph = result.graph().clone();
|
||||
|
||||
if level == DiagramLevel::Module {
|
||||
let workspace_toml = args.path.join("Cargo.toml");
|
||||
if workspace_toml.exists()
|
||||
&& let Ok(project_graph) = CargoWorkspaceAnalyzer::new().analyze(&args.path)
|
||||
{
|
||||
merge_project_deps_as_module_edges(&mut graph, &project_graph);
|
||||
}
|
||||
if args.strict {
|
||||
bail!(
|
||||
"analysis produced {} warning(s) in strict mode",
|
||||
result.warnings().len()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
graph
|
||||
};
|
||||
let mut graph = result.graph().clone();
|
||||
|
||||
let renderer: Box<dyn archlens_domain::ports::DiagramRenderer> = match &args.format[..] {
|
||||
"mermaid" => Box::new(MermaidRenderer::with_level(level)),
|
||||
"ascii" => Box::new(AsciiRenderer::new()),
|
||||
if level == DiagramLevel::Module {
|
||||
let workspace_toml = args.path.join("Cargo.toml");
|
||||
if workspace_toml.exists()
|
||||
&& let Ok(project_graph) = CargoWorkspaceAnalyzer::new().analyze(&args.path)
|
||||
{
|
||||
merge_project_deps_as_module_edges(&mut graph, &project_graph);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(graph)
|
||||
}
|
||||
|
||||
fn create_renderer(
|
||||
format: &str,
|
||||
level: DiagramLevel,
|
||||
) -> Result<Box<dyn archlens_domain::ports::DiagramRenderer>> {
|
||||
match format {
|
||||
"mermaid" => Ok(Box::new(MermaidRenderer::with_level(level))),
|
||||
"ascii" => Ok(Box::new(AsciiRenderer::new())),
|
||||
fmt => bail!("unknown format: {fmt}"),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
let ext = match &args.format[..] {
|
||||
fn format_extension(format: &str) -> &str {
|
||||
match format {
|
||||
"mermaid" => "mmd",
|
||||
_ => "txt",
|
||||
}
|
||||
}
|
||||
|
||||
fn check_freshness(
|
||||
output: &Option<String>,
|
||||
graph: &CodeGraph,
|
||||
renderer: &dyn archlens_domain::ports::DiagramRenderer,
|
||||
) -> Result<()> {
|
||||
let Some(path) = output else {
|
||||
bail!("--check requires --output to specify the file to check against");
|
||||
};
|
||||
|
||||
if args.check {
|
||||
if let Some(ref path) = args.output {
|
||||
let output = renderer.render(&graph)?;
|
||||
let current = output.files().first().map(|f| f.content()).unwrap_or("");
|
||||
let existing = std::fs::read_to_string(path).unwrap_or_default();
|
||||
if current != existing {
|
||||
eprintln!("Architecture diagram is outdated: {path}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
println!("Architecture diagram is up to date.");
|
||||
return Ok(());
|
||||
} else {
|
||||
bail!("--check requires --output to specify the file to check against");
|
||||
}
|
||||
let rendered = renderer.render(graph)?;
|
||||
let current = rendered.files().first().map(|f| f.content()).unwrap_or("");
|
||||
let existing = std::fs::read_to_string(path).unwrap_or_default();
|
||||
if current != existing {
|
||||
eprintln!("Architecture diagram is outdated: {path}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
if args.split_by_module {
|
||||
write_split(&graph, &*renderer, &args.output, ext)?;
|
||||
} else {
|
||||
write_single(&graph, &*renderer, &args.output)?;
|
||||
}
|
||||
|
||||
println!("Architecture diagram is up to date.");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -213,8 +236,8 @@ fn merge_project_deps_as_module_edges(
|
||||
let tgt_module = crate_to_module.get(rel.target());
|
||||
|
||||
if let (Some(src), Some(tgt)) = (src_module, tgt_module) {
|
||||
let src_cap = capitalize(src);
|
||||
let tgt_cap = capitalize(tgt);
|
||||
let src_cap = ModuleName::capitalize(src);
|
||||
let tgt_cap = ModuleName::capitalize(tgt);
|
||||
|
||||
if src_cap != tgt_cap
|
||||
&& graph_modules.contains(&src_cap)
|
||||
@@ -231,62 +254,12 @@ fn merge_project_deps_as_module_edges(
|
||||
}
|
||||
}
|
||||
|
||||
fn capitalize(s: &str) -> String {
|
||||
s.split('-')
|
||||
.map(|seg| {
|
||||
if seg.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
format!("{}{}", seg[..1].to_uppercase(), &seg[1..])
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("-")
|
||||
}
|
||||
|
||||
fn run_diff(args: &Cli, existing_path: &std::path::Path) -> Result<()> {
|
||||
init_tracing(args.verbose);
|
||||
|
||||
let config_loader = match &args.config {
|
||||
Some(path) => TomlConfigLoader::from_path(std::path::Path::new(path))?,
|
||||
None => {
|
||||
let default_path = args.path.join("archlens.toml");
|
||||
if default_path.exists() {
|
||||
TomlConfigLoader::from_path(&default_path)?
|
||||
} else {
|
||||
TomlConfigLoader::default()
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let mut analysis_config = config_loader.load_analysis_config()?;
|
||||
let level = parse_level(&args.level);
|
||||
analysis_config = analysis_config.with_level(level);
|
||||
|
||||
let graph = if level == DiagramLevel::Project {
|
||||
CargoWorkspaceAnalyzer::new().analyze(&args.path)?
|
||||
} else {
|
||||
let discovery = WalkdirDiscovery::new();
|
||||
let analyzer = TreeSitterAnalyzer::new();
|
||||
let analyze = AnalyzeCodebase::new(discovery, analyzer);
|
||||
let result = analyze.execute(&args.path, &analysis_config)?;
|
||||
let mut graph = result.graph().clone();
|
||||
if level == DiagramLevel::Module {
|
||||
let workspace_toml = args.path.join("Cargo.toml");
|
||||
if workspace_toml.exists()
|
||||
&& let Ok(project_graph) = CargoWorkspaceAnalyzer::new().analyze(&args.path)
|
||||
{
|
||||
merge_project_deps_as_module_edges(&mut graph, &project_graph);
|
||||
}
|
||||
}
|
||||
graph
|
||||
};
|
||||
|
||||
let renderer: Box<dyn archlens_domain::ports::DiagramRenderer> = match &args.format[..] {
|
||||
"mermaid" => Box::new(MermaidRenderer::with_level(level)),
|
||||
"ascii" => Box::new(AsciiRenderer::new()),
|
||||
fmt => bail!("unknown format: {fmt}"),
|
||||
};
|
||||
let graph = build_graph(args, level)?;
|
||||
let renderer = create_renderer(&args.format, level)?;
|
||||
|
||||
let output = renderer.render(&graph)?;
|
||||
let current = output.files().first().map(|f| f.content()).unwrap_or("");
|
||||
|
||||
Reference in New Issue
Block a user