From 889edbef718b071ebaa57320afa6a9bce12e8896 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 2 Apr 2021 12:34:39 +0800 Subject: [PATCH] Allow cross-loading of relative file paths in FileModuleResolver. --- CHANGELOG.md | 12 +++ README.md | 3 +- scripts/module.rhai | 2 +- src/bin/rhai-repl.rs | 50 ++++++++---- src/bin/rhai-run.rs | 33 +++++++- src/engine.rs | 11 ++- src/engine_api.rs | 53 ++++++------ src/fn_native.rs | 6 +- src/fn_register.rs | 6 +- src/module/mod.rs | 6 +- src/module/resolvers/collection.rs | 3 +- src/module/resolvers/dummy.rs | 1 + src/module/resolvers/file.rs | 125 ++++++++++++++++++++--------- src/module/resolvers/mod.rs | 2 + src/module/resolvers/stat.rs | 1 + src/serde/metadata.rs | 5 +- 16 files changed, 219 insertions(+), 100 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ad1559a..f6192021 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,18 @@ Rhai Release Notes Version 0.19.16 =============== +Breaking changes +---------------- + +* `ModuleResolver` trait methods take an additional parameter `source_path` that contains the path of the current environment. This is to facilitate loading other script files always from the current directory. +* `FileModuleResolver` now resolves relative paths under the source path if there is no base path set. +* `FileModuleResolver::base_path` now returns `Option<&str>` which is `None` if there is no base path set. + +New features +------------ + +* `FileModuleResolver` resolves relative paths under the parent path (i.e. the path holding the script that does the loading). This allows seamless cross-loading of scripts from a directory hierarchy instead of having all relative paths load from the current working directory. + Version 0.19.15 =============== diff --git a/README.md b/README.md index 8042448b..46c440de 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ Rhai - Embedded Scripting for Rust ![GitHub last commit](https://img.shields.io/github/last-commit/rhaiscript/rhai?logo=github) [![Build Status](https://github.com/rhaiscript/rhai/workflows/Build/badge.svg)](https://github.com/rhaiscript/rhai/actions) +[![stars](https://img.shields.io/github/stars/rhaiscript/rhai?logo=github)](https://github.com/rhaiscript/rhai) [![license](https://img.shields.io/crates/l/rhai)](https://github.com/license/rhaiscript/rhai) [![crates.io](https://img.shields.io/crates/v/rhai?logo=rust)](https://crates.io/crates/rhai/) [![crates.io](https://img.shields.io/crates/d/rhai?logo=rust)](https://crates.io/crates/rhai/) @@ -35,7 +36,7 @@ Standard features * Built-in support for most common [data types](https://rhai.rs/book/language/values-and-types.html) including booleans, [integers](https://rhai.rs/book/language/numbers.html), [floating-point numbers](https://rhai.rs/book/language/numbers.html) (including [`Decimal`](https://crates.io/crates/rust_decimal)), [strings](https://rhai.rs/book/language/strings-chars.html), [Unicode characters](https://rhai.rs/book/language/strings-chars.html), [arrays](https://rhai.rs/book/language/arrays.html) and [object maps](https://rhai.rs/book/language/object-maps.html). * Easily [call a script-defined function](https://rhai.rs/book/engine/call-fn.html) from Rust. * Relatively little `unsafe` code (yes there are some for performance reasons). -* Few dependencies (currently only [`smallvec`](https://crates.io/crates/smallvec), [`num-traits`](https://crates.io/crates/num-traits), [`ahash`](https://crates.io/crates/ahash)) and [`smartstring`](https://crates.io/crates/smartstring)). +* Few dependencies - currently only [`smallvec`](https://crates.io/crates/smallvec), [`num-traits`](https://crates.io/crates/num-traits), [`ahash`](https://crates.io/crates/ahash) and [`smartstring`](https://crates.io/crates/smartstring). * Re-entrant scripting engine can be made `Send + Sync` (via the `sync` feature). * Compile once to [AST](https://rhai.rs/book/engine/compile.html) form for repeated evaluations. * Scripts are [optimized](https://rhai.rs/book/engine/optimize.html) (useful for template-based machine-generated scripts). diff --git a/scripts/module.rhai b/scripts/module.rhai index 0ad387c1..77ad12ac 100644 --- a/scripts/module.rhai +++ b/scripts/module.rhai @@ -1,3 +1,3 @@ -import "scripts/loop"; +import "loop"; print("Module test!"); diff --git a/src/bin/rhai-repl.rs b/src/bin/rhai-repl.rs index 5dc2fd75..43662ef3 100644 --- a/src/bin/rhai-repl.rs +++ b/src/bin/rhai-repl.rs @@ -1,9 +1,12 @@ -use rhai::{Dynamic, Engine, EvalAltResult, Module, Scope, AST}; +use rhai::{ + module_resolvers::FileModuleResolver, Dynamic, Engine, EvalAltResult, Module, Scope, AST, +}; use std::{ env, fs::File, io::{stdin, stdout, Read, Write}, + path::Path, process::exit, }; @@ -64,28 +67,39 @@ fn main() { #[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_std"))] { - // Set a file module resolver without caching - let mut resolver = rhai::module_resolvers::FileModuleResolver::new(); - resolver.enable_cache(false); - engine.set_module_resolver(resolver); - // Load init scripts let mut contents = String::new(); let mut has_init_scripts = false; for filename in env::args().skip(1) { + let filename = match Path::new(&filename).canonicalize() { + Err(err) => { + eprintln!("Error script file path: {}\n{}", filename, err); + exit(1); + } + Ok(f) => f, + }; + contents.clear(); let mut f = match File::open(&filename) { Err(err) => { - eprintln!("Error reading script file: {}\n{}", filename, err); + eprintln!( + "Error reading script file: {}\n{}", + filename.to_string_lossy(), + err + ); exit(1); } Ok(f) => f, }; if let Err(err) = f.read_to_string(&mut contents) { - println!("Error reading script file: {}\n{}", filename, err); + println!( + "Error reading script file: {}\n{}", + filename.to_string_lossy(), + err + ); exit(1); } @@ -93,10 +107,12 @@ fn main() { .compile(&contents) .map_err(|err| err.into()) .and_then(|mut ast| { - ast.set_source(&filename); + ast.set_source(filename.to_string_lossy()); Module::eval_ast_as_new(Default::default(), &ast, &engine) }) { Err(err) => { + let filename = filename.to_string_lossy(); + eprintln!("{:=<1$}", "", filename.len()); eprintln!("{}", filename); eprintln!("{:=<1$}", "", filename.len()); @@ -112,7 +128,7 @@ fn main() { has_init_scripts = true; - println!("Script '{}' loaded.", filename); + println!("Script '{}' loaded.", filename.to_string_lossy()); } if has_init_scripts { @@ -124,17 +140,23 @@ fn main() { #[cfg(not(feature = "no_optimize"))] engine.set_optimization_level(rhai::OptimizationLevel::None); + // Set a file module resolver without caching + let mut resolver = FileModuleResolver::new(); + resolver.enable_cache(false); + engine.set_module_resolver(resolver); + + // Make Engine immutable + let engine = engine; + + // Create scope let mut scope = Scope::new(); + // REPL loop let mut input = String::new(); let mut main_ast: AST = Default::default(); let mut ast_u: AST = Default::default(); let mut ast: AST = Default::default(); - // Make Engine immutable - let engine = engine; - - // REPL loop 'main_loop: loop { print!("rhai-repl> "); stdout().flush().expect("couldn't flush stdout"); diff --git a/src/bin/rhai-run.rs b/src/bin/rhai-run.rs index 268390a1..bdef6b64 100644 --- a/src/bin/rhai-run.rs +++ b/src/bin/rhai-run.rs @@ -3,7 +3,7 @@ use rhai::{Engine, EvalAltResult, Position}; #[cfg(not(feature = "no_optimize"))] use rhai::OptimizationLevel; -use std::{env, fs::File, io::Read, process::exit}; +use std::{env, fs::File, io::Read, path::Path, process::exit}; fn eprint_error(input: &str, mut err: EvalAltResult) { fn eprint_line(lines: &[&str], pos: Position, err_msg: &str) { @@ -38,6 +38,14 @@ fn main() { let mut contents = String::new(); for filename in env::args().skip(1) { + let filename = match Path::new(&filename).canonicalize() { + Err(err) => { + eprintln!("Error script file path: {}\n{}", filename, err); + exit(1); + } + Ok(f) => f, + }; + let mut engine = Engine::new(); #[cfg(not(feature = "no_optimize"))] @@ -45,7 +53,11 @@ fn main() { let mut f = match File::open(&filename) { Err(err) => { - eprintln!("Error reading script file: {}\n{}", filename, err); + eprintln!( + "Error reading script file: {}\n{}", + filename.to_string_lossy(), + err + ); exit(1); } Ok(f) => f, @@ -54,7 +66,11 @@ fn main() { contents.clear(); if let Err(err) = f.read_to_string(&mut contents) { - eprintln!("Error reading script file: {}\n{}", filename, err); + eprintln!( + "Error reading script file: {}\n{}", + filename.to_string_lossy(), + err + ); exit(1); } @@ -65,7 +81,16 @@ fn main() { &contents[..] }; - if let Err(err) = engine.consume(contents) { + if let Err(err) = engine + .compile(contents) + .map_err(|err| Box::new(err.into()) as Box) + .and_then(|mut ast| { + ast.set_source(filename.to_string_lossy()); + engine.consume_ast(&ast) + }) + { + let filename = filename.to_string_lossy(); + eprintln!("{:=<1$}", "", filename.len()); eprintln!("{}", filename); eprintln!("{:=<1$}", "", filename.len()); diff --git a/src/engine.rs b/src/engine.rs index 362fb0b4..69a87931 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -645,7 +645,7 @@ impl<'x, 'px> EvalContext<'_, 'x, 'px, '_, '_, '_, '_> { self.mods.iter() } /// _(INTERNALS)_ The current set of modules imported via `import` statements. - /// Available under the `internals` feature only. + /// Exported under the `internals` feature only. #[cfg(feature = "internals")] #[cfg(not(feature = "no_module"))] #[inline(always)] @@ -658,7 +658,7 @@ impl<'x, 'px> EvalContext<'_, 'x, 'px, '_, '_, '_, '_> { self.lib.iter().cloned() } /// _(INTERNALS)_ The current set of namespaces containing definitions of all script-defined functions. - /// Available under the `internals` feature only. + /// Exported under the `internals` feature only. #[cfg(feature = "internals")] #[inline(always)] pub fn namespaces(&self) -> &[&Module] { @@ -2433,19 +2433,22 @@ impl Engine { { use crate::ModuleResolver; + let source = state.source.as_ref().map(|s| s.as_str()); let expr_pos = expr.position(); let module = state .resolver .as_ref() - .and_then(|r| match r.resolve(self, &path, expr_pos) { + .and_then(|r| match r.resolve(self, source, &path, expr_pos) { Ok(m) => return Some(Ok(m)), Err(err) => match *err { EvalAltResult::ErrorModuleNotFound(_, _) => None, _ => return Some(Err(err)), }, }) - .unwrap_or_else(|| self.module_resolver.resolve(self, &path, expr_pos))?; + .unwrap_or_else(|| { + self.module_resolver.resolve(self, source, &path, expr_pos) + })?; if let Some(name) = export.as_ref().map(|x| x.name.clone()) { if !module.is_indexed() { diff --git a/src/engine_api.rs b/src/engine_api.rs index 3f1e1674..b1a68ea9 100644 --- a/src/engine_api.rs +++ b/src/engine_api.rs @@ -317,10 +317,10 @@ impl Engine { /// ``` #[cfg(not(feature = "no_object"))] #[inline(always)] - pub fn register_get( + pub fn register_get( &mut self, name: &str, - get_fn: impl Fn(&mut T) -> U + SendSync + 'static, + get_fn: impl Fn(&mut T) -> V + SendSync + 'static, ) -> &mut Self { use crate::engine::make_getter; self.register_fn(&make_getter(name), get_fn) @@ -364,10 +364,10 @@ impl Engine { /// ``` #[cfg(not(feature = "no_object"))] #[inline(always)] - pub fn register_get_result( + pub fn register_get_result( &mut self, name: &str, - get_fn: impl Fn(&mut T) -> Result> + SendSync + 'static, + get_fn: impl Fn(&mut T) -> Result> + SendSync + 'static, ) -> &mut Self { use crate::engine::make_getter; self.register_result_fn(&make_getter(name), get_fn) @@ -410,10 +410,10 @@ impl Engine { /// ``` #[cfg(not(feature = "no_object"))] #[inline(always)] - pub fn register_set( + pub fn register_set( &mut self, name: &str, - set_fn: impl Fn(&mut T, U) + SendSync + 'static, + set_fn: impl Fn(&mut T, V) + SendSync + 'static, ) -> &mut Self { use crate::engine::make_setter; self.register_fn(&make_setter(name), set_fn) @@ -459,13 +459,13 @@ impl Engine { /// ``` #[cfg(not(feature = "no_object"))] #[inline(always)] - pub fn register_set_result( + pub fn register_set_result( &mut self, name: &str, - set_fn: impl Fn(&mut T, U) -> Result<(), Box> + SendSync + 'static, + set_fn: impl Fn(&mut T, V) -> Result<(), Box> + SendSync + 'static, ) -> &mut Self { use crate::engine::make_setter; - self.register_result_fn(&make_setter(name), move |obj: &mut T, value: U| { + self.register_result_fn(&make_setter(name), move |obj: &mut T, value: V| { set_fn(obj, value) }) } @@ -510,11 +510,11 @@ impl Engine { /// ``` #[cfg(not(feature = "no_object"))] #[inline(always)] - pub fn register_get_set( + pub fn register_get_set( &mut self, name: &str, - get_fn: impl Fn(&mut T) -> U + SendSync + 'static, - set_fn: impl Fn(&mut T, U) + SendSync + 'static, + get_fn: impl Fn(&mut T) -> V + SendSync + 'static, + set_fn: impl Fn(&mut T, V) + SendSync + 'static, ) -> &mut Self { self.register_get(name, get_fn).register_set(name, set_fn) } @@ -562,9 +562,9 @@ impl Engine { /// ``` #[cfg(not(feature = "no_index"))] #[inline(always)] - pub fn register_indexer_get( + pub fn register_indexer_get( &mut self, - get_fn: impl Fn(&mut T, X) -> U + SendSync + 'static, + get_fn: impl Fn(&mut T, X) -> V + SendSync + 'static, ) -> &mut Self { if TypeId::of::() == TypeId::of::() { panic!("Cannot register indexer for arrays."); @@ -631,10 +631,10 @@ impl Engine { pub fn register_indexer_get_result< T: Variant + Clone, X: Variant + Clone, - U: Variant + Clone, + V: Variant + Clone, >( &mut self, - get_fn: impl Fn(&mut T, X) -> Result> + SendSync + 'static, + get_fn: impl Fn(&mut T, X) -> Result> + SendSync + 'static, ) -> &mut Self { if TypeId::of::() == TypeId::of::() { panic!("Cannot register indexer for arrays."); @@ -696,9 +696,9 @@ impl Engine { /// ``` #[cfg(not(feature = "no_index"))] #[inline(always)] - pub fn register_indexer_set( + pub fn register_indexer_set( &mut self, - set_fn: impl Fn(&mut T, X, U) + SendSync + 'static, + set_fn: impl Fn(&mut T, X, V) + SendSync + 'static, ) -> &mut Self { if TypeId::of::() == TypeId::of::() { panic!("Cannot register indexer for arrays."); @@ -766,10 +766,10 @@ impl Engine { pub fn register_indexer_set_result< T: Variant + Clone, X: Variant + Clone, - U: Variant + Clone, + V: Variant + Clone, >( &mut self, - set_fn: impl Fn(&mut T, X, U) -> Result<(), Box> + SendSync + 'static, + set_fn: impl Fn(&mut T, X, V) -> Result<(), Box> + SendSync + 'static, ) -> &mut Self { if TypeId::of::() == TypeId::of::() { panic!("Cannot register indexer for arrays."); @@ -787,7 +787,7 @@ impl Engine { self.register_result_fn( crate::engine::FN_IDX_SET, - move |obj: &mut T, index: X, value: U| set_fn(obj, index, value), + move |obj: &mut T, index: X, value: V| set_fn(obj, index, value), ) } /// Short-hand for register both index getter and setter functions for a custom type with the [`Engine`]. @@ -834,10 +834,10 @@ impl Engine { /// ``` #[cfg(not(feature = "no_index"))] #[inline(always)] - pub fn register_indexer_get_set( + pub fn register_indexer_get_set( &mut self, - get_fn: impl Fn(&mut T, X) -> U + SendSync + 'static, - set_fn: impl Fn(&mut T, X, U) -> () + SendSync + 'static, + get_fn: impl Fn(&mut T, X) -> V + SendSync + 'static, + set_fn: impl Fn(&mut T, X, V) -> () + SendSync + 'static, ) -> &mut Self { self.register_indexer_get(get_fn) .register_indexer_set(set_fn) @@ -1070,7 +1070,7 @@ impl Engine { match self .module_resolver - .resolve_ast(self, &path, Position::NONE) + .resolve_ast(self, None, &path, Position::NONE) { Some(Ok(module_ast)) => { collect_imports(&module_ast, &mut resolver, &mut imports) @@ -1081,6 +1081,7 @@ impl Engine { let module = shared_take_or_clone(self.module_resolver.resolve( self, + None, &path, Position::NONE, )?); @@ -1977,7 +1978,7 @@ impl Engine { crate::optimize::optimize_into_ast(self, scope, stmt.into_vec(), lib, optimization_level) } /// Generate a list of all registered functions. - /// Available under the `metadata` feature only. + /// Exported under the `metadata` feature only. /// /// Functions from the following sources are included, in order: /// 1) Functions registered into the global namespace diff --git a/src/fn_native.rs b/src/fn_native.rs index f14ce5e6..3625cd08 100644 --- a/src/fn_native.rs +++ b/src/fn_native.rs @@ -100,7 +100,7 @@ impl<'a> NativeCallContext<'a> { } } /// _(INTERNALS)_ Create a new [`NativeCallContext`]. - /// Available under the `internals` feature only. + /// Exported under the `internals` feature only. #[cfg(feature = "internals")] #[cfg(not(feature = "no_module"))] #[inline(always)] @@ -150,7 +150,7 @@ impl<'a> NativeCallContext<'a> { self.mods.iter().flat_map(|&m| m.iter_raw()) } /// _(INTERNALS)_ The current set of modules imported via `import` statements. - /// Available under the `internals` feature only. + /// Exported under the `internals` feature only. #[cfg(feature = "internals")] #[cfg(not(feature = "no_module"))] #[inline(always)] @@ -163,7 +163,7 @@ impl<'a> NativeCallContext<'a> { self.lib.iter().cloned() } /// _(INTERNALS)_ The current set of namespaces containing definitions of all script-defined functions. - /// Available under the `internals` feature only. + /// Exported under the `internals` feature only. #[cfg(feature = "internals")] #[inline(always)] pub fn namespaces(&self) -> &[&Module] { diff --git a/src/fn_register.rs b/src/fn_register.rs index 134e55ad..48fae4b8 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -60,15 +60,15 @@ pub trait RegisterNativeFunction { /// Get the type ID's of this function's parameters. fn param_types() -> Box<[TypeId]>; /// Get the type names of this function's parameters. - /// Available under the `metadata` feature only. + /// Exported under the `metadata` feature only. #[cfg(feature = "metadata")] fn param_names() -> Box<[&'static str]>; /// Get the type ID of this function's return value. - /// Available under the `metadata` feature only. + /// Exported under the `metadata` feature only. #[cfg(feature = "metadata")] fn return_type() -> TypeId; /// Get the type name of this function's return value. - /// Available under the `metadata` feature only. + /// Exported under the `metadata` feature only. #[cfg(feature = "metadata")] fn return_type_name() -> &'static str; } diff --git a/src/module/mod.rs b/src/module/mod.rs index 8ae91968..34b23693 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -67,7 +67,7 @@ pub struct FuncInfo { impl FuncInfo { /// Generate a signature of the function. - /// Available under the `metadata` feature only. + /// Exported under the `metadata` feature only. #[cfg(feature = "metadata")] pub fn gen_signature(&self) -> String { let mut sig = format!("{}(", self.name); @@ -363,7 +363,7 @@ impl Module { } /// Generate signatures for all the non-private functions in the [`Module`]. - /// Available under the `metadata` feature only. + /// Exported under the `metadata` feature only. #[cfg(feature = "metadata")] #[inline(always)] pub fn gen_fn_signatures(&self) -> impl Iterator + '_ { @@ -605,7 +605,7 @@ impl Module { } /// Update the metadata (parameter names/types and return type) of a registered function. - /// Available under the `metadata` feature only. + /// Exported under the `metadata` feature only. /// /// The [`u64`] hash is returned by the [`set_native_fn`][Module::set_native_fn] call. /// diff --git a/src/module/resolvers/collection.rs b/src/module/resolvers/collection.rs index 03a967b3..f974ce09 100644 --- a/src/module/resolvers/collection.rs +++ b/src/module/resolvers/collection.rs @@ -111,11 +111,12 @@ impl ModuleResolver for ModuleResolversCollection { fn resolve( &self, engine: &Engine, + source_path: Option<&str>, path: &str, pos: Position, ) -> Result, Box> { for resolver in self.0.iter() { - match resolver.resolve(engine, path, pos) { + match resolver.resolve(engine, source_path, path, pos) { Ok(module) => return Ok(module), Err(err) => match *err { EvalAltResult::ErrorModuleNotFound(_, _) => continue, diff --git a/src/module/resolvers/dummy.rs b/src/module/resolvers/dummy.rs index d238b448..157de26d 100644 --- a/src/module/resolvers/dummy.rs +++ b/src/module/resolvers/dummy.rs @@ -40,6 +40,7 @@ impl ModuleResolver for DummyModuleResolver { fn resolve( &self, _: &Engine, + _: Option<&str>, path: &str, pos: Position, ) -> Result, Box> { diff --git a/src/module/resolvers/file.rs b/src/module/resolvers/file.rs index 79ba4cf0..081a8692 100644 --- a/src/module/resolvers/file.rs +++ b/src/module/resolvers/file.rs @@ -7,6 +7,8 @@ use crate::stdlib::{ }; use crate::{Engine, EvalAltResult, Module, ModuleResolver, Position, Shared}; +pub const RHAI_SCRIPT_EXTENSION: &str = "rhai"; + /// A [module][Module] resolution service that loads [module][Module] script files from the file system. /// /// ## Caching @@ -39,7 +41,7 @@ use crate::{Engine, EvalAltResult, Module, ModuleResolver, Position, Shared}; /// ``` #[derive(Debug)] pub struct FileModuleResolver { - base_path: PathBuf, + base_path: Option, extension: String, cache_enabled: bool, @@ -52,11 +54,33 @@ pub struct FileModuleResolver { impl Default for FileModuleResolver { #[inline(always)] fn default() -> Self { - Self::new_with_path(PathBuf::default()) + Self::new() } } impl FileModuleResolver { + /// Create a new [`FileModuleResolver`] with the current directory as base path. + /// + /// The default extension is `.rhai`. + /// + /// # 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(resolver); + /// ``` + #[inline(always)] + pub fn new() -> Self { + Self::new_with_extension(RHAI_SCRIPT_EXTENSION) + } + /// Create a new [`FileModuleResolver`] with a specific base path. /// /// The default extension is `.rhai`. @@ -76,7 +100,31 @@ impl FileModuleResolver { /// ``` #[inline(always)] pub fn new_with_path(path: impl Into) -> Self { - Self::new_with_path_and_extension(path, "rhai") + Self::new_with_path_and_extension(path, RHAI_SCRIPT_EXTENSION) + } + + /// Create a new [`FileModuleResolver`] with a file extension. + /// + /// # Example + /// + /// ``` + /// use rhai::Engine; + /// use rhai::module_resolvers::FileModuleResolver; + /// + /// // Create a new 'FileModuleResolver' loading scripts with file extension '.rhai' (the default). + /// let resolver = FileModuleResolver::new_with_extension("rhai"); + /// + /// let mut engine = Engine::new(); + /// engine.set_module_resolver(resolver); + /// ``` + #[inline(always)] + pub fn new_with_extension(extension: impl Into) -> Self { + Self { + base_path: None, + extension: extension.into(), + cache_enabled: true, + cache: Default::default(), + } } /// Create a new [`FileModuleResolver`] with a specific base path and file extension. @@ -100,44 +148,22 @@ impl FileModuleResolver { extension: impl Into, ) -> Self { Self { - base_path: path.into(), + base_path: Some(path.into()), extension: extension.into(), cache_enabled: true, cache: Default::default(), } } - /// Create a new [`FileModuleResolver`] with the current directory as base path. - /// - /// The default extension is `.rhai`. - /// - /// # 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(resolver); - /// ``` - #[inline(always)] - pub fn new() -> Self { - Default::default() - } - /// Get the base path for script files. #[inline(always)] - pub fn base_path(&self) -> &Path { - self.base_path.as_ref() + pub fn base_path(&self) -> Option<&Path> { + self.base_path.as_ref().map(PathBuf::as_ref) } /// Set the base path for script files. #[inline(always)] pub fn set_base_path(&mut self, path: impl Into) -> &mut Self { - self.base_path = path.into(); + self.base_path = Some(path.into()); self } @@ -168,12 +194,12 @@ impl FileModuleResolver { /// Is a particular path cached? #[inline(always)] - pub fn is_cached(&self, path: &str) -> bool { + pub fn is_cached(&self, path: &str, source_path: Option<&str>) -> bool { if !self.cache_enabled { return false; } - let file_path = self.get_file_path(path); + let file_path = self.get_file_path(path, source_path); #[cfg(not(feature = "sync"))] return self.cache.borrow_mut().contains_key(&file_path); @@ -192,8 +218,12 @@ impl FileModuleResolver { /// /// The next time this path is resolved, the script file will be loaded once again. #[inline(always)] - pub fn clear_cache_for_path(&mut self, path: &str) -> Option> { - let file_path = self.get_file_path(path); + pub fn clear_cache_for_path( + &mut self, + path: &str, + source_path: Option<&str>, + ) -> Option> { + let file_path = self.get_file_path(path, source_path); #[cfg(not(feature = "sync"))] return self @@ -210,9 +240,22 @@ impl FileModuleResolver { .map(|(_, v)| v); } /// Construct a full file path. - fn get_file_path(&self, path: &str) -> PathBuf { - let mut file_path = self.base_path.clone(); - file_path.push(path); + fn get_file_path(&self, path: &str, source_path: Option<&str>) -> PathBuf { + let path = Path::new(path); + + let mut file_path; + + if path.is_relative() { + file_path = self + .base_path + .clone() + .or_else(|| source_path.map(|p| p.into())) + .unwrap_or_default(); + file_path.push(path); + } else { + file_path = path.into(); + } + file_path.set_extension(&self.extension); // Force extension file_path } @@ -222,11 +265,16 @@ impl ModuleResolver for FileModuleResolver { fn resolve( &self, engine: &Engine, + source_path: Option<&str>, path: &str, pos: Position, ) -> Result, Box> { + // Load relative paths from source if there is no base path specified + let source_path = + source_path.and_then(|p| Path::new(p).parent().map(|p| p.to_string_lossy())); + // Construct the script file path - let file_path = self.get_file_path(path); + let file_path = self.get_file_path(path, source_path.as_ref().map(|p| p.as_ref())); // See if it is cached if self.is_cache_enabled() { @@ -276,11 +324,12 @@ impl ModuleResolver for FileModuleResolver { fn resolve_ast( &self, engine: &Engine, + source_path: Option<&str>, path: &str, pos: Position, ) -> Option>> { // Construct the script file path - let file_path = self.get_file_path(path); + let file_path = self.get_file_path(path, source_path); // Load the script file and compile it match engine.compile_file(file_path).map_err(|err| match *err { diff --git a/src/module/resolvers/mod.rs b/src/module/resolvers/mod.rs index 84b3448f..56eb6c03 100644 --- a/src/module/resolvers/mod.rs +++ b/src/module/resolvers/mod.rs @@ -25,6 +25,7 @@ pub trait ModuleResolver: SendSync { fn resolve( &self, engine: &Engine, + source_path: Option<&str>, path: &str, pos: Position, ) -> Result, Box>; @@ -42,6 +43,7 @@ pub trait ModuleResolver: SendSync { fn resolve_ast( &self, engine: &Engine, + source_path: Option<&str>, path: &str, pos: Position, ) -> Option>> { diff --git a/src/module/resolvers/stat.rs b/src/module/resolvers/stat.rs index 89cc458f..e8a0c705 100644 --- a/src/module/resolvers/stat.rs +++ b/src/module/resolvers/stat.rs @@ -115,6 +115,7 @@ impl ModuleResolver for StaticModuleResolver { fn resolve( &self, _: &Engine, + _: Option<&str>, path: &str, pos: Position, ) -> Result, Box> { diff --git a/src/serde/metadata.rs b/src/serde/metadata.rs index dbb92ce3..96b67acd 100644 --- a/src/serde/metadata.rs +++ b/src/serde/metadata.rs @@ -212,7 +212,7 @@ impl From<&crate::Module> for ModuleMetadata { impl Engine { /// _(METADATA)_ Generate a list of all functions (including those defined in an /// [`AST`][crate::AST]) in JSON format. - /// Available under the `metadata` feature only. + /// Exported under the `metadata` feature only. /// /// Functions from the following sources are included: /// 1) Functions defined in an [`AST`][crate::AST] @@ -221,9 +221,10 @@ impl Engine { /// 4) Functions in global modules (optional) pub fn gen_fn_metadata_with_ast_to_json( &self, - _ast: &AST, + ast: &AST, include_global: bool, ) -> serde_json::Result { + let _ast = ast; let mut global: ModuleMetadata = Default::default(); if include_global {