refactor: make GenerateDiagram pure — return RenderOutput instead of writing files

This commit is contained in:
2026-06-17 13:13:27 +02:00
parent a700fc6160
commit 692a64a622
3 changed files with 184 additions and 127 deletions

View File

@@ -6,13 +6,13 @@ use archlens_application::queries::AnalyzeCodebase;
use archlens_application::use_cases::{
check_freshness::CheckFreshness,
diff_diagram::DiffDiagram,
generate_diagram::{GenerateDiagram, write_split},
generate_diagram::GenerateDiagram,
};
use archlens_ascii::AsciiRenderer;
use archlens_cargo_workspace::CargoWorkspaceAnalyzer;
use archlens_d2::D2Renderer;
use archlens_domain::{
BoundaryRule, DiagramLevel, NormalizedGraph,
BoundaryRule, DiagramLevel, NormalizedGraph, RenderOutput,
ports::{ConfigLoader, ProjectAnalyzer},
};
use archlens_html::HtmlRenderer;
@@ -70,7 +70,6 @@ pub fn run(args: Cli) -> Result<()> {
.iter()
.filter_map(|s| BoundaryRule::parse(s))
.collect();
let output_dir = args.output.as_ref().map(std::path::PathBuf::from);
let use_case = GenerateDiagram {
graph,
@@ -78,22 +77,44 @@ pub fn run(args: Cli) -> Result<()> {
allow_rules: allow,
deny_rules: deny,
split_by_module: args.split_by_module,
format_ext: format_extension(&args.format).to_string(),
output_dir,
};
let violations = use_case.check_violations_only();
if args.strict && !violations.is_empty() {
let result = use_case.execute()?;
if args.strict && !result.violations.is_empty() {
bail!(
"{} boundary rule violation(s) in strict mode",
violations.len()
result.violations.len()
);
}
use_case.execute()?;
for v in &result.violations {
eprintln!("RULE VIOLATION: {}", v.message());
}
write_diagram_output(&result.output, args.output.as_deref(), args.split_by_module)?;
Ok(())
}
fn write_diagram_output(output: &RenderOutput, output_path: Option<&str>, split: bool) -> Result<()> {
if split {
let dir = std::path::PathBuf::from(output_path.unwrap_or("."));
std::fs::create_dir_all(&dir)?;
for file in output.files() {
std::fs::write(dir.join(file.name()), file.content())?;
}
} else if let Some(path) = output_path {
let p = std::path::Path::new(path);
if let Some(parent) = p.parent() {
std::fs::create_dir_all(parent)?;
}
let content = output.files().first().map(|f| f.content()).unwrap_or("");
std::fs::write(p, content)?;
} else {
let content = output.files().first().map(|f| f.content()).unwrap_or("");
print!("{content}");
}
Ok(())
}
fn load_config(args: &Cli) -> Result<TomlConfigLoader> {
match &args.config {
Some(path) => Ok(TomlConfigLoader::from_path(std::path::Path::new(path))?),
@@ -194,15 +215,6 @@ fn create_renderer(
}
}
fn format_extension(format: &str) -> &str {
match format {
"mermaid" => "mmd",
"d2" => "d2",
"html" => "html",
_ => "txt",
}
}
fn run_diff(args: &Cli, existing_path: &std::path::Path) -> Result<()> {
init_tracing(args.verbose);
@@ -332,25 +344,12 @@ fn run_watch(args: Cli) -> Result<()> {
use std::time::{Duration, Instant};
let level = parse_level(&args.level);
let ext = format_extension(&args.format);
let debounce = Duration::from_millis(500);
let run_once = |args: &Cli| -> Result<()> {
let config_loader = load_config(args)?;
let graph = build_graph(args, level)?;
let renderer = create_renderer(&args.format, level, !args.no_weights)?;
let output_dir = args.output.as_ref().map(std::path::PathBuf::from);
if args.split_by_module {
write_split(&graph, &*renderer, &output_dir, ext)?;
} else {
let rendered = renderer.render(graph.as_graph())?;
let content = rendered.files().first().map(|f| f.content()).unwrap_or("");
match &output_dir {
Some(path) => std::fs::write(path, content)?,
None => print!("{content}"),
}
}
let (raw_allow, raw_deny) = config_loader.load_rules();
let allow: Vec<BoundaryRule> = raw_allow
@@ -361,19 +360,18 @@ fn run_watch(args: Cli) -> Result<()> {
.iter()
.filter_map(|s| BoundaryRule::parse(s))
.collect();
if !allow.is_empty() || !deny.is_empty() {
let use_case = GenerateDiagram {
graph,
renderer,
allow_rules: allow,
deny_rules: deny,
split_by_module: false,
format_ext: ext.to_string(),
output_dir: None,
};
for v in use_case.check_violations_only() {
eprintln!("RULE VIOLATION: {v}");
}
let use_case = GenerateDiagram {
graph,
renderer,
allow_rules: allow,
deny_rules: deny,
split_by_module: args.split_by_module,
};
let result = use_case.execute()?;
write_diagram_output(&result.output, args.output.as_deref(), args.split_by_module)?;
for v in &result.violations {
eprintln!("RULE VIOLATION: {}", v.message());
}
Ok(())
};