prospero_core/caliband/
sources.rs1use std::path::Path;
6
7pub use prospero_types::Source;
11
12fn is_checkout(p: &Path) -> bool {
14 p.join(".git").exists()
15}
16
17#[must_use]
21pub fn discover_sources(workspace_root: &Path) -> Vec<Source> {
22 let mut out = Vec::new();
23 if is_checkout(workspace_root)
24 && let Some(name) = workspace_root.file_name().and_then(|n| n.to_str())
25 {
26 out.push(Source {
27 name: name.to_string(),
28 path: workspace_root.to_path_buf(),
29 });
30 return out;
31 }
32 if let Ok(entries) = std::fs::read_dir(workspace_root) {
33 for e in entries.flatten() {
34 let path = e.path();
35 if path.is_dir()
36 && is_checkout(&path)
37 && let Some(name) = path.file_name().and_then(|n| n.to_str())
38 {
39 out.push(Source {
40 name: name.to_string(),
41 path,
42 });
43 }
44 }
45 }
46 out.sort_by(|a, b| a.name.cmp(&b.name));
47 out
48}
49
50#[cfg(test)]
51mod tests {
52 use super::*;
53
54 fn mk_checkout(dir: &Path) {
55 std::fs::create_dir_all(dir.join(".git")).unwrap();
56 }
57
58 #[test]
59 fn root_is_the_single_source_when_a_checkout() {
60 let d = tempfile::tempdir().unwrap();
61 mk_checkout(d.path());
62 let s = discover_sources(d.path());
63 assert_eq!(s.len(), 1);
64 assert_eq!(s[0].path, d.path());
65 }
66
67 #[test]
68 fn immediate_child_checkouts_are_sources_sorted() {
69 let d = tempfile::tempdir().unwrap();
70 mk_checkout(&d.path().join("beta"));
71 mk_checkout(&d.path().join("alpha"));
72 std::fs::create_dir_all(d.path().join("not-a-repo")).unwrap();
73 let s = discover_sources(d.path());
74 assert_eq!(
75 s.iter().map(|x| x.name.as_str()).collect::<Vec<_>>(),
76 vec!["alpha", "beta"]
77 );
78 }
79
80 #[test]
81 fn empty_when_no_checkouts() {
82 let d = tempfile::tempdir().unwrap();
83 std::fs::create_dir_all(d.path().join("plain")).unwrap();
84 assert!(discover_sources(d.path()).is_empty());
85 }
86
87 #[test]
88 fn missing_root_is_empty_not_panic() {
89 assert!(discover_sources(Path::new("/no/such/path/here")).is_empty());
90 }
91}