Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

138 changes: 136 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<f32> {
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::<f32>().unwrap_or(0.0))
}

pub(crate) fn wish_sort_and_dedupe_bins(mut bins: Vec<(f32, String)>) -> Vec<String> {
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<String> = Vec::new();
for (_, p) in bins {
if seen.insert(p.clone()) {
candidates.push(p);
}
}
candidates
}

pub(crate) fn wish_fallback_candidates() -> Vec<String> {
vec!["wish9.0".into(), "wish8.7".into(), "wish8.6".into(), "wish".into()]
}

fn init() -> &'static afrish::TkTopLevel {
static WISH: OnceLock<afrish::TkTopLevel> = 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()
};
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -260,4 +394,4 @@ mod tests {
// We wait the afrim to end properly.
afrim_wish_thread.join().unwrap();
}
}
}