From 1d1baf75a3acb5eb32913a8bdad42bae42844aa2 Mon Sep 17 00:00:00 2001 From: 魏曹先生 <1992414357@qq.com> Date: Wed, 24 Jun 2026 12:01:38 +0800 Subject: Redesign hook system with structured info types and ProgramControls --- mingling_core/src/program.rs | 4 +- mingling_core/src/program/collection.rs | 8 + mingling_core/src/program/exec.rs | 320 +++++++++------ mingling_core/src/program/hook.rs | 470 ++++++++++++++-------- mingling_core/src/program/hook/control_unit.rs | 140 +++++++ mingling_core/src/program/hook/hook_info.rs | 119 ++++++ mingling_core/src/program/once_exec.rs | 9 +- mingling_core/src/program/repl_exec.rs | 61 ++- mingling_core/tests/test-all/tests/integration.rs | 4 +- 9 files changed, 817 insertions(+), 318 deletions(-) create mode 100644 mingling_core/src/program/hook/control_unit.rs (limited to 'mingling_core') 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, 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, crate::error::ProgramInternalExecuteError> { + unreachable!() + } + /// Get all registered dispatcher names from the program #[cfg(feature = "dispatch_tree")] fn get_nodes() -> Vec<(String, &'static (dyn Dispatcher + 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( where C: ProgramCollect, { - // 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::(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::(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::(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::(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::(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( where C: ProgramCollect, { - // 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::(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::(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::(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::(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::(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) } @@ -286,6 +356,32 @@ where } } +#[inline] +pub(crate) fn handle_program_control>( + program: &Program, + controls: ProgramControls, + current: &mut AnyOutput, + exit_code: &mut i32, +) -> Option { + 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::(program, any_output); + r.exit_code = *exit_code; + return Some(r); + } + super::hook::ProgramControlUnit::RouteToHelp(any_output) => { + let mut r = render_help::(program, any_output); + r.exit_code = *exit_code; + return Some(r); + } + } + } + None +} + #[inline] #[allow(unused_variables)] fn render>(program: &Program, any: AnyOutput) -> RenderResult { 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 where C: ProgramCollect, { /// Executes when the program starts running - pub begin: Option, + pub begin: Option>, /// Executes before the program dispatches - pub pre_dispatch: Option, + pub pre_dispatch: + Option Fn(&HookPreDispatchInfo<'a>) -> ProgramControls + Send + Sync>>, /// Executes after the program dispatches - pub post_dispatch: Option, + pub post_dispatch: Option< + Box Fn(&HookPostDispatchInfo<'a, C>) -> ProgramControls + Send + Sync>, + >, /// Executes before the type enters the chain - pub pre_chain: Option, + pub pre_chain: + Option Fn(&HookPreChainInfo<'a, C>) -> ProgramControls + Send + Sync>>, /// Executes after the chain processing for the type ends - pub post_chain: Option)>, + pub post_chain: + Option Fn(&HookPostChainInfo<'a, C>) -> ProgramControls + Send + Sync>>, /// Executes before the type enters the renderer - pub pre_render: Option, + pub pre_render: + Option Fn(&HookPreRenderInfo<'a, C>) -> ProgramControls + Send + Sync>>, /// Executes after the type enters the renderer - pub post_render: Option, + pub post_render: + Option Fn(&HookPostRenderInfo<'a>) -> ProgramControls + Send + Sync>>, /// Executes before the program ends - pub finish: Option i32>, + pub finish: Option ProgramControls + Send + Sync>>, /// Executes when the program panics #[cfg(not(feature = "async"))] - pub exec_panic: Option, + pub exec_panic: Option Fn(&HookPanicInfo<'a>) + Send + Sync>>, /// Executes when the REPL starts (only available with `repl` feature) #[cfg(feature = "repl")] - pub repl_on_begin: Option, + pub repl_on_begin: Option>, /// Executes before reading the next REPL line (only available with `repl` feature) #[cfg(feature = "repl")] - pub repl_pre_readline: Option, + pub repl_pre_readline: Option>, /// 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 Option>, + pub repl_readline: Option Option + Send + Sync>>, /// Executes after reading a REPL line (only available with `repl` feature) #[cfg(feature = "repl")] - pub repl_post_readline: Option, + pub repl_post_readline: + Option Fn(&HookREPLPostReadlineInfo<'a>) + Send + Sync>>, /// Executes before executing a REPL command (only available with `repl` feature) #[cfg(feature = "repl")] - pub repl_pre_exec: Option, + pub repl_pre_exec: Option Fn(&HookREPLPreExecInfo<'a>) + Send + Sync>>, /// Executes after executing a REPL command (only available with `repl` feature) #[cfg(feature = "repl")] - pub repl_post_exec: Option, + pub repl_post_exec: Option>, /// Executes when the REPL receives a render result (only available with `repl` feature) #[cfg(feature = "repl")] - pub repl_on_receive_result: Option, + pub repl_on_receive_result: + Option 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, + pub repl_on_panic: Option Fn(&HookREPLOnPanicInfo<'a>) + Send + Sync>>, /// Executes when the REPL exits (only available with `repl` feature) #[cfg(feature = "repl")] - pub repl_exit: Option, + pub repl_exit: Option>, /// Executes after each REPL loop iteration (only available with `repl` feature) #[cfg(feature = "repl")] - pub repl_loop_once: Option, + pub repl_loop_once: Option>, } impl Program @@ -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 { 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, + ) -> ProgramControls { 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) -> ProgramControls { 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) { + pub(crate) fn run_hook_post_chain(&self, info: HookPostChainInfo) -> ProgramControls { 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) -> ProgramControls { 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 { 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 { 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 { + pub(crate) fn run_hook_repl_readline(&self, info: HookREPLReadlineInfo) -> Option { 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(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(mut self, handler: F) -> Self + where + F: for<'a> Fn(&HookPreDispatchInfo<'a>) -> R + 'static + Send + Sync, + R: Into>, + { + 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(mut self, handler: F) -> Self + where + F: for<'a> Fn(&HookPostDispatchInfo<'a, C>) -> R + 'static + Send + Sync, + R: Into>, + { + 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(mut self, handler: F) -> Self + where + F: for<'a> Fn(&HookPreChainInfo<'a, C>) -> R + 'static + Send + Sync, + R: Into>, + { + 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)) -> Self { - let _ = self.post_chain.insert(handler); + pub fn on_post_chain(mut self, handler: F) -> Self + where + F: for<'a> Fn(&HookPostChainInfo<'a, C>) -> R + 'static + Send + Sync, + R: Into>, + { + 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(mut self, handler: F) -> Self + where + F: for<'a> Fn(&HookPreRenderInfo<'a, C>) -> R + 'static + Send + Sync, + R: Into>, + { + 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(mut self, handler: F) -> Self + where + F: for<'a> Fn(&HookPostRenderInfo<'a>) -> R + 'static + Send + Sync, + R: Into>, + { + 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(mut self, handler: F) -> Self + where + F: Fn(&HookFinishInfo) -> R + 'static + Send + Sync, + R: Into>, + { + 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(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(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(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) -> Self { - let _ = self.repl_readline.insert(handler); + pub fn on_repl_readline(mut self, handler: F) -> Self + where + F: Fn(&HookREPLReadlineInfo) -> Option + '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(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(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(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(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(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(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(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 { + fn build_renderer_not_found(_member_id: MockHookEnum) -> crate::AnyOutput { unreachable!() } - fn build_dispatcher_not_found(_args: Vec) -> AnyOutput { + fn build_dispatcher_not_found(_args: Vec) -> crate::AnyOutput { unreachable!() } - fn build_empty_result() -> AnyOutput { + fn build_empty_result() -> crate::AnyOutput { unreachable!() } - fn render(_any: AnyOutput, _r: &mut RenderResult) { + fn render(_any: crate::AnyOutput, _r: &mut crate::RenderResult) { unreachable!() } - fn render_help(_any: AnyOutput, _r: &mut RenderResult) { + fn render_help(_any: crate::AnyOutput, _r: &mut crate::RenderResult) { unreachable!() } - fn do_chain(_any: AnyOutput) -> crate::ChainProcess { + fn do_chain(_any: crate::AnyOutput) -> crate::ChainProcess { unreachable!() } - fn has_renderer(_any: &AnyOutput) -> bool { + fn has_renderer(_any: &crate::AnyOutput) -> bool { unreachable!() } - fn has_chain(_any: &AnyOutput) -> bool { + fn has_chain(_any: &crate::AnyOutput) -> bool { unreachable!() } #[cfg(feature = "comp")] - fn do_comp(_any: &AnyOutput, _ctx: &crate::ShellContext) -> crate::Suggest { + fn do_comp( + _any: &crate::AnyOutput, + _ctx: &crate::ShellContext, + ) -> crate::Suggest { unreachable!() } #[cfg(feature = "general_renderer")] fn general_render( - _any: AnyOutput, + _any: crate::AnyOutput, _setting: &crate::GeneralRendererSetting, ) -> Result { unreachable!() @@ -637,35 +726,42 @@ mod tests { #[test] fn test_hook_on_begin() { static CALLED: AtomicBool = AtomicBool::new(false); - let hook = ProgramHook::::empty().on_begin(|| { + let hook = ProgramHook::::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::::empty().on_pre_dispatch(|args| { - assert_eq!(args, &["a", "b"]); - CALLED.store(true, Ordering::SeqCst); - }); + let hook = + ProgramHook::::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::::empty().on_post_dispatch(|entry| { - assert_eq!(*entry, MockHookEnum::A); - CALLED.store(true, Ordering::SeqCst); - }); + let hook = ProgramHook::::empty().on_post_dispatch( + |info: &HookPostDispatchInfo| { + 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::::empty().on_pre_chain( - |input: &MockHookEnum, _raw: &dyn Any| { - assert_eq!(*input, MockHookEnum::A); + |info: &HookPreChainInfo| { + 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::::empty().on_post_chain( - |_output: &AnyOutput| { + |_info: &HookPostChainInfo| { 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::::empty().on_pre_render( - |input: &MockHookEnum, _raw: &dyn Any| { - assert_eq!(*input, MockHookEnum::A); + |info: &HookPreRenderInfo| { + 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::::empty().on_post_render(|_result: &RenderResult| { - CALLED.store(true, Ordering::SeqCst); - }); + let hook = + ProgramHook::::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::::empty().on_finish(|| 42); + let hook = ProgramHook::::empty() + .on_finish(|_: &HookFinishInfo| ProgramControlUnit::OverrideExitCode(42)); assert!(hook.finish.is_some()); - assert_eq!((hook.finish.unwrap())(), 42); + let controls: Vec> = + (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::::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| ()) + .on_pre_chain(|_: &HookPreChainInfo| ()) + .on_post_chain(|_: &HookPostChainInfo| ()) + .on_pre_render(|_: &HookPreRenderInfo| ()) + .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 +where + C: ProgramCollect, +{ + /// Empty collection. + Empty, + + /// A single control unit. + Single(ProgramControlUnit), + + /// A collection of multiple control units. + Multi(Vec>), +} + +impl ProgramControls +where + C: ProgramCollect, +{ + /// Returns `true` if the collection is empty. + pub fn is_empty(&self) -> bool { + matches!(self, ProgramControls::Empty) + } +} + +impl From<()> for ProgramControls +where + C: ProgramCollect, +{ + fn from(_: ()) -> Self { + Self::Empty + } +} + +impl From> for ProgramControls +where + C: ProgramCollect, +{ + fn from(unit: ProgramControlUnit) -> Self { + Self::Single(unit) + } +} + +impl From>> for ProgramControls +where + C: ProgramCollect, +{ + fn from(units: Vec>) -> Self { + Self::Multi(units) + } +} + +impl IntoIterator for ProgramControls +where + C: ProgramCollect, +{ + type Item = ProgramControlUnit; + type IntoIter = ProgramControlsIter; + + 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 +where + C: ProgramCollect, +{ + inner: std::vec::IntoIter>, +} + +impl Iterator for ProgramControlsIter +where + C: ProgramCollect, +{ + type Item = ProgramControlUnit; + + fn next(&mut self) -> Option { + self.inner.next() + } + + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } +} + +impl std::iter::FusedIterator for ProgramControlsIter +where + C: ProgramCollect, +{ + // 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 +where + C: ProgramCollect, +{ + /// 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` to be rendered. + RouteToRender(AnyOutput), + + /// Route to the chain processing flow. + /// + /// Transfers control to the next chained processor, + /// carrying the `AnyOutput` that needs to be passed along. + RouteToChain(AnyOutput), + + /// Route to the help information flow. + /// + /// Transfers control to the help information display module, + /// carrying the `AnyOutput` containing help-related content. + RouteToHelp(AnyOutput), +} 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, +{ + /// 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, +{ + /// 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, +{ + /// Output of the chain + pub output: &'a AnyOutput, +} + +/// Represents the data passed to `pre_render` hook. +pub struct HookPreRenderInfo<'a, C> +where + C: ProgramCollect, +{ + /// 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::>() .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::().res::().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::().res::().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::>() .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::::empty().on_begin(|| { + let hook = ProgramHook::::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)); } -- cgit