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