rust-utils 0.16.0

Various utility routines used in the rust programs I have written
Documentation
use std::{
    fs,
    path::Path,
    collections::HashMap
};
use crate::chainable;
use chrono::{Local, Datelike, Timelike};
use super::MONTHS;

/// Basic unix manpage generator
#[chainable]
pub struct ManpageBuilder {
    name: String,
    short_desc: String,
    desc: String,
    author_name: String,
    author_email: String,

    #[chainable(collapse_option, use_into_impl, doc = "Add in a comment next to the name in the file comment")]
    name_comment: Option<String>,
    other_pages: Vec<String>,
    other_sections: HashMap<String, String>
}

impl ManpageBuilder {
    /// Create a new `ManpageBuilder`
    pub fn new(
        name: &str,
        short_desc: &str,
        author_name: &str,
        author_email: &str,
        desc: &str
    ) -> Self {
        ManpageBuilder {
            name: name.to_string(),
            short_desc: short_desc.to_string(),
            author_name: author_name.to_string(),
            author_email: author_email.to_string(),
            desc: desc.replace("\n", "\n.br\n"),
            name_comment: None,
            other_pages: vec![],
            other_sections: HashMap::new()
        }
    }

    /// Add a link to a page in the "SEE ALSO" section
    pub fn other_page(mut self, name: &str) -> Self {
        self.other_pages.push(name.to_string());
        self
    }

    /// Add in a section
    ///
    /// The name should be in all capital letters
    pub fn section(mut self, name: &str, text: &str) -> Self {
        self.other_sections.insert(name.to_string(), text.replace("\n", "\n.br\n"));
        self
    }

    /// Generate and save the manpage at the specified path
    pub fn build<P: AsRef<Path>>(&self, app_ver: &str, path: P) {
        // 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 date_str = format!("{day} {month} {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 syn_str = if let Some(synopsis) = self.other_sections.get("SYNOPSIS") {
            format!(
                ".SH SYNOPSIS\n\
                {synopsis}\n"
            )
        }
        else { String::new() };

        let examples_str = if let Some(examples) = self.other_sections.get("EXAMPLES") {
            format!(
                ".SH EXAMPLES\n\
                {examples}\n"
            )
        }
        else { String::new() };

        let name_comment = if let Some(ref comment) = self.name_comment {
                format!(" ({comment})")
            }
            else { String::new() };

        let header_str = format!(
            ".\\\" Manpage for {name}{name_comment}.\n\
            .\\\" Created on {month} {day}, {year} at {time_str}\n\
            .TH {name} 1 \"{date_str}\" \"{app_ver}\" \"{name} man page\"\n\
            .SH NAME\n\
            {name} \\- {}\n\
            {syn_str}\
            .SH DESCRIPTION\n\
            {}\n\
            {examples_str}",
            self.short_desc,
            self.desc,
            name = self.name,
        );

        let author_str = format!(
            ".SH AUTHOR\n\
            {} ({})",
            self.author_name,
            self.author_email
        );

        let footer_str = if self.other_pages.len() > 0 {
            let mut sect_header = String::from(".SH SEE ALSO\n");
            for (i, other_page) in self.other_pages.iter().enumerate() {
                sect_header.push_str(&format!("{other_page}(1)\n"));

                if i < self.other_pages.len() - 1{
                    sect_header.push_str(".br\n");
                }
            }

            format!("{sect_header}{author_str}")
        }
        else { author_str };

        let mut body_str = String::new();

        for (sect_title, sect_body) in &self.other_sections {
            if sect_title != "SYNOPSIS" && sect_title != "EXAMPLES" {
                body_str.push_str(&format!(
                    ".SH {sect_title}\n\
                    {sect_body}\n"
                ));
            }
        }

        let file_str = format!("{header_str}{body_str}{footer_str}");
        fs::remove_file(&path).unwrap_or(());
        fs::write(&path, &file_str).unwrap();
    }
}