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