use std::path::Path;
use anyhow::Result;
use sinter_resolve::qualified_of;
use sinter_store::EdgeFilter;
use crate::lookup::{open_store, unique_symbol};
use crate::render::node_json;
pub fn run(
repo: &Path,
symbol: &str,
filter: &EdgeFilter,
max_depth: usize,
limit: usize,
json: bool,
) -> Result<bool> {
let store = open_store(repo)?;
let node = unique_symbol(&store, symbol)?;
let mut reached = store.dependencies(&node.id, filter, max_depth)?;
let total = reached.len();
let root = crate::pipeline::discover_root(repo);
let unresolved = store
.references_in(&node.file)?
.iter()
.filter(|r| r.enclosing.as_ref() == Some(&node.id))
.count();
if json {
let entries: Vec<serde_json::Value> = reached
.iter()
.take(limit)
.map(|r| {
let mut entry = serde_json::json!({
"s": qualified_of(r.node.id.as_str()),
"k": r.node.kind.as_str(),
"f": r.node.file,
"e": format!("{}/{}", r.via.relation.as_str(), r.via.evidence.as_str()),
"d": r.depth,
});
let site = crate::render::site_json(&root, &r.via);
if !site.is_null() {
entry["site"] = site;
}
entry
})
.collect();
let mut counts = std::collections::HashMap::<String, u64>::new();
for r in &reached {
*counts.entry(r.node.file.clone()).or_default() += 1;
}
let mut pairs: Vec<_> = counts.into_iter().collect();
pairs.sort_by(|a, b| b.1.cmp(&a.1).then_with(|| a.0.cmp(&b.0)));
pairs.truncate(10);
let mut out = serde_json::json!({
"symbol": node_json(&node),
"total": total,
"unresolved_refs_in_symbol": unresolved,
"by_file": pairs,
"dependencies": entries,
});
if total > limit {
out["truncated"] = serde_json::json!(total - limit);
}
println!("{}", serde_json::to_string_pretty(&out)?);
return Ok(total > 0);
}
reached.truncate(limit);
println!(
"{} dependencies of {} ({})",
total,
qualified_of(node.id.as_str()),
node.file
);
let mut children: std::collections::HashMap<&str, Vec<&sinter_store::Reached>> =
std::collections::HashMap::new();
for r in &reached {
children.entry(r.via.src.as_str()).or_default().push(r);
}
let mut stack: Vec<(&sinter_store::Reached, usize)> = Vec::new();
let mut roots: Vec<&&sinter_store::Reached> = Vec::new();
let reached_ids: std::collections::HashSet<&str> =
reached.iter().map(|r| r.node.id.as_str()).collect();
for (parent, kids) in &children {
if !reached_ids.contains(parent) {
roots.extend(kids.iter());
}
}
roots.sort_by_key(|r| r.node.id.as_str());
for r in roots.iter().rev() {
stack.push((r, 1));
}
while let Some((r, depth)) = stack.pop() {
let site = crate::render::site_location(&root, &r.via)
.map(|s| format!(" at {s}"))
.unwrap_or_default();
println!(
" {}{} {} {} [{}/{}]{}",
" ".repeat(depth - 1),
qualified_of(r.node.id.as_str()),
r.node.kind.as_str(),
r.node.file,
r.via.relation.as_str(),
r.via.evidence.as_str(),
site,
);
if let Some(kids) = children.get(r.node.id.as_str()) {
for kid in kids.iter().rev() {
stack.push((kid, depth + 1));
}
}
}
if total > limit {
println!(
"{} more dependencies below cutoff · `sinter deps --limit {}` to widen",
total - limit,
total,
);
}
if unresolved > 0 {
println!(
" note: {unresolved} unresolved ref(s) inside {} — dependencies may be missing; `sinter scip` would bind them",
node.name
);
}
Ok(total > 0)
}