fold-rs 0.1.0

Minimal, dependency-free resolver for standard base directories
Documentation
use std::path::{Path, PathBuf};

use crate::dirs::CONFIG;

#[derive(PartialEq, Eq, Debug)]
pub enum UserDir {
    Desktop,
    Download,
    Documents,
    Music,
    Pictures,
    Videos,
    Templates,
    Public,
    Projects,
}

impl UserDir {
    #[cfg(target_os = "linux")]
    fn key(&self) -> &'static str {
        match self {
            UserDir::Desktop => "XDG_DESKTOP_DIR",
            UserDir::Download => "XDG_DOWNLOAD_DIR",
            UserDir::Documents => "XDG_DOCUMENTS_DIR",
            UserDir::Music => "XDG_MUSIC_DIR",
            UserDir::Pictures => "XDG_PICTURES_DIR",
            UserDir::Videos => "XDG_VIDEOS_DIR",
            UserDir::Templates => "XDG_TEMPLATES_DIR",
            UserDir::Public => "XDG_PUBLICSHARE_DIR",
            UserDir::Projects => "XDG_PROJECTS_DIR",
        }
    }

    #[cfg(target_os = "macos")]
    fn mac_rel(&self) -> Option<&'static str> {
        match self {
            UserDir::Desktop => Some("Desktop"),
            UserDir::Download => Some("Downloads"),
            UserDir::Documents => Some("Documents"),
            UserDir::Music => Some("Music"),
            UserDir::Pictures => Some("Pictures"),
            UserDir::Videos => Some("Movies"),
            UserDir::Templates => None,
            UserDir::Public => None,
            UserDir::Projects => None,
        }
    }

    #[cfg(target_os = "linux")]
    fn linux_rel(&self) -> Option<&'static str> {
        match self {
            UserDir::Desktop => Some("Desktop"),
            UserDir::Download => Some("Downloads"),
            UserDir::Documents => Some("Documents"),
            UserDir::Music => Some("Music"),
            UserDir::Pictures => Some("Pictures"),
            UserDir::Videos => Some("Videos"),
            UserDir::Templates => Some("Templates"),
            UserDir::Public => Some("Public"),
            UserDir::Projects => Some("Projects"),
        }
    }
    fn resolve(&self) -> Option<PathBuf> {
        #[cfg(target_os = "linux")]
        {
            parse_user_dirs(self.key(), self.linux_rel())
        }

        #[cfg(target_os = "macos")]
        {
            Some(crate::home().ok()?.join(self.mac_rel()?))
        }

        #[cfg(not(any(target_os = "linux", target_os = "macos")))]
        {
            None
        }
    }
}

#[cfg(target_os = "linux")]
fn parse_user_dirs(key: &str, default: Option<&str>) -> Option<PathBuf> {
    let home = super::home().ok()?;
    let file = CONFIG.resolve()?.join("user-dirs.dirs");

    if let Ok(contents) = std::fs::read_to_string(&file) {
        for line in contents.lines() {
            let line = line.trim();
            if line.is_empty() || line.starts_with('#') {
                continue;
            }

            let Some((name, value)) = line.split_once('=') else {
                continue;
            };

            if name.trim() != key {
                continue;
            }

            let value = value.trim().trim_matches('"');

            let path = if let Some(rest) = value.strip_prefix("$HOME") {
                let rest = rest.trim_start_matches('/');
                if rest.is_empty() {
                    return None; // value == $HOME means the dir is DISABLED
                }
                home.join(rest)
            } else if Path::new(value).is_absolute() {
                PathBuf::from(value)
            } else {
                continue;
            };
            return Some(path);
        }
    }
    Some(home.join(default?))
}

impl From<UserDir> for Option<PathBuf> {
    fn from(value: UserDir) -> Self {
        value.resolve()
    }
}