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 --- CHANGELOG.md | 40 ++ README.md | 34 +- examples/example-async-support/src/main.rs | 2 +- examples/example-hook/src/main.rs | 23 +- examples/example-panic-unwind/src/main.rs | 5 +- examples/example-repl-basic/src/main.rs | 2 +- mingling/src/example_docs.rs | 32 +- mingling/src/setups/exit_code.rs | 11 +- mingling/src/setups/repl_basic.rs | 11 +- 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 +- mling/src/cli.rs | 6 +- 19 files changed, 933 insertions(+), 368 deletions(-) create mode 100644 mingling_core/src/program/hook/control_unit.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 1fefdfb..47b6448 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -309,6 +309,46 @@ let value = route!(prev.pick_or_route((), Error::default().to_chain()).unpack()) let value = route!(prev.pick_or_route((), Error::default()).unpack()); ``` +5. **\[core\]** **\[hook\]** Refactored the hook system to use structured info types and return `ProgramControls` instead of raw values. + + The hook system has been redesigned for better type safety, extensibility, and control flow management: + + - **All hook callbacks now receive structured info types** (e.g., `&HookPreDispatchInfo`, `&HookPostChainInfo`) instead of raw tuples or bare values. Each hook event has a dedicated info struct with named fields, making hook signatures self-documenting and easier to evolve. + + - **Hook signatures changed from `fn(...)` to `Box R>**`, with `R: Into>`. Closures that return `()`are automatically converted to`ProgramControls::Empty`via the`From<()>` impl. + + ```rust + // Before + .on_begin(|| println!("Program started")) + .on_pre_dispatch(|args| println!("Dispatching: {args:?}")) + .on_finish(|| 0) // returns i32 as exit code + + // After + .on_begin::<_, ()>(|_| println!("Program started")) + .on_pre_dispatch(|info| println!("Dispatching: {}", info.arguments.join(" "))) + .on_finish(|_| ProgramControlUnit::OverrideExitCode(0)) + ``` + + - **Added `ProgramControls` and `ProgramControlUnit`** — a new control flow system that replaces the previous approach where only the `finish` hook could return a value (exit code). Now any hook can issue control instructions: + - `ProgramControlUnit::OverrideExitCode(i32)` — override the program's exit code + - `ProgramControlUnit::RouteToChain(AnyOutput)` — route to another chain processor + - `ProgramControlUnit::RouteToRender(AnyOutput)` — route directly to the renderer + - `ProgramControlUnit::RouteToHelp(AnyOutput)` — route to help display + + - **Added `handle_program_control` function** in `exec.rs` that processes `ProgramControls` returned by hooks, updating the current execution state (exit code, current `AnyOutput`) or triggering early returns (e.g., routing to render/help). + + - **`ExitCodeSetup` updated** — its `on_finish` hook now returns `ProgramControlUnit::OverrideExitCode(this.exit_code)` instead of just `this.exit_code`. + + - **`HookPostReadlineInfo` now wraps `line: &mut String`** — the `repl_post_readline` hook receives a structured info object instead of a raw `&mut String`. + + - **`HookOnReceiveResultInfo` now wraps `result: &RenderResult`** — the `repl_on_receive_result` hook receives the result through an info struct with a `.result` field instead of directly. + + - **`hook` module made public** — moved from `#[doc(hidden)]` to a documented public module (`pub mod hook`), along with all associated info types and control unit types. + + - **Added `dispatch_args_trie` default method** on `ProgramCollect` (behind `#[cfg(not(feature = "dispatch_tree"))]`) that calls `unreachable!()` by default, avoiding `#[cfg]` gymnastics in `exec.rs`. + + - **Examples and internal callers updated** throughout the codebase to use the new hook API patterns. + ### Release 0.1.9 (2026-05-29) #### Fixes: diff --git a/README.md b/README.md index b2f8b11..b5d3362 100644 --- a/README.md +++ b/README.md @@ -624,28 +624,34 @@ fn handle_exit(_prev: EntryExit, repl: &mut ResREPL) { Mingling provides a `ProgramHook` system for observing every stage of the execution pipeline. Useful for debugging, logging, or telemetry. ```rust -use mingling::prelude::*; -use mingling::hook::ProgramHook; +use mingling::{ + hook::{ProgramControlUnit, ProgramHook}, + prelude::*, +}; dispatcher!("greet", CMDGreet => EntryGreet); fn main() { let mut program = ThisProgram::new(); - program.with_dispatcher(CMDGreet); + program.with_hook( ProgramHook::::empty() - .on_begin(|| println!("[DEBUG] Program started")) - .on_pre_dispatch(|args| println!("[DEBUG] Dispatching: {args:?}")) - .on_post_dispatch(|entry| println!("[DEBUG] Dispatched: {entry:?}")) - .on_pre_chain(|entry, _| println!("[DEBUG] Pre chain: {entry}")) - .on_post_chain(|output| println!("[DEBUG] Post chain: {}", output.member_id)) - .on_pre_render(|ty, _| println!("[DEBUG] Pre render: {ty}")) - .on_post_render(|_| println!("[DEBUG] Post render")) - .on_finish(|| { - println!("[DEBUG] Program end"); - 0 // override exit code - }), + .on_begin::<_, ()>(|_| println!("[DEBUG] Program is begin")) + .on_pre_dispatch(|info| println!("[DEBUG] Pre dispatch: {}", info.arguments.join(" "))) + .on_post_dispatch(|info| println!("[DEBUG] Post dispatch: {}", info.entry)) + .on_pre_chain(|info| { + println!("[DEBUG] Pre chain: {}", info.input); + }) + .on_post_chain(|info| println!("[DEBUG] Post chain: {}", info.output.member_id)) + .on_finish(|_| { + println!("[DEBUG] Loop end"); + ProgramControlUnit::OverrideExitCode(0) // Override exit code + }) + .on_pre_render(|info| println!("[DEBUG] Pre render: {}", info.input)) + .on_post_render(|_| println!("[DEBUG] Post render")), ); + + program.with_dispatcher(CMDGreet); program.exec_and_exit(); } ``` diff --git a/examples/example-async-support/src/main.rs b/examples/example-async-support/src/main.rs index 5ded5e5..920b3dd 100644 --- a/examples/example-async-support/src/main.rs +++ b/examples/example-async-support/src/main.rs @@ -33,7 +33,7 @@ async fn main() { program.with_dispatcher(CMDDownload); // Add a hook to display when the download begins - program.with_hook(ProgramHook::empty().on_begin(|| println!("Download begin"))); + program.with_hook(ProgramHook::empty().on_begin::<_, ()>(|_| println!("Download begin"))); // --------- IMPORTANT --------- // The return values of `exec_*()` related functions have been replaced with Futures diff --git a/examples/example-hook/src/main.rs b/examples/example-hook/src/main.rs index d6a2dd0..d9a8fd1 100644 --- a/examples/example-hook/src/main.rs +++ b/examples/example-hook/src/main.rs @@ -20,7 +20,10 @@ //! Hello, Alice! //! ``` -use mingling::{hook::ProgramHook, prelude::*}; +use mingling::{ + hook::{ProgramControlUnit, ProgramHook}, + prelude::*, +}; dispatcher!("greet", CMDGreet => EntryGreet); @@ -30,18 +33,18 @@ fn main() { // --------- IMPORTANT --------- program.with_hook( ProgramHook::::empty() - .on_begin(|| println!("[DEBUG] Program is begin")) - .on_pre_dispatch(|args| println!("[DEBUG] Pre dispatch: {args:?}")) - .on_post_dispatch(|c: &_| println!("[DEBUG] Post dispatch: {c:?}")) - .on_pre_chain(|c: &_, _| { - println!("[DEBUG] Pre chain: {c}"); + .on_begin::<_, ()>(|_| println!("[DEBUG] Program is begin")) + .on_pre_dispatch(|info| println!("[DEBUG] Pre dispatch: {}", info.arguments.join(" "))) + .on_post_dispatch(|info| println!("[DEBUG] Post dispatch: {}", info.entry)) + .on_pre_chain(|info| { + println!("[DEBUG] Pre chain: {}", info.input); }) - .on_post_chain(|any_output| println!("[DEBUG] Post chain: {}", any_output.member_id)) - .on_finish(|| { + .on_post_chain(|info| println!("[DEBUG] Post chain: {}", info.output.member_id)) + .on_finish(|_| { println!("[DEBUG] Loop end"); - 0 // Override exit code + ProgramControlUnit::OverrideExitCode(0) // Override exit code }) - .on_pre_render(|c: &_, _| println!("[DEBUG] Pre render: {c}")) + .on_pre_render(|info| println!("[DEBUG] Pre render: {}", info.input)) .on_post_render(|_| println!("[DEBUG] Post render")), ); // --------- IMPORTANT --------- diff --git a/examples/example-panic-unwind/src/main.rs b/examples/example-panic-unwind/src/main.rs index bb25541..ed032c5 100644 --- a/examples/example-panic-unwind/src/main.rs +++ b/examples/example-panic-unwind/src/main.rs @@ -29,7 +29,10 @@ fn main() { program.stdout_setting.silence_panic = true; // Define a hook to output &ProgramPanic when a Panic occurs - program.with_hook(ProgramHook::empty().on_exec_panic(|info| println!("Program panic: {info}"))); + program.with_hook( + ProgramHook::empty() + .on_exec_panic::<_, ()>(|info| println!("Program panic: {}", info.panic)), + ); // --------- IMPORTANT --------- let _ = program.exec(); diff --git a/examples/example-repl-basic/src/main.rs b/examples/example-repl-basic/src/main.rs index abea141..8df8c22 100644 --- a/examples/example-repl-basic/src/main.rs +++ b/examples/example-repl-basic/src/main.rs @@ -65,7 +65,7 @@ fn main() { })); // Add hooks to handle REPL-related events - program.with_hook(ProgramHook::empty().on_repl_begin(|| { + program.with_hook(ProgramHook::empty().on_repl_begin(|_| { // Print welcome message println!("Welcome!"); })); diff --git a/mingling/src/example_docs.rs b/mingling/src/example_docs.rs index 3171c22..ea8e539 100644 --- a/mingling/src/example_docs.rs +++ b/mingling/src/example_docs.rs @@ -175,7 +175,7 @@ pub mod example_argument_parse {} /// program.with_dispatcher(CMDDownload); /// /// // Add a hook to display when the download begins -/// program.with_hook(ProgramHook::empty().on_begin(|| println!("Download begin"))); +/// program.with_hook(ProgramHook::empty().on_begin::<_, ()>(|_| println!("Download begin"))); /// /// // --------- IMPORTANT --------- /// // The return values of `exec_*()` related functions have been replaced with Futures @@ -1359,7 +1359,10 @@ pub mod example_help {} /// /// Source code (./src/main.rs) /// ```ignore -/// use mingling::{hook::ProgramHook, prelude::*}; +/// use mingling::{ +/// hook::{ProgramControlUnit, ProgramHook}, +/// prelude::*, +/// }; /// /// dispatcher!("greet", CMDGreet => EntryGreet); /// @@ -1369,18 +1372,18 @@ pub mod example_help {} /// // --------- IMPORTANT --------- /// program.with_hook( /// ProgramHook::::empty() -/// .on_begin(|| println!("[DEBUG] Program is begin")) -/// .on_pre_dispatch(|args| println!("[DEBUG] Pre dispatch: {args:?}")) -/// .on_post_dispatch(|c: &_| println!("[DEBUG] Post dispatch: {c:?}")) -/// .on_pre_chain(|c: &_, _| { -/// println!("[DEBUG] Pre chain: {c}"); +/// .on_begin::<_, ()>(|_| println!("[DEBUG] Program is begin")) +/// .on_pre_dispatch(|info| println!("[DEBUG] Pre dispatch: {}", info.arguments.join(" "))) +/// .on_post_dispatch(|info| println!("[DEBUG] Post dispatch: {}", info.entry)) +/// .on_pre_chain(|info| { +/// println!("[DEBUG] Pre chain: {}", info.input); /// }) -/// .on_post_chain(|any_output| println!("[DEBUG] Post chain: {}", any_output.member_id)) -/// .on_finish(|| { +/// .on_post_chain(|info| println!("[DEBUG] Post chain: {}", info.output.member_id)) +/// .on_finish(|_| { /// println!("[DEBUG] Loop end"); -/// 0 // Override exit code +/// ProgramControlUnit::OverrideExitCode(0) // Override exit code /// }) -/// .on_pre_render(|c: &_, _| println!("[DEBUG] Pre render: {c}")) +/// .on_pre_render(|info| println!("[DEBUG] Pre render: {}", info.input)) /// .on_post_render(|_| println!("[DEBUG] Post render")), /// ); /// // --------- IMPORTANT --------- @@ -1854,7 +1857,10 @@ pub mod example_pack_err {} /// program.stdout_setting.silence_panic = true; /// /// // Define a hook to output &ProgramPanic when a Panic occurs -/// program.with_hook(ProgramHook::empty().on_exec_panic(|info| println!("Program panic: {info}"))); +/// program.with_hook( +/// ProgramHook::empty() +/// .on_exec_panic::<_, ()>(|info| println!("Program panic: {}", info.panic)), +/// ); /// // --------- IMPORTANT --------- /// /// let _ = program.exec(); @@ -1967,7 +1973,7 @@ pub mod example_panic_unwind {} /// })); /// /// // Add hooks to handle REPL-related events -/// program.with_hook(ProgramHook::empty().on_repl_begin(|| { +/// program.with_hook(ProgramHook::empty().on_repl_begin(|_| { /// // Print welcome message /// println!("Welcome!"); /// })); diff --git a/mingling/src/setups/exit_code.rs b/mingling/src/setups/exit_code.rs index ed8204c..025ed8a 100644 --- a/mingling/src/setups/exit_code.rs +++ b/mingling/src/setups/exit_code.rs @@ -1,6 +1,11 @@ use std::marker::PhantomData; -use mingling_core::{ProgramCollect, hook::ProgramHook, setup::ProgramSetup, this}; +use mingling_core::{ + ProgramCollect, + hook::{ProgramControlUnit, ProgramHook}, + setup::ProgramSetup, + this, +}; use crate::res::ResExitCode; @@ -32,9 +37,9 @@ where program.with_resource(ResExitCode { exit_code: 0 }); // Insert hook to override exit code before program ends - program.with_hook(ProgramHook::empty().on_finish(|| { + program.with_hook(ProgramHook::empty().on_finish(|_| { let this = this::().res_or_default::(); - this.exit_code + ProgramControlUnit::OverrideExitCode(this.exit_code) })); } } diff --git a/mingling/src/setups/repl_basic.rs b/mingling/src/setups/repl_basic.rs index 8b9b83f..71a38d2 100644 --- a/mingling/src/setups/repl_basic.rs +++ b/mingling/src/setups/repl_basic.rs @@ -9,7 +9,7 @@ where C: ProgramCollect, { fn setup(self, program: &mut Program) { - program.with_hook(ProgramHook::empty().on_repl_readline(|| readline().ok())); + program.with_hook(ProgramHook::empty().on_repl_readline(|_| readline().ok())); } } @@ -48,7 +48,7 @@ where print!("{}", PROMPT.get().unwrap()); let _ = std::io::stdout().flush(); } - program.with_hook(ProgramHook::empty().on_repl_pre_readline(print_prompt)); + program.with_hook(ProgramHook::empty().on_repl_pre_readline(|_| print_prompt())); } BasicREPLPromptSetup::Func(f) => { static FUNC: std::sync::OnceLock String> = std::sync::OnceLock::new(); @@ -57,7 +57,8 @@ where print!("{}", FUNC.get().unwrap()()); let _ = std::io::stdout().flush(); } - program.with_hook(ProgramHook::empty().on_repl_pre_readline(print_func_prompt)); + program + .with_hook(ProgramHook::empty().on_repl_pre_readline(|_| print_func_prompt())); } } } @@ -71,8 +72,8 @@ where { fn setup(self, program: &mut Program) { program.with_hook(ProgramHook::empty().on_repl_receive_result(|r| { - if !r.is_empty() { - println!("{}", r.trim()) + if !r.result.is_empty() { + println!("{}", r.result.trim()) } })); } 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)); } diff --git a/mling/src/cli.rs b/mling/src/cli.rs index 123cca3..a6d099b 100644 --- a/mling/src/cli.rs +++ b/mling/src/cli.rs @@ -39,7 +39,8 @@ pub fn run() { }); // Intercept Help - program.with_hook(ProgramHook::empty().on_post_dispatch(|c| match c { + program.with_hook( + ProgramHook::empty().on_post_dispatch(|info| match info.entry { // When dispatcher is not found ThisProgram::ErrorDispatcherNotFound // And user requests Help @@ -49,7 +50,8 @@ pub fn run() { exit(0) } _ => {} - })); + }), + ); // Commands program.with_dispatcher(CMDCompletion); -- cgit