rust-utils 0.16.0

Various utility routines used in the rust programs I have written
Documentation
//! Linux specific utilities

use std::{
    env, fs,
    path::Path
};
use crate::{
    chainable,
    utils::run_command
};
use chrono::{Local, Datelike, Timelike};

mod manpage;

pub use manpage::ManpageBuilder;

const MONTHS: [&str; 12] = [
    "January",
    "February",
    "March",
    "April",
    "May",
    "June",
    "July",
    "August",
    "September",
    "October",
    "November",
    "December"
];

/// Builder to create a basic `.desktop` file
#[chainable]
pub struct DesktopEntryFileBuilder {
    name: String,
    exec: String,
    terminal: bool,
    version: String,

    #[chainable(collapse_option, use_into_impl, doc = "Add a comment to the desktop file")]
    comment: Option<String>,

    #[chainable(collapse_option, use_into_impl, doc = "Add an icon to the desktop file")]
    icon: Option<String>,

    categories: Vec<String>
}

impl DesktopEntryFileBuilder {
    /// Create a new [`DesktopEntryFileBuilder`]
    pub fn new(name: &str, exec: &str, terminal: bool, version: &str) -> Self {
        DesktopEntryFileBuilder {
            name: name.to_string(),
            exec: exec.to_string(),
            terminal,
            version: version.to_string(),
            comment: None,
            icon: None,
            categories: vec![]
        }
    }

    /// Add a category to the desktop file
    pub fn category(mut self, category: &str) -> Self {
        self.categories.push(category.to_string());
        self
    }

    /// Build and save the desktop file to the specified path
    pub fn build<P: AsRef<Path>>(&self, path: P) {
        let mut file_str = format!(
            "[Desktop Entry]\n\
            Version={}\n\
            Type=Application\n\
            Name={}\n\
            Exec={}\n\
            Terminal={}\n\
            StartupNotify=false",
            self.version, self.name, self.exec, self.terminal
        );

        if let Some(ref comment) = self.comment {
            file_str.push_str(&format!("\nComment={comment}"));
        }

        if let Some(ref icon) = self.icon {
            file_str.push_str(&format!("\nIcon={icon}"));
        }

        if self.categories.len() > 0 {
            file_str.push_str("\nCategories=");
            for category in &self.categories {
                file_str.push_str(&format!("{category};"));
            }
        }

        fs::remove_file(&path).unwrap_or(());
        fs::write(&path, &file_str).unwrap();
    }
}

/// Builder to create a basic [Arch Linux PKGBUILD](https://wiki.archlinux.org/title/PKGBUILD) for an app
pub struct AurPkgbuildBuilder {
    name: String,
    version: String,
    author_name: String,
    author_email: String,
    desc: String,
    source_url: String,
    url: String,
    license: String,
    deps: Vec<String>,
    make_deps: Vec<String>,
    build_bash: String,
    pkg_bash: String
}

impl AurPkgbuildBuilder {
    /// Create a new [`AurPkgbuildBuilder`]
    pub fn new(
        name: &str,
        version: &str,
        author_name: &str,
        author_email: &str,
        desc: &str,
        source_url: &str,
        url: &str,
        license: &str,
        build_bash: &str,
        pkg_bash: &str
    ) -> Self {
        AurPkgbuildBuilder {
            name: name.to_string(),
            version: version.to_string(),
            author_name: author_name.to_string(),
            author_email: author_email.to_string(),
            desc: desc.to_string(),
            source_url: source_url.to_string(),
            url: url.to_string(),
            license: license.to_string(),
            build_bash: build_bash.to_string(),
            pkg_bash: pkg_bash.to_string(),
            deps: vec![],
            make_deps: vec![]
        }
    }

    /// Add a dependency for the PKGBUILD
    pub fn dependency(mut self, dep: &str) -> Self {
        self.deps.push(dep.to_string());
        self
    }

    /// Add make a dependency for the PKGBUILD
    pub fn make_dependency(mut self, dep: &str) -> Self {
        self.make_deps.push(dep.to_string());
        self
    }

    /// Build and save the PKGBUILD
    pub fn build<P: AsRef<Path>>(&self, dir: P) {
        if env::set_current_dir(dir.as_ref()).is_err() { return; }

        // get the current date and time (formatted)
        let date = Local::now().date_naive();
        let month = MONTHS[date.month() as usize - 1];
        let day = date.day();
        let year = date.year();
        let time = Local::now().time();
        let hour = time.hour();
        let minute = time.minute();
        let second = time.second();
        let time_str = format!("{hour:02}:{minute:02}:{second:02}");
        let mut deps_str = String::new();
        let mut make_deps_str = String::new();        

        for (i, dep) in self.deps.iter().enumerate() {
            if i > 0 { deps_str.push(' '); }
            deps_str.push_str(&format!("'{dep}'"));
        }

        for (i, dep) in self.make_deps.iter().enumerate() {
            if i > 0 { make_deps_str.push(' '); }
            make_deps_str.push_str(&format!("'{dep}'"));
        }

        let mut build_bash = String::new();
        let mut pkg_bash = String::new();

        for line in self.build_bash.lines() {
            build_bash.push_str("    ");
            build_bash.push_str(&line);
            build_bash.push('\n');
        }

        for line in self.pkg_bash.lines() {
            pkg_bash.push_str("    ");
            pkg_bash.push_str(&line);
            pkg_bash.push('\n');
        }

        let file_str = format!(
            "# Maintainer: {} <{}>\n\
            # Generated by cargo on {month} {day}, {year} at {time_str}\n\
            pkgname={}\n\
            pkgver={}\n\
            pkgrel=1\n\
            pkgdesc=\"{}\"\n\
            arch=('i686' 'x86_64')\n\
            url=\"{}\"\n\
            license=('{}')\n\
            depends=({deps_str})\n\
            makedepends=({make_deps_str})\n\
            source=(\"{}\")\n\
            md5sums=('SKIP')\n\
            \n\
            build() {{\n{build_bash}}}\n\
            \n\
            package() {{\n{pkg_bash}}}",
            self.author_name,
            self.author_email,
            self.name,
            self.version,
            self.desc,
            self.url,
            self.license,
            self.source_url
        );

        fs::remove_file("PKGBUILD").unwrap_or(());
        fs::remove_file(".SRCINFO").unwrap_or(());
        fs::write("PKGBUILD", &file_str).unwrap();
        let cmd = run_command("makepkg", false, ["--printsrcinfo"]);
        fs::write(".SRCINFO", cmd.output).unwrap();
    }
}