diff --git a/src/definition.rs b/src/definition.rs index c900293..4bd24e4 100644 --- a/src/definition.rs +++ b/src/definition.rs @@ -10,29 +10,29 @@ use rquickjs::{ /// /// ``` /// use rquickjs::{Ctx, JsLifetime, Object, Result}; -/// use rquickjs_module::{ModuleDefExt, ModuleImpl}; +/// use rquickjs_extension::{Extension, ModuleImpl}; /// /// #[derive(JsLifetime, Debug)] -/// struct MyModuleOptions { +/// struct MyExtensionOptions { /// user: String, /// } /// -/// struct MyModule { -/// options: MyModuleOptions, +/// struct MyExtension { +/// options: MyExtensionOptions, /// } /// -/// impl MyModule { +/// impl MyExtension { /// pub fn new>(user: T) -> Self { /// Self { -/// options: MyModuleOptions { +/// options: MyExtensionOptions { /// user: user.into(), /// }, /// } /// } /// } /// -/// impl ModuleDefExt for MyModule { -/// type Implementation = ModuleImpl; +/// impl Extension for MyExtension { +/// type Implementation = ModuleImpl; /// /// fn implementation() -> &'static Self::Implementation { /// &ModuleImpl { @@ -48,11 +48,11 @@ use rquickjs::{ /// } /// } /// -/// fn options(self) -> MyModuleOptions { +/// fn options(self) -> MyExtensionOptions { /// self.options /// } /// -/// fn globals(globals: &Object<'_>, options: &MyModuleOptions) -> Result<()> { +/// fn globals(globals: &Object<'_>, options: &MyExtensionOptions) -> Result<()> { /// globals.set("user", options.user.clone())?; /// Ok(()) /// } diff --git a/src/lib.rs b/src/lib.rs index c52fd51..e358d46 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,7 +8,7 @@ //! that would allow it to have options as input and set global. pub use self::definition::{Extension, GlobalsOnly, ModuleImpl}; -pub use self::loader::{GlobalInitializer, ModuleLoader, ModuleLoaderBuilder, ModuleResolver}; +pub use self::loader::{ExtensionBuilder, GlobalInitializer, ModuleLoader, ModuleResolver}; mod definition; mod loader; diff --git a/src/loader/builder.rs b/src/loader/builder.rs deleted file mode 100644 index 773691e..0000000 --- a/src/loader/builder.rs +++ /dev/null @@ -1,128 +0,0 @@ -use std::collections::{HashMap, HashSet}; - -use rquickjs::{ - module::{Module, ModuleDef}, - Ctx, JsLifetime, Object, Result, -}; - -use super::{GlobalInitializer, GlobalLoadFn, ModuleLoadFn, ModuleLoader, ModuleResolver}; -use crate::wrapper::{IntoModule, ModuleMeta}; - -fn load_module_func(ctx: Ctx<'_>, name: Vec) -> Result> { - Module::declare_def::(ctx, name) -} - -/// Builder to create a [`ModuleLoader`], [`ModuleResolver`] and [`GlobalInitializer`] -/// -/// # Example -/// ```rust -/// use rquickjs_module::{ModuleLoader, ModuleDefExt, ModuleImpl}; -/// -/// struct MyModule; -/// -/// impl ModuleDefExt for MyModule { -/// type Implementation = ModuleImpl<()>; -/// -/// fn implementation() -> &'static Self::Implementation { -/// &ModuleImpl { -/// declare: |decl| { -/// decl.declare("hello")?; -/// Ok(()) -/// }, -/// evaluate: |ctx, exports, options| { -/// exports.export("hello", "world".to_string())?; -/// Ok(()) -/// }, -/// name: "my-module", -/// } -/// } -/// -/// fn options(self) -> () {} -/// } -/// -/// ``` -#[derive(Default)] -pub struct ModuleLoaderBuilder { - modules: HashMap<&'static str, ModuleLoadFn>, - globals: Vec, - names: HashSet<&'static str>, -} - -impl ModuleLoaderBuilder { - #[must_use] - pub fn with_module(mut self, module: M) -> Self - where - for<'js> O: JsLifetime<'js> + Send + Sync + 'static, - R: ModuleDef + ModuleMeta, - M: IntoModule, - { - self.process_module(module, None); - self - } - - #[must_use] - pub fn with_module_named(mut self, module: M, name: &'static str) -> Self - where - for<'js> O: JsLifetime<'js> + Send + Sync + 'static, - R: ModuleDef + ModuleMeta, - M: IntoModule, - { - self.process_module(module, Some(name)); - self - } - - pub fn add_module(&mut self, module: M) -> &mut Self - where - for<'js> O: JsLifetime<'js> + Send + Sync + 'static, - R: ModuleDef + ModuleMeta, - M: IntoModule, - { - self.process_module(module, None) - } - - pub fn add_module_named(&mut self, module: M, name: &'static str) -> &mut Self - where - for<'js> O: JsLifetime<'js> + Send + Sync + 'static, - R: ModuleDef + ModuleMeta, - M: IntoModule, - { - self.process_module(module, Some(name)) - } - - fn process_module(&mut self, module: M, name: Option<&'static str>) -> &mut Self - where - for<'js> O: JsLifetime<'js> + Send + Sync + 'static, - R: ModuleDef + ModuleMeta, - M: IntoModule, - { - let o = module.options(); - - // Create a new closure that explicitly captures 'js lifetime - let globals_fn = move |ctx: &Ctx<'_>, globals: &Object<'_>| { - let globals_fn = M::globals; - globals_fn(globals, &o)?; - let _ = ctx.store_userdata(o); - Ok(()) - }; - - // Box the closure with explicit lifetime bounds - let boxed_globals: GlobalLoadFn = Box::new(globals_fn); - - if R::is_module() { - let name = name.unwrap_or(R::name()); - self.names.insert(name); - self.modules.insert(name, load_module_func::); - } - - self.globals.push(boxed_globals); - self - } - - pub fn build(self) -> (ModuleLoader, ModuleResolver, GlobalInitializer) { - ( - ModuleLoader::new(self.modules), - ModuleResolver::new(self.names), - GlobalInitializer::new(self.globals), - ) - } -} diff --git a/src/loader/loader.rs b/src/loader/loader.rs new file mode 100644 index 0000000..37827cc --- /dev/null +++ b/src/loader/loader.rs @@ -0,0 +1,30 @@ +use std::collections::HashMap; + +use rquickjs::{loader::Loader, Ctx, Error, Module, Result}; + +use super::ModuleLoadFn; + +/// Rquickjs [`Loader`](rquickjs::loader::Loader) for Rust modules +/// defined using [`ModuleDefExt`](crate::ModuleDefExt). +/// +/// See [`ModuleLoaderBuilder`] for usage. +pub struct ModuleLoader { + modules: HashMap<&'static str, ModuleLoadFn>, +} + +impl ModuleLoader { + pub(crate) fn new(modules: HashMap<&'static str, ModuleLoadFn>) -> Self { + Self { modules } + } +} + +impl Loader for ModuleLoader { + fn load<'js>(&mut self, ctx: &Ctx<'js>, path: &str) -> Result> { + let load = self + .modules + .remove(path) + .ok_or_else(|| Error::new_loading(path))?; + + (load)(ctx.clone(), Vec::from(path)) + } +} diff --git a/src/loader/mod.rs b/src/loader/mod.rs index 0e81702..158a124 100644 --- a/src/loader/mod.rs +++ b/src/loader/mod.rs @@ -1,43 +1,142 @@ -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; -use rquickjs::{loader::Loader, Ctx, Error, Module, Object, Result}; +use rquickjs::{ + module::{Module, ModuleDef}, + Ctx, JsLifetime, Object, Result, +}; -pub use self::builder::ModuleLoaderBuilder; pub use self::global::GlobalInitializer; +pub use self::loader::ModuleLoader; pub use self::resolver::ModuleResolver; +use crate::wrapper::{IntoModule, ModuleMeta}; -mod builder; mod global; +#[allow(clippy::module_inception)] +mod loader; mod resolver; type GlobalLoadFn = Box FnOnce(&Ctx<'js>, &Object<'js>) -> Result<()> + Send + Sync>; type ModuleLoadFn = for<'js> fn(Ctx<'js>, Vec) -> Result>; -/// Rquickjs [`Loader`](rquickjs::loader::Loader) for Rust modules -/// defined using [`ModuleDefExt`](crate::ModuleDefExt). +fn load_module_func(ctx: Ctx<'_>, name: Vec) -> Result> { + Module::declare_def::(ctx, name) +} + +/// Builder to create a [`ModuleLoader`], [`ModuleResolver`] and [`GlobalInitializer`] +/// +/// # Example +/// ```rust +/// use rquickjs_module::{ModuleLoader, ModuleDefExt, ModuleImpl}; +/// +/// struct MyExtension; +/// +/// impl Extension for MyExtension { +/// type Implementation = ModuleImpl<()>; /// -/// See [`ModuleLoaderBuilder`] for usage. -pub struct ModuleLoader { +/// fn implementation() -> &'static Self::Implementation { +/// &ModuleImpl { +/// declare: |decl| { +/// decl.declare("hello")?; +/// Ok(()) +/// }, +/// evaluate: |ctx, exports, options| { +/// exports.export("hello", "world".to_string())?; +/// Ok(()) +/// }, +/// name: "my-module", +/// } +/// } +/// +/// fn options(self) -> () {} +/// } +/// +/// ``` +#[derive(Default)] +pub struct ExtensionBuilder { modules: HashMap<&'static str, ModuleLoadFn>, + globals: Vec, + names: HashSet<&'static str>, } -impl ModuleLoader { - pub(crate) fn new(modules: HashMap<&'static str, ModuleLoadFn>) -> Self { - Self { modules } +impl ExtensionBuilder { + pub fn new() -> Self { + Self::default() } - pub fn builder() -> ModuleLoaderBuilder { - ModuleLoaderBuilder::default() + #[must_use] + pub fn with_extension(mut self, extension: M) -> Self + where + for<'js> O: JsLifetime<'js> + Send + Sync + 'static, + R: ModuleDef + ModuleMeta, + M: IntoModule, + { + self.process_extension(extension, None); + self + } + + #[must_use] + pub fn with_extension_named(mut self, extension: M, name: &'static str) -> Self + where + for<'js> O: JsLifetime<'js> + Send + Sync + 'static, + R: ModuleDef + ModuleMeta, + M: IntoModule, + { + self.process_extension(extension, Some(name)); + self } -} -impl Loader for ModuleLoader { - fn load<'js>(&mut self, ctx: &Ctx<'js>, path: &str) -> Result> { - let load = self - .modules - .remove(path) - .ok_or_else(|| Error::new_loading(path))?; + pub fn add_extension(&mut self, extension: M) -> &mut Self + where + for<'js> O: JsLifetime<'js> + Send + Sync + 'static, + R: ModuleDef + ModuleMeta, + M: IntoModule, + { + self.process_extension(extension, None) + } + + pub fn add_extension_named(&mut self, extension: M, name: &'static str) -> &mut Self + where + for<'js> O: JsLifetime<'js> + Send + Sync + 'static, + R: ModuleDef + ModuleMeta, + M: IntoModule, + { + self.process_extension(extension, Some(name)) + } + + fn process_extension(&mut self, extension: M, name: Option<&'static str>) -> &mut Self + where + for<'js> O: JsLifetime<'js> + Send + Sync + 'static, + R: ModuleDef + ModuleMeta, + M: IntoModule, + { + let o = extension.options(); + + // Create a new closure that explicitly captures 'js lifetime + let globals_fn = move |ctx: &Ctx<'_>, globals: &Object<'_>| { + let globals_fn = M::globals; + globals_fn(globals, &o)?; + let _ = ctx.store_userdata(o); + Ok(()) + }; + + // Box the closure with explicit lifetime bounds + let boxed_globals: GlobalLoadFn = Box::new(globals_fn); + + if R::is_module() { + let name = name.unwrap_or(R::name()); + self.names.insert(name); + self.modules.insert(name, load_module_func::); + } + + self.globals.push(boxed_globals); + self + } - (load)(ctx.clone(), Vec::from(path)) + pub fn build(self) -> (ModuleLoader, ModuleResolver, GlobalInitializer) { + ( + ModuleLoader::new(self.modules), + ModuleResolver::new(self.names), + GlobalInitializer::new(self.globals), + ) } } diff --git a/src/wrapper/mod.rs b/src/wrapper/mod.rs index 2f6d2a3..9e10f7d 100644 --- a/src/wrapper/mod.rs +++ b/src/wrapper/mod.rs @@ -8,7 +8,7 @@ mod module; /// Module metadata /// /// We use this trait to still access metadata once we have -/// converted it from a [`ModuleDefExt`] to a [`ModuleDef`]. +/// converted it from an [`Extension`] to a [`ModuleDef`]. /// /// This is necessary for the loader to work. pub trait ModuleMeta { @@ -16,7 +16,7 @@ pub trait ModuleMeta { fn is_module() -> bool; } -/// Semantically convert a [`ModuleDefExt`] to a [`ModuleDef`] and [`ModuleMeta`] +/// Semantically convert an [`Extension`] to a [`ModuleDef`] and [`ModuleMeta`] pub trait IntoModule where Self: Extension, diff --git a/tests/globals.rs b/tests/globals.rs index 09d7613..2aa6df1 100644 --- a/tests/globals.rs +++ b/tests/globals.rs @@ -1,5 +1,5 @@ use rquickjs::{async_with, AsyncContext, AsyncRuntime, CatchResultExt, Object, Result}; -use rquickjs_extension::{globals_only_module, GlobalsOnly, Extension, ModuleLoader}; +use rquickjs_extension::{globals_only_module, Extension, ExtensionBuilder, GlobalsOnly}; use self::common::{Printer, PrinterOptions}; @@ -46,8 +46,8 @@ globals_only_module!(PrinterModule2, |globals| { async fn test_global() { let rt = AsyncRuntime::new().unwrap(); - let (loader, resolver, initalizer) = ModuleLoader::builder() - .with_module(PrinterModule::new("world")) + let (loader, resolver, initalizer) = ExtensionBuilder::new() + .with_extension(PrinterModule::new("world")) .build(); rt.set_loader(resolver, loader).await; diff --git a/tests/module.rs b/tests/module.rs index c099b10..6775216 100644 --- a/tests/module.rs +++ b/tests/module.rs @@ -1,7 +1,7 @@ use rquickjs::{ async_with, AsyncContext, AsyncRuntime, CatchResultExt, Function, Module, Object, Result, }; -use rquickjs_extension::{Extension, ModuleImpl, ModuleLoader}; +use rquickjs_extension::{Extension, ExtensionBuilder, ModuleImpl}; use self::common::{Printer, PrinterOptions}; @@ -52,8 +52,8 @@ impl Extension for PrinterModule { async fn test_module() { let rt = AsyncRuntime::new().unwrap(); - let (loader, resolver, initalizer) = ModuleLoader::builder() - .with_module(PrinterModule::new("john")) + let (loader, resolver, initalizer) = ExtensionBuilder::new() + .with_extension(PrinterModule::new("john")) .build(); rt.set_loader(resolver, loader).await; @@ -80,8 +80,8 @@ async fn test_module() { async fn test_module_named() { let rt = AsyncRuntime::new().unwrap(); - let (loader, resolver, initalizer) = ModuleLoader::builder() - .with_module_named(PrinterModule::new("arnold"), "custom_printer") + let (loader, resolver, initalizer) = ExtensionBuilder::new() + .with_extension_named(PrinterModule::new("arnold"), "custom_printer") .build(); rt.set_loader(resolver, loader).await; @@ -108,8 +108,8 @@ async fn test_module_named() { async fn test_module_global() { let rt = AsyncRuntime::new().unwrap(); - let (loader, resolver, initalizer) = ModuleLoader::builder() - .with_module(PrinterModule::new("david")) + let (loader, resolver, initalizer) = ExtensionBuilder::new() + .with_extension(PrinterModule::new("david")) .build(); rt.set_loader(resolver, loader).await;