tar-install 0.2.0

Library core for safely installing Linux app tarballs into desktop-friendly locations.
Documentation
use anyhow::{Context, Result};
use std::fs;
use std::path::Path;

#[derive(Debug, Clone)]
pub struct DesktopEntryInput<'a> {
    pub name: &'a str,
    pub generic_name: Option<&'a str>,
    pub comment: Option<&'a str>,
    pub exec_path: &'a Path,
    pub icon_name: &'a str,
    pub categories: &'a [String],
    pub terminal: bool,
}

pub fn write_desktop_entry(path: &Path, input: &DesktopEntryInput<'_>) -> Result<()> {
    if let Some(parent) = path.parent() {
        fs::create_dir_all(parent).with_context(|| format!("failed to create desktop dir: {}", parent.display()))?;
    }

    let categories = if input.categories.is_empty() {
        "Utility;".to_string()
    } else {
        let mut joined = input.categories.join(";");
        if !joined.ends_with(';') { joined.push(';'); }
        joined
    };

    let mut text = String::new();
    text.push_str("[Desktop Entry]\n");
    text.push_str("Type=Application\n");
    text.push_str(&format!("Name={}\n", escape_value(input.name)));
    if let Some(generic) = input.generic_name {
        text.push_str(&format!("GenericName={}\n", escape_value(generic)));
    }
    if let Some(comment) = input.comment {
        text.push_str(&format!("Comment={}\n", escape_value(comment)));
    }
    text.push_str(&format!("Exec={} %U\n", quote_exec(input.exec_path)));
    text.push_str(&format!("Icon={}\n", escape_value(input.icon_name)));
    text.push_str(&format!("Terminal={}\n", if input.terminal { "true" } else { "false" }));
    text.push_str(&format!("Categories={}\n", categories));
    text.push_str("StartupNotify=true\n");

    fs::write(path, text).with_context(|| format!("failed to write desktop entry: {}", path.display()))?;
    Ok(())
}

fn escape_value(value: &str) -> String {
    value.replace('\\', "\\\\").replace('\n', "\\n")
}

fn quote_exec(path: &Path) -> String {
    let s = path.to_string_lossy();
    if s.contains(' ') {
        format!("\"{}\"", s.replace('"', "\\\""))
    } else {
        s.to_string()
    }
}