Skip to main content

maw/model/
layout.rs

1//! Manifold directory layout and initialization.
2
3use std::fs;
4use std::io::{self, Write};
5use std::path::Path;
6
7/// Manifold metadata directory name.
8pub const MANIFOLD_DIR: &str = ".manifold";
9
10/// Subdirectory for epoch data.
11pub const EPOCHS_DIR: &str = "epochs";
12
13/// Subdirectory for artifacts.
14pub const ARTIFACTS_DIR: &str = "artifacts";
15
16/// Subdirectory for workspace artifacts.
17pub const WS_ARTIFACTS_DIR: &str = "ws";
18
19/// Subdirectory for merge artifacts.
20pub const MERGE_ARTIFACTS_DIR: &str = "merge";
21
22/// Config file name.
23pub const CONFIG_FILE: &str = "config.toml";
24
25/// Initialize the .manifold directory structure.
26///
27/// This function is idempotent:
28/// - Missing directories are created.
29/// - Existing directories are left alone.
30/// - `config.toml` is created with defaults if missing.
31#[allow(clippy::missing_errors_doc)]
32pub fn init_manifold_dir(root: &Path) -> io::Result<()> {
33    let manifold_path = root.join(MANIFOLD_DIR);
34
35    // Create directory structure
36    create_dir_all_idempotent(&manifold_path)?;
37    create_dir_all_idempotent(&manifold_path.join(EPOCHS_DIR))?;
38
39    let artifacts_path = manifold_path.join(ARTIFACTS_DIR);
40    create_dir_all_idempotent(&artifacts_path)?;
41    create_dir_all_idempotent(&artifacts_path.join(WS_ARTIFACTS_DIR))?;
42    create_dir_all_idempotent(&artifacts_path.join(MERGE_ARTIFACTS_DIR))?;
43
44    // Initialize default config if missing
45    init_config_if_missing(&manifold_path.join(CONFIG_FILE))?;
46
47    Ok(())
48}
49
50fn create_dir_all_idempotent(path: &Path) -> io::Result<()> {
51    if !path.exists() {
52        fs::create_dir_all(path)?;
53    }
54    Ok(())
55}
56
57fn init_config_if_missing(path: &Path) -> io::Result<()> {
58    if !path.exists() {
59        let mut file = fs::File::create(path)?;
60        writeln!(file, "# Manifold repository configuration")?;
61        writeln!(
62            file,
63            "# For full options see: https://github.com/mariozechner/manifold"
64        )?;
65        writeln!(file)?;
66        writeln!(file, "[repo]")?;
67        writeln!(file, "branch = \"main\"")?;
68    }
69    Ok(())
70}
71
72
73#[cfg(test)]
74mod tests {
75    use super::*;
76    use tempfile::tempdir;
77
78    #[test]
79    fn test_init_manifold_dir() {
80        let dir = tempdir().unwrap();
81        let root = dir.path();
82
83        init_manifold_dir(root).unwrap();
84
85        assert!(root.join(".manifold").is_dir());
86        assert!(root.join(".manifold/epochs").is_dir());
87        assert!(root.join(".manifold/artifacts").is_dir());
88        assert!(root.join(".manifold/artifacts/ws").is_dir());
89        assert!(root.join(".manifold/artifacts/merge").is_dir());
90        assert!(root.join(".manifold/config.toml").is_file());
91        assert!(!root.join(".gitignore").exists());
92    }
93
94    #[test]
95    fn test_idempotency() {
96        let dir = tempdir().unwrap();
97        let root = dir.path();
98
99        init_manifold_dir(root).unwrap();
100        let config_first = fs::read_to_string(root.join(".manifold/config.toml")).unwrap();
101
102        init_manifold_dir(root).unwrap();
103        let config_second = fs::read_to_string(root.join(".manifold/config.toml")).unwrap();
104
105        assert_eq!(config_first, config_second);
106    }
107}