From b093626a21fcc72fc30101d6000d10fdba97934c Mon Sep 17 00:00:00 2001 From: Lucien Greathouse Date: Fri, 11 Oct 2019 15:45:02 -0700 Subject: [PATCH] User plugin foundations for 0.6.0 (#257) Starts work on #55. This is similar to the previous work in #125. It's gated behind a new Cargo feature, `user-plugins`. This time, the config gate is much smaller. The `plugins` member of projects is still accessible when plugins aren't enabled, but is always empty. Additionally, user plugins are only enabled if there's a Lua state present in the snapshot context when the `SnapshotUserPlugins` snapshot middleware runs. This not ever the case currently. This code has very little possibility of rotting while we focus on other work, since it'll be guaranteed to still compile and can be tested in isolation without the feature being turned on. --- Cargo.lock | 22 ++++ Cargo.toml | 5 + src/change_processor.rs | 7 +- src/commands/build.rs | 5 +- src/commands/upload.rs | 5 +- src/project.rs | 28 +++++ src/serve_session.rs | 10 +- src/snapshot_middleware/context.rs | 49 ++++++++- src/snapshot_middleware/dir.rs | 4 +- src/snapshot_middleware/error.rs | 30 +++++- src/snapshot_middleware/mod.rs | 10 +- src/snapshot_middleware/project.rs | 8 +- src/snapshot_middleware/user_plugins.rs | 114 +++++++++++++++++++++ test-projects/plugins/default.project.json | 9 ++ test-projects/plugins/src/hello.moon | 1 + test-projects/plugins/test-plugin.lua | 20 ++++ tests/read_projects.rs | 1 + 17 files changed, 306 insertions(+), 22 deletions(-) create mode 100644 src/snapshot_middleware/user_plugins.rs create mode 100644 test-projects/plugins/default.project.json create mode 100644 test-projects/plugins/src/hello.moon create mode 100644 test-projects/plugins/test-plugin.lua diff --git a/Cargo.lock b/Cargo.lock index 9f2c1f6a..3d072dec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -920,6 +920,14 @@ dependencies = [ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "num-traits" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "num_cpus" version = "1.10.1" @@ -1450,6 +1458,17 @@ dependencies = [ "snax 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "rlua" +version = "0.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rojo" version = "0.6.0-dev" @@ -1477,6 +1496,7 @@ dependencies = [ "regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "reqwest 0.9.21 (registry+https://github.com/rust-lang/crates.io-index)", "ritz 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rlua 0.16.3 (registry+https://github.com/rust-lang/crates.io-index)", "rojo-insta-ext 0.1.0", "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2294,6 +2314,7 @@ dependencies = [ "checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88" "checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945" "checksum notify 4.0.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1191efa2b8fe041decb55c238a125b7a1aeb6fad7a525133a02be5ec949ff3cb" +"checksum num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "6ba9a427cfca2be13aa6f6403b0b7e7368fe982bfa16fccc450ce74c46cd9b32" "checksum num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bcef43580c035376c0705c42792c294b66974abbfd2789b511784023f71f3273" "checksum opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" "checksum openssl 0.10.25 (registry+https://github.com/rust-lang/crates.io-index)" = "2f372b2b53ce10fb823a337aaa674e3a7d072b957c6264d0f4ff0bd86e657449" @@ -2350,6 +2371,7 @@ dependencies = [ "checksum reqwest 0.9.21 (registry+https://github.com/rust-lang/crates.io-index)" = "02b7e953e14c6f3102b7e8d1f1ee3abf5ecee80b427f5565c9389835cecae95c" "checksum ritz 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e7b52479a28408dacd24819d32f3562146b5f03eb0a06a8b2d7b11e34fbfe52d" "checksum ritz_impl 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fa7bba143ce94ca7e580094b8c4f6338b960b3bfa5ad7f479700e414b24d5d7b" +"checksum rlua 0.16.3 (registry+https://github.com/rust-lang/crates.io-index)" = "62fc0e980c94fe9ef795b1bb3874649c8c6e9bb67d3b90d48380ba24c69c23ea" "checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" "checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" "checksum ryu 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c92464b447c0ee8c4fb3824ecc8383b81717b9f1e74ba2e72540aef7b9f82997" diff --git a/Cargo.toml b/Cargo.toml index e52c5fae..3408bb39 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,10 @@ default = [] # Enable this feature to live-reload assets from the web UI. dev-live-assets = [] +# Enables specifying user plugins in the project file. User plugins are Lua +# files and currently not fully implemented. See issue #55. +user-plugins = [] + [workspace] members = [ "rojo-test", @@ -58,6 +62,7 @@ rbx_xml = "0.11.0" regex = "1.0" reqwest = "0.9.20" ritz = "0.1.0" +rlua = "0.16.3" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" termcolor = "1.0.5" diff --git a/src/change_processor.rs b/src/change_processor.rs index 533a982c..e0ad8496 100644 --- a/src/change_processor.rs +++ b/src/change_processor.rs @@ -12,7 +12,7 @@ use crate::{ imfs::{Imfs, ImfsEvent, ImfsFetcher}, message_queue::MessageQueue, snapshot::{apply_patch_set, compute_patch_set, AppliedPatchSet, InstigatingSource, RojoTree}, - snapshot_middleware::snapshot_from_imfs, + snapshot_middleware::{snapshot_from_imfs, InstanceSnapshotContext}, }; pub struct ChangeProcessor { @@ -99,7 +99,10 @@ impl ChangeProcessor { .get(path) .expect("could not get instigating path from filesystem"); - let snapshot = snapshot_from_imfs(&mut imfs, &entry) + // TODO: Use persisted snapshot + // context struct instead of + // recreating it every time. + let snapshot = snapshot_from_imfs(&mut InstanceSnapshotContext::default(), &mut imfs, &entry) .expect("snapshot failed") .expect("snapshot did not return an instance"); diff --git a/src/commands/build.rs b/src/commands/build.rs index f3dad7bd..de10ef34 100644 --- a/src/commands/build.rs +++ b/src/commands/build.rs @@ -11,7 +11,7 @@ use rbx_dom_weak::RbxInstanceProperties; use crate::{ imfs::{FsError, Imfs, RealFetcher, WatchMode}, snapshot::{apply_patch_set, compute_patch_set, InstancePropertiesWithMeta, RojoTree}, - snapshot_middleware::snapshot_from_imfs, + snapshot_middleware::{snapshot_from_imfs, InstanceSnapshotContext}, }; #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -96,8 +96,9 @@ pub fn build(options: &BuildOptions) -> Result<(), BuildError> { .get(&options.fuzzy_project_path) .expect("could not get project path"); + // TODO: Compute snapshot context from project. log::trace!("Generating snapshot of instances from IMFS"); - let snapshot = snapshot_from_imfs(&mut imfs, &entry) + let snapshot = snapshot_from_imfs(&mut InstanceSnapshotContext::default(), &mut imfs, &entry) .expect("snapshot failed") .expect("snapshot did not return an instance"); diff --git a/src/commands/upload.rs b/src/commands/upload.rs index 0aaee43e..ded0446a 100644 --- a/src/commands/upload.rs +++ b/src/commands/upload.rs @@ -8,7 +8,7 @@ use crate::{ auth_cookie::get_auth_cookie, imfs::{Imfs, RealFetcher, WatchMode}, snapshot::{apply_patch_set, compute_patch_set, InstancePropertiesWithMeta, RojoTree}, - snapshot_middleware::snapshot_from_imfs, + snapshot_middleware::{snapshot_from_imfs, InstanceSnapshotContext}, }; #[derive(Debug, Fail)] @@ -63,8 +63,9 @@ pub fn upload(options: UploadOptions) -> Result<(), UploadError> { .get(&options.fuzzy_project_path) .expect("could not get project path"); + // TODO: Compute snapshot context from project. log::trace!("Generating snapshot of instances from IMFS"); - let snapshot = snapshot_from_imfs(&mut imfs, &entry) + let snapshot = snapshot_from_imfs(&mut InstanceSnapshotContext::default(), &mut imfs, &entry) .expect("snapshot failed") .expect("snapshot did not return an instance"); diff --git a/src/project.rs b/src/project.rs index ba0d6616..fa2e7cc8 100644 --- a/src/project.rs +++ b/src/project.rs @@ -30,6 +30,10 @@ struct SourceProject { #[serde(skip_serializing_if = "Option::is_none")] serve_place_ids: Option>, + + #[serde(default, skip_serializing_if = "Vec::is_empty")] + #[cfg_attr(not(feature = "user-plugins"), serde(skip_deserializing))] + plugins: Vec, } impl SourceProject { @@ -37,11 +41,19 @@ impl SourceProject { pub fn into_project(self, project_file_location: &Path) -> Project { let tree = self.tree.into_project_node(project_file_location); + let project_folder = project_file_location.parent().unwrap(); + let plugins = self + .plugins + .into_iter() + .map(|path| project_folder.join(path)) + .collect(); + Project { name: self.name, tree, serve_port: self.serve_port, serve_place_ids: self.serve_place_ids, + plugins, file_location: PathBuf::from(project_file_location), } } @@ -318,6 +330,7 @@ pub struct Project { pub tree: ProjectNode, pub serve_port: Option, pub serve_place_ids: Option>, + pub plugins: Vec, pub file_location: PathBuf, } @@ -391,6 +404,7 @@ impl Project { tree, serve_port: None, serve_place_ids: None, + plugins: Vec::new(), file_location: project_path.clone(), }; @@ -557,10 +571,24 @@ impl Project { } fn to_source_project(&self) -> SourceProject { + // TODO: Use path_serializer instead of transforming paths between + // String and PathBuf? + let plugins = self + .plugins + .iter() + .map(|path| { + path.strip_prefix(self.folder_location()) + .unwrap() + .display() + .to_string() + }) + .collect(); + SourceProject { name: self.name.clone(), tree: self.tree.to_source_node(&self.file_location), serve_port: self.serve_port, + plugins, serve_place_ids: self.serve_place_ids.clone(), } } diff --git a/src/serve_session.rs b/src/serve_session.rs index 30438c26..ebac8567 100644 --- a/src/serve_session.rs +++ b/src/serve_session.rs @@ -16,7 +16,7 @@ use crate::{ snapshot::{ apply_patch_set, compute_patch_set, AppliedPatchSet, InstancePropertiesWithMeta, RojoTree, }, - snapshot_middleware::snapshot_from_imfs, + snapshot_middleware::{snapshot_from_imfs, InstanceSnapshotContext}, }; /// Contains all of the state for a Rojo serve session. @@ -111,10 +111,12 @@ impl ServeSession { log::trace!("Loading start path: {}", start_path.display()); let entry = imfs.get(start_path).expect("could not get project path"); + // TODO: Compute snapshot context from project. log::trace!("Snapshotting start path"); - let snapshot = snapshot_from_imfs(&mut imfs, &entry) - .expect("snapshot failed") - .expect("snapshot did not return an instance"); + let snapshot = + snapshot_from_imfs(&mut InstanceSnapshotContext::default(), &mut imfs, &entry) + .expect("snapshot failed") + .expect("snapshot did not return an instance"); log::trace!("Computing initial patch set"); let patch_set = compute_patch_set(&snapshot, &tree, root_id); diff --git a/src/snapshot_middleware/context.rs b/src/snapshot_middleware/context.rs index b10e7192..0a7179a8 100644 --- a/src/snapshot_middleware/context.rs +++ b/src/snapshot_middleware/context.rs @@ -1,8 +1,12 @@ +use std::{fmt, ops::Deref, path::PathBuf}; + +use rlua::{Lua, RegistryKey}; + #[derive(Debug)] pub struct InstanceSnapshotContext { /// Empty struct that will be used later to fill out required Lua state for /// user plugins. - pub plugin_context: Option<()>, + pub plugin_context: Option, } impl Default for InstanceSnapshotContext { @@ -13,4 +17,47 @@ impl Default for InstanceSnapshotContext { } } +#[derive(Debug)] +pub struct SnapshotPluginContext { + pub state: IgnoreDebug, + + /// Paths to the user plugins files. These paths are generated by the root + /// project file, if there is one. + pub plugin_paths: Vec, + + /// Lazy-initialized registry keys pointing to the values returned by each + /// user plugin. When processing user plugins, these should be applied in + /// order. + pub plugin_functions: Option>, +} + +impl SnapshotPluginContext { + pub fn new(plugin_paths: Vec) -> Self { + Self { + state: IgnoreDebug(Lua::new()), + plugin_paths, + plugin_functions: None, + } + } +} + +/// Utility type to enable having a field of a struct not implement Debug and +/// instead show a placeholder. +#[derive(Clone)] +pub struct IgnoreDebug(pub T); + +impl fmt::Debug for IgnoreDebug { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + write!(formatter, "") + } +} + +impl Deref for IgnoreDebug { + type Target = T; + + fn deref(&self) -> &T { + &self.0 + } +} + pub struct ImfsSnapshotContext; diff --git a/src/snapshot_middleware/dir.rs b/src/snapshot_middleware/dir.rs index 1c36dc6f..7f4cab39 100644 --- a/src/snapshot_middleware/dir.rs +++ b/src/snapshot_middleware/dir.rs @@ -18,7 +18,7 @@ pub struct SnapshotDir; impl SnapshotMiddleware for SnapshotDir { fn from_imfs( - _context: &mut InstanceSnapshotContext, + context: &mut InstanceSnapshotContext, imfs: &mut Imfs, entry: &ImfsEntry, ) -> SnapshotInstanceResult<'static> { @@ -31,7 +31,7 @@ impl SnapshotMiddleware for SnapshotDir { let mut snapshot_children = Vec::new(); for child in children.into_iter() { - if let Some(child_snapshot) = snapshot_from_imfs(imfs, &child)? { + if let Some(child_snapshot) = snapshot_from_imfs(context, imfs, &child)? { snapshot_children.push(child_snapshot); } } diff --git a/src/snapshot_middleware/error.rs b/src/snapshot_middleware/error.rs index 3f1805de..c2a72b59 100644 --- a/src/snapshot_middleware/error.rs +++ b/src/snapshot_middleware/error.rs @@ -16,6 +16,13 @@ impl SnapshotError { } } + pub(crate) fn wrap(inner: impl Into, path: impl Into) -> Self { + SnapshotError { + detail: inner.into(), + path: Some(path.into()), + } + } + pub(crate) fn file_did_not_exist(path: impl Into) -> SnapshotError { SnapshotError { detail: SnapshotErrorDetail::FileDidNotExist, @@ -70,27 +77,45 @@ impl From for SnapshotError { fn from(error: FsError) -> Self { let (inner, path) = error.into_raw(); - let detail = SnapshotErrorDetail::IoError { inner }; + Self::new(inner.into(), Some(path)) + } +} - Self::new(detail, Some(path)) +impl From for SnapshotError { + fn from(error: rlua::Error) -> Self { + Self::new(error.into(), Option::::None) } } #[derive(Debug)] pub enum SnapshotErrorDetail { IoError { inner: io::Error }, + Lua { inner: rlua::Error }, FileDidNotExist, FileNameBadUnicode, FileContentsBadUnicode { inner: std::str::Utf8Error }, MalformedProject { inner: serde_json::Error }, } +impl From for SnapshotErrorDetail { + fn from(inner: io::Error) -> Self { + SnapshotErrorDetail::IoError { inner } + } +} + +impl From for SnapshotErrorDetail { + fn from(inner: rlua::Error) -> Self { + SnapshotErrorDetail::Lua { inner } + } +} + impl SnapshotErrorDetail { fn source(&self) -> Option<&(dyn Error + 'static)> { use self::SnapshotErrorDetail::*; match self { IoError { inner } => Some(inner), + Lua { inner } => Some(inner), FileContentsBadUnicode { inner } => Some(inner), MalformedProject { inner } => Some(inner), _ => None, @@ -104,6 +129,7 @@ impl fmt::Display for SnapshotErrorDetail { match self { IoError { inner } => write!(formatter, "I/O error: {}", inner), + Lua { inner } => write!(formatter, "{}", inner), FileDidNotExist => write!(formatter, "file did not exist"), FileNameBadUnicode => write!(formatter, "file name had malformed Unicode"), FileContentsBadUnicode { inner } => { diff --git a/src/snapshot_middleware/mod.rs b/src/snapshot_middleware/mod.rs index 26968482..4809b87f 100644 --- a/src/snapshot_middleware/mod.rs +++ b/src/snapshot_middleware/mod.rs @@ -16,14 +16,15 @@ mod rbxlx; mod rbxm; mod rbxmx; mod txt; +mod user_plugins; mod util; +pub use self::context::*; pub use self::error::*; use rbx_dom_weak::{RbxId, RbxTree}; use self::{ - context::InstanceSnapshotContext, csv::SnapshotCsv, dir::SnapshotDir, json_model::SnapshotJsonModel, @@ -34,6 +35,7 @@ use self::{ rbxm::SnapshotRbxm, rbxmx::SnapshotRbxmx, txt::SnapshotTxt, + user_plugins::SnapshotUserPlugins, }; use crate::imfs::{Imfs, ImfsEntry, ImfsFetcher}; @@ -41,15 +43,14 @@ macro_rules! middlewares { ( $($middleware: ident,)* ) => { /// Generates a snapshot of instances from the given ImfsEntry. pub fn snapshot_from_imfs( + context: &mut InstanceSnapshotContext, imfs: &mut Imfs, entry: &ImfsEntry, ) -> SnapshotInstanceResult<'static> { - let mut context = InstanceSnapshotContext::default(); - $( log::trace!("trying middleware {} on {}", stringify!($middleware), entry.path().display()); - if let Some(snapshot) = $middleware::from_imfs(&mut context, imfs, entry)? { + if let Some(snapshot) = $middleware::from_imfs(context, imfs, entry)? { log::trace!("middleware {} success on {}", stringify!($middleware), entry.path().display()); return Ok(Some(snapshot)); } @@ -75,6 +76,7 @@ macro_rules! middlewares { middlewares! { SnapshotProject, + SnapshotUserPlugins, SnapshotJsonModel, SnapshotRbxlx, SnapshotRbxmx, diff --git a/src/snapshot_middleware/project.rs b/src/snapshot_middleware/project.rs index d1e0d9f4..c275d047 100644 --- a/src/snapshot_middleware/project.rs +++ b/src/snapshot_middleware/project.rs @@ -48,7 +48,8 @@ impl SnapshotMiddleware for SnapshotProject { // Snapshotting a project should always return an instance, so this // unwrap is safe. - let mut snapshot = snapshot_project_node(&project.name, &project.tree, imfs)?.unwrap(); + let mut snapshot = + snapshot_project_node(context, &project.name, &project.tree, imfs)?.unwrap(); // Setting the instigating source to the project file path is a little // coarse. @@ -76,6 +77,7 @@ impl SnapshotMiddleware for SnapshotProject { } fn snapshot_project_node( + context: &mut InstanceSnapshotContext, instance_name: &str, node: &ProjectNode, imfs: &mut Imfs, @@ -92,7 +94,7 @@ fn snapshot_project_node( if let Some(path) = &node.path { let entry = imfs.get(path)?; - if let Some(snapshot) = snapshot_from_imfs(imfs, &entry)? { + if let Some(snapshot) = snapshot_from_imfs(context, imfs, &entry)? { // If a class name was already specified, then it'll override the // class name of this snapshot ONLY if it's a Folder. // @@ -142,7 +144,7 @@ fn snapshot_project_node( .expect("$className or $path must be specified"); for (child_name, child_project_node) in &node.children { - if let Some(child) = snapshot_project_node(child_name, child_project_node, imfs)? { + if let Some(child) = snapshot_project_node(context, child_name, child_project_node, imfs)? { children.push(child); } } diff --git a/src/snapshot_middleware/user_plugins.rs b/src/snapshot_middleware/user_plugins.rs new file mode 100644 index 00000000..c98dda3b --- /dev/null +++ b/src/snapshot_middleware/user_plugins.rs @@ -0,0 +1,114 @@ +use std::{fs, path::Path}; + +use rlua::{Lua, RegistryKey}; + +use crate::imfs::{Imfs, ImfsEntry, ImfsFetcher}; + +use super::{ + context::InstanceSnapshotContext, + error::SnapshotError, + middleware::{SnapshotInstanceResult, SnapshotMiddleware}, +}; + +/// Handles snapshotting of any file that a user plugin wants to handle. +/// +/// User plugins are specified in the project file, but there are never user +/// plugins specified unless a Cargo feature is enabled, `user-plugins`. +/// Additionally, extra data needs to be set up inside the snapshot context +/// which is not currently wired up. +pub struct SnapshotUserPlugins; + +impl SnapshotMiddleware for SnapshotUserPlugins { + fn from_imfs( + context: &mut InstanceSnapshotContext, + _imfs: &mut Imfs, + _entry: &ImfsEntry, + ) -> SnapshotInstanceResult<'static> { + // User plugins are only enabled if present on the snapshot context. + let plugin_context = match &mut context.plugin_context { + Some(ctx) => ctx, + None => return Ok(None), + }; + + // If the plugins listed for use haven't been loaded yet, read them into + // memory, run them, and keep the result they return as a registry key + // into our Lua state. + let keys = match &plugin_context.plugin_functions { + Some(keys) => keys, + None => { + plugin_context.plugin_functions = Some(initialize_plugins( + &plugin_context.state, + &plugin_context.plugin_paths, + )?); + plugin_context.plugin_functions.as_ref().unwrap() + } + }; + + plugin_context.state.context(|lua_context| { + lua_context.scope(|_scope| { + for _key in keys { + // TODO: Invoke plugin here and get result out. + + // The current plan for plugins here is to make them work + // like Redux/Rodux middleware. A plugin will be a function + // that accepts the next middleware in the chain as a + // function and the snapshot subject (the IMFS entry). + // + // Plugins can (but don't have to) invoke the next snapshot + // function and may or may not mutate the result. The hope + // is that this model enables the most flexibility possible + // for plugins to modify existing Rojo output, as well as + // generate new outputs. + // + // Open questions: + // * How will middleware be ordered? Does putting user + // middleware always at the beginning or always at the end + // of the chain reduce the scope of what that middleware + // can do? + // + // * Will plugins hurt Rojo's ability to parallelize + // snapshotting in the future? + // + // * Do the mutable handles to the Imfs and the snapshot + // context prevent plugins from invoking other plugins + // indirectly? + // + // * Will there be problems using a single Lua state because + // of re-entrancy? + // + // * Can the Lua <-> Rojo bindings used for middleware be + // reused for or from another project like Remodel? + } + }) + }); + + Ok(None) + } +} + +fn initialize_plugins>( + lua_state: &Lua, + plugin_paths: &[P], +) -> Result, SnapshotError> { + plugin_paths + .iter() + .map(|path| { + let path = path.as_ref(); + + let content = fs::read_to_string(path).map_err(|err| SnapshotError::wrap(err, path))?; + + lua_state.context(|lua_context| { + // Plugins are currently expected to return a function that will + // be run when a snapshot needs to be generated. + let result = lua_context + .load(&content) + .set_name(&path.display().to_string())? + .call::<_, rlua::Function>(())?; + + let key = lua_context.create_registry_value(result)?; + + Ok(key) + }) + }) + .collect::, _>>() +} diff --git a/test-projects/plugins/default.project.json b/test-projects/plugins/default.project.json new file mode 100644 index 00000000..02387e7e --- /dev/null +++ b/test-projects/plugins/default.project.json @@ -0,0 +1,9 @@ +{ + "name": "plugins", + "tree": { + "$path": "src" + }, + "plugins": [ + "test-plugin.lua" + ] +} \ No newline at end of file diff --git a/test-projects/plugins/src/hello.moon b/test-projects/plugins/src/hello.moon new file mode 100644 index 00000000..ddb781cf --- /dev/null +++ b/test-projects/plugins/src/hello.moon @@ -0,0 +1 @@ +print 'Hello, world!' \ No newline at end of file diff --git a/test-projects/plugins/test-plugin.lua b/test-projects/plugins/test-plugin.lua new file mode 100644 index 00000000..147463cb --- /dev/null +++ b/test-projects/plugins/test-plugin.lua @@ -0,0 +1,20 @@ +print("test-plugin initializing...") + +return function(nextDispatch, entry) + if entry:isDirectory() then + return nextDispatch(entry) + end + + local name = entry:fileName() + local instanceName = name:match("(.-)%.moon$") + + if instanceName == nil then + return nextDispatch(entry) + end + + return rojo.instance({ + Name = instanceName, + ClassName = "ModuleScript", + Source = compileMoonScript(entry:contents()), + }) +end \ No newline at end of file diff --git a/tests/read_projects.rs b/tests/read_projects.rs index 55f0e1da..25844b9b 100644 --- a/tests/read_projects.rs +++ b/tests/read_projects.rs @@ -87,6 +87,7 @@ fn single_partition_game() { tree: root_node, serve_port: None, serve_place_ids: None, + plugins: Vec::new(), file_location: project_location.join("default.project.json"), } };