use archlens_domain::{ CodeElement, CodeGraph, DomainError, RelationshipKind, RenderOutput, RenderedFile, ports::DiagramRenderer, }; use archlens_rendering_primitives::non_import_rels; pub struct AsciiRenderer; impl Default for AsciiRenderer { fn default() -> Self { Self::new() } } impl AsciiRenderer { pub fn new() -> Self { Self } fn format_kind(element: &CodeElement) -> &'static str { match element.kind() { archlens_domain::CodeElementKind::Class => "cls", archlens_domain::CodeElementKind::Struct => "str", archlens_domain::CodeElementKind::Trait => "trt", archlens_domain::CodeElementKind::Interface => "ifc", archlens_domain::CodeElementKind::Enum => "enm", archlens_domain::CodeElementKind::Project => "prj", } } } impl DiagramRenderer for AsciiRenderer { fn render(&self, graph: &CodeGraph) -> Result { let mut lines = Vec::new(); let total_elements = graph.elements().len(); let total_rels = graph.relationships().len(); let total_modules = graph.modules().len(); lines.push("╔══════════════════════════════════════╗".to_string()); lines.push("║ Architecture Overview ║".to_string()); lines.push("╠══════════════════════════════════════╣".to_string()); lines.push(format!( "║ Elements: {:<5} Modules: {:<5} ║", total_elements, total_modules )); lines.push(format!("║ Relationships: {:<19} ║", total_rels)); lines.push("╚══════════════════════════════════════╝".to_string()); if graph.elements().is_empty() { lines.push(" (no elements found)".to_string()); let content = lines.join("\n"); let file = RenderedFile::new("diagram.txt", &content)?; return Ok(RenderOutput::single(file)); } let (grouped, ungrouped) = graph.elements_by_module(); if !ungrouped.is_empty() { lines.push(String::new()); lines.push("┌─ (ungrouped)".to_string()); for el in &ungrouped { lines.push(format!("│ [{}] {}", Self::format_kind(el), el.name())); } lines.push("└───".to_string()); } let mut module_names: Vec<&String> = grouped.keys().collect(); module_names.sort(); for module in module_names { let elements = &grouped[module]; lines.push(String::new()); lines.push(format!("┌─ {} ({} types)", module, elements.len())); lines.push("│".to_string()); for (i, el) in elements.iter().enumerate() { let prefix = if i == elements.len() - 1 { "└──" } else { "├──" }; let generics = if el.generics().is_empty() { String::new() } else { format!("<{}>", el.generics().join(", ")) }; lines.push(format!( "│ {} [{}] {}{}", prefix, Self::format_kind(el), el.name(), generics )); } lines.push("└───".to_string()); } let filtered_rels: Vec<_> = non_import_rels(graph.relationships()).collect(); if !filtered_rels.is_empty() { lines.push(String::new()); lines.push("── Relationships ──".to_string()); for rel in &filtered_rels { let arrow = match rel.kind() { RelationshipKind::Inheritance => "extends", RelationshipKind::Composition => "has", RelationshipKind::Import => unreachable!("imports filtered by non_import_rels"), }; lines.push(format!( " {} ─[{}]─> {}", rel.source(), arrow, rel.target() )); } } let import_rels: Vec<_> = graph .relationships() .iter() .filter(|r| r.kind() == RelationshipKind::Import) .collect(); if !import_rels.is_empty() { lines.push(String::new()); lines.push(format!("── Imports ({}) ──", import_rels.len())); for rel in &import_rels { lines.push(format!(" {} ···> {}", rel.source(), rel.target())); } } let content = lines.join("\n"); let file = RenderedFile::new("diagram.txt", &content)?; Ok(RenderOutput::single(file)) } }