1use anyhow::{Result, bail};
11use colored::Colorize;
12use comfy_table::Table;
13use comfy_table::presets::UTF8_FULL;
14use zoi_core::alternatives::{self, AlternativeStatus};
15
16use crate::cli::AltCommand;
17
18pub fn run(args: &AltCommand) -> Result<()> {
31 match args {
32 AltCommand::List { verbose } => list(*verbose),
33 AltCommand::Get { name } => get(name),
34 AltCommand::Set { name, path } => set(name, path),
35 AltCommand::Auto { name } => auto(name),
36 AltCommand::Remove { name, path } => remove(name, path)
37 }
38}
39
40fn list(verbose: bool) -> Result<()> {
42 let statuses = alternatives::status()?;
43
44 if statuses.is_empty() {
45 println!(
46 "No alternatives are registered. A package registers one by \
47 declaring an 'alternatives' block in its .pkg.lua."
48 );
49 return Ok(());
50 }
51
52 if !verbose {
53 let mut table = Table::new();
54 table.load_style(UTF8_FULL);
55 table.set_header(vec![
56 "Name",
57 "Link",
58 "Current",
59 "Source",
60 "Registered",
61 ]);
62
63 for status in &statuses {
64 let current = status.current.clone().unwrap_or_else(|| "-".into());
65
66 table.add_row(vec![
67 status.name.clone(),
68 status.link.clone(),
69 current,
70 if status.manual { "manual" } else { "auto" }.to_string(),
71 status
72 .alternatives
73 .iter()
74 .map(|a| format!("{} ({})", a.path, a.priority))
75 .collect::<Vec<_>>()
76 .join("\n"),
77 ]);
78 }
79
80 println!("{table}");
81 return Ok(());
82 }
83
84 for status in &statuses {
87 print_group(status);
88 }
89
90 Ok(())
91}
92
93fn print_group(status: &AlternativeStatus) {
95 println!("{} -> {}", status.name.bold(), status.link);
96
97 let current = status.current.clone().unwrap_or_else(|| "-".into());
98 let mut table = Table::new();
99 table.load_style(UTF8_FULL);
100 table.set_header(vec!["Priority", "Path", "Package", "Current"]);
101
102 let mut sorted = status.alternatives.clone();
105 sorted.sort_by_key(|a| std::cmp::Reverse(a.priority));
106
107 for alternative in sorted {
108 let is_current = Some(&alternative.path) == status.current.as_ref();
109 table.add_row(vec![
110 alternative.priority.to_string(),
111 alternative.path.clone(),
112 alternative.package.clone(),
113 if is_current {
114 "*".to_string()
115 } else {
116 String::new()
117 },
118 ]);
119 }
120
121 println!("{table}");
122 println!(
123 " selected: {} ({})\n",
124 current,
125 if status.manual {
126 "manual"
127 } else {
128 "highest priority"
129 }
130 );
131}
132
133fn get(name: &str) -> Result<()> {
135 let Some(group) = alternatives::get(name)? else {
136 bail!("No such alternative: '{name}'. Run 'zoi alt list' to see them.");
137 };
138
139 print_group(&group.status());
140 Ok(())
141}
142
143fn set(name: &str, path: &str) -> Result<()> {
145 alternatives::set(name, path)?;
146
147 let group = alternatives::get(name)?
148 .ok_or_else(|| anyhow::anyhow!("Alternative '{name}' disappeared"))?;
149
150 println!(
151 "{} '{}' now points to {}",
152 "Updated".green().bold(),
153 group.link,
154 path.bold()
155 );
156
157 Ok(())
158}
159
160fn auto(name: &str) -> Result<()> {
162 if let Some(selected) = alternatives::auto(name)? {
163 println!(
164 "{} '{}' now points to {} (highest priority)",
165 "Updated".green().bold(),
166 name,
167 selected.bold()
168 );
169 Ok(())
170 } else {
171 println!("Alternative '{name}' has no registered implementations.");
172 Ok(())
173 }
174}
175
176fn remove(name: &str, path: &str) -> Result<()> {
178 alternatives::remove_implementation(name, path)?;
179
180 println!(
181 "{} '{path}' from alternative '{name}'",
182 "Removed".green().bold()
183 );
184
185 if let Some(group) = alternatives::get(name)?
187 && let Some(current) = group.current()
188 {
189 println!("{} now points to {}", name, current.path.as_str().bold());
190 }
191
192 Ok(())
193}
194
195pub fn announce_changes(changed: &[String]) {
201 if changed.is_empty() {
202 return;
203 }
204
205 for name in changed {
206 match alternatives::get(name) {
207 Ok(Some(group)) => match group.current() {
208 Some(current) => println!(
209 "{} alternative '{}' -> {}",
210 "::".blue().bold(),
211 name,
212 current.path.as_str().bold()
213 ),
214 None => println!(
215 "{} alternative '{}' has no implementations",
216 "::".blue().bold(),
217 name
218 )
219 },
220 Ok(None) => {
221 println!("{} alternative '{name}' removed", "::".blue().bold());
222 }
223 Err(_) => {}
225 }
226 }
227}