diff options
Diffstat (limited to 'mingling_core/src/program')
| -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 |
6 files changed, 468 insertions, 19 deletions
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| { |
