use std::ffi::OsStr;
use std::path::{Path, PathBuf};
use crate::tscircuit::error::RunnerError;
use crate::tscircuit::{Runner, RunnerKind};
pub fn detect_runner(current_dir: &Path, path_var: Option<&OsStr>) -> Result<Runner, RunnerError> {
if let Some(local_path) = find_local_tsci(current_dir) {
return Ok(Runner::new(RunnerKind::Local(local_path)));
}
if let Some(path_var) = path_var {
if let Some(global_path) = find_binary_in_path("tsci", path_var)
.or_else(|| find_binary_in_path("tscircuit", path_var))
{
return Ok(Runner::new(RunnerKind::Global(global_path)));
}
}
Err(RunnerError::NotFound)
}
fn find_local_tsci(dir: &Path) -> Option<PathBuf> {
let dirs = [
dir.join("node_modules").join(".bin"),
dir.join("hardware").join("node_modules").join(".bin"),
];
for bin_dir in dirs {
let candidates = [
bin_dir.join("tsci"),
bin_dir.join("tsci.cmd"),
bin_dir.join("tsci.exe"),
bin_dir.join("tscircuit"),
bin_dir.join("tscircuit.cmd"),
bin_dir.join("tscircuit.exe"),
];
if let Some(found) = candidates.into_iter().find(|c| is_executable(c)) {
return Some(found);
}
}
None
}
pub(crate) fn find_binary_in_path(binary: &str, path_var: &OsStr) -> Option<PathBuf> {
std::env::split_paths(path_var).find_map(|dir| {
let candidate = dir.join(binary);
if is_executable(&candidate) {
return Some(candidate);
}
find_windows_binary(&dir, binary)
})
}
fn find_windows_binary(dir: &Path, binary: &str) -> Option<PathBuf> {
#[cfg(windows)]
{
for ext in &[".cmd", ".exe", ".bat"] {
let candidate_ext = dir.join(format!("{binary}{ext}"));
if is_executable(&candidate_ext) {
return Some(candidate_ext);
}
}
}
#[cfg(not(windows))]
let _ = (dir, binary);
None
}
#[cfg(unix)]
pub(crate) fn is_executable(path: &Path) -> bool {
use std::os::unix::fs::PermissionsExt;
if !path.is_file() {
return false;
}
path.metadata()
.map(|meta| meta.permissions().mode() & 0o111 != 0)
.unwrap_or(false)
}
#[cfg(not(unix))]
pub(crate) fn is_executable(path: &Path) -> bool {
path.is_file()
}