use crate::reflect::Reflective;
use pyo3::prelude::*;
use quarb::Value;
use quarb::library::{CommandLibrary, LibCall, LibFailure, Synopsis};
pub struct ModuleEnvironment {
alias: String,
target: String,
root: &'static str,
reflect: Reflective,
}
impl ModuleEnvironment {
pub fn mount(alias: &str, target: &str) -> Result<ModuleEnvironment, String> {
let is_file = target.ends_with(".py");
let root: &'static str = if is_file {
use std::hash::{Hash, Hasher};
let mut h = std::collections::hash_map::DefaultHasher::new();
target.hash(&mut h);
Box::leak(format!("quarb_env_{:016x}", h.finish()).into_boxed_str())
} else {
Box::leak(target.to_string().into_boxed_str())
};
static MOUNTING: std::sync::Mutex<()> = std::sync::Mutex::new(());
let _one = MOUNTING.lock().unwrap_or_else(|e| e.into_inner());
Python::attach(|py| -> Result<(), String> {
crate::foreign::extend_sys_path(py).map_err(|e| e.to_string())?;
let fail = |e: PyErr| format!("'{alias}=python:{target}': {}", e.value(py));
if is_file {
let modules = py
.import("sys")
.map_err(fail)?
.getattr("modules")
.map_err(fail)?;
if modules.contains(root).map_err(fail)? {
return Ok(());
}
let util = py.import("importlib.util").map_err(fail)?;
let spec = util
.call_method1("spec_from_file_location", (root, target))
.map_err(fail)?;
if spec.is_none() {
return Err(format!("'{alias}=python:{target}': not a module file"));
}
let module = util
.call_method1("module_from_spec", (&spec,))
.map_err(fail)?;
spec.getattr("loader")
.map_err(fail)?
.call_method1("exec_module", (&module,))
.map_err(fail)?;
modules.set_item(root, &module).map_err(fail)?;
} else {
py.import(root).map_err(fail)?;
}
Ok(())
})?;
Ok(ModuleEnvironment {
alias: alias.to_string(),
target: target.to_string(),
root,
reflect: Reflective::module(root),
})
}
fn relabel(&self, m: String) -> String {
let m = m.replace(self.root, &format!("python:{}", self.target));
format!("environment '{}': {m}", self.alias)
}
}
impl std::fmt::Debug for ModuleEnvironment {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ModuleEnvironment")
.field("alias", &self.alias)
.field("target", &self.target)
.finish()
}
}
impl CommandLibrary for ModuleEnvironment {
fn prefix(&self) -> &str {
&self.alias
}
fn commands(&self) -> &[Synopsis] {
&[]
}
fn resolve(&self, name: &str) -> Result<&Synopsis, String> {
self.reflect.resolve(name).map_err(|m| self.relabel(m))
}
fn call(&self, name: &str, call: LibCall) -> Result<Value, LibFailure> {
let syn = self.resolve(name).map_err(LibFailure::Unknown)?;
self.reflect.call(syn, name, call)
}
}