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