rrcm 0.4.3

Rust RC file Management commands
Documentation
use crate::path::expand_env_var;
use anyhow::ensure;
use anyhow::{bail, Ok, Result};
use indexmap::IndexMap;
use reqwest;
use serde::{Deserialize, Serialize};
use std::path::Path;
use std::path::PathBuf;
use url::Url;

#[derive(Debug, Serialize, Deserialize)]
pub struct OsPath {
    pub windows: Option<String>,
    pub mac: Option<String>,
    pub linux: Option<String>,
}
impl OsPath {
    pub fn to_pathbuf(&self) -> Result<PathBuf> {
        #[cfg(target_os = "windows")]
        {
            let Some(path) = &self.windows else {
                bail!("Windows Path not defined.");
            };
            Ok(PathBuf::from(expand_env_var(path)?))
        }

        #[cfg(target_os = "macos")]
        {
            let Some(path) = &self.mac else {
                bail!("Mac Path not defined.");
            };
            Ok(PathBuf::from(expand_env_var(path)?))
        }

        #[cfg(target_os = "linux")]
        {
            let Some(path) = &self.linux else {
                bail!("Linux Path not defined.");
            };
            Ok(PathBuf::from(expand_env_var(path)?))
        }
    }
}

#[derive(Debug, Serialize, Deserialize)]
pub struct Repository {
    pub name: String,
    pub url: String,
    pub deploy: IndexMap<String, OsPath>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct AppConfig {
    pub dotfiles: OsPath,
    pub repos: Vec<Repository>,
}

impl Default for AppConfig {
    fn default() -> Self {
        let dotfiles = OsPath {
            windows: Some("%USERPROFILE%\\dotfiles".to_string()),
            mac: Some("${HOME}/.dotfiles".to_string()),
            linux: Some("${HOME}/.dotfiles".to_string()),
        };

        let repos = Vec::new();
        Self { dotfiles, repos }
    }
}

impl AppConfig {
    pub fn to_pathbuf(&self) -> Result<PathBuf> {
        self.dotfiles.to_pathbuf()
    }
}

pub fn init_app_config<P>(path: P) -> Result<()>
where
    P: AsRef<Path>,
{
    let path = path.as_ref();
    ensure!(
        !path.exists(),
        format!("{} already exists.", path.display())
    );
    if let Some(parent) = path.parent() {
        if !parent.exists() {
            std::fs::create_dir_all(parent)?;
        }
    }
    let config = AppConfig::default();
    confy::store_path(path, &config)?;
    Ok(())
}

pub fn download_app_config<P>(path: P, url: Url) -> Result<()>
where
    P: AsRef<Path>,
{
    let path = path.as_ref();
    ensure!(
        !path.exists(),
        format!("{} already exists.", path.display())
    );
    let res = reqwest::blocking::get(url)?.error_for_status()?;
    if let Some(parent) = path.parent() {
        if !parent.exists() {
            std::fs::create_dir_all(parent)?;
        }
    }
    std::fs::write(path, res.bytes()?)?;
    Ok(())
}

pub fn load_app_config<P>(path: P) -> Result<AppConfig>
where
    P: AsRef<Path>,
{
    let path = path.as_ref();
    ensure!(path.exists(), format!("{} does not exist.", path.display()));
    Ok(confy::load_path(path)?)
}

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

    #[test]
    fn test_app_config() {
        let config = AppConfig::default();
        assert_eq!(
            config.dotfiles.windows,
            Some("%USERPROFILE%\\dotfiles".to_string())
        );
        assert_eq!(config.dotfiles.mac, Some("${HOME}/.dotfiles".to_string()));
        assert_eq!(config.dotfiles.linux, Some("${HOME}/.dotfiles".to_string()));
        assert_eq!(config.repos.len(), 0);
    }

    #[test]
    fn test_os_path() {
        let os_path = OsPath {
            windows: Some("%USERPROFILE%\\dotfiles".to_string()),
            mac: Some("${HOME}/.dotfiles".to_string()),
            linux: Some("${HOME}/.dotfiles".to_string()),
        };
        assert_eq!(
            os_path.to_pathbuf().unwrap(),
            if cfg!(target_os = "windows") {
                PathBuf::from(std::env::var("USERPROFILE").unwrap() + "\\dotfiles")
            } else {
                PathBuf::from(std::env::var("HOME").unwrap() + "/.dotfiles")
            }
        );
    }
}