1use crate::corpus::Corpus;
5use crate::graph::{Graph, Resolution};
6use crate::workspace::owners::matcher;
7use ignore::WalkBuilder;
8use serde::Serialize;
9use std::collections::BTreeMap;
10
11#[derive(Debug, Serialize)]
12pub struct Census {
13 pub docs: usize,
14 pub by_root: BTreeMap<String, usize>,
15 pub by_type: BTreeMap<String, usize>,
16 pub by_status: BTreeMap<String, usize>,
17 pub owners_with_docs: usize,
18 pub strays: usize,
19 pub links: usize,
20 pub links_broken: usize,
21 pub links_via_alias: usize,
22 pub path_cites: usize,
23 pub attachments: usize,
24 pub attachment_bytes: u64,
25 pub aliases: usize,
26 pub legacy_paths: usize,
28}
29
30pub fn take(c: &Corpus, g: &Graph) -> Census {
31 let mut by_root = BTreeMap::new();
32 let mut by_type = BTreeMap::new();
33 let mut by_status = BTreeMap::new();
34 let (mut links, mut broken, mut via_alias, mut cites, mut attachments, mut bytes) = (0, 0, 0, 0, 0, 0u64);
35 for d in &c.docs {
36 let root = c
37 .roots
38 .iter()
39 .find(|r| d.path.starts_with(&format!("{}/", r.dir)))
40 .map(|r| r.dir.clone())
41 .unwrap_or_default();
42 *by_root.entry(root).or_insert(0) += 1;
43 *by_type.entry(d.id.ty.clone()).or_insert(0) += 1;
44 *by_status.entry(d.status().unwrap_or_else(|| "(none)".into())).or_insert(0) += 1;
45 for l in &d.links {
46 links += 1;
47 match g.resolve(&l.item) {
48 Resolution::Doc { via_alias: true, .. } => via_alias += 1,
49 Resolution::Doc { .. } => {}
50 _ => broken += 1,
51 }
52 }
53 cites += d.path_cites.len();
54 attachments += d.bundle_files.len();
55 bytes += d
56 .bundle_files
57 .iter()
58 .filter_map(|f| std::fs::metadata(c.cfg.root.join(f)).ok())
59 .map(|m| m.len())
60 .sum::<u64>();
61 }
62 let forbid: Vec<_> = c.cfg.raw.forbid.paths.iter().filter_map(|p| matcher(p)).collect();
63 let legacy_paths = if forbid.is_empty() {
64 0
65 } else {
66 WalkBuilder::new(&c.cfg.root)
67 .hidden(false)
68 .require_git(false)
69 .build()
70 .flatten()
71 .filter_map(|e| e.path().strip_prefix(&c.cfg.root).ok().map(|p| p.to_string_lossy().replace('\\', "/")))
72 .filter(|p| !p.is_empty() && forbid.iter().any(|m| m.is_match(p)))
73 .count()
74 };
75 Census {
76 docs: c.docs.len(),
77 owners_with_docs: by_root.len(),
78 by_root,
79 by_type,
80 by_status,
81 strays: c.strays.len(),
82 links,
83 links_broken: broken,
84 links_via_alias: via_alias,
85 path_cites: cites,
86 attachments,
87 attachment_bytes: bytes,
88 aliases: g.by_alias.len(),
89 legacy_paths,
90 }
91}