use std::collections::{BTreeMap, BTreeSet, VecDeque};
use crate::warehouse::dep_graph::CrossRepoEdge;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ExtDep {
pub name: String,
pub version: String,
pub depth: usize,
}
#[derive(Debug, Clone)]
struct LockPkg {
source: Option<String>,
deps: Vec<String>,
}
fn parse_lock(
lock_toml: &str,
) -> (
BTreeMap<(String, String), LockPkg>,
BTreeMap<String, Vec<String>>,
) {
let mut by_nv: BTreeMap<(String, String), LockPkg> = BTreeMap::new();
let mut versions: BTreeMap<String, Vec<String>> = BTreeMap::new();
let doc: toml::Value = match toml::from_str(lock_toml) {
Ok(d) => d,
Err(_) => return (by_nv, versions),
};
let Some(pkgs) = doc.get("package").and_then(|p| p.as_array()) else {
return (by_nv, versions);
};
for pkg in pkgs {
let (Some(name), Some(version)) = (
pkg.get("name").and_then(|v| v.as_str()),
pkg.get("version").and_then(|v| v.as_str()),
) else {
continue;
};
let source = pkg.get("source").and_then(|s| s.as_str()).map(str::to_string);
let deps = pkg
.get("dependencies")
.and_then(|d| d.as_array())
.map(|a| a.iter().filter_map(|d| d.as_str().map(str::to_string)).collect())
.unwrap_or_default();
versions.entry(name.to_string()).or_default().push(version.to_string());
by_nv.insert((name.to_string(), version.to_string()), LockPkg { source, deps });
}
(by_nv, versions)
}
fn resolve_ref(
dep: &str,
versions: &BTreeMap<String, Vec<String>>,
) -> Option<(String, String)> {
let mut it = dep.splitn(2, ' ');
let name = it.next()?.to_string();
if let Some(ver) = it.next() {
return Some((name, ver.to_string()));
}
let vs = versions.get(&name)?;
let mut sorted = vs.clone();
sorted.sort();
sorted.first().map(|v| (name.clone(), v.clone()))
}
pub fn external_closure(lock_toml: &str, roots: &[&str], max_depth: usize) -> Vec<ExtDep> {
let (by_nv, versions) = parse_lock(lock_toml);
let mut queue: VecDeque<((String, String), usize)> = VecDeque::new();
let mut visited: BTreeSet<(String, String)> = BTreeSet::new();
for r in roots {
if let Some(vs) = versions.get(*r) {
for v in vs {
let key = ((*r).to_string(), v.clone());
if visited.insert(key.clone()) {
queue.push_back((key, 0));
}
}
}
}
let mut found: BTreeMap<String, ExtDep> = BTreeMap::new();
while let Some((key, depth)) = queue.pop_front() {
let Some(pkg) = by_nv.get(&key) else { continue };
if depth > 0 && pkg.source.is_some() {
found
.entry(key.0.clone())
.and_modify(|e| {
if depth < e.depth {
e.depth = depth;
}
})
.or_insert(ExtDep { name: key.0.clone(), version: key.1.clone(), depth });
}
if max_depth != 0 && depth >= max_depth {
continue;
}
for dep in &pkg.deps {
if let Some(next) = resolve_ref(dep, &versions) {
if visited.insert(next.clone()) {
queue.push_back((next, depth + 1));
}
}
}
}
let mut out: Vec<ExtDep> = found.into_values().collect();
out.sort_by(|a, b| a.depth.cmp(&b.depth).then_with(|| a.name.cmp(&b.name)));
out
}
pub fn workspace_members(lock_toml: &str) -> Vec<String> {
let (by_nv, _) = parse_lock(lock_toml);
let mut out: BTreeSet<String> = BTreeSet::new();
for ((name, _), pkg) in &by_nv {
if pkg.source.is_none() {
out.insert(name.clone());
}
}
out.into_iter().collect()
}
pub fn member_edges(lock_toml: &str) -> Vec<CrossRepoEdge> {
let (by_nv, versions) = parse_lock(lock_toml);
let members: BTreeSet<String> = by_nv
.iter()
.filter(|(_, p)| p.source.is_none())
.map(|((n, _), _)| n.clone())
.collect();
let mut seen: BTreeSet<(String, String)> = BTreeSet::new();
let mut out: Vec<CrossRepoEdge> = Vec::new();
for ((name, _), pkg) in &by_nv {
if pkg.source.is_some() || !members.contains(name) {
continue;
}
for dep in &pkg.deps {
if let Some((dn, _)) = resolve_ref(dep, &versions) {
if dn != *name && members.contains(&dn) && seen.insert((name.clone(), dn.clone())) {
out.push(CrossRepoEdge::normal(
name.clone(),
dn.clone(),
BTreeSet::from([dn.clone()]),
));
}
}
}
}
out.sort_by(|a, b| a.from.cmp(&b.from).then_with(|| a.to.cmp(&b.to)));
out
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BodySlot {
pub label: &'static str,
xi: i32,
yi: i32,
}
impl BodySlot {
pub fn x(self) -> f32 {
self.xi as f32 / 1000.0
}
pub fn y(self) -> f32 {
self.yi as f32 / 1000.0
}
}
pub const ANATOMY: &[BodySlot] = &[
BodySlot { label: "heart", xi: 0, yi: 150 },
BodySlot { label: "brain", xi: 0, yi: 850 },
BodySlot { label: "left lung", xi: -260, yi: 300 },
BodySlot { label: "right lung", xi: 260, yi: 300 },
BodySlot { label: "liver", xi: -180, yi: -20 },
BodySlot { label: "stomach", xi: 190, yi: -30 },
BodySlot { label: "left kidney", xi: -170, yi: -230 },
BodySlot { label: "right kidney", xi: 170, yi: -230 },
BodySlot { label: "spleen", xi: 60, yi: 60 },
BodySlot { label: "gut", xi: 0, yi: -330 },
BodySlot { label: "left shoulder", xi: -470, yi: 470 },
BodySlot { label: "right shoulder", xi: 470, yi: 470 },
BodySlot { label: "left hand", xi: -640, yi: 90 },
BodySlot { label: "right hand", xi: 640, yi: 90 },
BodySlot { label: "left knee", xi: -230, yi: -640 },
BodySlot { label: "right knee", xi: 230, yi: -640 },
BodySlot { label: "left foot", xi: -300, yi: -900 },
BodySlot { label: "right foot", xi: 300, yi: -900 },
];
#[derive(Debug, Clone)]
pub struct Organ {
pub crate_name: String,
pub organ: String,
pub x: f32,
pub y: f32,
pub in_degree: usize,
pub expanded: bool,
pub external: Vec<ExtDep>,
}
impl Organ {
pub fn max_depth(&self) -> usize {
self.external.iter().map(|e| e.depth).max().unwrap_or(0)
}
}
#[derive(Debug, Clone)]
pub struct Vessel {
pub from: String,
pub to: String,
pub via: Vec<String>,
}
#[derive(Debug, Clone, Default)]
pub struct Organism {
pub organs: Vec<Organ>,
pub vessels: Vec<Vessel>,
pub flow_phase: f32,
pub from_data: bool,
pub selected: Option<String>,
}
impl Organism {
pub fn build(crates: &[String], edges: &[CrossRepoEdge]) -> Self {
let mut in_deg: BTreeMap<&str, usize> = crates.iter().map(|c| (c.as_str(), 0)).collect();
for e in edges {
if let Some(d) = in_deg.get_mut(e.to.as_str()) {
*d += 1;
}
}
let mut ordered: Vec<&String> = crates.iter().collect();
ordered.sort_by(|a, b| {
in_deg
.get(b.as_str())
.cmp(&in_deg.get(a.as_str()))
.then_with(|| a.cmp(b))
});
let organs: Vec<Organ> = ordered
.into_iter()
.enumerate()
.map(|(i, name)| {
let (organ, x, y) = slot_position(i);
Organ {
crate_name: name.clone(),
organ,
x,
y,
in_degree: in_deg.get(name.as_str()).copied().unwrap_or(0),
expanded: false,
external: Vec::new(),
}
})
.collect();
let present: BTreeSet<&str> = crates.iter().map(String::as_str).collect();
let vessels: Vec<Vessel> = edges
.iter()
.filter(|e| present.contains(e.from.as_str()) && present.contains(e.to.as_str()))
.map(|e| Vessel {
from: e.from.clone(),
to: e.to.clone(),
via: e.via.iter().cloned().collect(),
})
.collect();
#[cfg(feature = "testmatrix")]
crate::selftest::emit(
"viz/bloodstream (data core)",
"organism_seated",
!organs.is_empty(),
&format!("{} organ(s), {} vessel(s)", organs.len(), vessels.len()),
);
Self {
organs,
vessels,
flow_phase: 0.0,
from_data: !crates.is_empty(),
selected: None,
}
}
pub fn set_clock(&mut self, seconds: f64) {
const RATE: f64 = 0.35; self.flow_phase = (seconds * RATE).rem_euclid(1.0) as f32;
}
pub fn organ(&self, crate_name: &str) -> Option<&Organ> {
self.organs.iter().find(|o| o.crate_name == crate_name)
}
pub fn toggle_expand(&mut self, crate_name: &str, lock_toml: &str, max_depth: usize) -> usize {
let Some(idx) = self.organs.iter().position(|o| o.crate_name == crate_name) else {
return 0;
};
if self.organs[idx].expanded {
self.organs[idx].expanded = false;
self.organs[idx].external.clear();
return 0;
}
let ext = external_closure(lock_toml, &[crate_name], max_depth);
let n = ext.len();
self.organs[idx].external = ext;
self.organs[idx].expanded = true;
n
}
pub fn external_count(&self) -> usize {
self.organs.iter().map(|o| o.external.len()).sum()
}
pub fn state_json(&self) -> serde_json::Value {
serde_json::json!({
"from_data": self.from_data,
"flow_phase": self.flow_phase,
"selected": self.selected,
"organ_count": self.organs.len(),
"vessel_count": self.vessels.len(),
"external_count": self.external_count(),
"organs": self.organs.iter().map(|o| serde_json::json!({
"crate": o.crate_name,
"organ": o.organ,
"x": o.x,
"y": o.y,
"in_degree": o.in_degree,
"expanded": o.expanded,
"external_count": o.external.len(),
"external_max_depth": o.max_depth(),
"external": o.external.iter().map(|e| serde_json::json!({
"name": e.name,
"version": e.version,
"depth": e.depth,
})).collect::<Vec<_>>(),
})).collect::<Vec<_>>(),
"vessels": self.vessels.iter().map(|v| serde_json::json!({
"from": v.from,
"to": v.to,
"via": v.via,
})).collect::<Vec<_>>(),
})
}
}
fn slot_position(i: usize) -> (String, f32, f32) {
if let Some(s) = ANATOMY.get(i) {
return (s.label.to_string(), s.x(), s.y());
}
let k = (i - ANATOMY.len()) as f32;
let angle = k * 2.399_963_2; let r = 0.30 + 0.055 * k.sqrt();
(
format!("node {i}"),
(r * angle.cos()).clamp(-0.98, 0.98),
(r * angle.sin()).clamp(-0.98, 0.98),
)
}
#[cfg(test)]
mod tests {
use super::*;
const SYNTH_LOCK: &str = r#"
version = 3
[[package]]
name = "app"
version = "0.1.0"
dependencies = [
"alpha",
"sibling",
]
[[package]]
name = "sibling"
version = "0.1.0"
dependencies = []
[[package]]
name = "alpha"
version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aaaa"
dependencies = [
"beta 2.0.0",
]
[[package]]
name = "beta"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbbb"
dependencies = []
[[package]]
name = "gamma"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cccc"
dependencies = []
"#;
#[test]
fn external_closure_is_recursive_and_excludes_workspace_members() {
let ext = external_closure(SYNTH_LOCK, &["app"], 0);
let names: Vec<&str> = ext.iter().map(|e| e.name.as_str()).collect();
assert!(names.contains(&"alpha"), "direct external dep present: {names:?}");
assert!(names.contains(&"beta"), "TRANSITIVE deps-of-deps reached: {names:?}");
assert!(!names.contains(&"sibling"), "workspace member excluded: {names:?}");
assert!(!names.contains(&"gamma"), "unreachable crate excluded: {names:?}");
assert_eq!(ext.iter().find(|e| e.name == "alpha").unwrap().depth, 1);
assert_eq!(ext.iter().find(|e| e.name == "beta").unwrap().depth, 2);
assert_eq!(ext.iter().map(|e| e.depth).max(), Some(2), "recursion went β₯2 deep");
}
#[test]
fn max_depth_bounds_the_recursion() {
let shallow = external_closure(SYNTH_LOCK, &["app"], 1);
let names: Vec<&str> = shallow.iter().map(|e| e.name.as_str()).collect();
assert!(names.contains(&"alpha"), "depth-1 dep kept");
assert!(!names.contains(&"beta"), "depth-2 dep cut by max_depth=1: {names:?}");
}
#[test]
fn workspace_members_and_member_edges_from_lock() {
let mut members = workspace_members(SYNTH_LOCK);
members.sort();
assert_eq!(members, vec!["app".to_string(), "sibling".to_string()]);
let edges = member_edges(SYNTH_LOCK);
assert_eq!(edges.len(), 1);
assert_eq!((edges[0].from.as_str(), edges[0].to.as_str()), ("app", "sibling"));
}
#[test]
fn build_seats_the_hub_at_the_heart() {
let crates = vec!["a".to_string(), "b".to_string(), "c".to_string()];
let edges = vec![
CrossRepoEdge::normal("a", "b", Default::default()),
CrossRepoEdge::normal("c", "b", Default::default()),
];
let org = Organism::build(&crates, &edges);
assert!(org.from_data);
assert_eq!(org.organs.len(), 3);
assert_eq!(org.vessels.len(), 2);
let heart = org.organ("b").unwrap();
assert_eq!(heart.organ, "heart", "the most-depended-on crate is the heart");
assert_eq!(heart.in_degree, 2);
}
#[test]
fn toggle_expand_loads_then_drops_the_capillaries() {
let crates = vec!["app".to_string()];
let mut org = Organism::build(&crates, &[]);
assert_eq!(org.external_count(), 0, "lazy: nothing loaded before expand");
let n = org.toggle_expand("app", SYNTH_LOCK, 0);
assert!(n >= 2, "expand loaded the recursive external layer: {n}");
assert!(org.organ("app").unwrap().expanded);
assert!(org.organ("app").unwrap().max_depth() >= 2, "capillaries reach β₯2 deep");
let j = org.state_json();
assert_eq!(j["external_count"], serde_json::json!(n));
let n2 = org.toggle_expand("app", SYNTH_LOCK, 0);
assert_eq!(n2, 0);
assert_eq!(org.external_count(), 0, "collapse dropped the capillaries");
}
#[test]
fn set_clock_is_pure_and_wraps() {
let mut org = Organism::build(&["a".to_string()], &[]);
org.set_clock(0.0);
assert_eq!(org.flow_phase, 0.0);
org.set_clock(1000.0);
let a = org.flow_phase;
org.set_clock(1000.0);
assert_eq!(org.flow_phase, a, "identical clock β identical phase (FC-7)");
assert!((0.0..1.0).contains(&org.flow_phase));
}
}