Skip to main content

system_env/
system.rs

1use crate::deps::{DependencyConfig, SystemDependency};
2use crate::env::*;
3use crate::error::Error;
4use crate::pm::*;
5use crate::pm_vendor::*;
6
7/// Represents the current system, including architecture, operating system,
8/// and package manager information.
9#[derive(Debug)]
10pub struct System {
11    /// Platform architecture.
12    pub arch: SystemArch,
13
14    /// Package manager.
15    pub manager: Option<SystemPackageManager>,
16
17    /// Operating system.
18    pub os: SystemOS,
19}
20
21impl Default for System {
22    fn default() -> Self {
23        Self {
24            arch: SystemArch::from_env(),
25            manager: SystemPackageManager::detect().ok(),
26            os: SystemOS::from_env(),
27        }
28    }
29}
30
31impl System {
32    /// Create a new instance and detect system information.
33    pub fn new() -> Result<Self, Error> {
34        Ok(Self::with_manager(SystemPackageManager::detect()?))
35    }
36
37    /// Create a new instance with the provided package manager.
38    pub fn with_manager(manager: SystemPackageManager) -> Self {
39        Self {
40            arch: SystemArch::from_env(),
41            manager: Some(manager),
42            os: SystemOS::from_env(),
43        }
44    }
45
46    /// Return the command and arguments to "install a package" for the
47    /// current package manager. Will replace `$` in an argument with the
48    /// dependency name, derived from [`DependencyName`].
49    ///
50    /// The [`DependencyConfig`] must be filtered for the current operating
51    /// system and package manager beforehad.
52    pub fn get_install_package_command(
53        &self,
54        dep_config: &DependencyConfig,
55        interactive: bool,
56    ) -> Result<Option<Vec<String>>, Error> {
57        let Some(base_args) = self.extract_command(CommandType::InstallPackage, interactive)?
58        else {
59            return Ok(None);
60        };
61
62        let mut args = vec![];
63        let pm = self.manager.as_ref().unwrap();
64        let pm_config = pm.get_config();
65
66        for arg in base_args {
67            if arg == "$" {
68                args.extend(self.extract_package_args(dep_config, &pm_config, pm)?);
69            } else {
70                args.push(arg);
71            }
72        }
73
74        Ok(Some(args))
75    }
76
77    /// Return the command and arguments to "install many packages" for the
78    /// current package manager. Will replace `$` in an argument with the
79    /// dependency name, derived from [`DependencyName`].
80    ///
81    /// The [`DependencyConfig`]s must be filtered for the current operating
82    /// system and package manager beforehad.
83    pub fn get_install_packages_command(
84        &self,
85        dep_configs: &[DependencyConfig],
86        interactive: bool,
87    ) -> Result<Option<Vec<String>>, Error> {
88        let Some(base_args) = self.extract_command(CommandType::InstallPackage, interactive)?
89        else {
90            return Ok(None);
91        };
92
93        let mut args = vec![];
94        let pm = self.manager.as_ref().unwrap();
95        let pm_config = pm.get_config();
96
97        for arg in base_args {
98            if arg == "$" {
99                for dep_config in dep_configs {
100                    args.extend(self.extract_package_args(dep_config, &pm_config, pm)?);
101                }
102            } else {
103                args.push(arg);
104            }
105        }
106
107        Ok(Some(args))
108    }
109
110    /// Return the command and arguments to "list installed packages"
111    /// for the current package manager.
112    pub fn get_list_packages_command(
113        &self,
114        interactive: bool,
115    ) -> Result<Option<Vec<String>>, Error> {
116        self.extract_command(CommandType::ListPackages, interactive)
117    }
118
119    /// Return the command and arguments to "update the registry index"
120    /// for the current package manager.
121    pub fn get_update_index_command(
122        &self,
123        interactive: bool,
124    ) -> Result<Option<Vec<String>>, Error> {
125        self.extract_command(CommandType::UpdateIndex, interactive)
126    }
127
128    /// Resolve and reduce the dependencies to a list that's applicable
129    /// to the current system.
130    pub fn resolve_dependencies(&self, deps: &[SystemDependency]) -> Vec<DependencyConfig> {
131        let mut configs = vec![];
132
133        for dep in deps {
134            let config = dep.to_config();
135
136            if config.os.as_ref().is_some_and(|o| o != &self.os) {
137                continue;
138            }
139
140            if config.arch.as_ref().is_some_and(|a| a != &self.arch) {
141                continue;
142            }
143
144            if let Some(pm) = &self.manager {
145                if config.manager.as_ref().is_some_and(|m| m != pm) {
146                    continue;
147                }
148            } else if config.manager.is_some() {
149                continue;
150            }
151
152            configs.push(config);
153        }
154
155        configs
156    }
157
158    fn append_interactive(
159        &self,
160        command: CommandType,
161        config: &PackageManagerConfig,
162        args: &mut Vec<String>,
163        interactive: bool,
164    ) {
165        if config.prompt_for.contains(&command) {
166            match &config.prompt_arg {
167                PromptArgument::None => {}
168                PromptArgument::Interactive(i) => {
169                    if interactive {
170                        args.push(i.to_owned());
171                    }
172                }
173                PromptArgument::Skip(y) => {
174                    if !interactive {
175                        args.push(y.to_owned());
176                    }
177                }
178            };
179        }
180    }
181
182    fn extract_command(
183        &self,
184        command: CommandType,
185        interactive: bool,
186    ) -> Result<Option<Vec<String>>, Error> {
187        let Some(pm) = self.manager else {
188            return Err(Error::RequiredPackageManager);
189        };
190
191        let pm_config = pm.get_config();
192
193        if let Some(args) = pm_config.commands.get(&command) {
194            let mut args = args.to_owned();
195
196            self.append_interactive(command, &pm_config, &mut args, interactive);
197
198            return Ok(Some(args));
199        }
200
201        Ok(None)
202    }
203
204    fn extract_package_args(
205        &self,
206        dep_config: &DependencyConfig,
207        pm_config: &PackageManagerConfig,
208        pm: &SystemPackageManager,
209    ) -> Result<Vec<String>, Error> {
210        let mut args = vec![];
211
212        for dep in dep_config.get_package_names(pm)? {
213            if let Some(ver) = &dep_config.version {
214                match &pm_config.version_arg {
215                    VersionArgument::None => {
216                        args.push(dep);
217                    }
218                    VersionArgument::Inline(op) => {
219                        args.push(format!("{dep}{op}{ver}"));
220                    }
221                    VersionArgument::Separate(opt) => {
222                        args.push(dep);
223                        args.push(opt.to_owned());
224                        args.push(ver.to_owned());
225                    }
226                };
227            } else {
228                args.push(dep);
229            }
230        }
231
232        Ok(args)
233    }
234}