diff --git a/Cargo.lock b/Cargo.lock index d3f3c0b..9d243a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5,7 +5,7 @@ version = 3 [[package]] name = "afrim" version = "0.6.0" -source = "git+https://github.com/pythonbrad/afrim?rev=5f40469#5f40469dd3970c68ede03f7d0041fc4644f2e59a" +source = "git+https://github.com/fodydev/afrim?rev=5f40469#5f40469dd3970c68ede03f7d0041fc4644f2e59a" dependencies = [ "afrim-config", "afrim-preprocessor", @@ -19,7 +19,7 @@ dependencies = [ [[package]] name = "afrim-config" version = "0.4.5" -source = "git+https://github.com/pythonbrad/afrim?rev=5f40469#5f40469dd3970c68ede03f7d0041fc4644f2e59a" +source = "git+https://github.com/fodydev/afrim?rev=5f40469#5f40469dd3970c68ede03f7d0041fc4644f2e59a" dependencies = [ "anyhow", "indexmap", @@ -31,12 +31,12 @@ dependencies = [ [[package]] name = "afrim-memory" version = "0.4.2" -source = "git+https://github.com/pythonbrad/afrim?rev=5f40469#5f40469dd3970c68ede03f7d0041fc4644f2e59a" +source = "git+https://github.com/fodydev/afrim?rev=5f40469#5f40469dd3970c68ede03f7d0041fc4644f2e59a" [[package]] name = "afrim-preprocessor" version = "0.6.1" -source = "git+https://github.com/pythonbrad/afrim?rev=5f40469#5f40469dd3970c68ede03f7d0041fc4644f2e59a" +source = "git+https://github.com/fodydev/afrim?rev=5f40469#5f40469dd3970c68ede03f7d0041fc4644f2e59a" dependencies = [ "afrim-memory", "keyboard-types", @@ -45,7 +45,7 @@ dependencies = [ [[package]] name = "afrim-translator" version = "0.2.1" -source = "git+https://github.com/pythonbrad/afrim?rev=5f40469#5f40469dd3970c68ede03f7d0041fc4644f2e59a" +source = "git+https://github.com/fodydev/afrim?rev=5f40469#5f40469dd3970c68ede03f7d0041fc4644f2e59a" dependencies = [ "indexmap", "rhai", diff --git a/src/lib.rs b/src/lib.rs index e8b25e7..9bb2a96 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,11 +22,108 @@ pub struct Wish { } impl Wish { + // Extract a version from a wish binary name. + // Examples: + // - "wish" => Some(0.0) + // - "wish9.0" => Some(9.0) + // - "wish8.6" => Some(8.6) + // - "wishx" => None (ignored) + pub(crate) fn wish_parse_version(name: &str) -> Option { + if !name.starts_with("wish") { + return None; + } + + let tail = &name[4..]; + let ver_str: String = tail + .chars() + .take_while(|c| c.is_ascii_digit() || *c == '.') + .collect(); + + if tail.is_empty() { + return Some(0.0); + } + + if ver_str.is_empty() { + return None; + } + + Some(ver_str.parse::().unwrap_or(0.0)) + } + + pub(crate) fn wish_sort_and_dedupe_bins(mut bins: Vec<(f32, String)>) -> Vec { + bins.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(std::cmp::Ordering::Equal)); + let mut seen = std::collections::HashSet::new(); + let mut candidates: Vec = Vec::new(); + for (_, p) in bins { + if seen.insert(p.clone()) { + candidates.push(p); + } + } + candidates + } + + pub(crate) fn wish_fallback_candidates() -> Vec { + vec!["wish9.0".into(), "wish8.7".into(), "wish8.6".into(), "wish".into()] + } + fn init() -> &'static afrish::TkTopLevel { static WISH: OnceLock = OnceLock::new(); WISH.get_or_init(|| { let wish = if cfg!(debug_assertions) { - afrish::trace_with("wish").unwrap() + // Prefer a user-provided Wish binary on macOS where /usr/bin/wish is often Tk 8.5. + let env_bin = std::env::var("WISH_BIN") + .or_else(|_| std::env::var("AFRISH_WISH_BIN")) + .ok(); + + if let Some(bin) = env_bin { + afrish::trace_with(&bin).unwrap_or_else(|e| { + panic!( + "Failed to start Wish at '{}'. Error: {:?}", + bin, e + ) + }) + } else { + let mut bins: Vec<(f32, String)> = Vec::new(); + if let Ok(path_var) = std::env::var("PATH") { + for dir in std::env::split_paths(&path_var) { + if let Ok(read_dir) = std::fs::read_dir(&dir) { + for entry in read_dir.flatten() { + let name_os = entry.file_name(); + if let Some(name) = name_os.to_str() { + if let Some(ver) = Self::wish_parse_version(name) { + let full = entry.path().to_string_lossy().to_string(); + bins.push((ver, full)); + } + } + } + } + } + } + + let mut candidates = Self::wish_sort_and_dedupe_bins(bins); + if candidates.is_empty() { + // Fallback to common names if PATH scan found nothing. + candidates = Self::wish_fallback_candidates(); + } + + let mut last_err: Option<(String, afrish::TkError)> = None; + for c in candidates { + match afrish::trace_with(&c) { + Ok(w) => return w, + Err(e) => last_err = Some((c, e)), + } + } + + match last_err { + Some((cmd, err)) => panic!( + "Failed to launch Wish (last tried '{}'). Set WISH_BIN to your Tk wish (e.g., /opt/homebrew/opt/tcl-tk/bin/wish or wish9.0). Error: {:?}", + cmd, err + ), + None => panic!( + "Failed to launch Wish. Set WISH_BIN to your Tk wish (e.g., /opt/homebrew/opt/tcl-tk/bin/wish or wish9.0)." + ), + } + } } else { afrish::start_wish().unwrap() }; @@ -147,6 +244,43 @@ mod tests { use std::thread; use std::time::Duration; + #[test] + fn test_wish_parse_version() { + assert_eq!(Wish::wish_parse_version("wish"), Some(0.0)); + assert_eq!(Wish::wish_parse_version("wish9.0"), Some(9.0)); + assert_eq!(Wish::wish_parse_version("wish8.6"), Some(8.6)); + assert_eq!(Wish::wish_parse_version("wish10"), Some(10.0)); + assert_eq!(Wish::wish_parse_version("wishx"), None); + assert_eq!(Wish::wish_parse_version("bash"), None); + } + + #[test] + fn test_wish_sort_and_dedupe_bins() { + let bins = vec![ + (8.6, "/bin/wish8.6".to_string()), + (9.0, "/opt/wish9.0".to_string()), + (0.0, "/usr/bin/wish".to_string()), + (9.0, "/opt/wish9.0".to_string()), // duplicate path + ]; + let out = Wish::wish_sort_and_dedupe_bins(bins); + assert_eq!(out, vec![ + "/opt/wish9.0".to_string(), + "/bin/wish8.6".to_string(), + "/usr/bin/wish".to_string(), + ]); + } + + #[test] + fn test_wish_fallback_candidates() { + let fall = Wish::wish_fallback_candidates(); + assert_eq!(fall, vec![ + "wish9.0".to_string(), + "wish8.7".to_string(), + "wish8.6".to_string(), + "wish".to_string(), + ]); + } + #[test] fn test_api() { let config = Config::from_file(Path::new("data/full_sample.toml")).unwrap(); @@ -260,4 +394,4 @@ mod tests { // We wait the afrim to end properly. afrim_wish_thread.join().unwrap(); } -} +} \ No newline at end of file