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    use std::sync::Mutex;
52
53    /// Serializes tests that mutate env vars. Same pattern as the
54    /// HOME_LOCK in `format.rs`.
55    static ENV_LOCK: Mutex<()> = Mutex::new(());
56
57    #[test]
58    fn global_config_dir_honors_env_var() {
59        let _guard = ENV_LOCK.lock().unwrap();
60        let prev = std::env::var_os("TESS_GLOBAL_CONFIG_DIR");
61        std::env::set_var("TESS_GLOBAL_CONFIG_DIR", "/tmp/tess-test-global");
62        assert_eq!(
63            global_config_dir(),
64            Some(PathBuf::from("/tmp/tess-test-global"))
65        );
66        match prev {
67            Some(v) => std::env::set_var("TESS_GLOBAL_CONFIG_DIR", v),
68            None => std::env::remove_var("TESS_GLOBAL_CONFIG_DIR"),
69        }
70    }
71
72    #[test]
73    fn global_config_dir_with_no_env_var_returns_etc_or_none() {
74        let _guard = ENV_LOCK.lock().unwrap();
75        let prev = std::env::var_os("TESS_GLOBAL_CONFIG_DIR");
76        std::env::remove_var("TESS_GLOBAL_CONFIG_DIR");
77        let result = global_config_dir();
78        // `/etc/tess` likely doesn't exist on the dev machine, but if it
79        // does the result is the etc path — accept either as long as it's
80        // not garbage.
81        if let Some(p) = &result {
82            assert_eq!(p, &PathBuf::from("/etc/tess"));
83        }
84        match prev {
85            Some(v) => std::env::set_var("TESS_GLOBAL_CONFIG_DIR", v),
86            None => std::env::remove_var("TESS_GLOBAL_CONFIG_DIR"),
87        }
88    }
89
90    #[test]
91    fn user_config_dir_appends_config_tess() {
92        let _guard = ENV_LOCK.lock().unwrap();
93        let prev = std::env::var_os("HOME");
94        std::env::set_var("HOME", "/tmp/fakehome");
95        assert_eq!(
96            user_config_dir(),
97            Some(PathBuf::from("/tmp/fakehome/.config/tess"))
98        );
99        match prev {
100            Some(v) => std::env::set_var("HOME", v),
101            None => std::env::remove_var("HOME"),
102        }
103    }
104}