Skip to main content

dotm/
loader.rs

1use crate::config::{HostConfig, RoleConfig, RootConfig};
2use anyhow::{Context, Result, bail};
3use std::path::{Path, PathBuf};
4
5pub struct ConfigLoader {
6    base_dir: PathBuf,
7    root: RootConfig,
8}
9
10impl ConfigLoader {
11    pub fn new(base_dir: &Path) -> Result<Self> {
12        let config_path = base_dir.join("dotm.toml");
13        let content = std::fs::read_to_string(&config_path)
14            .with_context(|| format!("failed to read {}", config_path.display()))?;
15        let root: RootConfig = toml::from_str(&content)
16            .with_context(|| format!("failed to parse {}", config_path.display()))?;
17
18        Ok(Self {
19            base_dir: base_dir.to_path_buf(),
20            root,
21        })
22    }
23
24    pub fn root(&self) -> &RootConfig {
25        &self.root
26    }
27
28    pub fn base_dir(&self) -> &Path {
29        &self.base_dir
30    }
31
32    pub fn packages_dir(&self) -> PathBuf {
33        self.base_dir.join(&self.root.dotm.packages_dir)
34    }
35
36    pub fn load_host(&self, hostname: &str) -> Result<HostConfig> {
37        let path = self.base_dir.join("hosts").join(format!("{hostname}.toml"));
38        if !path.exists() {
39            bail!("host config not found: {}", path.display());
40        }
41        let content = std::fs::read_to_string(&path)
42            .with_context(|| format!("failed to read {}", path.display()))?;
43        let config: HostConfig = toml::from_str(&content)
44            .with_context(|| format!("failed to parse {}", path.display()))?;
45        Ok(config)
46    }
47
48    pub fn list_hosts(&self) -> Result<Vec<String>> {
49        let hosts_dir = self.base_dir.join("hosts");
50        if !hosts_dir.is_dir() {
51            return Ok(Vec::new());
52        }
53        let mut names = Vec::new();
54        for entry in std::fs::read_dir(&hosts_dir)? {
55            let entry = entry?;
56            let path = entry.path();
57            if path.extension().and_then(|e| e.to_str()) == Some("toml") {
58                if let Some(stem) = path.file_stem().and_then(|s| s.to_str()) {
59                    names.push(stem.to_string());
60                }
61            }
62        }
63        names.sort();
64        Ok(names)
65    }
66
67    pub fn list_roles(&self) -> Result<Vec<String>> {
68        let roles_dir = self.base_dir.join("roles");
69        if !roles_dir.is_dir() {
70            return Ok(Vec::new());
71        }
72        let mut names = Vec::new();
73        for entry in std::fs::read_dir(&roles_dir)? {
74            let entry = entry?;
75            let path = entry.path();
76            if path.extension().and_then(|e| e.to_str()) == Some("toml") {
77                if let Some(stem) = path.file_stem().and_then(|s| s.to_str()) {
78                    names.push(stem.to_string());
79                }
80            }
81        }
82        names.sort();
83        Ok(names)
84    }
85
86    pub fn load_role(&self, name: &str) -> Result<RoleConfig> {
87        let path = self.base_dir.join("roles").join(format!("{name}.toml"));
88        if !path.exists() {
89            bail!("role config not found: {}", path.display());
90        }
91        let content = std::fs::read_to_string(&path)
92            .with_context(|| format!("failed to read {}", path.display()))?;
93        let config: RoleConfig = toml::from_str(&content)
94            .with_context(|| format!("failed to parse {}", path.display()))?;
95        Ok(config)
96    }
97}