rhai/src/module/resolvers/file.rs

181 lines
5.9 KiB
Rust
Raw Normal View History

2020-11-08 16:00:03 +01:00
use crate::stdlib::{
boxed::Box, collections::HashMap, io::Error as IoError, path::PathBuf, string::String,
};
2020-11-16 16:10:14 +01:00
use crate::{Engine, EvalAltResult, Locked, Module, ModuleResolver, Position, Shared};
/// Module resolution service that loads module script files from the file system.
///
/// Script files are cached so they are are not reloaded and recompiled in subsequent requests.
///
2020-11-20 09:52:28 +01:00
/// The [`new_with_path`][FileModuleResolver::new_with_path] and
/// [`new_with_path_and_extension`][FileModuleResolver::new_with_path_and_extension] constructor functions
/// allow specification of a base directory with module path used as a relative path offset
/// to the base directory. The script file is then forced to be in a specified extension
/// (default `.rhai`).
///
/// # Function Namespace
///
/// When a function within a script file module is loaded, all functions in the _global_ namespace
/// plus all those defined within the same module are _merged_ into a _unified_ namespace before
/// the call. Therefore, functions in a module script can cross-call each other.
///
2020-10-27 04:30:38 +01:00
/// # Example
///
/// ```
/// use rhai::Engine;
/// use rhai::module_resolvers::FileModuleResolver;
///
/// // Create a new 'FileModuleResolver' loading scripts from the 'scripts' subdirectory
/// // with file extension '.x'.
/// let resolver = FileModuleResolver::new_with_path_and_extension("./scripts", "x");
///
/// let mut engine = Engine::new();
///
/// engine.set_module_resolver(Some(resolver));
/// ```
#[derive(Debug)]
pub struct FileModuleResolver {
path: PathBuf,
extension: String,
2020-11-07 16:33:21 +01:00
cache: Locked<HashMap<PathBuf, Shared<Module>>>,
}
impl Default for FileModuleResolver {
2020-10-08 16:25:50 +02:00
#[inline(always)]
fn default() -> Self {
Self::new_with_path(PathBuf::default())
}
}
impl FileModuleResolver {
2020-11-20 09:52:28 +01:00
/// Create a new [`FileModuleResolver`] with a specific base path.
///
2020-10-27 04:30:38 +01:00
/// # Example
///
/// ```
/// use rhai::Engine;
/// use rhai::module_resolvers::FileModuleResolver;
///
/// // Create a new 'FileModuleResolver' loading scripts from the 'scripts' subdirectory
/// // with file extension '.rhai' (the default).
/// let resolver = FileModuleResolver::new_with_path("./scripts");
///
/// let mut engine = Engine::new();
/// engine.set_module_resolver(Some(resolver));
/// ```
2020-10-08 16:25:50 +02:00
#[inline(always)]
pub fn new_with_path<P: Into<PathBuf>>(path: P) -> Self {
Self::new_with_path_and_extension(path, "rhai")
}
2020-11-20 09:52:28 +01:00
/// Create a new [`FileModuleResolver`] with a specific base path and file extension.
///
/// The default extension is `.rhai`.
///
2020-10-27 04:30:38 +01:00
/// # Example
///
/// ```
/// use rhai::Engine;
/// use rhai::module_resolvers::FileModuleResolver;
///
/// // Create a new 'FileModuleResolver' loading scripts from the 'scripts' subdirectory
/// // with file extension '.x'.
/// let resolver = FileModuleResolver::new_with_path_and_extension("./scripts", "x");
///
/// let mut engine = Engine::new();
/// engine.set_module_resolver(Some(resolver));
/// ```
2020-10-08 16:25:50 +02:00
#[inline(always)]
pub fn new_with_path_and_extension<P: Into<PathBuf>, E: Into<String>>(
path: P,
extension: E,
) -> Self {
Self {
path: path.into(),
extension: extension.into(),
cache: Default::default(),
}
}
2020-11-20 09:52:28 +01:00
/// Create a new [`FileModuleResolver`] with the current directory as base path.
///
2020-10-27 04:30:38 +01:00
/// # Example
///
/// ```
/// use rhai::Engine;
/// use rhai::module_resolvers::FileModuleResolver;
///
/// // Create a new 'FileModuleResolver' loading scripts from the current directory
/// // with file extension '.rhai' (the default).
/// let resolver = FileModuleResolver::new();
///
/// let mut engine = Engine::new();
/// engine.set_module_resolver(Some(resolver));
/// ```
2020-10-08 16:25:50 +02:00
#[inline(always)]
pub fn new() -> Self {
Default::default()
}
}
impl ModuleResolver for FileModuleResolver {
fn resolve(
&self,
engine: &Engine,
path: &str,
pos: Position,
2020-11-07 16:33:21 +01:00
) -> Result<Shared<Module>, Box<EvalAltResult>> {
// Construct the script file path
let mut file_path = self.path.clone();
file_path.push(path);
file_path.set_extension(&self.extension); // Force extension
let scope = Default::default();
// See if it is cached
2020-11-08 16:00:03 +01:00
let mut module: Option<Shared<Module>> = None;
2020-11-07 16:33:21 +01:00
2020-11-08 16:00:03 +01:00
let mut module_ref = {
#[cfg(not(feature = "sync"))]
let c = self.cache.borrow();
#[cfg(feature = "sync")]
let c = self.cache.read().unwrap();
2020-11-07 16:33:21 +01:00
if let Some(module) = c.get(&file_path) {
2020-11-08 16:00:03 +01:00
Some(module.clone())
} else {
2020-11-08 16:00:03 +01:00
None
}
};
2020-11-08 16:00:03 +01:00
if module_ref.is_none() {
// Load the script file and compile it
let ast = engine
.compile_file(file_path.clone())
.map_err(|err| match *err {
EvalAltResult::ErrorSystem(_, err) if err.is::<IoError>() => {
Box::new(EvalAltResult::ErrorModuleNotFound(path.to_string(), pos))
}
_ => Box::new(EvalAltResult::ErrorInModule(path.to_string(), err, pos)),
})?;
2020-11-07 16:33:21 +01:00
2020-11-21 15:18:32 +01:00
let m = Module::eval_ast_as_new(scope, &ast, engine).map_err(|err| {
2020-11-08 16:00:03 +01:00
Box::new(EvalAltResult::ErrorInModule(path.to_string(), err, pos))
})?;
2020-11-07 16:33:21 +01:00
2020-11-08 16:00:03 +01:00
module = Some(m.into());
module_ref = module.clone();
};
2020-11-07 16:33:21 +01:00
if let Some(module) = module {
// Put it into the cache
#[cfg(not(feature = "sync"))]
2020-11-07 16:33:21 +01:00
self.cache.borrow_mut().insert(file_path, module);
#[cfg(feature = "sync")]
2020-11-07 16:33:21 +01:00
self.cache.write().unwrap().insert(file_path, module);
}
2020-11-08 16:00:03 +01:00
Ok(module_ref.unwrap())
}
}