1use std::process::Command;
6
7#[allow(dead_code)]
9pub enum DependencyCheck {
10 Binary {
12 name: &'static str,
13 alternatives: &'static [&'static str],
15 version_cmd: Option<(&'static str, &'static [&'static str])>,
18 },
19 PythonImport {
21 module: &'static str,
22 },
23 Command {
25 program: &'static str,
26 args: &'static [&'static str],
27 },
28}
29
30pub struct Dependency {
32 pub name: &'static str,
33 pub check: DependencyCheck,
34 pub install: &'static str,
35}
36
37pub struct DepStatus {
39 pub name: &'static str,
40 pub ok: bool,
41 pub detail: String,
43 pub install: &'static str,
45 pub warning: Option<String>,
47}
48
49pub fn check_dep(dep: Dependency) -> DepStatus {
51 match &dep.check {
52 DependencyCheck::Binary {
53 alternatives,
54 ..
55 } => {
56 for name in *alternatives {
57 if let Ok(path) = which::which(name) {
58 return DepStatus {
59 name: dep.name,
60 ok: true,
61 detail: path.display().to_string(),
62 install: dep.install,
63 warning: None,
64 };
65 }
66 for dir in extra_tool_dirs() {
67 let path = dir.join(name);
68 if path.is_file() {
69 return DepStatus {
70 name: dep.name,
71 ok: true,
72 detail: path.display().to_string(),
73 install: dep.install,
74 warning: None,
75 };
76 }
77 }
78 }
79 DepStatus {
80 name: dep.name,
81 ok: false,
82 detail: "not found".into(),
83 install: dep.install,
84 warning: None,
85 }
86 }
87 DependencyCheck::PythonImport { module } => {
88 let ok = Command::new("python3")
89 .args(["-c", &format!("import {module}")])
90 .stdout(std::process::Stdio::null())
91 .stderr(std::process::Stdio::null())
92 .status()
93 .is_ok_and(|s| s.success());
94 DepStatus {
95 name: dep.name,
96 ok,
97 detail: if ok {
98 format!("{module} importable")
99 } else {
100 format!("{module} not found")
101 },
102 install: dep.install,
103 warning: None,
104 }
105 }
106 DependencyCheck::Command { program, args } => {
107 let ok = Command::new(program)
108 .args(*args)
109 .stdout(std::process::Stdio::null())
110 .stderr(std::process::Stdio::null())
111 .status()
112 .is_ok_and(|s| s.success());
113 DepStatus {
114 name: dep.name,
115 ok,
116 detail: if ok { "ok".into() } else { "failed".into() },
117 install: dep.install,
118 warning: None,
119 }
120 }
121 }
122}
123
124pub fn find_bin(name: &str) -> String {
127 if let Ok(path) = which::which(name) {
128 return path.display().to_string();
129 }
130 for dir in extra_tool_dirs() {
131 let path = dir.join(name);
132 if path.is_file() {
133 return path.display().to_string();
134 }
135 }
136 name.to_string()
137}
138
139pub fn extra_tool_dirs() -> Vec<std::path::PathBuf> {
141 let mut dirs = Vec::new();
142 if let Ok(home) = std::env::var("HOME") {
143 let home = std::path::PathBuf::from(&home);
144 dirs.push(home.join(".dotnet/tools"));
145 dirs.push(home.join(".ghcup/bin"));
146 dirs.push(home.join(".cargo/bin"));
147 dirs.push(home.join(".local/bin"));
148 }
149 dirs
150}
151
152pub fn format_results(results: &[(&str, Vec<DepStatus>)]) -> String {
154 let mut out = String::new();
155 for (name, statuses) in results {
156 out.push_str(&format!("{name}:\n"));
157 for s in statuses {
158 let icon = if s.ok { "ok" } else { "MISSING" };
159 out.push_str(&format!(" {}: {} ({})\n", s.name, icon, s.detail));
160 if !s.ok {
161 out.push_str(&format!(" install: {}\n", s.install));
162 }
163 if let Some(warn) = &s.warning {
164 out.push_str(&format!(" WARNING: {warn}\n"));
165 }
166 }
167 }
168 out
169}