Skip to main content

idt/
main.rs

1//! Install language servers, linters, formatters, and developer helpers concurrently.
2//!
3//! # Errors
4//! - Missing required argument (`dev_tools_dir` / `bin_dir`).
5//! - Directory creation fails.
6//! - GitHub authentication fails.
7//! - Installer thread panics.
8//! - Individual tool installation fails.
9//! - Dead symlink cleanup fails.
10#![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
56/// Summarize installer thread outcomes; collect failing bin names.
57///
58/// # Errors
59/// Returns failing bin names; installers handle detailed error output.
60fn 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(), // removed bold
69                    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/// Install language servers, linters, formatters, and developer helpers concurrently.
85#[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        // Build HashMap for O(1) installer lookup instead of O(n) linear search
241        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}