1use anyhow::{Result, anyhow};
8use colored::Colorize;
9use dialoguer::Select;
10use dialoguer::theme::ColorfulTheme;
11pub use zoi_core::dependency::Dependency;
12use zoi_core::{types, utils};
13
14include!(concat!(env!("OUT_DIR"), "/generated_managers.rs"));
15
16pub fn parse_dependency_string(dep_str: &str) -> Result<Dependency<'_>> {
26 zoi_core::dependency::parse_dependency_string(dep_str, |m| {
27 m == "zoi" || m == "native" || MANAGERS.contains_key(m)
28 })
29}
30
31type ZoiUninstaller = dyn Fn(&str) -> Result<()>;
33
34pub fn uninstall_dependency(
46 dep_str: &str,
47 zoi_uninstaller: &ZoiUninstaller
48) -> Result<()> {
49 let dep = parse_dependency_string(dep_str)?;
50 println!(
51 "-> Attempting to uninstall dependency: {} via {}",
52 dep.package.cyan(),
53 dep.manager.yellow()
54 );
55
56 if dep.manager == "zoi" {
57 return zoi_uninstaller(dep.package);
58 }
59
60 if let Some(pm_commands) = MANAGERS.get(dep.manager) {
61 let mut uninstall_cmd =
62 pm_commands.uninstall.replace("{package}", dep.package);
63
64 if pm_commands.sudo_uninstall && !utils::is_admin() {
65 if let Some(escalator) = utils::get_privilege_escalator() {
66 uninstall_cmd = format!("{escalator} {uninstall_cmd}");
67 } else {
68 eprintln!(
69 "{}: root privileges are required for '{}' but neither \
70 'sudo' nor 'doas' was found. Attempting to run without \
71 escalation...",
72 "Warning".yellow(),
73 dep.manager
74 );
75 }
76 }
77
78 println!("Running uninstall command: {}", uninstall_cmd.italic());
79 utils::run_shell_command(&uninstall_cmd)
80 } else {
81 Err(anyhow!(
82 "Unknown or unsupported package manager for uninstall: {}",
83 dep.manager
84 ))
85 }
86}
87
88pub fn collect_dependencies_for_group(
104 group: &types::DependencyGroup,
105 sub_package_name: Option<&str>,
106 dep_type: Option<&str>,
107 yes: bool,
108 all_optional: bool
109) -> Result<(Vec<String>, Vec<String>, Vec<String>)> {
110 let mut deps = Vec::new();
111 let mut chosen_options = Vec::new();
112 let mut chosen_optionals = Vec::new();
113
114 match group {
115 types::DependencyGroup::Simple(d) => {
116 deps.extend(d.clone());
117 }
118 types::DependencyGroup::Complex(g) => {
119 deps.extend(g.required.clone());
120
121 let options = prompt_for_options(&g.options, yes)?;
122 chosen_options.extend(options.clone());
123 deps.extend(options);
124
125 let optionals =
126 prompt_for_optionals(&g.optional, dep_type, yes, all_optional)?;
127 chosen_optionals.extend(optionals.clone());
128 deps.extend(optionals);
129
130 if let Some(sub_name) = sub_package_name
131 && let Some(sub_deps_map) = &g.sub_packages
132 && let Some(sub_dep_group) = sub_deps_map.get(sub_name)
133 {
134 let (sub_d, sub_co, sub_coo) = collect_dependencies_for_group(
135 sub_dep_group,
136 None,
137 dep_type,
138 yes,
139 all_optional
140 )?;
141 deps.extend(sub_d);
142 chosen_options.extend(sub_co);
143 chosen_optionals.extend(sub_coo);
144 }
145 }
146 }
147 Ok((deps, chosen_options, chosen_optionals))
148}
149
150pub fn prompt_for_options(
157 option_groups: &[types::DependencyOptionGroup],
158 yes: bool
159) -> Result<Vec<String>> {
160 let mut chosen = Vec::new();
161 if option_groups.is_empty() {
162 return Ok(chosen);
163 }
164
165 for group in option_groups {
166 println!(
167 "{} There are {} options available for {}:",
168 "::".bold().blue(),
169 group.depends.len(),
170 group.desc.italic()
171 );
172
173 let parsed_deps: Vec<_> = group
174 .depends
175 .iter()
176 .map(|d| parse_dependency_string(d))
177 .collect::<Result<_>>()?;
178
179 if yes {
180 if group.all {
181 println!(
182 "--yes provided, selecting all options for '{}'",
183 group.name
184 );
185 chosen.extend(group.depends.clone());
186 } else {
187 println!(
188 "--yes provided, selecting first option for '{}'",
189 group.name
190 );
191 if let Some(dep) = group.depends.first() {
192 chosen.push(dep.clone());
193 }
194 }
195 continue;
196 }
197
198 if group.all {
199 let items: Vec<_> = parsed_deps
200 .iter()
201 .map(|d| {
202 format!(
203 "{}:{} - {}",
204 d.manager,
205 d.package,
206 d.description.unwrap_or("No description")
207 )
208 })
209 .collect();
210 let selections =
211 dialoguer::MultiSelect::with_theme(&ColorfulTheme::default())
212 .with_prompt(
213 "Choose which to install (space to select, enter to \
214 confirm)"
215 )
216 .items(&items)
217 .interact()?;
218
219 for i in selections {
220 if let Some(dep) = group.depends.get(i) {
221 chosen.push(dep.clone());
222 }
223 }
224 } else {
225 let items: Vec<_> = parsed_deps
226 .iter()
227 .map(|d| {
228 format!(
229 "{}:{} - {}",
230 d.manager,
231 d.package,
232 d.description.unwrap_or("No description")
233 )
234 })
235 .collect();
236 let selection = Select::with_theme(&ColorfulTheme::default())
237 .with_prompt("Choose one to install")
238 .items(&items)
239 .default(0)
240 .interact()?;
241 if let Some(dep) = group.depends.get(selection) {
242 chosen.push(dep.clone());
243 }
244 }
245 }
246 Ok(chosen)
247}
248
249pub fn prompt_for_optionals(
256 deps: &[String],
257 dep_type: Option<&str>,
258 yes: bool,
259 all_optional: bool
260) -> Result<Vec<String>> {
261 if deps.is_empty() {
262 return Ok(Vec::new());
263 }
264
265 let type_str = dep_type.map(|s| format!("{s} ")).unwrap_or_default();
266
267 if all_optional {
268 println!(
269 "{} Installing all optional {}dependencies...",
270 "::".bold().blue(),
271 type_str
272 );
273 return Ok(deps.to_vec());
274 }
275
276 if yes {
277 println!(
278 "{} Skipping optional {}dependencies (--yes provided without \
279 --all-optional).",
280 "::".bold().yellow(),
281 type_str
282 );
283 return Ok(Vec::new());
284 }
285
286 let items: Vec<_> = deps
287 .iter()
288 .map(|d| {
289 parse_dependency_string(d).map(|dep| {
290 format!(
291 "{}:{} - {}",
292 dep.manager,
293 dep.package,
294 dep.description.unwrap_or("No description")
295 )
296 })
297 })
298 .collect::<Result<_>>()?;
299
300 let selections =
301 dialoguer::MultiSelect::with_theme(&ColorfulTheme::default())
302 .with_prompt(format!(
303 "Select optional {type_str}dependencies to install"
304 ))
305 .items(&items)
306 .defaults(&vec![false; deps.len()])
307 .interact()?;
308
309 let mut chosen = Vec::new();
310 for i in selections {
311 if let Some(dep) = deps.get(i) {
312 chosen.push(dep.clone());
313 }
314 }
315 Ok(chosen)
316}