rust_utils/linux/
mod.rs

1//! Linux specific utilities
2
3use std::{
4    env, fs,
5    path::Path
6};
7use crate::{
8    chainable,
9    utils::run_command
10};
11use chrono::{Local, Datelike, Timelike};
12
13mod manpage;
14
15pub use manpage::ManpageBuilder;
16
17const MONTHS: [&str; 12] = [
18    "January",
19    "February",
20    "March",
21    "April",
22    "May",
23    "June",
24    "July",
25    "August",
26    "September",
27    "October",
28    "November",
29    "December"
30];
31
32/// Builder to create a basic `.desktop` file
33#[chainable]
34pub struct DesktopEntryFileBuilder {
35    name: String,
36    exec: String,
37    terminal: bool,
38    version: String,
39
40    #[chainable(collapse_option, use_into_impl, doc = "Add a comment to the desktop file")]
41    comment: Option<String>,
42
43    #[chainable(collapse_option, use_into_impl, doc = "Add an icon to the desktop file")]
44    icon: Option<String>,
45
46    categories: Vec<String>
47}
48
49impl DesktopEntryFileBuilder {
50    /// Create a new [`DesktopEntryFileBuilder`]
51    pub fn new(name: &str, exec: &str, terminal: bool, version: &str) -> Self {
52        DesktopEntryFileBuilder {
53            name: name.to_string(),
54            exec: exec.to_string(),
55            terminal,
56            version: version.to_string(),
57            comment: None,
58            icon: None,
59            categories: vec![]
60        }
61    }
62
63    /// Add a category to the desktop file
64    pub fn category(mut self, category: &str) -> Self {
65        self.categories.push(category.to_string());
66        self
67    }
68
69    /// Build and save the desktop file to the specified path
70    pub fn build<P: AsRef<Path>>(&self, path: P) {
71        let mut file_str = format!(
72            "[Desktop Entry]\n\
73            Version={}\n\
74            Type=Application\n\
75            Name={}\n\
76            Exec={}\n\
77            Terminal={}\n\
78            StartupNotify=false",
79            self.version, self.name, self.exec, self.terminal
80        );
81
82        if let Some(ref comment) = self.comment {
83            file_str.push_str(&format!("\nComment={comment}"));
84        }
85
86        if let Some(ref icon) = self.icon {
87            file_str.push_str(&format!("\nIcon={icon}"));
88        }
89
90        if self.categories.len() > 0 {
91            file_str.push_str("\nCategories=");
92            for category in &self.categories {
93                file_str.push_str(&format!("{category};"));
94            }
95        }
96
97        fs::remove_file(&path).unwrap_or(());
98        fs::write(&path, &file_str).unwrap();
99    }
100}
101
102/// Builder to create a basic [Arch Linux PKGBUILD](https://wiki.archlinux.org/title/PKGBUILD) for an app
103pub struct AurPkgbuildBuilder {
104    name: String,
105    version: String,
106    author_name: String,
107    author_email: String,
108    desc: String,
109    source_url: String,
110    url: String,
111    license: String,
112    deps: Vec<String>,
113    make_deps: Vec<String>,
114    build_bash: String,
115    pkg_bash: String
116}
117
118impl AurPkgbuildBuilder {
119    /// Create a new [`AurPkgbuildBuilder`]
120    pub fn new(
121        name: &str,
122        version: &str,
123        author_name: &str,
124        author_email: &str,
125        desc: &str,
126        source_url: &str,
127        url: &str,
128        license: &str,
129        build_bash: &str,
130        pkg_bash: &str
131    ) -> Self {
132        AurPkgbuildBuilder {
133            name: name.to_string(),
134            version: version.to_string(),
135            author_name: author_name.to_string(),
136            author_email: author_email.to_string(),
137            desc: desc.to_string(),
138            source_url: source_url.to_string(),
139            url: url.to_string(),
140            license: license.to_string(),
141            build_bash: build_bash.to_string(),
142            pkg_bash: pkg_bash.to_string(),
143            deps: vec![],
144            make_deps: vec![]
145        }
146    }
147
148    /// Add a dependency for the PKGBUILD
149    pub fn dependency(mut self, dep: &str) -> Self {
150        self.deps.push(dep.to_string());
151        self
152    }
153
154    /// Add make a dependency for the PKGBUILD
155    pub fn make_dependency(mut self, dep: &str) -> Self {
156        self.make_deps.push(dep.to_string());
157        self
158    }
159
160    /// Build and save the PKGBUILD
161    pub fn build<P: AsRef<Path>>(&self, dir: P) {
162        if env::set_current_dir(dir.as_ref()).is_err() { return; }
163
164        // get the current date and time (formatted)
165        let date = Local::now().date_naive();
166        let month = MONTHS[date.month() as usize - 1];
167        let day = date.day();
168        let year = date.year();
169        let time = Local::now().time();
170        let hour = time.hour();
171        let minute = time.minute();
172        let second = time.second();
173        let time_str = format!("{hour:02}:{minute:02}:{second:02}");
174        let mut deps_str = String::new();
175        let mut make_deps_str = String::new();        
176
177        for (i, dep) in self.deps.iter().enumerate() {
178            if i > 0 { deps_str.push(' '); }
179            deps_str.push_str(&format!("'{dep}'"));
180        }
181
182        for (i, dep) in self.make_deps.iter().enumerate() {
183            if i > 0 { make_deps_str.push(' '); }
184            make_deps_str.push_str(&format!("'{dep}'"));
185        }
186
187        let mut build_bash = String::new();
188        let mut pkg_bash = String::new();
189
190        for line in self.build_bash.lines() {
191            build_bash.push_str("    ");
192            build_bash.push_str(&line);
193            build_bash.push('\n');
194        }
195
196        for line in self.pkg_bash.lines() {
197            pkg_bash.push_str("    ");
198            pkg_bash.push_str(&line);
199            pkg_bash.push('\n');
200        }
201
202        let file_str = format!(
203            "# Maintainer: {} <{}>\n\
204            # Generated by cargo on {month} {day}, {year} at {time_str}\n\
205            pkgname={}\n\
206            pkgver={}\n\
207            pkgrel=1\n\
208            pkgdesc=\"{}\"\n\
209            arch=('i686' 'x86_64')\n\
210            url=\"{}\"\n\
211            license=('{}')\n\
212            depends=({deps_str})\n\
213            makedepends=({make_deps_str})\n\
214            source=(\"{}\")\n\
215            md5sums=('SKIP')\n\
216            \n\
217            build() {{\n{build_bash}}}\n\
218            \n\
219            package() {{\n{pkg_bash}}}",
220            self.author_name,
221            self.author_email,
222            self.name,
223            self.version,
224            self.desc,
225            self.url,
226            self.license,
227            self.source_url
228        );
229
230        fs::remove_file("PKGBUILD").unwrap_or(());
231        fs::remove_file(".SRCINFO").unwrap_or(());
232        fs::write("PKGBUILD", &file_str).unwrap();
233        let cmd = run_command("makepkg", false, ["--printsrcinfo"]);
234        fs::write(".SRCINFO", cmd.output).unwrap();
235    }
236}