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