tar-install 0.2.0

Library core for safely installing Linux app tarballs into desktop-friendly locations.
Documentation
use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use std::env;
use std::path::PathBuf;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum InstallScope {
    User,
    System,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InstallTargets {
    pub scope: InstallScope,
    pub app_id: String,
    pub command_name: String,
    pub app_dir: PathBuf,
    pub command_path: PathBuf,
    pub desktop_path: PathBuf,
    pub icon_dir: PathBuf,
    pub state_path: PathBuf,
}

pub fn home_dir() -> Result<PathBuf> {
    env::var_os("HOME")
        .map(PathBuf::from)
        .context("HOME is not set; cannot resolve user install directories")
}

fn xdg_data_home() -> Result<PathBuf> {
    Ok(env::var_os("XDG_DATA_HOME")
        .map(PathBuf::from)
        .unwrap_or(home_dir()?.join(".local/share")))
}

fn xdg_state_home() -> Result<PathBuf> {
    Ok(env::var_os("XDG_STATE_HOME")
        .map(PathBuf::from)
        .unwrap_or(home_dir()?.join(".local/state")))
}

pub fn targets(scope: InstallScope, app_id: &str, command_name: &str) -> Result<InstallTargets> {
    let cleaned_id = crate::recipe::sanitize_id(app_id);
    let cleaned_cmd = crate::recipe::sanitize_command(command_name);

    let t = match scope {
        InstallScope::User => {
            let data = xdg_data_home()?;
            let state = xdg_state_home()?;
            InstallTargets {
                scope,
                app_id: cleaned_id.clone(),
                command_name: cleaned_cmd.clone(),
                app_dir: data.join("tarapp/apps").join(&cleaned_id),
                command_path: home_dir()?.join(".local/bin").join(&cleaned_cmd),
                desktop_path: data.join("applications").join(format!("{}.desktop", cleaned_id)),
                icon_dir: data.join("icons/hicolor/256x256/apps"),
                state_path: state.join("tarapp/apps.json"),
            }
        }
        InstallScope::System => InstallTargets {
            scope,
            app_id: cleaned_id.clone(),
            command_name: cleaned_cmd.clone(),
            app_dir: PathBuf::from("/opt").join(&cleaned_id),
            command_path: PathBuf::from("/usr/local/bin").join(&cleaned_cmd),
            desktop_path: PathBuf::from("/usr/share/applications").join(format!("{}.desktop", cleaned_id)),
            icon_dir: PathBuf::from("/usr/share/icons/hicolor/256x256/apps"),
            state_path: PathBuf::from("/var/lib/tarapp/apps.json"),
        },
    };

    Ok(t)
}