init: archlens — architecture diagram generator
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:
2026-06-16 16:13:04 +02:00
commit 35f27d00b0
106 changed files with 6744 additions and 0 deletions

View 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))
}
}