Skip to main content

tess/
config_path.rs

1//! Discovery of the global and local config directories. Owned here so
2//! `format.rs` and `keys.rs` don't drift on path logic.
3
4use std::path::PathBuf;
5
6/// Source layer a piece of config came from. Embedded in compiled
7/// `LogFormat` and `Group` entries so `--list-formats` can annotate them.
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
9pub enum ConfigSource {
10    #[default]
11    Builtin,
12    Global,
13    Local,
14}
15
16/// Resolve the global config directory.
17///
18/// Priority:
19/// 1. `$TESS_GLOBAL_CONFIG_DIR` if set — returned as-is, **not**
20///    existence-checked (so tests and CI can point at a fresh tempdir
21///    before populating it).
22/// 2. `/etc/tess` if it exists as a directory. The existence probe is
23///    deliberate so a default-empty install doesn't claim a global
24///    layer that isn't there.
25/// 3. Otherwise `None`.
26pub fn global_config_dir() -> Option<PathBuf> {
27    if let Some(v) = std::env::var_os("TESS_GLOBAL_CONFIG_DIR") {
28        return Some(PathBuf::from(v));
29    }
30    let etc = PathBuf::from("/etc/tess");
31    if etc.is_dir() {
32        return Some(etc);
33    }
34    None
35}
36
37/// Resolve the per-user config directory (`~/.config/tess`). Returns
38/// `None` if `$HOME` is not set.
39pub fn user_config_dir() -> Option<PathBuf> {
40    std::env::var_os("HOME").map(|h| {
41        let mut p = PathBuf::from(h);
42        p.push(".config");
43        p.push("tess");
44        p
45    })
46}
47
48#[cfg(test)]
49mod tests {
50    use super::*;
51
52    #[test]
53    fn global_config_dir_honors_env_var() {
54        let _guard = crate::test_env::lock();
55        let prev = std::env::var_os("TESS_GLOBAL_CONFIG_DIR");
56        std::env::set_var("TESS_GLOBAL_CONFIG_DIR", "/tmp/tess-test-global");
57        assert_eq!(
58            global_config_dir(),
59            Some(PathBuf::from("/tmp/tess-test-global"))
60        );
61        match prev {
62            Some(v) => std::env::set_var("TESS_GLOBAL_CONFIG_DIR", v),
63            None => std::env::remove_var("TESS_GLOBAL_CONFIG_DIR"),
64        }
65    }
66
67    #[test]
68    fn global_config_dir_with_no_env_var_returns_etc_or_none() {
69        let _guard = crate::test_env::lock();
70        let prev = std::env::var_os("TESS_GLOBAL_CONFIG_DIR");
71        std::env::remove_var("TESS_GLOBAL_CONFIG_DIR");
72        let result = global_config_dir();
73        // `/etc/tess` likely doesn't exist on the dev machine, but if it
74        // does the result is the etc path — accept either as long as it's
75        // not garbage.
76        if let Some(p) = &result {
77            assert_eq!(p, &PathBuf::from("/etc/tess"));
78        }
79        match prev {
80            Some(v) => std::env::set_var("TESS_GLOBAL_CONFIG_DIR", v),
81            None => std::env::remove_var("TESS_GLOBAL_CONFIG_DIR"),
82        }
83    }
84
85    #[test]
86    fn user_config_dir_appends_config_tess() {
87        let _guard = crate::test_env::lock();
88        let prev = std::env::var_os("HOME");
89        std::env::set_var("HOME", "/tmp/fakehome");
90        assert_eq!(
91            user_config_dir(),
92            Some(PathBuf::from("/tmp/fakehome/.config/tess"))
93        );
94        match prev {
95            Some(v) => std::env::set_var("HOME", v),
96            None => std::env::remove_var("HOME"),
97        }
98    }
99}