cchain/commons/
packages.rs1use std::{collections::HashSet, env::consts::OS};
2use std::path::PathBuf;
3use anyhow::{anyhow, Error, Result};
4use which::which;
5
6use super::shell::execute_system_native_script;
7
8#[derive(Debug, Clone, PartialEq, Eq, Hash)]
10pub struct Package {
11 name: String,
12}
13
14impl Package {
15 pub fn new(name: String) -> Self {
16 Package { name }
17 }
18
19 pub fn access_package_name(&self) -> &str {
21 &self.name
22 }
23
24 pub fn get_available_packages() -> Result<HashSet<Package>, Error> {
25 let output: String = if cfg!(target_os = "windows") {
26 execute_system_native_script("where /Q *")?
28 } else {
29 execute_system_native_script("compgen -c")?
31 };
32
33 Ok(
34 output
35 .lines()
36 .map(|s| Package { name: s.to_string() })
37 .collect()
38 )
39 }
40}
41
42#[derive(Debug, Clone, PartialEq, Eq, Hash)]
44pub struct PackageManager {
45 name: String,
46 path: PathBuf,
47}
48
49impl PackageManager {
50 pub fn install_package(&self, package_name: &str) -> Result<(), Error> {
51 let command = match self.name.as_str() {
52 "brew" => format!("brew install {}", package_name),
53 "MacPorts" => format!("port install {}", package_name),
54 "apt" => format!("sudo apt-get install -y {}", package_name),
55 "snap" => format!("sudo snap install {}", package_name),
56 "yum" => format!("sudo yum install -y {}", package_name),
57 "dnf" => format!("sudo dnf install -y {}", package_name),
58 "pacman" => format!("sudo pacman -S --noconfirm {}", package_name),
59 "zypper" => format!("sudo zypper install -y {}", package_name),
60 "emerge" => format!("sudo emerge {}", package_name),
61 "choco" => format!("choco install -y {}", package_name),
62 "scoop" => format!("scoop install {}", package_name),
63 "winget" => format!("winget install --id {}", package_name),
64 _ => return Err(anyhow!("Unsupported package manager: {}", self.name)),
65 };
66
67 let status = std::process::Command::new("sh")
68 .arg("-c")
69 .arg(command)
70 .status()?;
71
72 if !status.success() {
73 return Err(anyhow!("Failed to install package: {}, error code: {}", package_name, status.code().unwrap()));
74 }
75
76 Ok(())
77 }
78
79 fn check_package_managers_availability<'a, T: Iterator<Item = &'a str>>(package_managers: T) -> Vec<PackageManager> {
80 let mut results: Vec<PackageManager> = Vec::new();
81 for package_manager in package_managers {
82 if let Ok(path_buf) = which(package_manager) {
83 results.push(
84 PackageManager {
85 name: package_manager.to_string(),
86 path: path_buf,
87 }
88 );
89 }
90 }
91
92 results
93 }
94
95 pub fn get_available_package_managers() -> Result<Vec<PackageManager>, Error> {
96 let os: &str = OS;
98 let mut package_managers: Vec<PackageManager> = Vec::new();
99
100 match os {
101 "macos" => {
102 let macos_package_managers: [&str; 2] = ["brew", "MacPorts"];
103 package_managers.extend(PackageManager::check_package_managers_availability(macos_package_managers.into_iter()));
104 }
105 "linux" => {
106 let pm_commands: [&str; 7] = ["apt", "snap", "yum", "dnf", "pacman", "zypper", "emerge"];
108 package_managers.extend(PackageManager::check_package_managers_availability(pm_commands.into_iter()));
109 }
110 "windows" => {
111 let pm_commands: [&str; 3] = ["choco", "scoop", "winget"];
113 package_managers.extend(PackageManager::check_package_managers_availability(pm_commands.into_iter()));
114 }
115 _ => {
116 return Err(anyhow!("Unsupported OS platform: {}. Please install a package manager, or file an issue on GitHub.", os));
117 }
118 }
119
120 if package_managers.is_empty() {
121 return Err(anyhow!("No package managers detected."));
122 }
123
124 Ok(package_managers)
125 }
126}
127
128pub trait AvailablePackageManager {
130 fn get_available_package_managers(&self) -> bool;
131}
132
133pub trait AvailablePackages {
135 fn get_required_packages(&self) -> Result<HashSet<Package>, Error>;
137
138 fn get_missing_packages(&self) -> Result<HashSet<Package>, Error> {
140 let required_packages: HashSet<Package> = self.get_required_packages()?;
141 let available_packages: HashSet<Package> = Package::get_available_packages()?;
142
143 Ok(
144 required_packages.into_iter()
145 .filter(|pkg| !available_packages.contains(pkg) && !PathBuf::from(pkg.access_package_name()).exists())
146 .collect()
147 )
148 }
149}