Skip to main content

mana_core/ops/
sync.rs

1use std::path::Path;
2
3use anyhow::Result;
4
5use crate::index::{count_unit_formats, ArchiveIndex, Index};
6
7/// Result of a sync (index rebuild) operation.
8pub struct SyncResult {
9    pub unit_count: usize,
10    pub archive_count: usize,
11    pub md_count: usize,
12    pub yaml_count: usize,
13    pub mixed_formats: bool,
14}
15
16/// Rebuild index from unit files on disk.
17///
18/// Force rebuilds both the main index and archive index unconditionally.
19/// Returns structured counts so callers can format output.
20pub fn sync(mana_dir: &Path) -> Result<SyncResult> {
21    let (md_count, yaml_count) = count_unit_formats(mana_dir)?;
22
23    let index = Index::build(mana_dir)?;
24    let unit_count = index.units.len();
25    index.save(mana_dir)?;
26
27    let archive_index = ArchiveIndex::build(mana_dir)?;
28    let archive_count = archive_index.units.len();
29    if archive_count > 0 || mana_dir.join("archive.yaml").exists() {
30        archive_index.save(mana_dir)?;
31    }
32
33    Ok(SyncResult {
34        unit_count,
35        archive_count,
36        md_count,
37        yaml_count,
38        mixed_formats: md_count > 0 && yaml_count > 0,
39    })
40}
41
42#[cfg(test)]
43mod tests {
44    use super::*;
45    use crate::unit::{Status, Unit};
46    use crate::util::title_to_slug;
47    use std::fs;
48    use tempfile::TempDir;
49
50    #[test]
51    fn sync_rebuilds_index() {
52        let dir = TempDir::new().unwrap();
53        let mana_dir = dir.path().join(".mana");
54        fs::create_dir(&mana_dir).unwrap();
55
56        let unit1 = Unit::new("1", "Task one");
57        let unit2 = Unit::new("2", "Task two");
58        let slug1 = title_to_slug(&unit1.title);
59        let slug2 = title_to_slug(&unit2.title);
60        unit1
61            .to_file(mana_dir.join(format!("1-{}.md", slug1)))
62            .unwrap();
63        unit2
64            .to_file(mana_dir.join(format!("2-{}.md", slug2)))
65            .unwrap();
66
67        let result = sync(&mana_dir).unwrap();
68
69        assert_eq!(result.unit_count, 2);
70        assert!(!result.mixed_formats);
71    }
72
73    #[test]
74    fn sync_empty_project() {
75        let dir = TempDir::new().unwrap();
76        let mana_dir = dir.path().join(".mana");
77        fs::create_dir(&mana_dir).unwrap();
78
79        let result = sync(&mana_dir).unwrap();
80
81        assert_eq!(result.unit_count, 0);
82        assert_eq!(result.archive_count, 0);
83    }
84
85    #[test]
86    fn sync_rebuilds_archive() {
87        let dir = TempDir::new().unwrap();
88        let mana_dir = dir.path().join(".mana");
89        fs::create_dir(&mana_dir).unwrap();
90
91        let archive_dir = mana_dir.join("archive").join("2026").join("03");
92        fs::create_dir_all(&archive_dir).unwrap();
93
94        let mut unit = Unit::new("10", "Archived ten");
95        unit.status = Status::Closed;
96        unit.is_archived = true;
97        let slug = title_to_slug(&unit.title);
98        unit.to_file(archive_dir.join(format!("10-{}.md", slug)))
99            .unwrap();
100
101        let result = sync(&mana_dir).unwrap();
102
103        assert_eq!(result.archive_count, 1);
104        assert!(mana_dir.join("archive.yaml").exists());
105    }
106}