diff options
Diffstat (limited to 'mingling_core')
| -rw-r--r-- | mingling_core/src/program.rs | 4 | ||||
| -rw-r--r-- | mingling_core/src/program/collection.rs | 8 | ||||
| -rw-r--r-- | mingling_core/src/program/exec.rs | 320 | ||||
| -rw-r--r-- | mingling_core/src/program/hook.rs | 470 | ||||
| -rw-r--r-- | mingling_core/src/program/hook/control_unit.rs | 140 | ||||
| -rw-r--r-- | mingling_core/src/program/hook/hook_info.rs | 119 | ||||
| -rw-r--r-- | mingling_core/src/program/once_exec.rs | 9 | ||||
| -rw-r--r-- | mingling_core/src/program/repl_exec.rs | 61 | ||||
| -rw-r--r-- | mingling_core/tests/test-all/tests/integration.rs | 4 |
9 files changed, 817 insertions, 318 deletions
diff --git a/mingling_core/src/program.rs b/mingling_core/src/program.rs index a1b803e..e791d86 100644 --- a/mingling_core/src/program.rs +++ b/mingling_core/src/program.rs @@ -15,10 +15,10 @@ pub mod error; #[doc(hidden)] pub mod exec; #[doc(hidden)] -pub mod hook; -#[doc(hidden)] pub mod setup; +pub mod hook; + mod collection; pub use collection::*; diff --git a/mingling_core/src/program/collection.rs b/mingling_core/src/program/collection.rs index 078f736..36a0c94 100644 --- a/mingling_core/src/program/collection.rs +++ b/mingling_core/src/program/collection.rs @@ -31,6 +31,14 @@ pub trait ProgramCollect { raw: &[String], ) -> Result<AnyOutput<Self::Enum>, crate::error::ProgramInternalExecuteError>; + #[cfg(not(feature = "dispatch_tree"))] + /// Use a prefix tree to quickly match arguments and dispatch to an Entry + fn dispatch_args_trie( + _raw: &[String], + ) -> Result<AnyOutput<Self::Enum>, crate::error::ProgramInternalExecuteError> { + unreachable!() + } + /// Get all registered dispatcher names from the program #[cfg(feature = "dispatch_tree")] fn get_nodes() -> Vec<(String, &'static (dyn Dispatcher<Self::Enum> + Send + Sync))>; diff --git a/mingling_core/src/program/exec.rs b/mingling_core/src/program/exec.rs index 0cadc6a..0ade92f 100644 --- a/mingling_core/src/program/exec.rs +++ b/mingling_core/src/program/exec.rs @@ -2,7 +2,7 @@ use crate::{ AnyOutput, ChainProcess, Dispatcher, NextProcess, Program, ProgramCollect, RenderResult, - error::ProgramInternalExecuteError, + error::ProgramInternalExecuteError, hook::ProgramControls, }; #[doc(hidden)] @@ -34,17 +34,37 @@ pub async fn exec_with_args<C>( where C: ProgramCollect<Enum = C>, { - // Run hooks - program.run_hook_pre_dispatch(args); + // Exit code + let mut exit_code: i32 = 0; + + // Current + let mut current = C::build_dispatcher_not_found(vec![]); + + macro_rules! control { + ($hook_call:expr) => { + let __ccc = $hook_call; + if let Some(r) = handle_program_control(program, __ccc, &mut current, &mut exit_code) { + return Ok(r); + } + }; + } - #[cfg(not(feature = "dispatch_tree"))] - let mut current = dispatch_args_dynamic(program, args)?; + // Run hooks + control!(program.run_hook_pre_dispatch(crate::hook::HookPreDispatchInfo { arguments: args })); - #[cfg(feature = "dispatch_tree")] - let mut current = C::dispatch_args_trie(args)?; + // Dispatch args - either via dynamic dispatch or trie dispatch based on feature flag + let mut current = if cfg!(not(feature = "dispatch_tree")) { + dispatch_args_dynamic(program, args)? + } else { + C::dispatch_args_trie(args)? + }; // Run hook - program.run_hook_post_dispatch(¤t.member_id); + control!( + program.run_hook_post_dispatch(crate::hook::HookPostDispatchInfo { + entry: ¤t.member_id, + }) + ); let mut stop_next = false; @@ -53,60 +73,76 @@ where let mut render_result = render_help::<C>(program, current); // Run hook - render_result.exit_code = program.run_hook_finish(); - + control!(program.run_hook_finish(crate::hook::HookFinishInfo {})); + render_result.exit_code = exit_code; return Ok(render_result); } loop { let final_exec = stop_next; - 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, NextProcess::Renderer)) => { - 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, NextProcess::Chain)) => { - // Run hook - program.run_hook_post_chain(&any); - any - } - ChainProcess::Err(e) => { - // Run hook - program.run_hook_finish(); - return Err(e.into()); + current = + { + // If a chain exists, execute as a chain + if C::has_chain(¤t) { + // Run hook + control!(program.run_hook_pre_chain(crate::hook::HookPreChainInfo { + input: ¤t.member_id, + raw: current.inner.as_ref(), + })); + + match C::do_chain(current).await { + ChainProcess::Ok((any, NextProcess::Renderer)) => { + { + let mut render_result = render::<C>(program, any); + + // Run hook + control!(program.run_hook_finish(crate::hook::HookFinishInfo {})); + render_result.exit_code = exit_code; + return Ok(render_result); + }; + } + ChainProcess::Ok((any, NextProcess::Chain)) => { + // Run hook + control!(program.run_hook_post_chain(crate::hook::HookPostChainInfo { + output: &any + })); + any + } + ChainProcess::Err(e) => { + // Run hook + control!(program.run_hook_finish(crate::hook::HookFinishInfo {})); + return Err(e.into()); + } } } - } - // If no chain exists, attempt to render - else if C::has_renderer(¤t) { - // 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 { - stop_next = true; - C::build_renderer_not_found(current.member_id) - } - }; + // If no chain exists, attempt to render + else if C::has_renderer(¤t) { + // Run hook + control!(program.run_hook_pre_render(crate::hook::HookPreRenderInfo { + input: ¤t.member_id, + raw: current.inner.as_ref(), + })); + + let mut render_result = render::<C>(program, current); + + // Run hooks + control!( + program.run_hook_post_render(crate::hook::HookPostRenderInfo { + result: &render_result, + }) + ); + + control!(program.run_hook_finish(crate::hook::HookFinishInfo {})); + render_result.exit_code = exit_code; + return Ok(render_result); + } + // No renderer exists + else { + stop_next = true; + C::build_renderer_not_found(current.member_id) + } + }; if final_exec && stop_next { break; @@ -115,8 +151,8 @@ where let mut render_result = RenderResult::default(); // Run hook - render_result.exit_code = program.run_hook_finish(); - + control!(program.run_hook_finish(crate::hook::HookFinishInfo {})); + render_result.exit_code = exit_code; Ok(render_result) } @@ -128,17 +164,37 @@ pub fn exec_with_args<C>( where C: ProgramCollect<Enum = C>, { - // Run hooks - program.run_hook_pre_dispatch(args); + // Exit code + let mut exit_code: i32 = 0; - #[cfg(not(feature = "dispatch_tree"))] - let mut current = dispatch_args_dynamic(program, args)?; + // Current + let mut current = C::build_dispatcher_not_found(vec![]); - #[cfg(feature = "dispatch_tree")] - let mut current = C::dispatch_args_trie(args)?; + macro_rules! control { + ($hook_call:expr) => { + let __ccc = $hook_call; + if let Some(r) = handle_program_control(program, __ccc, &mut current, &mut exit_code) { + return Ok(r); + } + }; + } + + // Run hooks + control!(program.run_hook_pre_dispatch(crate::hook::HookPreDispatchInfo { arguments: args })); + + // Dispatch args - either via dynamic dispatch or trie dispatch based on feature flag + let mut current = if cfg!(not(feature = "dispatch_tree")) { + dispatch_args_dynamic(program, args)? + } else { + C::dispatch_args_trie(args)? + }; // Run hook - program.run_hook_post_dispatch(¤t.member_id); + control!( + program.run_hook_post_dispatch(crate::hook::HookPostDispatchInfo { + entry: ¤t.member_id, + }) + ); let mut stop_next = false; @@ -147,62 +203,76 @@ where let mut render_result = render_help::<C>(program, current); // Run hook - render_result.exit_code = program.run_hook_finish(); - + control!(program.run_hook_finish(crate::hook::HookFinishInfo {})); + render_result.exit_code = exit_code; return Ok(render_result); } loop { let final_exec = stop_next; - 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, NextProcess::Renderer)) => { - { - let mut render_result = render::<C>(program, any); - + current = + { + // If a chain exists, execute as a chain + if C::has_chain(¤t) { + // Run hook + control!(program.run_hook_pre_chain(crate::hook::HookPreChainInfo { + input: ¤t.member_id, + raw: current.inner.as_ref(), + })); + + match C::do_chain(current) { + ChainProcess::Ok((any, NextProcess::Renderer)) => { + { + let mut render_result = render::<C>(program, any); + + // Run hook + control!(program.run_hook_finish(crate::hook::HookFinishInfo {})); + render_result.exit_code = exit_code; + return Ok(render_result); + }; + } + ChainProcess::Ok((any, NextProcess::Chain)) => { // Run hook - render_result.exit_code = program.run_hook_finish(); - - return Ok(render_result); - }; - } - ChainProcess::Ok((any, NextProcess::Chain)) => { - // Run hook - program.run_hook_post_chain(&any); - any - } - ChainProcess::Err(e) => { - // Run hook - program.run_hook_finish(); - return Err(e.into()); + control!(program.run_hook_post_chain(crate::hook::HookPostChainInfo { + output: &any + })); + any + } + ChainProcess::Err(e) => { + // Run hook + control!(program.run_hook_finish(crate::hook::HookFinishInfo {})); + return Err(e.into()); + } } } - } - // If no chain exists, attempt to render - else if C::has_renderer(¤t) { - // 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 { - stop_next = true; - C::build_renderer_not_found(current.member_id) - } - }; + // If no chain exists, attempt to render + else if C::has_renderer(¤t) { + // Run hook + control!(program.run_hook_pre_render(crate::hook::HookPreRenderInfo { + input: ¤t.member_id, + raw: current.inner.as_ref(), + })); + + let mut render_result = render::<C>(program, current); + + // Run hooks + control!( + program.run_hook_post_render(crate::hook::HookPostRenderInfo { + result: &render_result, + }) + ); + + control!(program.run_hook_finish(crate::hook::HookFinishInfo {})); + render_result.exit_code = exit_code; + return Ok(render_result); + } + // No renderer exists + else { + stop_next = true; + C::build_renderer_not_found(current.member_id) + } + }; if final_exec && stop_next { break; @@ -211,8 +281,8 @@ where let mut render_result = RenderResult::default(); // Run hook - render_result.exit_code = program.run_hook_finish(); - + control!(program.run_hook_finish(crate::hook::HookFinishInfo {})); + render_result.exit_code = exit_code; Ok(render_result) } @@ -287,6 +357,32 @@ where } #[inline] +pub(crate) fn handle_program_control<C: ProgramCollect<Enum = C>>( + program: &Program<C>, + controls: ProgramControls<C>, + current: &mut AnyOutput<C>, + exit_code: &mut i32, +) -> Option<RenderResult> { + for unit in controls.into_iter() { + match unit { + super::hook::ProgramControlUnit::OverrideExitCode(c) => *exit_code = c, + super::hook::ProgramControlUnit::RouteToChain(any_output) => *current = any_output, + super::hook::ProgramControlUnit::RouteToRender(any_output) => { + let mut r = render::<C>(program, any_output); + r.exit_code = *exit_code; + return Some(r); + } + super::hook::ProgramControlUnit::RouteToHelp(any_output) => { + let mut r = render_help::<C>(program, any_output); + r.exit_code = *exit_code; + return Some(r); + } + } + } + None +} + +#[inline] #[allow(unused_variables)] fn render<C: ProgramCollect<Enum = C>>(program: &Program<C>, any: AnyOutput<C>) -> RenderResult { #[cfg(not(feature = "general_renderer"))] diff --git a/mingling_core/src/program/hook.rs b/mingling_core/src/program/hook.rs index f6df2e2..7b07d90 100644 --- a/mingling_core/src/program/hook.rs +++ b/mingling_core/src/program/hook.rs @@ -1,84 +1,96 @@ #![allow(dead_code)] -use std::any::Any; +use crate::{Program, ProgramCollect}; -use crate::{AnyOutput, Program, ProgramCollect, RenderResult}; +mod hook_info; +pub use hook_info::*; -#[cfg(not(feature = "async"))] -use crate::error::ProgramPanic; +mod control_unit; +pub use control_unit::*; #[derive(Default)] +#[allow(clippy::type_complexity)] // Shutup! pub struct ProgramHook<C> where C: ProgramCollect<Enum = C>, { /// Executes when the program starts running - pub begin: Option<fn()>, + pub begin: Option<Box<dyn Fn(&HookBeginInfo) + Send + Sync>>, /// Executes before the program dispatches - pub pre_dispatch: Option<fn(args: &[String])>, + pub pre_dispatch: + Option<Box<dyn for<'a> Fn(&HookPreDispatchInfo<'a>) -> ProgramControls<C> + Send + Sync>>, /// Executes after the program dispatches - pub post_dispatch: Option<fn(entry: &C)>, + pub post_dispatch: Option< + Box<dyn for<'a> Fn(&HookPostDispatchInfo<'a, C>) -> ProgramControls<C> + Send + Sync>, + >, /// Executes before the type enters the chain - pub pre_chain: Option<fn(input: &C, raw: &dyn Any)>, + pub pre_chain: + Option<Box<dyn for<'a> Fn(&HookPreChainInfo<'a, C>) -> ProgramControls<C> + Send + Sync>>, /// Executes after the chain processing for the type ends - pub post_chain: Option<fn(output: &AnyOutput<C>)>, + pub post_chain: + Option<Box<dyn for<'a> Fn(&HookPostChainInfo<'a, C>) -> ProgramControls<C> + Send + Sync>>, /// Executes before the type enters the renderer - pub pre_render: Option<fn(input: &C, raw: &dyn Any)>, + pub pre_render: + Option<Box<dyn for<'a> Fn(&HookPreRenderInfo<'a, C>) -> ProgramControls<C> + Send + Sync>>, /// Executes after the type enters the renderer - pub post_render: Option<fn(result: &RenderResult)>, + pub post_render: + Option<Box<dyn for<'a> Fn(&HookPostRenderInfo<'a>) -> ProgramControls<C> + Send + Sync>>, /// Executes before the program ends - pub finish: Option<fn() -> i32>, + pub finish: Option<Box<dyn Fn(&HookFinishInfo) -> ProgramControls<C> + Send + Sync>>, /// Executes when the program panics #[cfg(not(feature = "async"))] - pub exec_panic: Option<fn(&ProgramPanic)>, + pub exec_panic: Option<Box<dyn for<'a> Fn(&HookPanicInfo<'a>) + Send + Sync>>, /// Executes when the REPL starts (only available with `repl` feature) #[cfg(feature = "repl")] - pub repl_on_begin: Option<fn()>, + pub repl_on_begin: Option<Box<dyn Fn(&HookREPLBeginInfo) + Send + Sync>>, /// Executes before reading the next REPL line (only available with `repl` feature) #[cfg(feature = "repl")] - pub repl_pre_readline: Option<fn()>, + pub repl_pre_readline: Option<Box<dyn Fn(&HookREPLPreReadlineInfo) + Send + Sync>>, /// Custom REPL line reader (only available with `repl` feature) + /// Returns `Some(line)` to provide a custom input line. #[cfg(feature = "repl")] - pub repl_readline: Option<fn() -> Option<String>>, + pub repl_readline: Option<Box<dyn Fn(&HookREPLReadlineInfo) -> Option<String> + Send + Sync>>, /// Executes after reading a REPL line (only available with `repl` feature) #[cfg(feature = "repl")] - pub repl_post_readline: Option<fn(line: &mut String)>, + pub repl_post_readline: + Option<Box<dyn for<'a> Fn(&HookREPLPostReadlineInfo<'a>) + Send + Sync>>, /// Executes before executing a REPL command (only available with `repl` feature) #[cfg(feature = "repl")] - pub repl_pre_exec: Option<fn(args: &[String])>, + pub repl_pre_exec: Option<Box<dyn for<'a> Fn(&HookREPLPreExecInfo<'a>) + Send + Sync>>, /// Executes after executing a REPL command (only available with `repl` feature) #[cfg(feature = "repl")] - pub repl_post_exec: Option<fn()>, + pub repl_post_exec: Option<Box<dyn Fn(&HookREPLPostExecInfo) + Send + Sync>>, /// Executes when the REPL receives a render result (only available with `repl` feature) #[cfg(feature = "repl")] - pub repl_on_receive_result: Option<fn(&RenderResult)>, + pub repl_on_receive_result: + Option<Box<dyn for<'a> Fn(&HookREPLOnReceiveResultInfo<'a>) + Send + Sync>>, /// Executes when the REPL panics (only available with `repl` feature) #[cfg(all(feature = "repl", not(feature = "async")))] - pub repl_on_panic: Option<fn(&ProgramPanic)>, + pub repl_on_panic: Option<Box<dyn for<'a> Fn(&HookREPLOnPanicInfo<'a>) + Send + Sync>>, /// Executes when the REPL exits (only available with `repl` feature) #[cfg(feature = "repl")] - pub repl_exit: Option<fn()>, + pub repl_exit: Option<Box<dyn Fn(&HookREPLExitInfo) + Send + Sync>>, /// Executes after each REPL loop iteration (only available with `repl` feature) #[cfg(feature = "repl")] - pub repl_loop_once: Option<fn()>, + pub repl_loop_once: Option<Box<dyn Fn(&HookREPLLoopOnceInfo) + Send + Sync>>, } impl<C> Program<C> @@ -91,145 +103,157 @@ where self.hooks.push(hook); } - pub(crate) fn run_hook_on_begin(&self) { + pub(crate) fn run_hook_on_begin(&self, info: HookBeginInfo) { if !self.user_context.run_hook { return; } for hook in &self.hooks { - if let Some(begin) = hook.begin { - begin(); + if let Some(ref begin) = hook.begin { + begin(&info); } } } - pub(crate) fn run_hook_pre_dispatch(&self, args: &[String]) { + pub(crate) fn run_hook_pre_dispatch(&self, info: HookPreDispatchInfo) -> ProgramControls<C> { if !self.user_context.run_hook { - return; + return ProgramControls::Empty; } + let mut controls = ProgramControls::Empty; for hook in &self.hooks { - if let Some(pre_dispatch) = hook.pre_dispatch { - pre_dispatch(args); + if let Some(ref pre_dispatch) = hook.pre_dispatch { + controls = pre_dispatch(&info); } } + controls } - pub(crate) fn run_hook_post_dispatch(&self, entry: &C) { + pub(crate) fn run_hook_post_dispatch( + &self, + info: HookPostDispatchInfo<C>, + ) -> ProgramControls<C> { if !self.user_context.run_hook { - return; + return ProgramControls::Empty; } + let mut controls = ProgramControls::Empty; for hook in &self.hooks { - if let Some(post_dispatch) = hook.post_dispatch { - post_dispatch(entry); + if let Some(ref post_dispatch) = hook.post_dispatch { + controls = post_dispatch(&info); } } + controls } - pub(crate) fn run_hook_pre_chain(&self, input: &C, raw: &dyn Any) { + pub(crate) fn run_hook_pre_chain(&self, info: HookPreChainInfo<C>) -> ProgramControls<C> { if !self.user_context.run_hook { - return; + return ProgramControls::Empty; } + let mut controls = ProgramControls::Empty; for hook in &self.hooks { - if let Some(pre_chain) = hook.pre_chain { - pre_chain(input, raw); + if let Some(ref pre_chain) = hook.pre_chain { + controls = pre_chain(&info); } } + controls } - pub(crate) fn run_hook_post_chain(&self, output: &AnyOutput<C>) { + pub(crate) fn run_hook_post_chain(&self, info: HookPostChainInfo<C>) -> ProgramControls<C> { if !self.user_context.run_hook { - return; + return ProgramControls::Empty; } + let mut controls = ProgramControls::Empty; for hook in &self.hooks { - if let Some(post_chain) = hook.post_chain { - post_chain(output); + if let Some(ref post_chain) = hook.post_chain { + controls = post_chain(&info); } } + controls } - pub(crate) fn run_hook_pre_render(&self, input: &C, raw: &dyn Any) { + pub(crate) fn run_hook_pre_render(&self, info: HookPreRenderInfo<C>) -> ProgramControls<C> { if !self.user_context.run_hook { - return; + return ProgramControls::Empty; } + let mut controls = ProgramControls::Empty; for hook in &self.hooks { - if let Some(pre_render) = hook.pre_render { - pre_render(input, raw); + if let Some(ref pre_render) = hook.pre_render { + controls = pre_render(&info); } } + controls } - pub(crate) fn run_hook_post_render(&self, result: &RenderResult) { + pub(crate) fn run_hook_post_render(&self, info: HookPostRenderInfo) -> ProgramControls<C> { if !self.user_context.run_hook { - return; + return ProgramControls::Empty; } + let mut controls = ProgramControls::Empty; for hook in &self.hooks { - if let Some(post_render) = hook.post_render { - post_render(result); + if let Some(ref post_render) = hook.post_render { + controls = post_render(&info); } } + controls } #[allow(dead_code)] #[cfg(not(feature = "async"))] - pub(crate) fn run_hook_exec_panic(&self, panic_info: &ProgramPanic) { + pub(crate) fn run_hook_exec_panic(&self, info: HookPanicInfo) { if !self.user_context.run_hook { return; } for hook in &self.hooks { - if let Some(exec_panic) = hook.exec_panic { - exec_panic(panic_info); + if let Some(ref exec_panic) = hook.exec_panic { + exec_panic(&info); } } } - pub(crate) fn run_hook_finish(&self) -> i32 { + pub(crate) fn run_hook_finish(&self, info: HookFinishInfo) -> ProgramControls<C> { if !self.user_context.run_hook { - return 0; + return ProgramControls::Empty; } - let mut exit_code = 0; + let mut controls = ProgramControls::Empty; for hook in &self.hooks { - if let Some(finish) = hook.finish { - exit_code = finish(); - if exit_code != 0 { - return exit_code; - } + if let Some(ref finish) = hook.finish { + controls = finish(&info); } } - exit_code + controls } /// Runs the REPL begin hooks (only available with `repl` feature) #[cfg(feature = "repl")] - pub(crate) fn run_hook_repl_on_begin(&self) { + pub(crate) fn run_hook_repl_on_begin(&self, info: HookREPLBeginInfo) { if !self.user_context.run_hook { return; } for hook in &self.hooks { - if let Some(repl_on_begin) = hook.repl_on_begin { - repl_on_begin() + if let Some(ref repl_on_begin) = hook.repl_on_begin { + repl_on_begin(&info); } } } /// Runs the REPL pre-readline hooks (only available with `repl` feature) #[cfg(feature = "repl")] - pub(crate) fn run_hook_repl_pre_readline(&self) { + pub(crate) fn run_hook_repl_pre_readline(&self, info: HookREPLPreReadlineInfo) { if !self.user_context.run_hook { return; } for hook in &self.hooks { - if let Some(repl_pre_readline) = hook.repl_pre_readline { - repl_pre_readline() + if let Some(ref repl_pre_readline) = hook.repl_pre_readline { + repl_pre_readline(&info); } } } @@ -237,14 +261,14 @@ where /// Runs the custom REPL readline hook (only available with `repl` feature) /// Returns `Some(line)` if a hook was set and returned Some, otherwise `None`. #[cfg(feature = "repl")] - pub(crate) fn run_hook_repl_readline(&self) -> Option<String> { + pub(crate) fn run_hook_repl_readline(&self, info: HookREPLReadlineInfo) -> Option<String> { if !self.user_context.run_hook { return None; } for hook in &self.hooks { - if let Some(repl_readline) = hook.repl_readline { - return repl_readline(); + if let Some(ref repl_readline) = hook.repl_readline { + return repl_readline(&info); } } None @@ -252,98 +276,98 @@ where /// Runs the REPL post-readline hooks (only available with `repl` feature) #[cfg(feature = "repl")] - pub(crate) fn run_hook_repl_post_readline(&self, line: &mut String) { + pub(crate) fn run_hook_repl_post_readline(&self, info: HookREPLPostReadlineInfo) { if !self.user_context.run_hook { return; } for hook in &self.hooks { - if let Some(repl_post_readline) = hook.repl_post_readline { - repl_post_readline(line) + if let Some(ref repl_post_readline) = hook.repl_post_readline { + repl_post_readline(&info); } } } /// Runs the REPL pre-exec hooks (only available with `repl` feature) #[cfg(feature = "repl")] - pub(crate) fn run_hook_repl_pre_exec(&self, args: &[String]) { + pub(crate) fn run_hook_repl_pre_exec(&self, info: HookREPLPreExecInfo) { if !self.user_context.run_hook { return; } for hook in &self.hooks { - if let Some(repl_pre_exec) = hook.repl_pre_exec { - repl_pre_exec(args) + if let Some(ref repl_pre_exec) = hook.repl_pre_exec { + repl_pre_exec(&info); } } } /// Runs the REPL post-exec hooks (only available with `repl` feature) #[cfg(feature = "repl")] - pub(crate) fn run_hook_repl_post_exec(&self) { + pub(crate) fn run_hook_repl_post_exec(&self, info: HookREPLPostExecInfo) { if !self.user_context.run_hook { return; } for hook in &self.hooks { - if let Some(repl_post_exec) = hook.repl_post_exec { - repl_post_exec() + if let Some(ref repl_post_exec) = hook.repl_post_exec { + repl_post_exec(&info); } } } /// Runs the REPL receive result hooks (only available with `repl` feature) #[cfg(feature = "repl")] - pub(crate) fn run_hook_repl_on_receive_result(&self, result: &RenderResult) { + pub(crate) fn run_hook_repl_on_receive_result(&self, info: HookREPLOnReceiveResultInfo) { if !self.user_context.run_hook { return; } for hook in &self.hooks { - if let Some(repl_on_receive_result) = hook.repl_on_receive_result { - repl_on_receive_result(result) + if let Some(ref repl_on_receive_result) = hook.repl_on_receive_result { + repl_on_receive_result(&info); } } } /// Runs the REPL panic hooks (only available with `repl` feature) #[cfg(all(feature = "repl", not(feature = "async")))] - pub(crate) fn run_hook_repl_on_panic(&self, panic_info: &ProgramPanic) { + pub(crate) fn run_hook_repl_on_panic(&self, info: HookREPLOnPanicInfo) { if !self.user_context.run_hook { return; } for hook in &self.hooks { - if let Some(repl_on_panic) = hook.repl_on_panic { - repl_on_panic(panic_info) + if let Some(ref repl_on_panic) = hook.repl_on_panic { + repl_on_panic(&info); } } } /// Runs the REPL exit hooks (only available with `repl` feature) #[cfg(feature = "repl")] - pub(crate) fn run_hook_repl_exit(&self) { + pub(crate) fn run_hook_repl_exit(&self, info: HookREPLExitInfo) { if !self.user_context.run_hook { return; } for hook in &self.hooks { - if let Some(repl_exit) = hook.repl_exit { - repl_exit() + if let Some(ref repl_exit) = hook.repl_exit { + repl_exit(&info); } } } /// Runs the REPL loop_once hooks (only available with `repl` feature) #[cfg(feature = "repl")] - pub(crate) fn run_hook_repl_loop_once(&self) { + pub(crate) fn run_hook_repl_loop_once(&self, info: HookREPLLoopOnceInfo) { if !self.user_context.run_hook { return; } for hook in &self.hooks { - if let Some(repl_loop_once) = hook.repl_loop_once { - repl_loop_once() + if let Some(ref repl_loop_once) = hook.repl_loop_once { + repl_loop_once(&info); } } } @@ -392,73 +416,110 @@ where /// Sets the handler for the `begin` event. #[must_use] - pub fn on_begin(mut self, handler: fn()) -> Self { - let _ = self.begin.insert(handler); + pub fn on_begin<F, R>(mut self, handler: F) -> Self + where + F: Fn(&HookBeginInfo) + 'static + Send + Sync, + { + self.begin = Some(Box::new(move |info| handler(info))); self } /// Sets the handler for the `pre_dispatch` event. #[must_use] - pub fn on_pre_dispatch(mut self, handler: fn(args: &[String])) -> Self { - let _ = self.pre_dispatch.insert(handler); + pub fn on_pre_dispatch<F, R>(mut self, handler: F) -> Self + where + F: for<'a> Fn(&HookPreDispatchInfo<'a>) -> R + 'static + Send + Sync, + R: Into<ProgramControls<C>>, + { + self.pre_dispatch = Some(Box::new(move |info| handler(info).into())); self } /// Sets the handler for the `post_dispatch` event. #[must_use] - pub fn on_post_dispatch(mut self, handler: fn(entry: &C)) -> Self { - let _ = self.post_dispatch.insert(handler); + pub fn on_post_dispatch<F, R>(mut self, handler: F) -> Self + where + F: for<'a> Fn(&HookPostDispatchInfo<'a, C>) -> R + 'static + Send + Sync, + R: Into<ProgramControls<C>>, + { + self.post_dispatch = Some(Box::new(move |info| handler(info).into())); self } /// Sets the handler for the `pre_chain` event. #[must_use] - pub fn on_pre_chain(mut self, handler: fn(input: &C, raw: &dyn Any)) -> Self { - let _ = self.pre_chain.insert(handler); + pub fn on_pre_chain<F, R>(mut self, handler: F) -> Self + where + F: for<'a> Fn(&HookPreChainInfo<'a, C>) -> R + 'static + Send + Sync, + R: Into<ProgramControls<C>>, + { + self.pre_chain = Some(Box::new(move |info| handler(info).into())); self } /// Sets the handler for the `post_chain` event. #[must_use] - pub fn on_post_chain(mut self, handler: fn(output: &AnyOutput<C>)) -> Self { - let _ = self.post_chain.insert(handler); + pub fn on_post_chain<F, R>(mut self, handler: F) -> Self + where + F: for<'a> Fn(&HookPostChainInfo<'a, C>) -> R + 'static + Send + Sync, + R: Into<ProgramControls<C>>, + { + self.post_chain = Some(Box::new(move |info| handler(info).into())); self } /// Sets the handler for the `pre_render` event. #[must_use] - pub fn on_pre_render(mut self, handler: fn(input: &C, raw: &dyn Any)) -> Self { - let _ = self.pre_render.insert(handler); + pub fn on_pre_render<F, R>(mut self, handler: F) -> Self + where + F: for<'a> Fn(&HookPreRenderInfo<'a, C>) -> R + 'static + Send + Sync, + R: Into<ProgramControls<C>>, + { + self.pre_render = Some(Box::new(move |info| handler(info).into())); self } /// Sets the handler for the `post_render` event. #[must_use] - pub fn on_post_render(mut self, handler: fn(result: &RenderResult)) -> Self { - let _ = self.post_render.insert(handler); + pub fn on_post_render<F, R>(mut self, handler: F) -> Self + where + F: for<'a> Fn(&HookPostRenderInfo<'a>) -> R + 'static + Send + Sync, + R: Into<ProgramControls<C>>, + { + self.post_render = Some(Box::new(move |info| handler(info).into())); self } /// Sets the handler for the `finish` event. #[must_use] - pub fn on_finish(mut self, handler: fn() -> i32) -> Self { - let _ = self.finish.insert(handler); + pub fn on_finish<F, R>(mut self, handler: F) -> Self + where + F: Fn(&HookFinishInfo) -> R + 'static + Send + Sync, + R: Into<ProgramControls<C>>, + { + self.finish = Some(Box::new(move |info| handler(info).into())); self } /// Sets the handler for the `exec_panic` event. #[cfg(not(feature = "async"))] #[must_use] - pub fn on_exec_panic(mut self, handler: fn(&ProgramPanic)) -> Self { - let _ = self.exec_panic.insert(handler); + pub fn on_exec_panic<F, R>(mut self, handler: F) -> Self + where + F: for<'a> Fn(&HookPanicInfo<'a>) + 'static + Send + Sync, + { + self.exec_panic = Some(Box::new(move |info| handler(info))); self } /// Sets the handler for the REPL begin event (only available with `repl` feature). #[cfg(feature = "repl")] #[must_use] - pub fn on_repl_begin(mut self, handler: fn()) -> Self { - let _ = self.repl_on_begin.insert(handler); + pub fn on_repl_begin<F>(mut self, handler: F) -> Self + where + F: Fn(&HookREPLBeginInfo) + 'static + Send + Sync, + { + self.repl_on_begin = Some(Box::new(move |info| handler(info))); self } @@ -466,27 +527,34 @@ where /// This hook runs after `on_repl_begin` but before reading the next input line. #[cfg(feature = "repl")] #[must_use] - pub fn on_repl_pre_readline(mut self, handler: fn()) -> Self { - let _ = self.repl_pre_readline.insert(handler); + pub fn on_repl_pre_readline<F>(mut self, handler: F) -> Self + where + F: Fn(&HookREPLPreReadlineInfo) + 'static + Send + Sync, + { + self.repl_pre_readline = Some(Box::new(move |info| handler(info))); self } /// Sets the custom REPL line reader (only available with `repl` feature). - /// If set, this function will be called to read a line instead of the default mechanism. - /// Returning `None` signals that there is no input (e.g., EOF). #[cfg(feature = "repl")] #[must_use] - pub fn on_repl_readline(mut self, handler: fn() -> Option<String>) -> Self { - let _ = self.repl_readline.insert(handler); + pub fn on_repl_readline<F>(mut self, handler: F) -> Self + where + F: Fn(&HookREPLReadlineInfo) -> Option<String> + 'static + Send + Sync, + { + self.repl_readline = Some(Box::new(move |info| handler(info))); self } /// Sets the handler for the REPL post-readline event (only available with `repl` feature). - /// This hook runs after reading a line of input and receives a mutable reference to the line. + /// This hook runs after reading a line of input and receives the read line info. #[cfg(feature = "repl")] #[must_use] - pub fn on_repl_post_readline(mut self, handler: fn(line: &mut String)) -> Self { - let _ = self.repl_post_readline.insert(handler); + pub fn on_repl_post_readline<F>(mut self, handler: F) -> Self + where + F: for<'a> Fn(&HookREPLPostReadlineInfo<'a>) + 'static + Send + Sync, + { + self.repl_post_readline = Some(Box::new(move |info| handler(info))); self } @@ -494,8 +562,11 @@ where /// This hook runs before executing a REPL command, receiving the parsed arguments. #[cfg(feature = "repl")] #[must_use] - pub fn on_repl_pre_exec(mut self, handler: fn(args: &[String])) -> Self { - let _ = self.repl_pre_exec.insert(handler); + pub fn on_repl_pre_exec<F>(mut self, handler: F) -> Self + where + F: for<'a> Fn(&HookREPLPreExecInfo<'a>) + 'static + Send + Sync, + { + self.repl_pre_exec = Some(Box::new(move |info| handler(info))); self } @@ -503,8 +574,11 @@ where /// This hook runs after executing a REPL command. #[cfg(feature = "repl")] #[must_use] - pub fn on_repl_post_exec(mut self, handler: fn()) -> Self { - let _ = self.repl_post_exec.insert(handler); + pub fn on_repl_post_exec<F>(mut self, handler: F) -> Self + where + F: Fn(&HookREPLPostExecInfo) + 'static + Send + Sync, + { + self.repl_post_exec = Some(Box::new(move |info| handler(info))); self } @@ -512,16 +586,22 @@ where /// This hook runs after a command is executed, receiving the render result on success. #[cfg(feature = "repl")] #[must_use] - pub fn on_repl_receive_result(mut self, handler: fn(result: &RenderResult)) -> Self { - let _ = self.repl_on_receive_result.insert(handler); + pub fn on_repl_receive_result<F>(mut self, handler: F) -> Self + where + F: for<'a> Fn(&HookREPLOnReceiveResultInfo<'a>) + 'static + Send + Sync, + { + self.repl_on_receive_result = Some(Box::new(move |info| handler(info))); self } /// Sets the handler for the REPL panic event (only available with `repl` feature). #[cfg(all(feature = "repl", not(feature = "async")))] #[must_use] - pub fn on_repl_panic(mut self, handler: fn(panic: &ProgramPanic)) -> Self { - let _ = self.repl_on_panic.insert(handler); + pub fn on_repl_panic<F>(mut self, handler: F) -> Self + where + F: for<'a> Fn(&HookREPLOnPanicInfo<'a>) + 'static + Send + Sync, + { + self.repl_on_panic = Some(Box::new(move |info| handler(info))); self } @@ -529,8 +609,11 @@ where /// This hook runs when the REPL is about to exit. #[cfg(feature = "repl")] #[must_use] - pub fn on_repl_exit(mut self, handler: fn()) -> Self { - let _ = self.repl_exit.insert(handler); + pub fn on_repl_exit<F>(mut self, handler: F) -> Self + where + F: Fn(&HookREPLExitInfo) + 'static + Send + Sync, + { + self.repl_exit = Some(Box::new(move |info| handler(info))); self } @@ -538,8 +621,11 @@ where /// This hook runs after each REPL loop iteration. #[cfg(feature = "repl")] #[must_use] - pub fn on_repl_loop_once(mut self, handler: fn()) -> Self { - let _ = self.repl_loop_once.insert(handler); + pub fn on_repl_loop_once<F>(mut self, handler: F) -> Self + where + F: Fn(&HookREPLLoopOnceInfo) + 'static + Send + Sync, + { + self.repl_loop_once = Some(Box::new(move |info| handler(info))); self } } @@ -575,46 +661,49 @@ mod tests { type ErrorRendererNotFound = MockHookEnum; type ResultEmpty = MockHookEnum; - fn build_renderer_not_found(_member_id: MockHookEnum) -> AnyOutput<MockHookEnum> { + fn build_renderer_not_found(_member_id: MockHookEnum) -> crate::AnyOutput<MockHookEnum> { unreachable!() } - fn build_dispatcher_not_found(_args: Vec<String>) -> AnyOutput<MockHookEnum> { + fn build_dispatcher_not_found(_args: Vec<String>) -> crate::AnyOutput<MockHookEnum> { unreachable!() } - fn build_empty_result() -> AnyOutput<MockHookEnum> { + fn build_empty_result() -> crate::AnyOutput<MockHookEnum> { unreachable!() } - fn render(_any: AnyOutput<MockHookEnum>, _r: &mut RenderResult) { + fn render(_any: crate::AnyOutput<MockHookEnum>, _r: &mut crate::RenderResult) { unreachable!() } - fn render_help(_any: AnyOutput<MockHookEnum>, _r: &mut RenderResult) { + fn render_help(_any: crate::AnyOutput<MockHookEnum>, _r: &mut crate::RenderResult) { unreachable!() } - fn do_chain(_any: AnyOutput<MockHookEnum>) -> crate::ChainProcess<MockHookEnum> { + fn do_chain(_any: crate::AnyOutput<MockHookEnum>) -> crate::ChainProcess<MockHookEnum> { unreachable!() } - fn has_renderer(_any: &AnyOutput<MockHookEnum>) -> bool { + fn has_renderer(_any: &crate::AnyOutput<MockHookEnum>) -> bool { unreachable!() } - fn has_chain(_any: &AnyOutput<MockHookEnum>) -> bool { + fn has_chain(_any: &crate::AnyOutput<MockHookEnum>) -> bool { unreachable!() } #[cfg(feature = "comp")] - fn do_comp(_any: &AnyOutput<MockHookEnum>, _ctx: &crate::ShellContext) -> crate::Suggest { + fn do_comp( + _any: &crate::AnyOutput<MockHookEnum>, + _ctx: &crate::ShellContext, + ) -> crate::Suggest { unreachable!() } #[cfg(feature = "general_renderer")] fn general_render( - _any: AnyOutput<MockHookEnum>, + _any: crate::AnyOutput<MockHookEnum>, _setting: &crate::GeneralRendererSetting, ) -> Result<crate::RenderResult, crate::error::GeneralRendererSerializeError> { unreachable!() @@ -637,35 +726,42 @@ mod tests { #[test] fn test_hook_on_begin() { static CALLED: AtomicBool = AtomicBool::new(false); - let hook = ProgramHook::<MockHookEnum>::empty().on_begin(|| { + let hook = ProgramHook::<MockHookEnum>::empty().on_begin::<_, ()>(|_: &HookBeginInfo| { CALLED.store(true, Ordering::SeqCst); }); assert!(hook.begin.is_some()); - (hook.begin.unwrap())(); + (hook.begin.as_ref().unwrap())(&HookBeginInfo {}); assert!(CALLED.load(Ordering::SeqCst)); } #[test] fn test_hook_on_pre_dispatch() { static CALLED: AtomicBool = AtomicBool::new(false); - let hook = ProgramHook::<MockHookEnum>::empty().on_pre_dispatch(|args| { - assert_eq!(args, &["a", "b"]); - CALLED.store(true, Ordering::SeqCst); - }); + let hook = + ProgramHook::<MockHookEnum>::empty().on_pre_dispatch(|info: &HookPreDispatchInfo| { + assert_eq!(info.arguments, &["a", "b"]); + CALLED.store(true, Ordering::SeqCst); + }); assert!(hook.pre_dispatch.is_some()); - (hook.pre_dispatch.unwrap())(&["a".to_string(), "b".to_string()]); + (hook.pre_dispatch.as_ref().unwrap())(&HookPreDispatchInfo { + arguments: &["a".to_string(), "b".to_string()], + }); assert!(CALLED.load(Ordering::SeqCst)); } #[test] fn test_hook_on_post_dispatch() { static CALLED: AtomicBool = AtomicBool::new(false); - let hook = ProgramHook::<MockHookEnum>::empty().on_post_dispatch(|entry| { - assert_eq!(*entry, MockHookEnum::A); - CALLED.store(true, Ordering::SeqCst); - }); + let hook = ProgramHook::<MockHookEnum>::empty().on_post_dispatch( + |info: &HookPostDispatchInfo<MockHookEnum>| { + assert_eq!(*info.entry, MockHookEnum::A); + CALLED.store(true, Ordering::SeqCst); + }, + ); assert!(hook.post_dispatch.is_some()); - (hook.post_dispatch.unwrap())(&MockHookEnum::A); + (hook.post_dispatch.as_ref().unwrap())(&HookPostDispatchInfo { + entry: &MockHookEnum::A, + }); assert!(CALLED.load(Ordering::SeqCst)); } @@ -673,13 +769,16 @@ mod tests { fn test_hook_on_pre_chain() { static CALLED: AtomicBool = AtomicBool::new(false); let hook = ProgramHook::<MockHookEnum>::empty().on_pre_chain( - |input: &MockHookEnum, _raw: &dyn Any| { - assert_eq!(*input, MockHookEnum::A); + |info: &HookPreChainInfo<MockHookEnum>| { + assert_eq!(*info.input, MockHookEnum::A); CALLED.store(true, Ordering::SeqCst); }, ); assert!(hook.pre_chain.is_some()); - (hook.pre_chain.unwrap())(&MockHookEnum::A, &42); + (hook.pre_chain.as_ref().unwrap())(&HookPreChainInfo { + input: &MockHookEnum::A, + raw: &42, + }); assert!(CALLED.load(Ordering::SeqCst)); } @@ -687,13 +786,13 @@ mod tests { fn test_hook_on_post_chain() { static CALLED: AtomicBool = AtomicBool::new(false); let hook = ProgramHook::<MockHookEnum>::empty().on_post_chain( - |_output: &AnyOutput<MockHookEnum>| { + |_info: &HookPostChainInfo<MockHookEnum>| { CALLED.store(true, Ordering::SeqCst); }, ); assert!(hook.post_chain.is_some()); - let output = AnyOutput::new(MockHookEnum::A); - (hook.post_chain.unwrap())(&output); + let output = crate::AnyOutput::new(MockHookEnum::A); + (hook.post_chain.as_ref().unwrap())(&HookPostChainInfo { output: &output }); assert!(CALLED.load(Ordering::SeqCst)); } @@ -701,46 +800,59 @@ mod tests { fn test_hook_on_pre_render() { static CALLED: AtomicBool = AtomicBool::new(false); let hook = ProgramHook::<MockHookEnum>::empty().on_pre_render( - |input: &MockHookEnum, _raw: &dyn Any| { - assert_eq!(*input, MockHookEnum::A); + |info: &HookPreRenderInfo<MockHookEnum>| { + assert_eq!(*info.input, MockHookEnum::A); CALLED.store(true, Ordering::SeqCst); }, ); assert!(hook.pre_render.is_some()); - (hook.pre_render.unwrap())(&MockHookEnum::A, &42); + (hook.pre_render.as_ref().unwrap())(&HookPreRenderInfo { + input: &MockHookEnum::A, + raw: &42, + }); assert!(CALLED.load(Ordering::SeqCst)); } #[test] fn test_hook_on_post_render() { static CALLED: AtomicBool = AtomicBool::new(false); - let hook = ProgramHook::<MockHookEnum>::empty().on_post_render(|_result: &RenderResult| { - CALLED.store(true, Ordering::SeqCst); - }); + let hook = + ProgramHook::<MockHookEnum>::empty().on_post_render(|_info: &HookPostRenderInfo| { + CALLED.store(true, Ordering::SeqCst); + }); assert!(hook.post_render.is_some()); - let result = RenderResult::default(); - (hook.post_render.unwrap())(&result); + let result = crate::RenderResult::default(); + (hook.post_render.as_ref().unwrap())(&HookPostRenderInfo { result: &result }); assert!(CALLED.load(Ordering::SeqCst)); } #[test] fn test_hook_on_finish() { - let hook = ProgramHook::<MockHookEnum>::empty().on_finish(|| 42); + let hook = ProgramHook::<MockHookEnum>::empty() + .on_finish(|_: &HookFinishInfo| ProgramControlUnit::OverrideExitCode(42)); assert!(hook.finish.is_some()); - assert_eq!((hook.finish.unwrap())(), 42); + let controls: Vec<ProgramControlUnit<MockHookEnum>> = + (hook.finish.as_ref().unwrap())(&HookFinishInfo {}) + .into_iter() + .collect(); + assert_eq!(controls.len(), 1); + match &controls[0] { + ProgramControlUnit::OverrideExitCode(code) => assert_eq!(*code, 42), + _ => panic!("Unexpected control unit"), + } } #[test] fn test_hook_builder_chaining() { let hook = ProgramHook::<MockHookEnum>::empty() - .on_begin(|| {}) - .on_pre_dispatch(|_| {}) - .on_post_dispatch(|_| {}) - .on_pre_chain(|_, _| {}) - .on_post_chain(|_| {}) - .on_pre_render(|_, _| {}) - .on_post_render(|_| {}) - .on_finish(|| 0); + .on_begin::<_, ()>(|_: &HookBeginInfo| ()) + .on_pre_dispatch(|_: &HookPreDispatchInfo| ()) + .on_post_dispatch(|_: &HookPostDispatchInfo<MockHookEnum>| ()) + .on_pre_chain(|_: &HookPreChainInfo<MockHookEnum>| ()) + .on_post_chain(|_: &HookPostChainInfo<MockHookEnum>| ()) + .on_pre_render(|_: &HookPreRenderInfo<MockHookEnum>| ()) + .on_post_render(|_: &HookPostRenderInfo| ()) + .on_finish(|_: &HookFinishInfo| ProgramControlUnit::OverrideExitCode(0)); assert!(hook.begin.is_some()); assert!(hook.pre_dispatch.is_some()); assert!(hook.post_dispatch.is_some()); diff --git a/mingling_core/src/program/hook/control_unit.rs b/mingling_core/src/program/hook/control_unit.rs new file mode 100644 index 0000000..b35cf3d --- /dev/null +++ b/mingling_core/src/program/hook/control_unit.rs @@ -0,0 +1,140 @@ +use crate::{AnyOutput, ProgramCollect}; + +/// Collection variants for program control instructions. +/// +/// Defines different forms of program control collections. +pub enum ProgramControls<C> +where + C: ProgramCollect<Enum = C>, +{ + /// Empty collection. + Empty, + + /// A single control unit. + Single(ProgramControlUnit<C>), + + /// A collection of multiple control units. + Multi(Vec<ProgramControlUnit<C>>), +} + +impl<C> ProgramControls<C> +where + C: ProgramCollect<Enum = C>, +{ + /// Returns `true` if the collection is empty. + pub fn is_empty(&self) -> bool { + matches!(self, ProgramControls::Empty) + } +} + +impl<C> From<()> for ProgramControls<C> +where + C: ProgramCollect<Enum = C>, +{ + fn from(_: ()) -> Self { + Self::Empty + } +} + +impl<C> From<ProgramControlUnit<C>> for ProgramControls<C> +where + C: ProgramCollect<Enum = C>, +{ + fn from(unit: ProgramControlUnit<C>) -> Self { + Self::Single(unit) + } +} + +impl<C> From<Vec<ProgramControlUnit<C>>> for ProgramControls<C> +where + C: ProgramCollect<Enum = C>, +{ + fn from(units: Vec<ProgramControlUnit<C>>) -> Self { + Self::Multi(units) + } +} + +impl<C> IntoIterator for ProgramControls<C> +where + C: ProgramCollect<Enum = C>, +{ + type Item = ProgramControlUnit<C>; + type IntoIter = ProgramControlsIter<C>; + + fn into_iter(self) -> Self::IntoIter { + match self { + ProgramControls::Empty => ProgramControlsIter { + inner: vec![].into_iter(), + }, + ProgramControls::Single(unit) => ProgramControlsIter { + inner: vec![unit].into_iter(), + }, + ProgramControls::Multi(units) => ProgramControlsIter { + inner: units.into_iter(), + }, + } + } +} + +/// An iterator over [`ProgramControlUnit`] values. +pub struct ProgramControlsIter<C> +where + C: ProgramCollect<Enum = C>, +{ + inner: std::vec::IntoIter<ProgramControlUnit<C>>, +} + +impl<C> Iterator for ProgramControlsIter<C> +where + C: ProgramCollect<Enum = C>, +{ + type Item = ProgramControlUnit<C>; + + fn next(&mut self) -> Option<Self::Item> { + self.inner.next() + } + + fn size_hint(&self) -> (usize, Option<usize>) { + self.inner.size_hint() + } +} + +impl<C> std::iter::FusedIterator for ProgramControlsIter<C> +where + C: ProgramCollect<Enum = C>, +{ + // Auto impl +} + +/// Enumeration of program control units. +/// +/// Defines the various control flow instructions that a program may encounter during execution, +/// used to alter the default execution flow (e.g., interruption, jump, or redirection). +pub enum ProgramControlUnit<C> +where + C: ProgramCollect<Enum = C>, +{ + /// Override the program exit code. + /// + /// Used when a non-default process exit code needs to be forcibly specified. + /// The contained `i32` value is the exit code to be set. + OverrideExitCode(i32), + + /// Route to the render flow. + /// + /// Transfers control to the rendering (output) stage, + /// carrying the `AnyOutput<C>` to be rendered. + RouteToRender(AnyOutput<C>), + + /// Route to the chain processing flow. + /// + /// Transfers control to the next chained processor, + /// carrying the `AnyOutput<C>` that needs to be passed along. + RouteToChain(AnyOutput<C>), + + /// Route to the help information flow. + /// + /// Transfers control to the help information display module, + /// carrying the `AnyOutput<C>` containing help-related content. + RouteToHelp(AnyOutput<C>), +} diff --git a/mingling_core/src/program/hook/hook_info.rs b/mingling_core/src/program/hook/hook_info.rs index e69de29..060e368 100644 --- a/mingling_core/src/program/hook/hook_info.rs +++ b/mingling_core/src/program/hook/hook_info.rs @@ -0,0 +1,119 @@ +use crate::{AnyOutput, ProgramCollect, RenderResult}; + +/// Represents the data passed to `begin` hook. +pub struct HookBeginInfo {} + +/// Represents the data passed to `pre_dispatch` hook. +pub struct HookPreDispatchInfo<'a> { + /// Arguments entered by the user before dispatching + pub arguments: &'a [String], +} + +/// Represents the data passed to `post_dispatch` hook. +pub struct HookPostDispatchInfo<'a, C> +where + C: ProgramCollect<Enum = C>, +{ + /// The entry point of dispatching + pub entry: &'a C, +} + +/// Represents the data passed to `pre_chain` hook. +pub struct HookPreChainInfo<'a, C> +where + C: ProgramCollect<Enum = C>, +{ + /// Input to the chain + pub input: &'a C, + + /// Raw data + pub raw: &'a dyn std::any::Any, +} + +/// Represents the data passed to `post_chain` hook. +pub struct HookPostChainInfo<'a, C> +where + C: ProgramCollect<Enum = C>, +{ + /// Output of the chain + pub output: &'a AnyOutput<C>, +} + +/// Represents the data passed to `pre_render` hook. +pub struct HookPreRenderInfo<'a, C> +where + C: ProgramCollect<Enum = C>, +{ + /// Render input + pub input: &'a C, + + /// The raw data to be rendered + pub raw: &'a dyn std::any::Any, +} + +/// Represents the data passed to `post_render` hook. +pub struct HookPostRenderInfo<'a> { + /// The rendering result + pub result: &'a RenderResult, +} + +/// Represents the data passed to `finish` hook. +pub struct HookFinishInfo {} + +/// Represents the data passed to `exec_panic` hook. +#[cfg(not(feature = "async"))] +pub struct HookPanicInfo<'a> { + /// Raw data of the panic + pub panic: &'a crate::error::ProgramPanic, +} + +#[cfg(feature = "repl")] +mod repl_hook { + use crate::RenderResult; + + /// Represents the data passed to `repl_on_begin` hook. + pub struct HookREPLBeginInfo {} + + /// Represents the data passed to `repl_pre_readline` hook. + pub struct HookREPLPreReadlineInfo {} + + /// Represents the data passed to `repl_readline` hook. + pub struct HookREPLReadlineInfo {} + + /// Represents the data passed to `repl_post_readline` hook. + pub struct HookREPLPostReadlineInfo<'a> { + /// The read line (mutable for editing) + pub line: &'a mut String, + } + + /// Represents the data passed to `repl_pre_exec` hook. + pub struct HookREPLPreExecInfo<'a> { + /// Arguments for the command + pub args: &'a [String], + } + + /// Represents the data passed to `repl_post_exec` hook. + pub struct HookREPLPostExecInfo {} + + /// Represents the data passed to `repl_on_receive_result` hook. + pub struct HookREPLOnReceiveResultInfo<'a> { + /// The rendering result + pub result: &'a RenderResult, + } + + /// Represents the data passed to `repl_exit` hook. + pub struct HookREPLExitInfo {} + + /// Represents the data passed to `repl_loop_once` hook. + pub struct HookREPLLoopOnceInfo {} + + /// Represents the data passed to `repl_on_panic` hook. + #[cfg(not(feature = "async"))] + pub struct HookREPLOnPanicInfo<'a> { + /// Raw data of the panic + pub panic: &'a crate::error::ProgramPanic, + } +} + +#[cfg(feature = "repl")] +pub use repl_hook::*; diff --git a/mingling_core/src/program/once_exec.rs b/mingling_core/src/program/once_exec.rs index fe90784..4c44597 100644 --- a/mingling_core/src/program/once_exec.rs +++ b/mingling_core/src/program/once_exec.rs @@ -38,7 +38,7 @@ where C: 'static + Send + Sync, { // Run hooks - self.run_hook_on_begin(); + self.run_hook_on_begin(crate::hook::HookBeginInfo {}); self.args = self.args.iter().skip(1).cloned().collect(); @@ -148,7 +148,7 @@ where C: 'static + Send + Sync, { // Run hooks - self.run_hook_on_begin(); + self.run_hook_on_begin(crate::hook::HookBeginInfo {}); self.args = self.args.iter().skip(1).cloned().collect(); @@ -171,7 +171,10 @@ where .downcast_ref::<Program<C>>() .unwrap(); - program.run_hook_exec_panic(&panic_payload); + program.run_hook_exec_panic(crate::hook::HookPanicInfo { + panic: &panic_payload, + }); + Err(ProgramExecuteError::Panic(panic_payload)) } } diff --git a/mingling_core/src/program/repl_exec.rs b/mingling_core/src/program/repl_exec.rs index d292be1..cbda9da 100644 --- a/mingling_core/src/program/repl_exec.rs +++ b/mingling_core/src/program/repl_exec.rs @@ -26,34 +26,42 @@ where // Inject default REPL resource self.with_resource(ResREPL::default()); - self.run_hook_repl_on_begin(); + self.run_hook_repl_on_begin(crate::hook::HookREPLBeginInfo {}); self.exec_wrapper(|p| -> () { loop { - p.run_hook_repl_pre_readline(); - let mut readline = p.run_hook_repl_readline().unwrap_or_default(); - p.run_hook_repl_post_readline(&mut readline); + p.run_hook_repl_pre_readline(crate::hook::HookREPLPreReadlineInfo {}); + let mut readline = p + .run_hook_repl_readline(crate::hook::HookREPLReadlineInfo {}) + .unwrap_or_default(); + p.run_hook_repl_post_readline(crate::hook::HookREPLPostReadlineInfo { + line: &mut readline, + }); let args = split_input_string(readline.clone()); - p.run_hook_repl_pre_exec(&args); + p.run_hook_repl_pre_exec(crate::hook::HookREPLPreExecInfo { args: &args }); match exec_once(p, args) { Ok(r) => { - p.run_hook_repl_on_receive_result(&r); + p.run_hook_repl_on_receive_result( + crate::hook::HookREPLOnReceiveResultInfo { result: &r }, + ); } Err(ProgramInternalExecuteError::REPLPanic(panic)) => { - p.run_hook_repl_on_panic(&panic); + p.run_hook_repl_on_panic(crate::hook::HookREPLOnPanicInfo { + panic: &panic, + }); } _ => {} } - p.run_hook_repl_post_exec(); + p.run_hook_repl_post_exec(crate::hook::HookREPLPostExecInfo {}); if this::<C>().res::<ResREPL>().unwrap().exit { - p.run_hook_repl_exit(); + p.run_hook_repl_exit(crate::hook::HookREPLExitInfo {}); break; } - p.run_hook_repl_loop_once(); + p.run_hook_repl_loop_once(crate::hook::HookREPLLoopOnceInfo {}); } }); } @@ -75,31 +83,42 @@ where // Inject default REPL resource self.with_resource(ResREPL::default()); - self.run_hook_repl_on_begin(); + self.run_hook_repl_on_begin(crate::hook::HookREPLBeginInfo {}); self.exec_wrapper(async |p| -> () { loop { - p.run_hook_repl_pre_readline(); - let mut readline = p.run_hook_repl_readline().unwrap_or_default(); - p.run_hook_repl_post_readline(&mut readline); + p.run_hook_repl_pre_readline(crate::hook::HookREPLPreReadlineInfo {}); + let mut readline = p + .run_hook_repl_readline(crate::hook::HookREPLReadlineInfo {}) + .unwrap_or_default(); + p.run_hook_repl_post_readline(crate::hook::HookREPLPostReadlineInfo { + line: &mut readline, + }); let args = split_input_string(readline.clone()); - p.run_hook_repl_pre_exec(&args); + p.run_hook_repl_pre_exec(crate::hook::HookREPLPreExecInfo { args: &args }); match exec_once(p, args).await { Ok(r) => { - p.run_hook_repl_on_receive_result(&r); + p.run_hook_repl_on_receive_result( + crate::hook::HookREPLOnReceiveResultInfo { result: &r }, + ); + } + Err(ProgramInternalExecuteError::REPLPanic(panic)) => { + p.run_hook_repl_on_panic(crate::hook::HookREPLOnPanicInfo { + panic: &panic, + }); } _ => {} } - p.run_hook_repl_post_exec(); + p.run_hook_repl_post_exec(crate::hook::HookREPLPostExecInfo {}); if this::<C>().res::<ResREPL>().unwrap().exit { - p.run_hook_repl_exit(); + p.run_hook_repl_exit(crate::hook::HookREPLExitInfo {}); break; } - p.run_hook_repl_loop_once(); + p.run_hook_repl_loop_once(crate::hook::HookREPLLoopOnceInfo {}); } }) .await; @@ -133,7 +152,9 @@ where .unwrap() .downcast_ref::<Program<C>>() .unwrap(); - program.run_hook_repl_on_panic(&panic_payload); + program.run_hook_repl_on_panic(crate::hook::HookREPLOnPanicInfo { + panic: &panic_payload, + }); Err(ProgramInternalExecuteError::REPLPanic(panic_payload)) } Ok(r) => r, diff --git a/mingling_core/tests/test-all/tests/integration.rs b/mingling_core/tests/test-all/tests/integration.rs index c622835..e173374 100644 --- a/mingling_core/tests/test-all/tests/integration.rs +++ b/mingling_core/tests/test-all/tests/integration.rs @@ -140,12 +140,12 @@ fn test_is_not_completing() { fn test_hook_setup() { static CALLED: AtomicBool = AtomicBool::new(false); - let hook = ProgramHook::<MockProgramCollect>::empty().on_begin(|| { + let hook = ProgramHook::<MockProgramCollect>::empty().on_begin::<_, ()>(|_| { CALLED.store(true, Ordering::SeqCst); }); assert!(hook.begin.is_some()); - (hook.begin.unwrap())(); + (hook.begin.unwrap())(&mingling::hook::HookBeginInfo {}); assert!(CALLED.load(Ordering::SeqCst)); } |
