1#![feature(exit_status_error)]
11
12use std::collections::HashMap;
13use std::path::Path;
14
15use owo_colors::OwoColorize;
16use rootcause::prelude::ResultExt;
17use rootcause::report;
18use ytil_sys::SysInfo;
19use ytil_sys::cli::Args;
20
21use crate::installers::Installer;
22use crate::installers::alacritty::Alacritty;
23use crate::installers::bash_language_server::BashLanguageServer;
24use crate::installers::commitlint::Commitlint;
25use crate::installers::deno::Deno;
26use crate::installers::docker_langserver::DockerLangServer;
27use crate::installers::eslint_d::EslintD;
28use crate::installers::graphql_lsp::GraphQlLsp;
29use crate::installers::hadolint::Hadolint;
30use crate::installers::harper_ls::HarperLs;
31use crate::installers::helm_ls::HelmLs;
32use crate::installers::lua_ls::LuaLanguageServer;
33use crate::installers::marksman::Marksman;
34use crate::installers::nvim::Nvim;
35use crate::installers::opencode::Opencode;
36use crate::installers::prettierd::PrettierD;
37use crate::installers::quicktype::Quicktype;
38use crate::installers::rio::Rio;
39use crate::installers::ruff_lsp::RuffLsp;
40use crate::installers::rust_analyzer::RustAnalyzer;
41use crate::installers::shellcheck::Shellcheck;
42use crate::installers::sql_language_server::SqlLanguageServer;
43use crate::installers::sqruff::Sqruff;
44use crate::installers::starship::Starship;
45use crate::installers::taplo::Taplo;
46use crate::installers::terraform_ls::TerraformLs;
47use crate::installers::typescript_language_server::TypescriptLanguageServer;
48use crate::installers::typos_lsp::TyposLsp;
49use crate::installers::vscode_langservers::VsCodeLangServers;
50use crate::installers::yaml_language_server::YamlLanguageServer;
51use crate::installers::zellij::Zellij;
52
53mod downloaders;
54mod installers;
55
56#[ytil_sys::main]
58fn main() -> rootcause::Result<()> {
59 let args = ytil_sys::cli::get();
60 if args.has_help() {
61 println!("{}", include_str!("../help.txt"));
62 return Ok(());
63 }
64 println!(
65 "{:#?} started with args {}",
66 std::env::current_exe()?.bold().cyan(),
67 format!("{args:#?}").white().bold()
68 );
69
70 let dev_tools_dir = args
71 .first()
72 .ok_or_else(|| report!("missing dev_tools_dir arg"))
73 .attach_with(|| format!("args={args:#?}"))?
74 .trim_end_matches('/');
75 let bin_dir = args
76 .get(1)
77 .ok_or_else(|| report!("missing bin_dir arg"))
78 .attach_with(|| format!("args={args:#?}"))?
79 .trim_end_matches('/');
80 let supplied_bin_names: Vec<&str> = args.iter().skip(2).map(AsRef::as_ref).collect();
81
82 let sys_info = SysInfo::get()?;
83
84 std::fs::create_dir_all(dev_tools_dir)?;
85 std::fs::create_dir_all(bin_dir)?;
86
87 let all_installers: Vec<Box<dyn Installer>> = vec![
88 Box::new(Alacritty {
89 dev_tools_dir: Path::new(dev_tools_dir),
90 bin_dir: Path::new(bin_dir),
91 }),
92 Box::new(BashLanguageServer {
93 dev_tools_dir: Path::new(dev_tools_dir),
94 bin_dir: Path::new(bin_dir),
95 }),
96 Box::new(Commitlint {
97 dev_tools_dir: Path::new(dev_tools_dir),
98 bin_dir: Path::new(bin_dir),
99 }),
100 Box::new(Deno {
101 bin_dir: Path::new(bin_dir),
102 sys_info: &sys_info,
103 }),
104 Box::new(DockerLangServer {
105 dev_tools_dir: Path::new(dev_tools_dir),
106 bin_dir: Path::new(bin_dir),
107 }),
108 Box::new(EslintD {
109 dev_tools_dir: Path::new(dev_tools_dir),
110 bin_dir: Path::new(bin_dir),
111 }),
112 Box::new(GraphQlLsp {
113 dev_tools_dir: Path::new(dev_tools_dir),
114 bin_dir: Path::new(bin_dir),
115 }),
116 Box::new(Hadolint {
117 bin_dir: Path::new(bin_dir),
118 sys_info: &sys_info,
119 }),
120 Box::new(HarperLs {
121 bin_dir: Path::new(bin_dir),
122 }),
123 Box::new(HelmLs {
124 bin_dir: Path::new(bin_dir),
125 sys_info: &sys_info,
126 }),
127 Box::new(LuaLanguageServer {
128 dev_tools_dir: Path::new(dev_tools_dir),
129 sys_info: &sys_info,
130 }),
131 Box::new(Marksman {
132 bin_dir: Path::new(bin_dir),
133 sys_info: &sys_info,
134 }),
135 Box::new(Nvim {
136 dev_tools_dir: Path::new(dev_tools_dir),
137 bin_dir: Path::new(bin_dir),
138 }),
139 Box::new(Opencode {
140 bin_dir: Path::new(bin_dir),
141 sys_info: &sys_info,
142 }),
143 Box::new(PrettierD {
144 dev_tools_dir: Path::new(dev_tools_dir),
145 bin_dir: Path::new(bin_dir),
146 }),
147 Box::new(Quicktype {
148 dev_tools_dir: Path::new(dev_tools_dir),
149 bin_dir: Path::new(bin_dir),
150 }),
151 Box::new(Rio {
152 dev_tools_dir: Path::new(dev_tools_dir),
153 bin_dir: Path::new(bin_dir),
154 }),
155 Box::new(RuffLsp {
156 dev_tools_dir: Path::new(dev_tools_dir),
157 bin_dir: Path::new(bin_dir),
158 }),
159 Box::new(RustAnalyzer {
160 bin_dir: Path::new(bin_dir),
161 sys_info: &sys_info,
162 }),
163 Box::new(Shellcheck {
164 bin_dir: Path::new(bin_dir),
165 sys_info: &sys_info,
166 }),
167 Box::new(Sqruff {
168 bin_dir: Path::new(bin_dir),
169 sys_info: &sys_info,
170 }),
171 Box::new(SqlLanguageServer {
172 dev_tools_dir: Path::new(dev_tools_dir),
173 bin_dir: Path::new(bin_dir),
174 }),
175 Box::new(Starship {
176 dev_tools_dir: Path::new(dev_tools_dir),
177 bin_dir: Path::new(bin_dir),
178 }),
179 Box::new(Taplo {
180 bin_dir: Path::new(bin_dir),
181 }),
182 Box::new(TerraformLs {
183 bin_dir: Path::new(bin_dir),
184 sys_info: &sys_info,
185 }),
186 Box::new(TypescriptLanguageServer {
187 dev_tools_dir: Path::new(dev_tools_dir),
188 bin_dir: Path::new(bin_dir),
189 }),
190 Box::new(TyposLsp {
191 bin_dir: Path::new(bin_dir),
192 sys_info: &sys_info,
193 }),
194 Box::new(VsCodeLangServers {
195 dev_tools_dir: Path::new(dev_tools_dir),
196 bin_dir: Path::new(bin_dir),
197 }),
198 Box::new(YamlLanguageServer {
199 dev_tools_dir: Path::new(dev_tools_dir),
200 bin_dir: Path::new(bin_dir),
201 }),
202 Box::new(Zellij {
203 dev_tools_dir: Path::new(dev_tools_dir),
204 bin_dir: Path::new(bin_dir),
205 }),
206 ];
207
208 let (selected_installers, unknown_bin_names): (Vec<_>, Vec<_>) = if supplied_bin_names.is_empty() {
209 (all_installers.iter().collect(), vec![])
210 } else {
211 let installer_map: HashMap<&str, &Box<dyn Installer>> =
213 all_installers.iter().map(|i| (i.bin_name(), i)).collect();
214
215 let mut selected_installers = Vec::with_capacity(supplied_bin_names.len());
216 let mut unknown_installers = vec![];
217 for chosen_bin in supplied_bin_names {
218 if let Some(&installer) = installer_map.get(chosen_bin) {
219 selected_installers.push(installer);
220 } else {
221 unknown_installers.push(chosen_bin);
222 }
223 }
224 (selected_installers, unknown_installers)
225 };
226
227 if !unknown_bin_names.is_empty() {
228 eprintln!(
229 "{} bins without matching installers",
230 format!("{unknown_bin_names:#?}").yellow().bold()
231 );
232 }
233
234 let installers_res = std::thread::scope(|scope| {
235 let mut handles = Vec::with_capacity(selected_installers.len());
236 for installer in selected_installers {
237 handles.push((installer.bin_name(), scope.spawn(move || installer.run())));
238 }
239 let mut res = Vec::with_capacity(handles.len());
240 for (bin_name, handle) in handles {
241 res.push((bin_name, handle.join()));
242 }
243 res
244 });
245
246 if let Err(errors) = report(&installers_res) {
247 eprintln!(
248 "{} | errors_count={} bin_names={errors:#?}",
249 "error installing tools".red(),
250 errors.len()
251 );
252 std::process::exit(1);
253 }
254
255 ytil_sys::rm::rm_dead_symlinks(bin_dir)?;
256
257 Ok(())
258}
259
260fn report<'a>(installers_res: &'a [(&'a str, std::thread::Result<rootcause::Result<()>>)]) -> Result<(), Vec<&'a str>> {
265 let mut errors_bins = vec![];
266
267 for (bin_name, result) in installers_res {
268 match result {
269 Err(err) => {
270 eprintln!(
271 "{} installer thread panicked error={}",
272 bin_name.red(), format!("{err:#?}").red()
274 );
275 errors_bins.push(*bin_name);
276 }
277 Ok(Err(_)) => errors_bins.push(bin_name),
278 Ok(Ok(())) => {}
279 }
280 }
281
282 if errors_bins.is_empty() {
283 return Ok(());
284 }
285 Err(errors_bins)
286}