Skip to main content

weavatrix_rust/engine/
repository_state.rs

1use super::RepositoryState;
2use crate::analyzer::Analyzer;
3use crate::model::{Error, Result, Snapshot};
4use std::path::Path;
5use std::sync::{Arc, OnceLock};
6use std::time::{Duration, Instant};
7use weavatrix_graph::{Graph, Node, NodeIndex, weakly_connected_components};
8use weavatrix_scan::ScanReport;
9
10impl RepositoryState {
11    pub(crate) fn build(analyzer: &Analyzer, root: impl AsRef<Path>) -> Result<Self> {
12        let started = Instant::now();
13        let (snapshot, scan) = analyzer.analyze_with_report(root)?;
14        let graph = Graph::try_from_sorted_parts(snapshot.nodes.clone(), snapshot.edges.clone())?;
15        let snapshot_root = std::path::PathBuf::from(&snapshot.repository);
16        let root = snapshot_root
17            .canonicalize()
18            .map_err(|source| Error::io(&snapshot_root, source))?;
19        Ok(Self {
20            root,
21            snapshot,
22            graph: Arc::new(graph),
23            scan,
24            build_time: started.elapsed(),
25            weak_components: Arc::new(OnceLock::new()),
26        })
27    }
28
29    pub(super) fn from_scan(analyzer: &Analyzer, root: &Path, scan: ScanReport) -> Result<Self> {
30        let started = Instant::now();
31        let snapshot = analyzer.analyze_report(root, &scan)?;
32        let graph = Graph::try_from_sorted_parts(snapshot.nodes.clone(), snapshot.edges.clone())?;
33        Ok(Self {
34            root: root.to_path_buf(),
35            snapshot,
36            graph: Arc::new(graph),
37            scan,
38            build_time: started.elapsed(),
39            weak_components: Arc::new(OnceLock::new()),
40        })
41    }
42
43    #[must_use]
44    pub fn root(&self) -> &Path {
45        &self.root
46    }
47
48    #[must_use]
49    pub const fn snapshot(&self) -> &Snapshot {
50        &self.snapshot
51    }
52
53    #[must_use]
54    pub fn graph(&self) -> &Graph {
55        self.graph.as_ref()
56    }
57
58    #[must_use]
59    pub const fn build_time(&self) -> Duration {
60        self.build_time
61    }
62
63    #[must_use]
64    pub const fn scan_report(&self) -> &ScanReport {
65        &self.scan
66    }
67
68    pub(crate) fn weak_components(&self) -> &[Vec<NodeIndex>] {
69        self.weak_components
70            .get_or_init(|| {
71                let mut components = weakly_connected_components(self.graph.as_ref());
72                components.sort_unstable_by_key(|right| std::cmp::Reverse(right.len()));
73                components
74            })
75            .as_slice()
76    }
77
78    /// Warms the cached repository communities in a background thread.
79    pub fn warm_communities(&self) {
80        if self.weak_components.get().is_some() {
81            return;
82        }
83        let graph = Arc::clone(&self.graph);
84        let destination = Arc::clone(&self.weak_components);
85        std::thread::spawn(move || {
86            std::thread::sleep(Duration::from_millis(10));
87            destination.get_or_init(|| {
88                let mut components = weakly_connected_components(graph.as_ref());
89                components.sort_unstable_by_key(|right| std::cmp::Reverse(right.len()));
90                components
91            });
92        });
93    }
94
95    pub(crate) fn resolve_node(&self, label: &str) -> std::result::Result<NodeIndex, String> {
96        if let Some(index) = self.graph.node_index(label) {
97            return Ok(index);
98        }
99        let matches = self
100            .graph
101            .nodes()
102            .iter()
103            .enumerate()
104            .filter(|(_, node)| node.label == label)
105            .collect::<Vec<_>>();
106        match matches.as_slice() {
107            [] => Err(format!("node not found: {label}")),
108            [(index, _)] => Ok(NodeIndex::new(
109                u32::try_from(*index).map_err(|_| "node index overflow")?,
110            )),
111            _ => Err(format!(
112                "ambiguous node label {label:?}; use one of: {}",
113                matches
114                    .iter()
115                    .take(8)
116                    .map(|(_, node)| node.id.as_str())
117                    .collect::<Vec<_>>()
118                    .join(", ")
119            )),
120        }
121    }
122
123    pub(crate) fn node(&self, index: NodeIndex) -> std::result::Result<&Node, String> {
124        self.graph
125            .node_at(index)
126            .ok_or_else(|| format!("node index out of range: {}", index.index()))
127    }
128}