#![cfg_attr(docsrs, feature(doc_cfg))]
mod error;
pub mod multipart;
mod response;
mod script;
#[cfg(feature = "tokio")]
mod async_;
pub use error::Error;
pub use response::{Response, Tensor};
pub use script::{IoSpec, RoutedScriptError, Script, ScriptBuilder, SharedScript};
use std::ffi::CStr;
use std::path::{Path, PathBuf};
use std::sync::Mutex;
pub fn load(path: impl AsRef<Path>) -> Result<Script, Error> {
builder(path).open()
}
pub fn find(name: &str) -> Result<Script, Error> {
let mut tried: Vec<String> = Vec::new();
for dir in search_dirs() {
for candidate in candidate_names(name) {
let path = dir.join(&candidate);
if path.is_file() {
return load(path);
}
tried.push(path.display().to_string());
}
}
Err(Error::Load {
path: PathBuf::from(name),
message: format!(
"no script named '{name}'. Tried, in order:\n {}\n\
(search order: $MANEDPATH, maned::search_path(), the working \
directory, ./mnd/)",
tried.join("\n ")
),
})
}
pub fn builder(path: impl AsRef<Path>) -> ScriptBuilder {
ScriptBuilder::new(path.as_ref().to_path_buf())
}
pub fn set_kernel_threads(n: i32) {
unsafe { maned_sys::mnd_set_kernel_threads(n) }
}
pub fn shutdown() {
script::close_all();
}
pub fn live_handles() -> usize {
script::live_count()
}
pub fn abi_version() -> i32 {
unsafe { maned_sys::mnd_abi_version() }
}
pub fn version() -> &'static str {
unsafe { CStr::from_ptr(maned_sys::mnd_version()) }
.to_str()
.unwrap_or("<non-utf8>")
}
pub fn search_path() -> Vec<PathBuf> {
SEARCH_PATH.lock().map(|p| p.clone()).unwrap_or_default()
}
pub fn add_search_path(dir: impl AsRef<Path>) {
if let Ok(mut p) = SEARCH_PATH.lock() {
p.push(dir.as_ref().to_path_buf());
}
}
pub fn set_search_path<I, P>(dirs: I)
where
I: IntoIterator<Item = P>,
P: AsRef<Path>,
{
if let Ok(mut p) = SEARCH_PATH.lock() {
*p = dirs.into_iter().map(|d| d.as_ref().to_path_buf()).collect();
}
}
static SEARCH_PATH: Mutex<Vec<PathBuf>> = Mutex::new(Vec::new());
fn search_dirs() -> Vec<PathBuf> {
let mut dirs: Vec<PathBuf> = Vec::new();
if let Some(p) = std::env::var_os("MANEDPATH") {
dirs.extend(std::env::split_paths(&p).filter(|d| !d.as_os_str().is_empty()));
}
dirs.extend(search_path());
dirs.push(std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")));
dirs.push(PathBuf::from("mnd"));
dirs
}
fn candidate_names(name: &str) -> Vec<PathBuf> {
let p = Path::new(name);
if p.extension().is_some() {
vec![p.to_path_buf()]
} else {
vec![p.with_extension("mnd"), p.to_path_buf()]
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn candidate_names_appends_mnd_only_when_there_is_no_extension() {
assert_eq!(
candidate_names("scoring"),
vec![PathBuf::from("scoring.mnd"), PathBuf::from("scoring")]
);
assert_eq!(
candidate_names("scoring.mnd"),
vec![PathBuf::from("scoring.mnd")]
);
assert_eq!(candidate_names("a/b.mnd"), vec![PathBuf::from("a/b.mnd")]);
}
#[test]
fn a_miss_names_every_directory_tried() {
let e = find("definitely-not-a-script-9d3f").unwrap_err();
let m = e.to_string();
assert!(m.contains("definitely-not-a-script-9d3f.mnd"), "{m}");
assert!(m.contains("mnd/definitely-not-a-script-9d3f.mnd"), "{m}");
assert!(m.contains("search order"), "{m}");
}
#[test]
fn the_linked_library_is_the_one_we_were_compiled_against() {
assert!(
maned_sys::abi_version_supported(abi_version()),
"linked libmaned_c reports ABI {}",
abi_version()
);
assert!(!version().is_empty());
}
}