pacman_helper/
lib.rs

1pub mod arch_package {
2
3    // Custom type for an arch package
4    #[derive(Default)]
5    pub struct Package {
6        pub name: String,
7        pub dependencies: Vec<String>,
8        pub opt_dependencies: Vec<String>,
9    }
10
11    impl Package {
12        pub fn new(database_content: String) -> Self {
13            let mut package = Package::default();
14
15            let mut lines_iter = database_content.lines();
16
17            while let Some(current_line) = lines_iter.next() {
18                let current_line = current_line.trim();
19
20                if current_line == "%NAME%" {
21                    if let Some(name) = lines_iter.next() {
22                        package.name.push_str(name);
23                    }
24                }
25
26                if current_line == "%DEPENDS%" {
27                    while let Some(dependency) = lines_iter.next() {
28                        let dependency = dependency.trim();
29
30                        if dependency.is_empty() {
31                            break;
32                        } else {
33                            package.dependencies.push(dependency.to_string());
34                        }
35                    }
36                }
37
38                if current_line == "%OPTDEPENDS%" {
39                    while let Some(dependency) = lines_iter.next() {
40                        let dependency = dependency.trim();
41
42                        if dependency.is_empty() {
43                            break;
44                        } else if let Some(colon_index) = dependency.find(':') {
45                            /* Getting rid of optional package description */
46                            let dependency = &dependency[..colon_index];
47                            package.opt_dependencies.push(dependency.to_string());
48                        } else {
49                            package.opt_dependencies.push(dependency.to_string());
50                        }
51                    }
52                }
53            }
54
55            package
56        }
57    }
58}
59
60// Module associated with reading the local package databases
61pub mod package_database_reader {
62    use super::arch_package::Package;
63    use std::error::Error;
64    use std::fs;
65    use std::path::Path;
66
67    // Method to read the packages, returning a Vector of packages with their fields populated
68    pub fn packages_reader(databases_path: &str) -> Result<Vec<Package>, Box<dyn Error>> {
69        let paths = fs::read_dir(databases_path)?;
70
71        let mut folders: Vec<String> = Vec::new();
72
73        for path in paths {
74            folders.push(format!(
75                "{}/{}",
76                path?.path().to_str().unwrap().to_owned(),
77                "desc"
78            ))
79        }
80
81        let mut packages: Vec<Package> = Vec::new();
82
83        for file in folders {
84            if Path::new(&file).exists() {
85                let content = fs::read_to_string(file)?;
86                let package = Package::new(content);
87                packages.push(package);
88            }
89        }
90
91        Ok(packages)
92    }
93}
94
95//Module associated with functions that get called when passing different command line options
96pub mod commandline_functions {
97    use super::arch_package::Package;
98    use crate::package_database_reader::packages_reader;
99    use std::collections::HashSet;
100    use std::process;
101
102    const DEFAULT_DATABASE_PATH: &str = "/var/lib/pacman/local/";
103
104    // A function to populate all the packages installed in the system
105    pub fn populate_packages() -> Vec<Package> {
106        let packages = match packages_reader(DEFAULT_DATABASE_PATH) {
107            Ok(packages) => packages,
108            Err(err) => {
109                eprintln!("Error: {}", err);
110                process::exit(1);
111            }
112        };
113        packages
114    }
115
116    // A function to get packages that share the same dependencies with the package name passed
117    pub fn get_packages_with_same_dependencies<'a>(
118        package_name: &str,
119        packages: &'a Vec<Package>,
120    ) -> Option<HashSet<&'a str>> {
121        let mut package_dependencies: &Vec<String> = &Vec::new();
122        let mut packages_with_same_dependencies: HashSet<&str> = HashSet::new();
123        let mut other_packages_found = false;
124
125        // Getting the package dependencies
126        for package in packages {
127            if package.name == package_name {
128                package_dependencies = &package.dependencies;
129                break;
130            }
131        }
132
133        // Getting the packages with similar dependencies
134        for package in packages {
135            if package.name == package_name {
136                continue;
137            }
138
139            for dep in &package.dependencies {
140                if package_dependencies.contains(dep) {
141                    packages_with_same_dependencies.insert(&package.name);
142                    other_packages_found = true;
143                }
144            }
145        }
146
147        if other_packages_found {
148            return Some(packages_with_same_dependencies);
149        }
150
151        None
152    }
153
154    // A function to get unique dependencies of a package
155    pub fn get_unique_dependencies(
156        package_name: &str,
157        packages: Vec<Package>,
158    ) -> Option<Vec<String>> {
159        let mut package_dependencies: &Vec<String> = &Vec::new();
160        let mut unique_dependencies_found = false;
161
162        // Getting the package dependencies
163        for package in &packages {
164            if package.name == package_name {
165                package_dependencies = &package.dependencies;
166                break;
167            }
168        }
169
170        let mut packages_dependencies_copy = package_dependencies.clone();
171
172        // Removing shared dependencies from the cloned dependencies vector
173        for package in &packages {
174            if package.name == package_name {
175                continue;
176            }
177
178            for dep in &package.dependencies {
179                if package_dependencies.contains(dep) {
180                    if let Some(pos) = packages_dependencies_copy.iter().position(|x| x == dep) {
181                        packages_dependencies_copy.remove(pos);
182                    }
183                    unique_dependencies_found = true;
184                }
185            }
186        }
187
188        if unique_dependencies_found && !packages_dependencies_copy.is_empty() {
189            return Some(packages_dependencies_copy);
190        }
191
192        None
193    }
194}
195
196#[cfg(test)]
197mod tests {
198    use std::collections::HashSet;
199
200    use super::arch_package::Package;
201    use super::commandline_functions::{
202        get_packages_with_same_dependencies, get_unique_dependencies,
203    };
204
205    /// A test for creating a package instance with it's fields
206    #[test]
207    fn package_creation_test() {
208        let package_database = "
209%NAME%
210linux-kernel
211
212%DEPENDS%
213coreutils
214kmod
215initramfs
216
217%OPTDEPENDS%
218wireless-regdb: to set the correct wireless channels of your country
219linux-firmware: firmware images needed for some devices
220";
221
222        let package = Package::new(package_database.to_string());
223
224        assert_eq!(package.name, "linux-kernel");
225        assert_eq!(package.dependencies, vec!["coreutils", "kmod", "initramfs"]);
226        assert_eq!(
227            package.opt_dependencies,
228            vec!["wireless-regdb", "linux-firmware"]
229        );
230    }
231
232    fn get_packages() -> Vec<Package> {
233        vec![
234            Package {
235                name: String::from("a"),
236                dependencies: vec![
237                    String::from("bottle"),
238                    String::from("cup"),
239                    String::from("plate"),
240                ],
241                opt_dependencies: vec![],
242            },
243            Package {
244                name: String::from("b"),
245                dependencies: vec![
246                    String::from("bulb"),
247                    String::from("wire"),
248                    String::from("plate"),
249                ],
250                opt_dependencies: vec![],
251            },
252            Package {
253                name: String::from("c"),
254                dependencies: vec![
255                    String::from("pluto"),
256                    String::from("mars"),
257                    String::from("cup"),
258                ],
259                opt_dependencies: vec![],
260            },
261        ]
262    }
263
264    /// A test for getting unique dependencies of a package out of others
265    #[test]
266    fn fetching_unique_dependencies_test() {
267        assert_eq!(
268            get_unique_dependencies("a", get_packages()),
269            Some(vec!["bottle".to_owned()])
270        );
271    }
272
273    /// A test for getting packages that share the same dependencies with the given package
274    #[test]
275    fn fetching_packages_with_same_deps_test() {
276        let mut packages_with_same_deps = HashSet::new();
277        packages_with_same_deps.insert("b");
278        packages_with_same_deps.insert("c");
279
280        assert_eq!(
281            get_packages_with_same_dependencies("a", &get_packages()),
282            Some(packages_with_same_deps)
283        )
284    }
285}