refactor: make GenerateDiagram pure — return RenderOutput instead of writing files
This commit is contained in:
@@ -1,122 +1,68 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use archlens_domain::{
|
||||
BoundaryRule, DomainError, NormalizedGraph, RenderOutput, RenderedFile, check_boundary_rules,
|
||||
ports::DiagramRenderer,
|
||||
BoundaryRule, DomainError, NormalizedGraph, RenderOutput, RenderedFile, RuleViolation,
|
||||
check_boundary_rules, ports::DiagramRenderer,
|
||||
};
|
||||
|
||||
/// Result of running the generate use case — exposed violations and any output
|
||||
/// that should be written to disk.
|
||||
pub struct GenerateDiagramResult {
|
||||
pub violations: Vec<String>,
|
||||
pub violations: Vec<RuleViolation>,
|
||||
pub output: RenderOutput,
|
||||
}
|
||||
|
||||
/// Orchestrates diagram generation: renders the graph (split or single),
|
||||
/// checks boundary rules, and returns the output for the caller to write.
|
||||
pub struct GenerateDiagram {
|
||||
pub graph: NormalizedGraph,
|
||||
pub renderer: Box<dyn DiagramRenderer>,
|
||||
pub allow_rules: Vec<BoundaryRule>,
|
||||
pub deny_rules: Vec<BoundaryRule>,
|
||||
pub split_by_module: bool,
|
||||
pub format_ext: String,
|
||||
pub output_dir: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl GenerateDiagram {
|
||||
pub fn execute(self) -> Result<(), DomainError> {
|
||||
// Boundary rule checking
|
||||
pub fn execute(self) -> Result<GenerateDiagramResult, DomainError> {
|
||||
let violations = if !self.allow_rules.is_empty() || !self.deny_rules.is_empty() {
|
||||
check_boundary_rules(self.graph.as_graph(), &self.allow_rules, &self.deny_rules)
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
// Render and write
|
||||
if self.split_by_module {
|
||||
write_split(
|
||||
&self.graph,
|
||||
&*self.renderer,
|
||||
&self.output_dir,
|
||||
&self.format_ext,
|
||||
)?;
|
||||
let output = if self.split_by_module {
|
||||
render_split(&self.graph, &*self.renderer)?
|
||||
} else {
|
||||
let rendered = self.renderer.render(self.graph.as_graph())?;
|
||||
write_to_output(rendered, &self.output_dir)?;
|
||||
}
|
||||
self.renderer.render(self.graph.as_graph())?
|
||||
};
|
||||
|
||||
// Report violations (after writing so the diagram is still produced)
|
||||
for v in &violations {
|
||||
eprintln!("RULE VIOLATION: {}", v.message());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn check_violations_only(&self) -> Vec<String> {
|
||||
if self.allow_rules.is_empty() && self.deny_rules.is_empty() {
|
||||
return Vec::new();
|
||||
}
|
||||
check_boundary_rules(self.graph.as_graph(), &self.allow_rules, &self.deny_rules)
|
||||
.into_iter()
|
||||
.map(|v| v.message())
|
||||
.collect()
|
||||
Ok(GenerateDiagramResult { violations, output })
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_split(
|
||||
fn render_split(
|
||||
graph: &NormalizedGraph,
|
||||
renderer: &dyn DiagramRenderer,
|
||||
output_dir: &Option<PathBuf>,
|
||||
ext: &str,
|
||||
) -> Result<(), DomainError> {
|
||||
let dir = output_dir.clone().unwrap_or_else(|| PathBuf::from("."));
|
||||
) -> Result<RenderOutput, DomainError> {
|
||||
let mut files = Vec::new();
|
||||
|
||||
let overview = renderer.render(graph.as_graph())?;
|
||||
let overview_file = RenderedFile::new(
|
||||
&format!("overview.{ext}"),
|
||||
overview.files().first().map(|f| f.content()).unwrap_or(""),
|
||||
)?;
|
||||
write_file_to_dir(&dir, overview_file)?;
|
||||
let ext = overview
|
||||
.files()
|
||||
.first()
|
||||
.and_then(|f| std::path::Path::new(f.name()).extension())
|
||||
.and_then(|e| e.to_str())
|
||||
.unwrap_or("txt");
|
||||
|
||||
if let Some(f) = overview.files().first() {
|
||||
files.push(RenderedFile::new(&format!("overview.{ext}"), f.content())?);
|
||||
}
|
||||
|
||||
for module in graph.modules() {
|
||||
let subgraph = graph.subgraph_by_module(&module);
|
||||
let cross_deps = graph.cross_module_deps_for(&module);
|
||||
let module_output = renderer.render_for_module(&subgraph, &module, &cross_deps)?;
|
||||
let module_file = RenderedFile::new(
|
||||
&format!("{}.{ext}", module.as_str().to_lowercase()),
|
||||
module_output
|
||||
.files()
|
||||
.first()
|
||||
.map(|f| f.content())
|
||||
.unwrap_or(""),
|
||||
)?;
|
||||
write_file_to_dir(&dir, module_file)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_file_to_dir(dir: &PathBuf, file: RenderedFile) -> Result<(), DomainError> {
|
||||
let path = dir.join(file.name());
|
||||
std::fs::create_dir_all(dir).map_err(|e| DomainError::IoError(e.to_string()))?;
|
||||
std::fs::write(&path, file.content()).map_err(|e| DomainError::IoError(e.to_string()))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_to_output(rendered: RenderOutput, output: &Option<PathBuf>) -> Result<(), DomainError> {
|
||||
let content = rendered.files().first().map(|f| f.content()).unwrap_or("");
|
||||
match output {
|
||||
Some(path) => {
|
||||
if let Some(parent) = path.parent() {
|
||||
std::fs::create_dir_all(parent).map_err(|e| DomainError::IoError(e.to_string()))?;
|
||||
}
|
||||
std::fs::write(path, content).map_err(|e| DomainError::IoError(e.to_string()))
|
||||
}
|
||||
None => {
|
||||
print!("{content}");
|
||||
Ok(())
|
||||
if let Some(f) = module_output.files().first() {
|
||||
files.push(RenderedFile::new(
|
||||
&format!("{}.{ext}", module.as_str().to_lowercase()),
|
||||
f.content(),
|
||||
)?);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(RenderOutput::new(files))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user