weavatrix_rust/engine/
repository_state.rs1use 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 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}