1use std::path::Path;
2use std::process;
3
4use crate::cli::{Cli, ToolCommand};
5use crate::config::{UbtConfig, load_config};
6use crate::detect::detect_tool;
7use crate::error::UbtError;
8use crate::plugin::PluginRegistry;
9
10use super::info::cmd_info;
11
12fn cmd_doctor(
13 cli: &Cli,
14 config: Option<&UbtConfig>,
15 project_root: &Path,
16 registry: &PluginRegistry,
17) -> Result<(), UbtError> {
18 let quiet = cli.quiet;
19 let mut any_fail = false;
20
21 macro_rules! check_ok {
22 ($msg:expr) => {
23 if !quiet {
24 println!("[ok] {}", $msg);
25 }
26 };
27 }
28 macro_rules! check_warn {
29 ($msg:expr) => {
30 if !quiet {
31 println!("[warn] {}", $msg);
32 }
33 };
34 }
35 macro_rules! check_fail {
36 ($msg:expr) => {{
37 any_fail = true;
38 eprintln!("[fail] {}", $msg);
39 }};
40 }
41
42 let config_tool = config.and_then(|c| c.project.as_ref()?.tool.as_deref());
44 match detect_tool(cli.tool.as_deref(), config_tool, project_root, registry) {
45 Err(e) => {
46 check_fail!(format!("Tool detection failed: {e}"));
47 }
48 Ok(detection) => {
49 if let Some((plugin, _)) = registry.get(&detection.plugin_name)
50 && let Some(variant) = plugin.variants.get(&detection.variant_name)
51 {
52 match which::which(&variant.binary) {
53 Ok(path) => {
54 let version = std::process::Command::new(&variant.binary)
56 .arg("--version")
57 .output()
58 .ok()
59 .and_then(|o| {
60 let out = if o.stdout.is_empty() {
61 o.stderr
62 } else {
63 o.stdout
64 };
65 String::from_utf8(out).ok()
66 })
67 .map(|s| s.lines().next().unwrap_or("").trim().to_string())
68 .filter(|s| !s.is_empty())
69 .unwrap_or_else(|| "version unknown".to_string());
70 check_ok!(format!(
71 "{} binary: {} ({})",
72 variant.binary,
73 path.display(),
74 version
75 ));
76 }
77 Err(_) => {
78 let hint = plugin
79 .install_help
80 .as_deref()
81 .map(|h| format!(" — install: {h}"))
82 .unwrap_or_default();
83 check_fail!(format!("{} is not installed{}", variant.binary, hint));
84 }
85 }
86 }
87 }
88 }
89
90 let cwd = std::env::current_dir()?;
92 match load_config(&cwd) {
93 Err(e) => check_fail!(format!("ubt.toml parse error: {e}")),
94 Ok(None) => check_warn!("No ubt.toml found in this directory tree"),
95 Ok(Some((cfg, config_root))) => {
96 check_ok!(format!(
97 "ubt.toml valid at {}",
98 config_root.join("ubt.toml").display()
99 ));
100
101 for (alias, target) in &cfg.aliases {
103 let first_word = target.split_whitespace().next().unwrap_or(target.as_str());
104 let known_commands = [
105 "dep.install",
106 "dep.install_pkg",
107 "dep.remove",
108 "dep.update",
109 "dep.outdated",
110 "dep.list",
111 "dep.audit",
112 "dep.lock",
113 "dep.why",
114 "build",
115 "build.dev",
116 "start",
117 "run",
118 "run-file",
119 "exec",
120 "test",
121 "lint",
122 "fmt",
123 "check",
124 "clean",
125 "release",
126 "publish",
127 "db.migrate",
128 "db.rollback",
129 "db.seed",
130 "db.create",
131 "db.drop",
132 "db.reset",
133 "db.status",
134 ];
135 if known_commands.contains(&first_word)
136 || cfg.aliases.contains_key(first_word)
137 || cfg.commands.contains_key(first_word)
138 {
139 check_ok!(format!("alias '{alias}' → valid target '{first_word}'"));
140 } else {
141 check_warn!(format!(
142 "alias '{alias}' target '{first_word}' is not a known ubt command"
143 ));
144 }
145 }
146 }
147 }
148
149 if any_fail {
150 process::exit(1);
151 }
152
153 Ok(())
154}
155
156pub fn cmd_tool(
157 sub: &ToolCommand,
158 cli: &Cli,
159 config: Option<&UbtConfig>,
160 project_root: &std::path::Path,
161 registry: &PluginRegistry,
162) -> Result<(), UbtError> {
163 match sub {
164 ToolCommand::Info => cmd_info(cli, config, project_root, registry),
165 ToolCommand::Doctor => cmd_doctor(cli, config, project_root, registry),
166 ToolCommand::List => {
167 if !cli.quiet {
168 println!("{:<12} {:<30} Variants", "Plugin", "Description");
169 println!("{}", "-".repeat(70));
170 let mut names: Vec<_> = registry.names();
171 names.sort();
172 for name in names {
173 if let Some((plugin, _)) = registry.get(name) {
174 let variants: Vec<_> = plugin.variants.keys().cloned().collect();
175 println!(
176 "{:<12} {:<30} {}",
177 plugin.name,
178 plugin.description,
179 variants.join(", ")
180 );
181 }
182 }
183 }
184 Ok(())
185 }
186 ToolCommand::Docs(args) => {
187 let config_tool = config.and_then(|c| c.project.as_ref()?.tool.as_deref());
188 let detection = detect_tool(cli.tool.as_deref(), config_tool, project_root, registry)?;
189 if let Some((plugin, _)) = registry.get(&detection.plugin_name) {
190 if let Some(hp) = &plugin.homepage {
191 if args.open {
192 if let Err(e) = open::that(hp) {
193 eprintln!("ubt: could not open browser: {e}");
194 if !cli.quiet {
195 println!("{hp}");
196 }
197 }
198 } else if !cli.quiet {
199 println!("{hp}");
200 }
201 } else if !cli.quiet {
202 println!("No documentation URL configured for {}", plugin.name);
203 }
204 }
205 Ok(())
206 }
207 }
208}