cchain/commons/
packages.rs

1use 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/// Represents a package
9#[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    // Get a reference to the package name
20    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            // Windows system: use 'where' command to list available commands
27            execute_system_native_script("where /Q *")?
28        } else {
29            // Unix system: use 'compgen -c' to list available commands
30            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/// Represents a package manager
43#[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        // Get the current operating system
97        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                // List of common Linux package managers (name, command)
107                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                // Windows package managers
112                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
128/// Impl this trait to verify if the package manager is available.
129pub trait AvailablePackageManager {
130    fn get_available_package_managers(&self) -> bool;
131}
132
133/// Impl this trait to verify if the packages are available
134pub trait AvailablePackages {
135    /// Get required packages
136    fn get_required_packages(&self) -> Result<HashSet<Package>, Error>;
137    
138    /// Return missed packages
139    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}