diff options
| -rw-r--r-- | mingling_core/src/asset/dispatcher.rs | 7 | ||||
| -rw-r--r-- | mingling_core/src/program.rs | 42 | ||||
| -rw-r--r-- | mingling_core/src/program/config.rs | 15 | ||||
| -rw-r--r-- | mingling_core/src/program/exec.rs | 114 | ||||
| -rw-r--r-- | mingling_core/src/program/flag.rs | 2 | ||||
| -rw-r--r-- | mingling_core/src/program/hook.rs | 349 | ||||
| -rw-r--r-- | mingling_core/src/program/setup.rs | 5 | ||||
| -rw-r--r-- | mingling_core/src/program/setup/general_renderer.rs | 2 | ||||
| -rw-r--r-- | mingling_core/src/renderer/render_result.rs | 1 |
9 files changed, 507 insertions, 30 deletions
diff --git a/mingling_core/src/asset/dispatcher.rs b/mingling_core/src/asset/dispatcher.rs index cf8fcca..95b3305 100644 --- a/mingling_core/src/asset/dispatcher.rs +++ b/mingling_core/src/asset/dispatcher.rs @@ -1,6 +1,6 @@ use std::fmt::Display; -use crate::{ChainProcess, Program, asset::node::Node}; +use crate::{ChainProcess, Program, ProgramCollect, asset::node::Node}; /// Dispatches user input commands to specific [ChainProcess](./enum.ChainProcess.html) /// @@ -27,7 +27,10 @@ where } } -impl<C: crate::program::ProgramCollect> Program<C> { +impl<C> Program<C> +where + C: ProgramCollect<Enum = C>, +{ /// Adds a dispatcher to the program. #[cfg(not(feature = "dispatch_tree"))] pub fn with_dispatcher<Disp>(&mut self, dispatcher: Disp) diff --git a/mingling_core/src/program.rs b/mingling_core/src/program.rs index 4d943cf..0d23770 100644 --- a/mingling_core/src/program.rs +++ b/mingling_core/src/program.rs @@ -11,6 +11,7 @@ use crate::{ AnyOutput, ChainProcess, GlobalResources, Groupped, RenderResult, asset::dispatcher::Dispatcher, error::{ChainProcessError, ProgramExecuteError}, + hook::{ProgramAnonymousHook, ProgramHook}, }; use std::{ collections::HashMap, @@ -24,6 +25,8 @@ use std::pin::Pin; #[doc(hidden)] pub mod exec; #[doc(hidden)] +pub mod hook; +#[doc(hidden)] pub mod setup; mod config; @@ -41,7 +44,7 @@ static THIS_PROGRAM: OnceLock<Option<Box<dyn std::any::Any + Send + Sync>>> = On /// Returns a reference to the current program instance, panics if not set. pub fn this<C>() -> &'static Program<C> where - C: ProgramCollect + 'static, + C: ProgramCollect<Enum = C> + 'static, { try_get_this_program().expect("Program not initialized") } @@ -49,7 +52,7 @@ where /// Returns a reference to the current program instance, if set. fn try_get_this_program<C>() -> Option<&'static Program<C>> where - C: ProgramCollect + 'static, + C: ProgramCollect<Enum = C> + 'static, { THIS_PROGRAM.get()?.as_ref()?.downcast_ref::<Program<C>>() } @@ -58,7 +61,7 @@ where #[derive(Default)] pub struct Program<C> where - C: ProgramCollect, + C: ProgramCollect<Enum = C>, { pub(crate) collect: std::marker::PhantomData<C>, @@ -73,6 +76,9 @@ where #[cfg(feature = "general_renderer")] pub general_renderer_name: GeneralRendererSetting, + pub(crate) hooks: Vec<ProgramHook<C>>, + pub(crate) anonymous_hooks: Vec<ProgramAnonymousHook>, + pub(crate) resources: GlobalResources, } @@ -113,6 +119,9 @@ where #[cfg(feature = "general_renderer")] general_renderer_name: GeneralRendererSetting::Disable, + hooks: Vec::new(), + anonymous_hooks: Vec::new(), + resources: Arc::new(Mutex::new(HashMap::new())), } } @@ -167,7 +176,7 @@ where #[cfg(feature = "async")] impl<C> Program<C> where - C: ProgramCollect<Enum = C>, + C: ProgramCollect<Enum = C> + std::fmt::Display, { /// Sets the current program instance and runs the provided async function. async fn set_instance_and_run<F, Fut>(self, f: F) -> Fut::Output @@ -237,7 +246,7 @@ where #[cfg(not(feature = "async"))] impl<C> Program<C> where - C: ProgramCollect<Enum = C>, + C: ProgramCollect<Enum = C> + Display, { /// Sets the current program instance and runs the provided function. fn set_instance_and_run<F, R>(self, f: F) -> R @@ -266,7 +275,7 @@ where } /// Run the command line program - pub fn exec(self) + pub fn exec(self) -> i32 where C: 'static + Send + Sync, { @@ -276,29 +285,44 @@ where Err(e) => match e { ProgramExecuteError::DispatcherNotFound => { eprintln!("Dispatcher not found"); - return; + return 1; } ProgramExecuteError::RendererNotFound(renderer_name) => { eprintln!("Renderer `{}` not found", renderer_name); - return; + return 1; } ProgramExecuteError::Other(e) => { eprintln!("{}", e); - return; + return 1; } }, }; // Render result if stdout_setting.render_output && !result.is_empty() { + let exit_code = result.exit_code; print!("{}", result); + if let Err(e) = std::io::Write::flush(&mut std::io::stdout()) && stdout_setting.error_output { eprintln!("{}", e); + 1 + } else { + exit_code } + } else { + 0 } } + + /// Run the command line program, then exit + pub fn exec_and_exit(self) + where + C: 'static + Send + Sync, + { + std::process::exit(self.exec()) + } } /// Collected program context diff --git a/mingling_core/src/program/config.rs b/mingling_core/src/program/config.rs index ac541fd..35b9392 100644 --- a/mingling_core/src/program/config.rs +++ b/mingling_core/src/program/config.rs @@ -35,13 +35,26 @@ impl Default for ProgramStdoutSetting { } /// Program user context -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone)] pub struct ProgramUserContext { /// View help information instead of running the command pub help: bool, /// Skip user confirmation step pub confirm: bool, + + /// Execute hooks during the program lifecycle + pub run_hook: bool, +} + +impl Default for ProgramUserContext { + fn default() -> Self { + Self { + help: false, + confirm: false, + run_hook: true, + } + } } #[cfg(feature = "general_renderer")] diff --git a/mingling_core/src/program/exec.rs b/mingling_core/src/program/exec.rs index 001b250..acd1bd6 100644 --- a/mingling_core/src/program/exec.rs +++ b/mingling_core/src/program/exec.rs @@ -13,19 +13,31 @@ pub async fn exec<C>( program: &'static Program<C>, ) -> Result<RenderResult, ProgramInternalExecuteError> where - C: ProgramCollect<Enum = C>, + C: ProgramCollect<Enum = C> + std::fmt::Display, { + // Run hooks + program.run_hook_on_begin(); + program.run_hook_pre_dispatch(&program.args); + #[cfg(not(feature = "dispatch_tree"))] let mut current = dispatch_args_dynamic(program, &program.args)?; #[cfg(feature = "dispatch_tree")] let mut current = C::dispatch_args_trie(&program.args)?; + // Run hook + program.run_hook_post_dispatch(¤t.member_id); + let mut stop_next = false; // If the program has Help enabled, skip actual logic and jump to Help if program.user_context.help { - return Ok(render_help::<C>(program, current)); + let mut render_result = render_help::<C>(program, current); + + // Run hook + render_result.exit_code = program.run_hook_finish(); + + return Ok(render_result); } loop { @@ -34,17 +46,42 @@ where current = { // If a chain exists, execute as a chain if C::has_chain(¤t) { + // Run hook + program.run_hook_pre_chain(¤t.member_id, current.inner.as_ref()); + match C::do_chain(current).await { ChainProcess::Ok((any, Next::Renderer)) => { - return Ok(render::<C>(program, any)); + let mut render_result = render::<C>(program, any); + + // Run hook + render_result.exit_code = program.run_hook_finish(); + + return Ok(render_result); + } + ChainProcess::Ok((any, Next::Chain)) => { + // Run hook + program.run_hook_post_chain(&any); + any + } + ChainProcess::Err(e) => { + // Run hook + program.run_hook_finish(); + return Err(e.into()); } - ChainProcess::Ok((any, Next::Chain)) => any, - ChainProcess::Err(e) => return Err(e.into()), } } // If no chain exists, attempt to render else if C::has_renderer(¤t) { - return Ok(render::<C>(program, current)); + // Run hook + program.run_hook_pre_render(¤t.member_id, current.inner.as_ref()); + + let mut render_result = render::<C>(program, current); + + // Run hooks + program.run_hook_post_render(&render_result); + render_result.exit_code = program.run_hook_finish(); + + return Ok(render_result); } // No renderer exists else { @@ -57,25 +94,42 @@ where break; } } - Ok(RenderResult::default()) + let mut render_result = RenderResult::default(); + + // Run hook + render_result.exit_code = program.run_hook_finish(); + + Ok(render_result) } #[cfg(not(feature = "async"))] pub fn exec<C>(program: &'static Program<C>) -> Result<RenderResult, ProgramInternalExecuteError> where - C: ProgramCollect<Enum = C>, + C: ProgramCollect<Enum = C> + std::fmt::Display, { + // Run hooks + program.run_hook_on_begin(); + program.run_hook_pre_dispatch(&program.args); + #[cfg(not(feature = "dispatch_tree"))] let mut current = dispatch_args_dynamic(program, &program.args)?; #[cfg(feature = "dispatch_tree")] let mut current = C::dispatch_args_trie(&program.args)?; + // Run hook + program.run_hook_post_dispatch(¤t.member_id); + let mut stop_next = false; // If the program has Help enabled, skip actual logic and jump to Help if program.user_context.help { - return Ok(render_help::<C>(program, current)); + let mut render_result = render_help::<C>(program, current); + + // Run hook + render_result.exit_code = program.run_hook_finish(); + + return Ok(render_result); } loop { @@ -84,17 +138,44 @@ where current = { // If a chain exists, execute as a chain if C::has_chain(¤t) { + // Run hook + program.run_hook_pre_chain(¤t.member_id, current.inner.as_ref()); + match C::do_chain(current) { ChainProcess::Ok((any, Next::Renderer)) => { - return Ok(render::<C>(program, any)); + { + let mut render_result = render::<C>(program, any); + + // Run hook + render_result.exit_code = program.run_hook_finish(); + + return Ok(render_result); + }; + } + ChainProcess::Ok((any, Next::Chain)) => { + // Run hook + program.run_hook_post_chain(&any); + any + } + ChainProcess::Err(e) => { + // Run hook + program.run_hook_finish(); + return Err(e.into()); } - ChainProcess::Ok((any, Next::Chain)) => any, - ChainProcess::Err(e) => return Err(e.into()), } } // If no chain exists, attempt to render else if C::has_renderer(¤t) { - return Ok(render::<C>(program, current)); + // Run hook + program.run_hook_pre_render(¤t.member_id, current.inner.as_ref()); + + let mut render_result = render::<C>(program, current); + + // Run hooks + program.run_hook_post_render(&render_result); + render_result.exit_code = program.run_hook_finish(); + + return Ok(render_result); } // No renderer exists else { @@ -107,7 +188,12 @@ where break; } } - Ok(RenderResult::default()) + let mut render_result = RenderResult::default(); + + // Run hook + render_result.exit_code = program.run_hook_finish(); + + Ok(render_result) } /// Dynamically dispatch input arguments to registered entry types diff --git a/mingling_core/src/program/flag.rs b/mingling_core/src/program/flag.rs index 0ab8d1c..210f2d6 100644 --- a/mingling_core/src/program/flag.rs +++ b/mingling_core/src/program/flag.rs @@ -474,7 +474,7 @@ mod tests { impl<C> Program<C> where - C: ProgramCollect, + C: ProgramCollect<Enum = C>, { /// Registers a global argument (with value) and its handler. pub fn global_argument<F, A>(&mut self, arguments: A, mut do_fn: F) diff --git a/mingling_core/src/program/hook.rs b/mingling_core/src/program/hook.rs new file mode 100644 index 0000000..d422e12 --- /dev/null +++ b/mingling_core/src/program/hook.rs @@ -0,0 +1,349 @@ +use std::{any::Any, fmt::Display}; + +use crate::{AnyOutput, Program, ProgramCollect, RenderResult}; + +#[derive(Default)] +pub struct ProgramHook<C> +where + C: ProgramCollect<Enum = C>, +{ + /// Executes when the program starts running + pub begin: Option<fn()>, + + /// Executes before the program dispatches + pub pre_dispatch: Option<fn(args: &Vec<String>)>, + + /// Executes after the program dispatches + pub post_dispatch: Option<fn(entry: &C)>, + + /// Executes before the type enters the chain + pub pre_chain: Option<fn(input: &C, raw: &dyn Any)>, + + /// Executes after the chain processing for the type ends + pub post_chain: Option<fn(output: &AnyOutput<C>)>, + + /// Executes before the type enters the renderer + pub pre_render: Option<fn(input: &C, raw: &dyn Any)>, + + /// Executes after the type enters the renderer + pub post_render: Option<fn(result: &RenderResult)>, + + /// Executes before the program ends + pub finish: Option<fn() -> i32>, +} + +#[derive(Default)] +pub struct ProgramAnonymousHook { + /// Executes when the program starts running + pub begin: Option<fn()>, + + /// Executes before the program dispatches + pub pre_dispatch: Option<fn(args: &Vec<String>)>, + + /// Executes after the program dispatches + pub post_dispatch: Option<fn(entry_name: &str)>, + + /// Executes before the type enters the chain + pub pre_chain: Option<fn(input_name: &str, raw: &dyn Any)>, + + /// Executes after the chain processing for the type ends + pub post_chain: Option<fn(output_name: &str)>, + + /// Executes before the type enters the renderer + pub pre_render: Option<fn(input_name: &str, raw: &dyn Any)>, + + /// Executes after the type enters the renderer + pub post_render: Option<fn(result: &RenderResult)>, + + /// Executes before the program ends + pub finish: Option<fn()>, +} + +impl<C> Program<C> +where + C: ProgramCollect<Enum = C> + Display, +{ + /// Adds a typed hook to the program. The hook will be called at the appropriate + /// lifecycle events. + pub fn with_hook(&mut self, hook: ProgramHook<C>) { + self.hooks.push(hook); + } + + /// Adds an anonymous hook to the program. The hook will be called at the appropriate + /// lifecycle events, but receives string representations instead of typed references. + pub fn with_hook_anonymous(&mut self, hook: ProgramAnonymousHook) { + self.anonymous_hooks.push(hook); + } + + pub(crate) fn run_hook_on_begin(&self) { + if !self.user_context.run_hook { + return; + } + + for hook in &self.hooks { + if let Some(begin) = hook.begin { + begin() + } + } + for anonymous_hook in &self.anonymous_hooks { + if let Some(begin) = anonymous_hook.begin { + begin() + } + } + } + + pub(crate) fn run_hook_pre_dispatch(&self, args: &Vec<String>) { + if !self.user_context.run_hook { + return; + } + + for hook in &self.hooks { + if let Some(pre_dispatch) = hook.pre_dispatch { + pre_dispatch(args) + } + } + for anonymous_hook in &self.anonymous_hooks { + if let Some(pre_dispatch) = anonymous_hook.pre_dispatch { + pre_dispatch(args) + } + } + } + + pub(crate) fn run_hook_post_dispatch(&self, entry: &C) { + if !self.user_context.run_hook { + return; + } + + for hook in &self.hooks { + if let Some(post_dispatch) = hook.post_dispatch { + post_dispatch(entry) + } + } + for anonymous_hook in &self.anonymous_hooks { + if let Some(post_dispatch) = anonymous_hook.post_dispatch { + post_dispatch(entry.to_string().as_str()) + } + } + } + + pub(crate) fn run_hook_pre_chain(&self, input: &C, raw: &dyn Any) { + if !self.user_context.run_hook { + return; + } + + for hook in &self.hooks { + if let Some(pre_chain) = hook.pre_chain { + pre_chain(input, raw) + } + } + for anonymous_hook in &self.anonymous_hooks { + if let Some(pre_chain) = anonymous_hook.pre_chain { + pre_chain(input.to_string().as_str(), raw) + } + } + } + + pub(crate) fn run_hook_post_chain(&self, output: &AnyOutput<C>) { + if !self.user_context.run_hook { + return; + } + + for hook in &self.hooks { + if let Some(post_chain) = hook.post_chain { + post_chain(output) + } + } + for anonymous_hook in &self.anonymous_hooks { + if let Some(post_chain) = anonymous_hook.post_chain { + post_chain(output.member_id.to_string().as_str()) + } + } + } + + pub(crate) fn run_hook_pre_render(&self, input: &C, raw: &dyn Any) { + if !self.user_context.run_hook { + return; + } + + for hook in &self.hooks { + if let Some(pre_render) = hook.pre_render { + pre_render(input, raw) + } + } + for anonymous_hook in &self.anonymous_hooks { + if let Some(pre_render) = anonymous_hook.pre_render { + pre_render(input.to_string().as_str(), raw) + } + } + } + + pub(crate) fn run_hook_post_render(&self, result: &RenderResult) { + if !self.user_context.run_hook { + return; + } + + for hook in &self.hooks { + if let Some(post_render) = hook.post_render { + post_render(result) + } + } + for anonymous_hook in &self.anonymous_hooks { + if let Some(post_render) = anonymous_hook.post_render { + post_render(result) + } + } + } + + pub(crate) fn run_hook_finish(&self) -> i32 { + if !self.user_context.run_hook { + return 0; + } + + let mut exit_code = 0; + for hook in &self.hooks { + if let Some(finish) = hook.finish { + exit_code = finish(); + if exit_code != 0 { + return exit_code; + } + } + } + for anonymous_hook in &self.anonymous_hooks { + if let Some(finish) = anonymous_hook.finish { + finish(); + } + } + exit_code + } +} + +impl<C> ProgramHook<C> +where + C: ProgramCollect<Enum = C>, +{ + /// Creates a new empty hook set with no handlers. + pub fn empty() -> Self { + Self { + begin: None, + pre_dispatch: None, + post_dispatch: None, + pre_chain: None, + post_chain: None, + pre_render: None, + post_render: None, + finish: None, + } + } + + /// Sets the handler for the `begin` event. + pub fn on_begin(mut self, handler: fn()) -> Self { + let _ = self.begin.insert(handler); + self + } + + /// Sets the handler for the `pre_dispatch` event. + pub fn on_pre_dispatch(mut self, handler: fn(args: &Vec<String>)) -> Self { + let _ = self.pre_dispatch.insert(handler); + self + } + + /// Sets the handler for the `post_dispatch` event. + pub fn on_post_dispatch(mut self, handler: fn(entry: &C)) -> Self { + let _ = self.post_dispatch.insert(handler); + self + } + + /// Sets the handler for the `pre_chain` event. + pub fn on_pre_chain(mut self, handler: fn(input: &C, raw: &dyn Any)) -> Self { + let _ = self.pre_chain.insert(handler); + self + } + + /// Sets the handler for the `post_chain` event. + pub fn on_post_chain(mut self, handler: fn(output: &AnyOutput<C>)) -> Self { + let _ = self.post_chain.insert(handler); + self + } + + /// Sets the handler for the `pre_render` event. + pub fn on_pre_render(mut self, handler: fn(input: &C, raw: &dyn Any)) -> Self { + let _ = self.pre_render.insert(handler); + self + } + + /// Sets the handler for the `post_render` event. + pub fn on_post_render(mut self, handler: fn(result: &RenderResult)) -> Self { + let _ = self.post_render.insert(handler); + self + } + + /// Sets the handler for the `finish` event. + pub fn on_finish(mut self, handler: fn() -> i32) -> Self { + let _ = self.finish.insert(handler); + self + } +} + +impl ProgramAnonymousHook { + /// Creates a new empty hook set with no handlers. + pub fn empty() -> Self { + Self { + begin: None, + pre_dispatch: None, + post_dispatch: None, + pre_chain: None, + post_chain: None, + pre_render: None, + post_render: None, + finish: None, + } + } + + /// Sets the handler for the `begin` event. + pub fn on_begin(mut self, handler: fn()) -> Self { + let _ = self.begin.insert(handler); + self + } + + /// Sets the handler for the `pre_dispatch` event. + pub fn on_pre_dispatch(mut self, handler: fn(args: &Vec<String>)) -> Self { + let _ = self.pre_dispatch.insert(handler); + self + } + + /// Sets the handler for the `post_dispatch` event. + pub fn on_post_dispatch(mut self, handler: fn(entry_name: &str)) -> Self { + let _ = self.post_dispatch.insert(handler); + self + } + + /// Sets the handler for the `pre_chain` event. + pub fn on_pre_chain(mut self, handler: fn(input_name: &str, raw: &dyn Any)) -> Self { + let _ = self.pre_chain.insert(handler); + self + } + + /// Sets the handler for the `post_chain` event. + pub fn on_post_chain(mut self, handler: fn(output_name: &str)) -> Self { + let _ = self.post_chain.insert(handler); + self + } + + /// Sets the handler for the `pre_render` event. + pub fn on_pre_render(mut self, handler: fn(input_name: &str, raw: &dyn Any)) -> Self { + let _ = self.pre_render.insert(handler); + self + } + + /// Sets the handler for the `post_render` event. + pub fn on_post_render(mut self, handler: fn(result: &RenderResult)) -> Self { + let _ = self.post_render.insert(handler); + self + } + + /// Sets the handler for the `finish` event. + pub fn on_finish(mut self, handler: fn()) -> Self { + let _ = self.finish.insert(handler); + self + } +} diff --git a/mingling_core/src/program/setup.rs b/mingling_core/src/program/setup.rs index 86228b9..28aa49b 100644 --- a/mingling_core/src/program/setup.rs +++ b/mingling_core/src/program/setup.rs @@ -5,19 +5,20 @@ pub use basic::*; #[cfg(feature = "general_renderer")] mod general_renderer; + #[cfg(feature = "general_renderer")] pub use general_renderer::*; pub trait ProgramSetup<C> where - C: ProgramCollect, + C: ProgramCollect<Enum = C>, { fn setup(&mut self, program: &mut Program<C>); } impl<C> Program<C> where - C: ProgramCollect, + C: ProgramCollect<Enum = C>, { /// Load and execute init logic pub fn with_setup<S: ProgramSetup<C> + 'static>(&mut self, mut setup: S) -> S { diff --git a/mingling_core/src/program/setup/general_renderer.rs b/mingling_core/src/program/setup/general_renderer.rs index 299aae3..d2666da 100644 --- a/mingling_core/src/program/setup/general_renderer.rs +++ b/mingling_core/src/program/setup/general_renderer.rs @@ -32,7 +32,7 @@ pub struct GeneralRendererSetup; impl<C> ProgramSetup<C> for GeneralRendererSetup where - C: ProgramCollect, + C: ProgramCollect<Enum = C>, { fn setup(&mut self, program: &mut Program<C>) { program.global_flag("--json", |p| { diff --git a/mingling_core/src/renderer/render_result.rs b/mingling_core/src/renderer/render_result.rs index 3bde1ab..2bf159a 100644 --- a/mingling_core/src/renderer/render_result.rs +++ b/mingling_core/src/renderer/render_result.rs @@ -8,6 +8,7 @@ use std::{ #[derive(Default, Debug, PartialEq)] pub struct RenderResult { render_text: String, + pub exit_code: i32, } impl Write for RenderResult { |
