ito_config/
context.rs

1//! Resolved configuration context.
2//!
3//! `ItoContext` is a convenience wrapper that ties together the resolved config
4//! JSON, the project root, and the discovered `.ito/` directory (if present).
5
6use std::path::{Path, PathBuf};
7
8use ito_common::fs::FileSystem;
9
10use crate::{ConfigContext, ResolvedConfig, ito_config_dir, load_cascading_project_config_fs};
11
12#[derive(Debug, Clone)]
13/// Resolved configuration for a single invocation.
14pub struct ItoContext {
15    /// Optional directory containing global config (e.g. `~/.config/ito`).
16    pub config_dir: Option<PathBuf>,
17
18    /// Project root used as the base for repo-local config.
19    pub project_root: PathBuf,
20
21    /// Resolved `.ito/` directory path, when it exists.
22    pub ito_path: Option<PathBuf>,
23
24    /// Fully merged configuration JSON and its provenance.
25    pub config: ResolvedConfig,
26}
27
28impl ItoContext {
29    /// Resolve context using the current process environment.
30    pub fn resolve<F: FileSystem>(fs: &F, project_root: &Path) -> Self {
31        let ctx = ConfigContext::from_process_env();
32        Self::resolve_with_ctx(fs, project_root, ctx)
33    }
34
35    /// Resolve context using an explicit [`ConfigContext`].
36    pub fn resolve_with_ctx<F: FileSystem>(
37        fs: &F,
38        project_root: &Path,
39        ctx: ConfigContext,
40    ) -> Self {
41        let project_root = project_root.to_path_buf();
42        let ito_path = crate::ito_dir::get_ito_path_fs(fs, &project_root, &ctx);
43        let config_dir = ito_config_dir(&ctx);
44
45        let config = load_cascading_project_config_fs(fs, &project_root, &ito_path, &ctx);
46
47        let ito_path = fs.is_dir(&ito_path).then_some(ito_path);
48
49        Self {
50            config_dir,
51            project_root,
52            ito_path,
53            config,
54        }
55    }
56}
57
58#[cfg(test)]
59mod tests {
60    use super::*;
61
62    use ito_common::fs::StdFs;
63
64    #[test]
65    fn resolve_with_ctx_sets_none_when_ito_dir_is_missing() {
66        let project = tempfile::tempdir().expect("tempdir");
67        let ctx = ConfigContext::default();
68
69        let resolved = ItoContext::resolve_with_ctx(&StdFs, project.path(), ctx);
70
71        assert_eq!(resolved.project_root, project.path());
72        assert_eq!(resolved.ito_path, None);
73        assert_eq!(resolved.config.loaded_from, Vec::<PathBuf>::new());
74    }
75
76    #[test]
77    fn resolve_with_ctx_sets_ito_path_when_directory_exists() {
78        let project = tempfile::tempdir().expect("tempdir");
79        let ito_dir = project.path().join(".ito");
80        std::fs::create_dir_all(&ito_dir).expect("create .ito dir");
81
82        let resolved =
83            ItoContext::resolve_with_ctx(&StdFs, project.path(), ConfigContext::default());
84
85        assert_eq!(resolved.ito_path, Some(ito_dir));
86    }
87
88    #[test]
89    fn resolve_with_ctx_uses_explicit_config_context_paths() {
90        let project = tempfile::tempdir().expect("tempdir");
91        let xdg_home = project.path().join("xdg");
92        let ctx = ConfigContext {
93            xdg_config_home: Some(xdg_home.clone()),
94            home_dir: Some(project.path().join("home")),
95            project_dir: None,
96        };
97
98        let resolved = ItoContext::resolve_with_ctx(&StdFs, project.path(), ctx);
99
100        assert_eq!(resolved.config_dir, Some(xdg_home.join("ito")));
101    }
102}