use std::path::{Path, PathBuf};
pub const CONFIG_FILE_NAME: &str = "config.toml";
pub const APP_DIR_NAME: &str = "all-smi";
#[cfg(target_os = "linux")]
pub const LINUX_SYSTEM_CONFIG_PATH: &str = "/etc/all-smi/config.toml";
#[cfg(target_os = "macos")]
pub const MACOS_SYSTEM_CONFIG_PATH: &str = "/Library/Application Support/all-smi/config.toml";
#[cfg(any(windows, test))]
pub fn program_data_app_dir(program_data_root: &Path) -> PathBuf {
program_data_root.join(APP_DIR_NAME)
}
#[cfg(windows)]
pub const PROGRAM_DATA_ENV: &str = "ProgramData";
#[cfg(windows)]
pub const PROGRAM_DATA_FALLBACK: &str = r"C:\ProgramData";
#[cfg(windows)]
pub fn program_data_root() -> PathBuf {
match std::env::var_os(PROGRAM_DATA_ENV) {
Some(v) if !v.is_empty() => PathBuf::from(v),
_ => PathBuf::from(PROGRAM_DATA_FALLBACK),
}
}
#[cfg(windows)]
pub fn windows_system_config_path() -> PathBuf {
program_data_app_dir(&program_data_root()).join(CONFIG_FILE_NAME)
}
pub fn expand_tilde(input: impl AsRef<Path>) -> PathBuf {
let path = input.as_ref();
let Some(s) = path.to_str() else {
return path.to_path_buf();
};
if let Some(rest) = s.strip_prefix("~/") {
if let Some(home) = dirs::home_dir() {
return home.join(rest);
}
return path.to_path_buf();
}
if s == "~" {
if let Some(home) = dirs::home_dir() {
return home;
}
return path.to_path_buf();
}
path.to_path_buf()
}
pub fn config_dir() -> Option<PathBuf> {
dirs::config_dir().map(|d| d.join(APP_DIR_NAME))
}
pub fn cache_dir() -> Option<PathBuf> {
dirs::cache_dir().map(|d| d.join(APP_DIR_NAME))
}
pub fn default_config_path() -> Option<PathBuf> {
config_dir().map(|d| d.join(CONFIG_FILE_NAME))
}
fn push_unique(out: &mut Vec<PathBuf>, path: PathBuf) {
if !out.iter().any(|p| p == &path) {
out.push(path);
}
}
pub fn candidate_config_paths() -> Vec<PathBuf> {
let mut out = Vec::new();
if let Some(primary) = default_config_path() {
push_unique(&mut out, primary);
}
#[cfg(target_os = "macos")]
{
if let Some(home) = dirs::home_dir() {
push_unique(
&mut out,
home.join(".config")
.join(APP_DIR_NAME)
.join(CONFIG_FILE_NAME),
);
}
}
#[cfg(target_os = "linux")]
{
push_unique(&mut out, PathBuf::from(LINUX_SYSTEM_CONFIG_PATH));
}
#[cfg(target_os = "macos")]
{
push_unique(&mut out, PathBuf::from(MACOS_SYSTEM_CONFIG_PATH));
}
#[cfg(windows)]
{
push_unique(&mut out, windows_system_config_path());
}
out
}
pub fn discover_existing_config() -> Option<PathBuf> {
candidate_config_paths().into_iter().find(|p| p.exists())
}
pub fn active_config_path() -> Option<PathBuf> {
discover_existing_config().or_else(default_config_path)
}
pub fn format_path_with_existence(path: Option<&Path>) -> String {
match path {
Some(p) => {
let marker = if p.exists() { "active" } else { "not found" };
format!("{} ({marker})", p.display())
}
None => "(no config path resolvable — set $HOME or $XDG_CONFIG_HOME)".to_string(),
}
}
pub fn ensure_parent_dir(path: &Path) -> std::io::Result<()> {
if let Some(parent) = path.parent()
&& !parent.as_os_str().is_empty()
{
std::fs::create_dir_all(parent)?;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn expand_tilde_noop_without_prefix() {
let p = expand_tilde(Path::new("/etc/passwd"));
assert_eq!(p, PathBuf::from("/etc/passwd"));
}
#[test]
fn expand_tilde_replaces_home_marker() {
if let Some(home) = dirs::home_dir() {
let p = expand_tilde(Path::new("~/foo/bar"));
assert_eq!(p, home.join("foo/bar"));
}
}
#[test]
fn expand_tilde_bare_tilde() {
if let Some(home) = dirs::home_dir() {
let p = expand_tilde(Path::new("~"));
assert_eq!(p, home);
}
}
#[test]
fn expand_tilde_passthrough_for_relative() {
let p = expand_tilde(Path::new("relative/path"));
assert_eq!(p, PathBuf::from("relative/path"));
}
#[test]
fn config_dir_ends_with_app_name() {
if let Some(dir) = config_dir() {
assert!(dir.ends_with(APP_DIR_NAME));
}
}
#[test]
fn cache_dir_ends_with_app_name() {
if let Some(dir) = cache_dir() {
assert!(dir.ends_with(APP_DIR_NAME));
}
}
#[test]
fn default_config_path_ends_with_file_name() {
if let Some(path) = default_config_path() {
assert!(path.ends_with(CONFIG_FILE_NAME));
}
}
#[test]
fn candidate_config_paths_nonempty_when_home_available() {
if dirs::home_dir().is_some() {
let paths = candidate_config_paths();
assert!(!paths.is_empty());
}
}
#[test]
fn push_unique_drops_repeats_and_preserves_order() {
let mut out = Vec::new();
push_unique(&mut out, PathBuf::from("/a"));
push_unique(&mut out, PathBuf::from("/b"));
push_unique(&mut out, PathBuf::from("/a"));
assert_eq!(out, vec![PathBuf::from("/a"), PathBuf::from("/b")]);
}
#[test]
fn candidate_config_paths_has_no_duplicates() {
let paths = candidate_config_paths();
let mut seen = paths.clone();
seen.sort();
seen.dedup();
assert_eq!(seen.len(), paths.len(), "duplicate candidates: {paths:?}");
}
#[cfg(target_os = "linux")]
#[test]
fn linux_candidates_include_the_system_wide_path() {
let paths = candidate_config_paths();
let system = PathBuf::from(LINUX_SYSTEM_CONFIG_PATH);
assert!(
paths.contains(&system),
"/etc/all-smi/config.toml must be a discovery candidate on Linux: {paths:?}"
);
}
#[cfg(target_os = "linux")]
#[test]
fn linux_system_candidate_is_ordered_after_the_user_candidate() {
let paths = candidate_config_paths();
let system = PathBuf::from(LINUX_SYSTEM_CONFIG_PATH);
let system_index = paths
.iter()
.position(|p| p == &system)
.expect("system candidate must be present");
if let Some(user) = default_config_path() {
let user_index = paths
.iter()
.position(|p| p == &user)
.expect("user candidate must be present");
assert!(
user_index < system_index,
"the per-user candidate must be probed first: {paths:?}"
);
}
}
#[cfg(target_os = "linux")]
#[test]
fn default_config_path_is_never_the_system_wide_path() {
if let Some(p) = default_config_path() {
assert_ne!(p, PathBuf::from(LINUX_SYSTEM_CONFIG_PATH));
}
}
#[cfg(target_os = "macos")]
#[test]
fn macos_candidates_include_the_system_wide_path() {
let paths = candidate_config_paths();
let system = PathBuf::from(MACOS_SYSTEM_CONFIG_PATH);
assert!(
paths.contains(&system),
"/Library/Application Support/all-smi/config.toml must be a discovery candidate on \
macOS: {paths:?}"
);
}
#[cfg(target_os = "macos")]
#[test]
fn macos_system_candidate_is_ordered_last() {
let paths = candidate_config_paths();
let system = PathBuf::from(MACOS_SYSTEM_CONFIG_PATH);
assert_eq!(
paths.iter().position(|p| p == &system),
Some(paths.len() - 1),
"the machine-wide candidate must be probed after every per-user one: {paths:?}"
);
}
#[cfg(target_os = "macos")]
#[test]
fn macos_default_config_path_is_never_the_system_wide_path() {
if let Some(p) = default_config_path() {
assert_ne!(p, PathBuf::from(MACOS_SYSTEM_CONFIG_PATH));
}
}
#[test]
fn program_data_app_dir_appends_the_app_directory() {
let dir = program_data_app_dir(Path::new(r"C:\ProgramData"));
assert!(dir.ends_with(APP_DIR_NAME), "got {}", dir.display());
assert_eq!(
dir.join(CONFIG_FILE_NAME).file_name(),
Some(std::ffi::OsStr::new(CONFIG_FILE_NAME))
);
}
#[cfg(windows)]
#[test]
fn windows_candidates_include_the_program_data_path() {
let paths = candidate_config_paths();
assert!(
paths.contains(&windows_system_config_path()),
"%PROGRAMDATA%\\all-smi\\config.toml must be a discovery candidate: {paths:?}"
);
}
#[cfg(windows)]
#[test]
fn windows_program_data_candidate_is_ordered_after_the_user_candidate() {
let paths = candidate_config_paths();
let system = windows_system_config_path();
let system_index = paths
.iter()
.position(|p| p == &system)
.expect("system candidate must be present");
if let Some(user) = default_config_path() {
let user_index = paths
.iter()
.position(|p| p == &user)
.expect("user candidate must be present");
assert!(
user_index < system_index,
"the per-user candidate must be probed first: {paths:?}"
);
}
}
#[cfg(windows)]
#[test]
fn windows_default_config_path_is_never_the_program_data_path() {
if let Some(p) = default_config_path() {
assert_ne!(p, windows_system_config_path());
}
}
#[test]
fn active_config_path_matches_loader_resolution() {
let expected = discover_existing_config().or_else(default_config_path);
assert_eq!(active_config_path(), expected);
}
#[test]
fn format_path_with_existence_marks_existing_file() {
let dir = tempfile::tempdir().unwrap();
let file = dir.path().join("config.toml");
std::fs::write(&file, b"# stub").unwrap();
let rendered = format_path_with_existence(Some(&file));
assert!(
rendered.contains("(active)"),
"expected (active) marker, got: {rendered}"
);
assert!(rendered.contains(&file.display().to_string()));
}
#[test]
fn format_path_with_existence_marks_missing_file() {
let dir = tempfile::tempdir().unwrap();
let absent = dir.path().join("nope.toml");
let rendered = format_path_with_existence(Some(&absent));
assert!(
rendered.contains("(not found)"),
"expected (not found) marker, got: {rendered}"
);
}
#[test]
fn format_path_with_existence_handles_none() {
let rendered = format_path_with_existence(None);
assert!(rendered.contains("no config path"));
assert!(rendered.contains("HOME"));
}
}