Skip to main content

bn/commands/
sync.rs

1use std::path::Path;
2
3use anyhow::Result;
4
5use crate::index::{count_bean_formats, Index};
6
7/// Force rebuild index unconditionally from YAML files
8pub fn cmd_sync(beans_dir: &Path) -> Result<()> {
9    // Check for mixed formats before building
10    let (md_count, yaml_count) = count_bean_formats(beans_dir)?;
11
12    let index = Index::build(beans_dir)?;
13    let count = index.beans.len();
14    index.save(beans_dir)?;
15
16    println!("Index rebuilt: {} beans indexed.", count);
17
18    // Warn about mixed formats
19    if md_count > 0 && yaml_count > 0 {
20        eprintln!();
21        eprintln!("Warning: Mixed bean formats detected!");
22        eprintln!("  {} .md files (current format)", md_count);
23        eprintln!("  {} .yaml files (legacy format)", yaml_count);
24        eprintln!();
25        eprintln!("This can cause confusion. Consider migrating legacy files:");
26        eprintln!("  - Remove or archive .yaml files: mkdir -p .beans/legacy && mv .beans/*.yaml .beans/legacy/");
27        eprintln!("  - Or run 'bn doctor' for more details");
28    }
29
30    Ok(())
31}
32
33#[cfg(test)]
34mod tests {
35    use super::*;
36    use crate::bean::Bean;
37    use crate::util::title_to_slug;
38    use std::fs;
39    use tempfile::TempDir;
40
41    #[test]
42    fn sync_rebuilds_index() {
43        let dir = TempDir::new().unwrap();
44        let beans_dir = dir.path().join(".beans");
45        fs::create_dir(&beans_dir).unwrap();
46
47        let bean1 = Bean::new("1", "Task one");
48        let bean2 = Bean::new("2", "Task two");
49
50        let slug1 = title_to_slug(&bean1.title);
51        let slug2 = title_to_slug(&bean2.title);
52
53        bean1
54            .to_file(beans_dir.join(format!("1-{}.md", slug1)))
55            .unwrap();
56        bean2
57            .to_file(beans_dir.join(format!("2-{}.md", slug2)))
58            .unwrap();
59
60        // Sync should create index with 2 beans
61        let result = cmd_sync(&beans_dir);
62        assert!(result.is_ok());
63
64        // Verify index was created
65        assert!(beans_dir.join("index.yaml").exists());
66
67        // Verify index contains both beans
68        let index = Index::load(&beans_dir).unwrap();
69        assert_eq!(index.beans.len(), 2);
70    }
71
72    #[test]
73    fn sync_counts_beans() {
74        let dir = TempDir::new().unwrap();
75        let beans_dir = dir.path().join(".beans");
76        fs::create_dir(&beans_dir).unwrap();
77
78        // Create 5 beans
79        for i in 1..=5 {
80            let bean = Bean::new(i.to_string(), format!("Task {}", i));
81            let slug = title_to_slug(&bean.title);
82            bean.to_file(beans_dir.join(format!("{}-{}.md", i, slug)))
83                .unwrap();
84        }
85
86        let result = cmd_sync(&beans_dir);
87        assert!(result.is_ok());
88
89        let index = Index::load(&beans_dir).unwrap();
90        assert_eq!(index.beans.len(), 5);
91    }
92
93    #[test]
94    fn sync_empty_project() {
95        let dir = TempDir::new().unwrap();
96        let beans_dir = dir.path().join(".beans");
97        fs::create_dir(&beans_dir).unwrap();
98
99        let result = cmd_sync(&beans_dir);
100        assert!(result.is_ok());
101
102        let index = Index::load(&beans_dir).unwrap();
103        assert_eq!(index.beans.len(), 0);
104    }
105}