1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
use std::{
    fs,
    path::PathBuf,
    collections::HashMap
};
use chrono::{Local, Datelike, Timelike};
use super::MONTHS;

/// Basic unix manpage generator
pub struct ManpageBuilder {
    name: String,
    short_desc: String,
    desc: String,
    author_name: String,
    author_email: String,
    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 in a comment next to the name in the file comment
    pub fn name_comment(mut self, comment: &str) -> Self {
        self.name_comment = Some(comment.to_string());
        self
    }

    /// 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: Into<PathBuf>>(&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::write(path.into(), &file_str).unwrap();
    }
}