aboutsummaryrefslogtreecommitdiff
path: root/mingling_core/src/program
diff options
context:
space:
mode:
Diffstat (limited to 'mingling_core/src/program')
-rw-r--r--mingling_core/src/program/config.rs15
-rw-r--r--mingling_core/src/program/exec.rs114
-rw-r--r--mingling_core/src/program/flag.rs2
-rw-r--r--mingling_core/src/program/hook.rs349
-rw-r--r--mingling_core/src/program/setup.rs5
-rw-r--r--mingling_core/src/program/setup/general_renderer.rs2
6 files changed, 468 insertions, 19 deletions
diff --git a/mingling_core/src/program/config.rs b/mingling_core/src/program/config.rs
index ac541fd..35b9392 100644
--- a/mingling_core/src/program/config.rs
+++ b/mingling_core/src/program/config.rs
@@ -35,13 +35,26 @@ impl Default for ProgramStdoutSetting {
}
/// Program user context
-#[derive(Debug, Clone, Default)]
+#[derive(Debug, Clone)]
pub struct ProgramUserContext {
/// View help information instead of running the command
pub help: bool,
/// Skip user confirmation step
pub confirm: bool,
+
+ /// Execute hooks during the program lifecycle
+ pub run_hook: bool,
+}
+
+impl Default for ProgramUserContext {
+ fn default() -> Self {
+ Self {
+ help: false,
+ confirm: false,
+ run_hook: true,
+ }
+ }
}
#[cfg(feature = "general_renderer")]
diff --git a/mingling_core/src/program/exec.rs b/mingling_core/src/program/exec.rs
index 001b250..acd1bd6 100644
--- a/mingling_core/src/program/exec.rs
+++ b/mingling_core/src/program/exec.rs
@@ -13,19 +13,31 @@ pub async fn exec<C>(
program: &'static Program<C>,
) -> Result<RenderResult, ProgramInternalExecuteError>
where
- C: ProgramCollect<Enum = C>,
+ C: ProgramCollect<Enum = C> + std::fmt::Display,
{
+ // Run hooks
+ program.run_hook_on_begin();
+ program.run_hook_pre_dispatch(&program.args);
+
#[cfg(not(feature = "dispatch_tree"))]
let mut current = dispatch_args_dynamic(program, &program.args)?;
#[cfg(feature = "dispatch_tree")]
let mut current = C::dispatch_args_trie(&program.args)?;
+ // Run hook
+ program.run_hook_post_dispatch(&current.member_id);
+
let mut stop_next = false;
// If the program has Help enabled, skip actual logic and jump to Help
if program.user_context.help {
- return Ok(render_help::<C>(program, current));
+ let mut render_result = render_help::<C>(program, current);
+
+ // Run hook
+ render_result.exit_code = program.run_hook_finish();
+
+ return Ok(render_result);
}
loop {
@@ -34,17 +46,42 @@ where
current = {
// If a chain exists, execute as a chain
if C::has_chain(&current) {
+ // Run hook
+ program.run_hook_pre_chain(&current.member_id, current.inner.as_ref());
+
match C::do_chain(current).await {
ChainProcess::Ok((any, Next::Renderer)) => {
- return Ok(render::<C>(program, any));
+ let mut render_result = render::<C>(program, any);
+
+ // Run hook
+ render_result.exit_code = program.run_hook_finish();
+
+ return Ok(render_result);
+ }
+ ChainProcess::Ok((any, Next::Chain)) => {
+ // Run hook
+ program.run_hook_post_chain(&any);
+ any
+ }
+ ChainProcess::Err(e) => {
+ // Run hook
+ program.run_hook_finish();
+ return Err(e.into());
}
- ChainProcess::Ok((any, Next::Chain)) => any,
- ChainProcess::Err(e) => return Err(e.into()),
}
}
// If no chain exists, attempt to render
else if C::has_renderer(&current) {
- return Ok(render::<C>(program, 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 {
@@ -57,25 +94,42 @@ where
break;
}
}
- Ok(RenderResult::default())
+ let mut render_result = RenderResult::default();
+
+ // Run hook
+ render_result.exit_code = program.run_hook_finish();
+
+ Ok(render_result)
}
#[cfg(not(feature = "async"))]
pub fn exec<C>(program: &'static Program<C>) -> Result<RenderResult, ProgramInternalExecuteError>
where
- C: ProgramCollect<Enum = C>,
+ C: ProgramCollect<Enum = C> + std::fmt::Display,
{
+ // Run hooks
+ program.run_hook_on_begin();
+ program.run_hook_pre_dispatch(&program.args);
+
#[cfg(not(feature = "dispatch_tree"))]
let mut current = dispatch_args_dynamic(program, &program.args)?;
#[cfg(feature = "dispatch_tree")]
let mut current = C::dispatch_args_trie(&program.args)?;
+ // Run hook
+ program.run_hook_post_dispatch(&current.member_id);
+
let mut stop_next = false;
// If the program has Help enabled, skip actual logic and jump to Help
if program.user_context.help {
- return Ok(render_help::<C>(program, current));
+ let mut render_result = render_help::<C>(program, current);
+
+ // Run hook
+ render_result.exit_code = program.run_hook_finish();
+
+ return Ok(render_result);
}
loop {
@@ -84,17 +138,44 @@ where
current = {
// If a chain exists, execute as a chain
if C::has_chain(&current) {
+ // Run hook
+ program.run_hook_pre_chain(&current.member_id, current.inner.as_ref());
+
match C::do_chain(current) {
ChainProcess::Ok((any, Next::Renderer)) => {
- return Ok(render::<C>(program, any));
+ {
+ let mut render_result = render::<C>(program, any);
+
+ // Run hook
+ render_result.exit_code = program.run_hook_finish();
+
+ return Ok(render_result);
+ };
+ }
+ ChainProcess::Ok((any, Next::Chain)) => {
+ // Run hook
+ program.run_hook_post_chain(&any);
+ any
+ }
+ ChainProcess::Err(e) => {
+ // Run hook
+ program.run_hook_finish();
+ return Err(e.into());
}
- ChainProcess::Ok((any, Next::Chain)) => any,
- ChainProcess::Err(e) => return Err(e.into()),
}
}
// If no chain exists, attempt to render
else if C::has_renderer(&current) {
- return Ok(render::<C>(program, 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 {
@@ -107,7 +188,12 @@ where
break;
}
}
- Ok(RenderResult::default())
+ let mut render_result = RenderResult::default();
+
+ // Run hook
+ render_result.exit_code = program.run_hook_finish();
+
+ Ok(render_result)
}
/// Dynamically dispatch input arguments to registered entry types
diff --git a/mingling_core/src/program/flag.rs b/mingling_core/src/program/flag.rs
index 0ab8d1c..210f2d6 100644
--- a/mingling_core/src/program/flag.rs
+++ b/mingling_core/src/program/flag.rs
@@ -474,7 +474,7 @@ mod tests {
impl<C> Program<C>
where
- C: ProgramCollect,
+ C: ProgramCollect<Enum = C>,
{
/// Registers a global argument (with value) and its handler.
pub fn global_argument<F, A>(&mut self, arguments: A, mut do_fn: F)
diff --git a/mingling_core/src/program/hook.rs b/mingling_core/src/program/hook.rs
new file mode 100644
index 0000000..d422e12
--- /dev/null
+++ b/mingling_core/src/program/hook.rs
@@ -0,0 +1,349 @@
+use std::{any::Any, fmt::Display};
+
+use crate::{AnyOutput, Program, ProgramCollect, RenderResult};
+
+#[derive(Default)]
+pub struct ProgramHook<C>
+where
+ C: ProgramCollect<Enum = C>,
+{
+ /// Executes when the program starts running
+ pub begin: Option<fn()>,
+
+ /// Executes before the program dispatches
+ pub pre_dispatch: Option<fn(args: &Vec<String>)>,
+
+ /// Executes after the program dispatches
+ pub post_dispatch: Option<fn(entry: &C)>,
+
+ /// Executes before the type enters the chain
+ pub pre_chain: Option<fn(input: &C, raw: &dyn Any)>,
+
+ /// Executes after the chain processing for the type ends
+ pub post_chain: Option<fn(output: &AnyOutput<C>)>,
+
+ /// Executes before the type enters the renderer
+ pub pre_render: Option<fn(input: &C, raw: &dyn Any)>,
+
+ /// Executes after the type enters the renderer
+ pub post_render: Option<fn(result: &RenderResult)>,
+
+ /// Executes before the program ends
+ pub finish: Option<fn() -> i32>,
+}
+
+#[derive(Default)]
+pub struct ProgramAnonymousHook {
+ /// Executes when the program starts running
+ pub begin: Option<fn()>,
+
+ /// Executes before the program dispatches
+ pub pre_dispatch: Option<fn(args: &Vec<String>)>,
+
+ /// Executes after the program dispatches
+ pub post_dispatch: Option<fn(entry_name: &str)>,
+
+ /// Executes before the type enters the chain
+ pub pre_chain: Option<fn(input_name: &str, raw: &dyn Any)>,
+
+ /// Executes after the chain processing for the type ends
+ pub post_chain: Option<fn(output_name: &str)>,
+
+ /// Executes before the type enters the renderer
+ pub pre_render: Option<fn(input_name: &str, raw: &dyn Any)>,
+
+ /// Executes after the type enters the renderer
+ pub post_render: Option<fn(result: &RenderResult)>,
+
+ /// Executes before the program ends
+ pub finish: Option<fn()>,
+}
+
+impl<C> Program<C>
+where
+ C: ProgramCollect<Enum = C> + Display,
+{
+ /// Adds a typed hook to the program. The hook will be called at the appropriate
+ /// lifecycle events.
+ pub fn with_hook(&mut self, hook: ProgramHook<C>) {
+ self.hooks.push(hook);
+ }
+
+ /// Adds an anonymous hook to the program. The hook will be called at the appropriate
+ /// lifecycle events, but receives string representations instead of typed references.
+ pub fn with_hook_anonymous(&mut self, hook: ProgramAnonymousHook) {
+ self.anonymous_hooks.push(hook);
+ }
+
+ pub(crate) fn run_hook_on_begin(&self) {
+ if !self.user_context.run_hook {
+ return;
+ }
+
+ for hook in &self.hooks {
+ if let Some(begin) = hook.begin {
+ begin()
+ }
+ }
+ for anonymous_hook in &self.anonymous_hooks {
+ if let Some(begin) = anonymous_hook.begin {
+ begin()
+ }
+ }
+ }
+
+ pub(crate) fn run_hook_pre_dispatch(&self, args: &Vec<String>) {
+ if !self.user_context.run_hook {
+ return;
+ }
+
+ for hook in &self.hooks {
+ if let Some(pre_dispatch) = hook.pre_dispatch {
+ pre_dispatch(args)
+ }
+ }
+ for anonymous_hook in &self.anonymous_hooks {
+ if let Some(pre_dispatch) = anonymous_hook.pre_dispatch {
+ pre_dispatch(args)
+ }
+ }
+ }
+
+ pub(crate) fn run_hook_post_dispatch(&self, entry: &C) {
+ if !self.user_context.run_hook {
+ return;
+ }
+
+ for hook in &self.hooks {
+ if let Some(post_dispatch) = hook.post_dispatch {
+ post_dispatch(entry)
+ }
+ }
+ for anonymous_hook in &self.anonymous_hooks {
+ if let Some(post_dispatch) = anonymous_hook.post_dispatch {
+ post_dispatch(entry.to_string().as_str())
+ }
+ }
+ }
+
+ pub(crate) fn run_hook_pre_chain(&self, input: &C, raw: &dyn Any) {
+ if !self.user_context.run_hook {
+ return;
+ }
+
+ for hook in &self.hooks {
+ if let Some(pre_chain) = hook.pre_chain {
+ pre_chain(input, raw)
+ }
+ }
+ for anonymous_hook in &self.anonymous_hooks {
+ if let Some(pre_chain) = anonymous_hook.pre_chain {
+ pre_chain(input.to_string().as_str(), raw)
+ }
+ }
+ }
+
+ pub(crate) fn run_hook_post_chain(&self, output: &AnyOutput<C>) {
+ if !self.user_context.run_hook {
+ return;
+ }
+
+ for hook in &self.hooks {
+ if let Some(post_chain) = hook.post_chain {
+ post_chain(output)
+ }
+ }
+ for anonymous_hook in &self.anonymous_hooks {
+ if let Some(post_chain) = anonymous_hook.post_chain {
+ post_chain(output.member_id.to_string().as_str())
+ }
+ }
+ }
+
+ pub(crate) fn run_hook_pre_render(&self, input: &C, raw: &dyn Any) {
+ if !self.user_context.run_hook {
+ return;
+ }
+
+ for hook in &self.hooks {
+ if let Some(pre_render) = hook.pre_render {
+ pre_render(input, raw)
+ }
+ }
+ for anonymous_hook in &self.anonymous_hooks {
+ if let Some(pre_render) = anonymous_hook.pre_render {
+ pre_render(input.to_string().as_str(), raw)
+ }
+ }
+ }
+
+ pub(crate) fn run_hook_post_render(&self, result: &RenderResult) {
+ if !self.user_context.run_hook {
+ return;
+ }
+
+ for hook in &self.hooks {
+ if let Some(post_render) = hook.post_render {
+ post_render(result)
+ }
+ }
+ for anonymous_hook in &self.anonymous_hooks {
+ if let Some(post_render) = anonymous_hook.post_render {
+ post_render(result)
+ }
+ }
+ }
+
+ pub(crate) fn run_hook_finish(&self) -> i32 {
+ if !self.user_context.run_hook {
+ return 0;
+ }
+
+ let mut exit_code = 0;
+ for hook in &self.hooks {
+ if let Some(finish) = hook.finish {
+ exit_code = finish();
+ if exit_code != 0 {
+ return exit_code;
+ }
+ }
+ }
+ for anonymous_hook in &self.anonymous_hooks {
+ if let Some(finish) = anonymous_hook.finish {
+ finish();
+ }
+ }
+ exit_code
+ }
+}
+
+impl<C> ProgramHook<C>
+where
+ C: ProgramCollect<Enum = C>,
+{
+ /// Creates a new empty hook set with no handlers.
+ pub fn empty() -> Self {
+ Self {
+ begin: None,
+ pre_dispatch: None,
+ post_dispatch: None,
+ pre_chain: None,
+ post_chain: None,
+ pre_render: None,
+ post_render: None,
+ finish: None,
+ }
+ }
+
+ /// Sets the handler for the `begin` event.
+ pub fn on_begin(mut self, handler: fn()) -> Self {
+ let _ = self.begin.insert(handler);
+ self
+ }
+
+ /// Sets the handler for the `pre_dispatch` event.
+ pub fn on_pre_dispatch(mut self, handler: fn(args: &Vec<String>)) -> Self {
+ let _ = self.pre_dispatch.insert(handler);
+ self
+ }
+
+ /// Sets the handler for the `post_dispatch` event.
+ pub fn on_post_dispatch(mut self, handler: fn(entry: &C)) -> Self {
+ let _ = self.post_dispatch.insert(handler);
+ self
+ }
+
+ /// Sets the handler for the `pre_chain` event.
+ pub fn on_pre_chain(mut self, handler: fn(input: &C, raw: &dyn Any)) -> Self {
+ let _ = self.pre_chain.insert(handler);
+ self
+ }
+
+ /// Sets the handler for the `post_chain` event.
+ pub fn on_post_chain(mut self, handler: fn(output: &AnyOutput<C>)) -> Self {
+ let _ = self.post_chain.insert(handler);
+ self
+ }
+
+ /// Sets the handler for the `pre_render` event.
+ pub fn on_pre_render(mut self, handler: fn(input: &C, raw: &dyn Any)) -> Self {
+ let _ = self.pre_render.insert(handler);
+ self
+ }
+
+ /// Sets the handler for the `post_render` event.
+ pub fn on_post_render(mut self, handler: fn(result: &RenderResult)) -> Self {
+ let _ = self.post_render.insert(handler);
+ self
+ }
+
+ /// Sets the handler for the `finish` event.
+ pub fn on_finish(mut self, handler: fn() -> i32) -> Self {
+ let _ = self.finish.insert(handler);
+ self
+ }
+}
+
+impl ProgramAnonymousHook {
+ /// Creates a new empty hook set with no handlers.
+ pub fn empty() -> Self {
+ Self {
+ begin: None,
+ pre_dispatch: None,
+ post_dispatch: None,
+ pre_chain: None,
+ post_chain: None,
+ pre_render: None,
+ post_render: None,
+ finish: None,
+ }
+ }
+
+ /// Sets the handler for the `begin` event.
+ pub fn on_begin(mut self, handler: fn()) -> Self {
+ let _ = self.begin.insert(handler);
+ self
+ }
+
+ /// Sets the handler for the `pre_dispatch` event.
+ pub fn on_pre_dispatch(mut self, handler: fn(args: &Vec<String>)) -> Self {
+ let _ = self.pre_dispatch.insert(handler);
+ self
+ }
+
+ /// Sets the handler for the `post_dispatch` event.
+ pub fn on_post_dispatch(mut self, handler: fn(entry_name: &str)) -> Self {
+ let _ = self.post_dispatch.insert(handler);
+ self
+ }
+
+ /// Sets the handler for the `pre_chain` event.
+ pub fn on_pre_chain(mut self, handler: fn(input_name: &str, raw: &dyn Any)) -> Self {
+ let _ = self.pre_chain.insert(handler);
+ self
+ }
+
+ /// Sets the handler for the `post_chain` event.
+ pub fn on_post_chain(mut self, handler: fn(output_name: &str)) -> Self {
+ let _ = self.post_chain.insert(handler);
+ self
+ }
+
+ /// Sets the handler for the `pre_render` event.
+ pub fn on_pre_render(mut self, handler: fn(input_name: &str, raw: &dyn Any)) -> Self {
+ let _ = self.pre_render.insert(handler);
+ self
+ }
+
+ /// Sets the handler for the `post_render` event.
+ pub fn on_post_render(mut self, handler: fn(result: &RenderResult)) -> Self {
+ let _ = self.post_render.insert(handler);
+ self
+ }
+
+ /// Sets the handler for the `finish` event.
+ pub fn on_finish(mut self, handler: fn()) -> Self {
+ let _ = self.finish.insert(handler);
+ self
+ }
+}
diff --git a/mingling_core/src/program/setup.rs b/mingling_core/src/program/setup.rs
index 86228b9..28aa49b 100644
--- a/mingling_core/src/program/setup.rs
+++ b/mingling_core/src/program/setup.rs
@@ -5,19 +5,20 @@ pub use basic::*;
#[cfg(feature = "general_renderer")]
mod general_renderer;
+
#[cfg(feature = "general_renderer")]
pub use general_renderer::*;
pub trait ProgramSetup<C>
where
- C: ProgramCollect,
+ C: ProgramCollect<Enum = C>,
{
fn setup(&mut self, program: &mut Program<C>);
}
impl<C> Program<C>
where
- C: ProgramCollect,
+ C: ProgramCollect<Enum = C>,
{
/// Load and execute init logic
pub fn with_setup<S: ProgramSetup<C> + 'static>(&mut self, mut setup: S) -> S {
diff --git a/mingling_core/src/program/setup/general_renderer.rs b/mingling_core/src/program/setup/general_renderer.rs
index 299aae3..d2666da 100644
--- a/mingling_core/src/program/setup/general_renderer.rs
+++ b/mingling_core/src/program/setup/general_renderer.rs
@@ -32,7 +32,7 @@ pub struct GeneralRendererSetup;
impl<C> ProgramSetup<C> for GeneralRendererSetup
where
- C: ProgramCollect,
+ C: ProgramCollect<Enum = C>,
{
fn setup(&mut self, program: &mut Program<C>) {
program.global_flag("--json", |p| {