specfold_core/audit/
pack.rs1use crate::corpus::Corpus;
5use crate::graph::Graph;
6use crate::model::lifecycle;
7use serde::Serialize;
8use std::collections::BTreeMap;
9
10#[derive(Debug, Clone, Serialize)]
11pub struct Pack {
12 pub topic: String,
14 pub docs: Vec<String>,
15 pub paths: Vec<String>,
16 pub bytes: u64,
17}
18
19fn size(c: &Corpus, i: usize) -> u64 {
20 let d = &c.docs[i];
21 std::iter::once(&d.path)
22 .chain(&d.chapters)
23 .filter_map(|p| std::fs::metadata(c.cfg.root.join(p)).ok())
24 .map(|m| m.len())
25 .sum()
26}
27
28pub fn build(c: &Corpus, g: &Graph, owners: &[String], budget: u64) -> Vec<Pack> {
31 let current = |i: usize| {
32 c.docs[i].status().as_deref().is_some_and(lifecycle::is_current)
33 && (owners.is_empty() || owners.contains(&c.docs[i].id.owner))
34 };
35 let mut groups: BTreeMap<String, Vec<usize>> = BTreeMap::new();
36 for i in (0..c.docs.len()).filter(|&i| current(i)) {
37 for gv in c.docs[i].rel().remove("governs").unwrap_or_default() {
38 groups.entry(format!("governs {}", crate::placement::literal_base(&gv))).or_default().push(i);
39 }
40 for e in g.edges_from(i).filter(|e| e.kind == "implements") {
41 groups.entry(format!("implements {}", c.docs[e.to].id)).or_default().push(i);
42 groups.entry(format!("implements {}", c.docs[e.to].id)).or_default().push(e.to);
43 }
44 }
45 let mut packs = vec![];
46 for (topic, mut members) in groups {
47 members.sort();
48 members.dedup();
49 if members.len() < 2 {
50 continue;
51 }
52 let mut cur: Vec<usize> = vec![];
53 let mut bytes = 0;
54 for i in members {
55 let s = size(c, i);
56 if !cur.is_empty() && bytes + s > budget {
57 packs.push(finish(c, &topic, &cur, bytes));
58 cur.clear();
59 bytes = 0;
60 }
61 cur.push(i);
62 bytes += s;
63 }
64 if cur.len() > 1 {
65 packs.push(finish(c, &topic, &cur, bytes));
66 }
67 }
68 packs
69}
70
71fn finish(c: &Corpus, topic: &str, members: &[usize], bytes: u64) -> Pack {
72 Pack {
73 topic: topic.to_string(),
74 docs: members.iter().map(|&i| c.docs[i].id.to_string()).collect(),
75 paths: members.iter().map(|&i| c.docs[i].path.clone()).collect(),
76 bytes,
77 }
78}