specfold_core/graph/
query.rs1use super::types::Graph;
5use crate::corpus::Corpus;
6use crate::workspace::owners::matcher;
7use std::collections::{BTreeSet, VecDeque};
8
9#[derive(Debug, Clone)]
11pub struct Governing {
12 pub idx: usize,
13 pub pattern: String,
14 pub specificity: usize,
15}
16
17pub fn governing(c: &Corpus, path: &str) -> Vec<Governing> {
20 let path = path.trim_start_matches("./").trim_end_matches('/');
21 let mut out = vec![];
22 for (i, d) in c.docs.iter().enumerate() {
23 let globs = d.rel().remove("governs").unwrap_or_default();
24 if globs.is_empty() {
25 let Some(owner) = c.owners.by_id(&d.id.owner) else { continue };
26 if !owner.path.is_empty() && (path == owner.path || path.starts_with(&format!("{}/", owner.path))) {
27 out.push(Governing { idx: i, pattern: format!("{}/**", owner.path), specificity: owner.path.len() });
28 }
29 continue;
30 }
31 for g in globs {
32 if matcher(&g).is_some_and(|m| m.is_match(path)) {
33 let specificity = g.split(['*', '?', '[', '{']).next().unwrap_or("").len();
34 out.push(Governing { idx: i, pattern: g, specificity });
35 break;
36 }
37 }
38 }
39 out.sort_by(|a, b| b.specificity.cmp(&a.specificity).then(a.idx.cmp(&b.idx)));
40 out
41}
42
43pub fn impact(g: &Graph, idx: usize) -> Vec<(usize, usize)> {
45 let mut seen = BTreeSet::from([idx]);
46 let mut out = vec![];
47 let mut q = VecDeque::from([(idx, 0usize)]);
48 while let Some((cur, depth)) = q.pop_front() {
49 for e in g.edges_to(cur) {
50 if seen.insert(e.from) {
51 out.push((e.from, depth + 1));
52 q.push_back((e.from, depth + 1));
53 }
54 }
55 }
56 out
57}
58
59pub fn chain(g: &Graph, idx: usize) -> Vec<usize> {
61 let mut path = vec![idx];
62 let mut cur = idx;
63 loop {
64 let next = g.edges_to(cur).find(|e| e.kind == "supersedes").map(|e| e.from);
65 match next {
66 Some(n) if !path.contains(&n) => {
67 path.push(n);
68 cur = n;
69 }
70 _ => return path,
71 }
72 }
73}