init: archlens — architecture diagram generator
Some checks failed
CI / Check / Test (push) Failing after 1m24s
Some checks failed
CI / Check / Test (push) Failing after 1m24s
Hex arch + DDD, tree-sitter parsing, Mermaid/ASCII output. Supports Rust + Python. 92 tests. CI, diff, --check for staleness detection.
This commit is contained in:
153
crates/adapters/ascii/src/ascii_renderer.rs
Normal file
153
crates/adapters/ascii/src/ascii_renderer.rs
Normal file
@@ -0,0 +1,153 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use archlens_domain::{
|
||||
CodeElement, CodeGraph, DomainError, RelationshipKind, RenderOutput, RenderedFile,
|
||||
ports::DiagramRenderer,
|
||||
};
|
||||
|
||||
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<RenderOutput, DomainError> {
|
||||
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 mut grouped: HashMap<String, Vec<&CodeElement>> = HashMap::new();
|
||||
let mut ungrouped: Vec<&CodeElement> = Vec::new();
|
||||
|
||||
for element in graph.elements() {
|
||||
if let Some(module) = element.module() {
|
||||
grouped
|
||||
.entry(module.as_str().to_string())
|
||||
.or_default()
|
||||
.push(element);
|
||||
} else {
|
||||
ungrouped.push(element);
|
||||
}
|
||||
}
|
||||
|
||||
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 non_import_rels: Vec<_> = graph
|
||||
.relationships()
|
||||
.iter()
|
||||
.filter(|r| r.kind() != RelationshipKind::Import)
|
||||
.collect();
|
||||
|
||||
if !non_import_rels.is_empty() {
|
||||
lines.push(String::new());
|
||||
lines.push("── Relationships ──".to_string());
|
||||
for rel in &non_import_rels {
|
||||
let arrow = match rel.kind() {
|
||||
RelationshipKind::Inheritance => "extends",
|
||||
RelationshipKind::Composition => "has",
|
||||
RelationshipKind::Import => "imports",
|
||||
};
|
||||
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))
|
||||
}
|
||||
}
|
||||
3
crates/adapters/ascii/src/lib.rs
Normal file
3
crates/adapters/ascii/src/lib.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
mod ascii_renderer;
|
||||
|
||||
pub use ascii_renderer::AsciiRenderer;
|
||||
Reference in New Issue
Block a user