ralph_workflow/platform/
binary_guidance.rs1use super::{known_binaries, Platform};
6
7#[derive(Debug)]
9pub struct InstallGuidance {
10 pub(crate) binary: String,
12 pub(crate) install_cmd: Option<String>,
14 pub(crate) alternative: Option<String>,
16 pub(crate) notes: Vec<String>,
18}
19
20impl InstallGuidance {
21 pub(crate) fn for_binary(binary: &str) -> Self {
23 let platform = Platform::detect();
24 Self::for_binary_on_platform(binary, platform)
25 }
26
27 pub(crate) fn for_binary_on_platform(binary: &str, platform: Platform) -> Self {
29 let mut guidance = Self {
30 binary: binary.to_string(),
31 install_cmd: None,
32 alternative: None,
33 notes: Vec::new(),
34 };
35
36 if !known_binaries::add_known_binary_guidance(&mut guidance, binary, platform) {
38 add_generic_guidance(&mut guidance, binary, platform);
40 }
41
42 guidance
43 }
44
45 pub(crate) fn format(&self) -> String {
47 let mut lines = Vec::new();
48
49 lines.push(format!("Binary '{}' not found in PATH.", self.binary));
50 lines.push(String::new());
51
52 for note in &self.notes {
53 lines.push(format!(" {note}"));
54 }
55
56 if let Some(ref cmd) = self.install_cmd {
57 lines.push(String::new());
58 lines.push(" To install:".to_string());
59 lines.push(format!(" {cmd}"));
60 }
61
62 if let Some(ref alt) = self.alternative {
63 lines.push(format!(" Or: {alt}"));
64 }
65
66 lines.join("\n")
67 }
68}
69
70fn add_generic_guidance(guidance: &mut InstallGuidance, binary: &str, platform: Platform) {
72 match platform {
73 Platform::MacWithBrew => {
74 guidance.install_cmd = Some(format!("brew install {binary}"));
75 guidance
76 .notes
77 .push("Or check if available via npm/pip".to_string());
78 }
79 Platform::MacWithoutBrew => {
80 guidance
81 .notes
82 .push("Consider installing Homebrew first:".to_string());
83 guidance.install_cmd = Some(
84 "/bin/bash -c \"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\"".to_string()
85 );
86 guidance.alternative = Some(format!("Then: brew install {binary}"));
87 }
88 Platform::DebianLinux => {
89 guidance.install_cmd = Some(format!("sudo apt-get install {binary}"));
90 guidance
91 .notes
92 .push("Or check if available via npm/pip".to_string());
93 }
94 Platform::RhelLinux => {
95 guidance.install_cmd = Some(format!("sudo dnf install {binary}"));
96 guidance
97 .notes
98 .push("Or check if available via npm/pip".to_string());
99 }
100 Platform::ArchLinux => {
101 guidance.install_cmd = Some(format!("sudo pacman -S {binary}"));
102 guidance
103 .notes
104 .push("Or check the AUR if not in official repos".to_string());
105 }
106 Platform::GenericLinux => {
107 guidance
108 .notes
109 .push("Check your distribution's package manager".to_string());
110 guidance
111 .notes
112 .push("Or try: npm/pip/cargo install".to_string());
113 }
114 Platform::Windows => {
115 guidance.install_cmd = Some(format!("winget install {binary}"));
116 guidance.alternative = Some("Or use Chocolatey: choco install ...".to_string());
117 }
118 Platform::Unknown => {
119 guidance
120 .notes
121 .push("Check the tool's documentation for installation instructions".to_string());
122 }
123 }
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129
130 #[test]
131 fn test_install_guidance_for_claude() {
132 let guidance = InstallGuidance::for_binary_on_platform("claude", Platform::MacWithBrew);
133 assert_eq!(guidance.binary, "claude");
134 assert!(guidance.install_cmd.is_some());
135 assert!(guidance.install_cmd.as_ref().unwrap().contains("npm"));
136 assert!(!guidance.notes.is_empty());
137 }
138
139 #[test]
140 fn test_install_guidance_for_aider_mac() {
141 let guidance = InstallGuidance::for_binary_on_platform("aider", Platform::MacWithBrew);
142 assert!(guidance.install_cmd.as_ref().unwrap().contains("brew"));
143 }
144
145 #[test]
146 fn test_install_guidance_for_aider_linux() {
147 let guidance = InstallGuidance::for_binary_on_platform("aider", Platform::DebianLinux);
148 assert!(guidance.install_cmd.as_ref().unwrap().contains("pip"));
149 }
150
151 #[test]
152 fn test_install_guidance_for_unknown_binary_mac_with_brew() {
153 let guidance =
154 InstallGuidance::for_binary_on_platform("unknown-tool", Platform::MacWithBrew);
155 assert!(guidance
156 .install_cmd
157 .as_ref()
158 .unwrap()
159 .contains("brew install unknown-tool"));
160 }
161
162 #[test]
163 fn test_install_guidance_for_unknown_binary_debian() {
164 let guidance =
165 InstallGuidance::for_binary_on_platform("unknown-tool", Platform::DebianLinux);
166 assert!(guidance.install_cmd.as_ref().unwrap().contains("apt-get"));
167 }
168
169 #[test]
170 fn test_install_guidance_mac_without_brew() {
171 let guidance =
172 InstallGuidance::for_binary_on_platform("unknown-tool", Platform::MacWithoutBrew);
173 assert!(guidance.install_cmd.as_ref().unwrap().contains("Homebrew"));
175 }
176
177 #[test]
178 fn test_install_guidance_format() {
179 let guidance = InstallGuidance::for_binary_on_platform("claude", Platform::MacWithBrew);
180 let formatted = guidance.format();
181 assert!(formatted.contains("claude"));
182 assert!(formatted.contains("not found"));
183 assert!(formatted.contains("install"));
184 }
185}