use std::path::{Path, PathBuf};
pub const CONFIG_FILE_NAME: &str = "config.toml";
pub const APP_DIR_NAME: &str = "all-smi";
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))
}
pub fn candidate_config_paths() -> Vec<PathBuf> {
let mut out = Vec::new();
if let Some(primary) = default_config_path() {
out.push(primary);
}
#[cfg(target_os = "macos")]
{
if let Some(home) = dirs::home_dir() {
let xdg_like = home
.join(".config")
.join(APP_DIR_NAME)
.join(CONFIG_FILE_NAME);
if !out.iter().any(|p| p == &xdg_like) {
out.push(xdg_like);
}
}
}
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 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"));
}
}