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:
123
crates/adapters/cargo-workspace/src/cargo_workspace_analyzer.rs
Normal file
123
crates/adapters/cargo-workspace/src/cargo_workspace_analyzer.rs
Normal file
@@ -0,0 +1,123 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::path::Path;
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
use archlens_domain::{
|
||||
CodeElement, CodeElementKind, CodeGraph, DomainError, FilePath, ModuleName, Relationship,
|
||||
RelationshipKind, ports::ProjectAnalyzer,
|
||||
};
|
||||
|
||||
pub struct CargoWorkspaceAnalyzer;
|
||||
|
||||
impl Default for CargoWorkspaceAnalyzer {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl CargoWorkspaceAnalyzer {
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct WorkspaceToml {
|
||||
workspace: Option<WorkspaceSection>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct WorkspaceSection {
|
||||
#[serde(default)]
|
||||
members: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct MemberToml {
|
||||
package: Option<PackageSection>,
|
||||
#[serde(default)]
|
||||
dependencies: HashMap<String, toml::Value>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct PackageSection {
|
||||
name: String,
|
||||
}
|
||||
|
||||
impl ProjectAnalyzer for CargoWorkspaceAnalyzer {
|
||||
fn analyze(&self, root: &Path) -> Result<CodeGraph, DomainError> {
|
||||
let workspace_toml_path = root.join("Cargo.toml");
|
||||
let content = std::fs::read_to_string(&workspace_toml_path)
|
||||
.map_err(|e| DomainError::IoError(e.to_string()))?;
|
||||
let workspace: WorkspaceToml =
|
||||
toml::from_str(&content).map_err(|e| DomainError::ConfigError(e.to_string()))?;
|
||||
|
||||
let members = workspace.workspace.map(|w| w.members).unwrap_or_default();
|
||||
|
||||
let mut graph = CodeGraph::new();
|
||||
let mut name_set: HashSet<String> = HashSet::new();
|
||||
let mut member_names: Vec<(String, String)> = Vec::new(); // (member_path, package_name)
|
||||
|
||||
for member_path in &members {
|
||||
let member_cargo = root.join(member_path).join("Cargo.toml");
|
||||
let member_content = std::fs::read_to_string(&member_cargo)
|
||||
.map_err(|e| DomainError::IoError(e.to_string()))?;
|
||||
let member: MemberToml = toml::from_str(&member_content)
|
||||
.map_err(|e| DomainError::ConfigError(e.to_string()))?;
|
||||
|
||||
let package_name = member
|
||||
.package
|
||||
.map(|p| p.name)
|
||||
.unwrap_or_else(|| member_path.clone());
|
||||
|
||||
name_set.insert(package_name.clone());
|
||||
member_names.push((member_path.clone(), package_name));
|
||||
}
|
||||
|
||||
for (member_path, package_name) in &member_names {
|
||||
let file_path = FilePath::new(&format!("{}/Cargo.toml", member_path))
|
||||
.map_err(|e| DomainError::IoError(e.to_string()))?;
|
||||
|
||||
let mut element =
|
||||
CodeElement::new(package_name, CodeElementKind::Project, file_path, 1)?;
|
||||
|
||||
if let Some(module) = infer_group(member_path) {
|
||||
element = element.with_module(module);
|
||||
}
|
||||
|
||||
graph.add_element(element);
|
||||
}
|
||||
|
||||
for (member_path, package_name) in &member_names {
|
||||
let member_cargo = root.join(member_path).join("Cargo.toml");
|
||||
let member_content = std::fs::read_to_string(&member_cargo)
|
||||
.map_err(|e| DomainError::IoError(e.to_string()))?;
|
||||
let member: MemberToml = toml::from_str(&member_content)
|
||||
.map_err(|e| DomainError::ConfigError(e.to_string()))?;
|
||||
|
||||
for dep_name in member.dependencies.keys() {
|
||||
let normalized = dep_name.replace('_', "-");
|
||||
if name_set.contains(&normalized)
|
||||
&& let Ok(rel) =
|
||||
Relationship::new(package_name, &normalized, RelationshipKind::Composition)
|
||||
{
|
||||
graph.add_relationship(rel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(graph)
|
||||
}
|
||||
}
|
||||
|
||||
fn infer_group(member_path: &str) -> Option<ModuleName> {
|
||||
let parts: Vec<&str> = member_path.split('/').collect();
|
||||
if parts.len() < 3 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let group = parts[parts.len() - 2];
|
||||
let capitalized = format!("{}{}", group[..1].to_uppercase(), &group[1..]);
|
||||
ModuleName::new(&capitalized).ok()
|
||||
}
|
||||
3
crates/adapters/cargo-workspace/src/lib.rs
Normal file
3
crates/adapters/cargo-workspace/src/lib.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
mod cargo_workspace_analyzer;
|
||||
|
||||
pub use cargo_workspace_analyzer::CargoWorkspaceAnalyzer;
|
||||
Reference in New Issue
Block a user