splashboard 2.10.2

A customizable terminal splash screen with plugin-based data sources
Documentation
#![allow(dead_code)]

use std::path::PathBuf;

/// All splashboard state lives under a single dotted directory in the user's home:
/// `$HOME/.splashboard/`. Keeps settings, dashboards, trust store, cache, and ReadStore data
/// in one place — same pattern as per-directory `.splashboard/` configs. Overridable via the
/// `SPLASHBOARD_HOME` env var for tests, CI, or power users who want to relocate.
///
/// Rationale over the platform-specific `dirs` crate: for a CLI tool, `~/Library/...` on
/// macOS is awkward (Unix-flavored OS, dotfile conventions are the norm for terminal
/// tools). `$HOME/.splashboard/` works the same on Linux, macOS, and Windows, and mirrors
/// the per-directory convention users already know.
pub fn splashboard_home() -> Option<PathBuf> {
    if let Ok(p) = std::env::var("SPLASHBOARD_HOME") {
        return Some(PathBuf::from(p));
    }
    dirs::home_dir().map(|h| h.join(".splashboard"))
}

/// User preferences (`[general]` + `[theme]`). Always lives in HOME — per-dir overrides are
/// out of scope for 0.x.
pub fn settings_path() -> Option<PathBuf> {
    splashboard_home().map(|d| d.join("settings.toml"))
}

/// Sensitive env vars (`GH_TOKEN`, `TODOIST_TOKEN`, etc.). Kept separate from `settings.toml`
/// so dotfiles-in-git users can git-ignore this one file without losing the rest of their
/// splashboard config.
pub fn secrets_path() -> Option<PathBuf> {
    splashboard_home().map(|d| d.join("secrets.toml"))
}

/// Default dashboard when no per-dir dashboard applies and the CWD isn't a project root.
pub fn home_dashboard_path() -> Option<PathBuf> {
    splashboard_home().map(|d| d.join("home.dashboard.toml"))
}

/// Default project dashboard used when the CWD is a git repo root without a per-dir dashboard.
pub fn project_dashboard_path() -> Option<PathBuf> {
    splashboard_home().map(|d| d.join("project.dashboard.toml"))
}

pub fn trust_store_path() -> Option<PathBuf> {
    splashboard_home().map(|d| d.join("trust.toml"))
}

pub fn cache_dir() -> Option<PathBuf> {
    splashboard_home().map(|d| d.join("cache"))
}

pub fn read_store_dir() -> Option<PathBuf> {
    splashboard_home().map(|d| d.join("store"))
}

/// Directory for the `tracing` file appender. Created lazily by the logging init path so
/// invocations that never log don't touch the disk.
pub fn logs_dir() -> Option<PathBuf> {
    splashboard_home().map(|d| d.join("logs"))
}

/// Shared mutex guarding any test that mutates `SPLASHBOARD_HOME`. Without this, parallel
/// tests in different modules can see each other's temporary values through the process-wide
/// env var. Exposed at crate level so other test modules (`fetcher::read_store`, etc.) can
/// lock the same mutex.
pub static TEST_ENV_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());

#[cfg(test)]
mod tests {
    use super::*;

    fn restore_env(previous: Option<String>) {
        unsafe {
            match previous {
                Some(value) => std::env::set_var("SPLASHBOARD_HOME", value),
                None => std::env::remove_var("SPLASHBOARD_HOME"),
            }
        }
    }

    /// All subpaths flow through `splashboard_home()` so overriding the env var relocates
    /// every splashboard-owned file atomically — important for test isolation and for
    /// power users who want to `SPLASHBOARD_HOME=/opt/splashboard` ship the whole state
    /// directory elsewhere.
    #[test]
    fn env_var_overrides_home() {
        let _lock = TEST_ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
        let tmp = tempfile::tempdir().unwrap();
        let path = tmp.path().to_path_buf();
        let outer_previous = std::env::var("SPLASHBOARD_HOME").ok();
        // SAFETY: mutating process env in tests. Scoped to restore on exit; mutex above
        // ensures no other test races with us on this env var.
        unsafe {
            std::env::set_var("SPLASHBOARD_HOME", tmp.path().join("before"));
        }
        let previous = std::env::var("SPLASHBOARD_HOME").ok();
        unsafe {
            std::env::set_var("SPLASHBOARD_HOME", &path);
        }
        assert_eq!(splashboard_home(), Some(path.clone()));
        assert_eq!(settings_path(), Some(path.join("settings.toml")));
        assert_eq!(secrets_path(), Some(path.join("secrets.toml")));
        assert_eq!(
            home_dashboard_path(),
            Some(path.join("home.dashboard.toml"))
        );
        assert_eq!(
            project_dashboard_path(),
            Some(path.join("project.dashboard.toml"))
        );
        assert_eq!(trust_store_path(), Some(path.join("trust.toml")));
        assert_eq!(cache_dir(), Some(path.join("cache")));
        assert_eq!(read_store_dir(), Some(path.join("store")));
        assert_eq!(logs_dir(), Some(path.join("logs")));
        restore_env(previous);
        restore_env(outer_previous);
    }

    #[test]
    fn falls_back_to_dot_splashboard_under_home_dir_when_env_is_missing() {
        let _lock = TEST_ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
        let previous = std::env::var("SPLASHBOARD_HOME").ok();
        unsafe {
            std::env::remove_var("SPLASHBOARD_HOME");
        }

        let home = dirs::home_dir().expect("test host should provide a home directory");

        assert_eq!(splashboard_home(), Some(home.join(".splashboard")));
        restore_env(previous);
    }
}