diff options
| author | 魏曹先生 <1992414357@qq.com> | 2026-05-18 16:02:57 +0800 |
|---|---|---|
| committer | 魏曹先生 <1992414357@qq.com> | 2026-05-18 16:02:57 +0800 |
| commit | ab7c5785fb290541ad4361c0d46241817c3ff5f9 (patch) | |
| tree | 042012fe0a6cac2e5da90ccff0df38e64ac50485 /mingling_core | |
| parent | f1408931eb19f90dd96092ea26bea8d14f5ec804 (diff) | |
Refactor program module into submodules
Diffstat (limited to 'mingling_core')
| -rw-r--r-- | mingling_core/src/program.rs | 338 | ||||
| -rw-r--r-- | mingling_core/src/program/collection.rs | 76 | ||||
| -rw-r--r-- | mingling_core/src/program/once_exec.rs | 235 | ||||
| -rw-r--r-- | mingling_core/src/program/repl_exec.rs | 12 | ||||
| -rw-r--r-- | mingling_core/src/program/single_instance.rs | 23 |
5 files changed, 361 insertions, 323 deletions
diff --git a/mingling_core/src/program.rs b/mingling_core/src/program.rs index 7c667e4..0a81cc7 100644 --- a/mingling_core/src/program.rs +++ b/mingling_core/src/program.rs @@ -1,28 +1,15 @@ -use crate::error::ProgramPanic; - -#[cfg(feature = "comp")] -use crate::{ShellContext, Suggest}; - -#[cfg(feature = "general_renderer")] -use crate::error::GeneralRendererSerializeError; - #[cfg(not(windows))] use std::env; use crate::{ - AnyOutput, ChainProcess, GlobalResources, Groupped, RenderResult, - asset::dispatcher::Dispatcher, - error::{ChainProcessError, ProgramExecuteError}, + AnyOutput, GlobalResources, asset::dispatcher::Dispatcher, error::ChainProcessError, hook::ProgramHook, }; use std::{ collections::HashMap, - sync::{Arc, Mutex, OnceLock}, + sync::{Arc, Mutex}, }; -#[cfg(feature = "async")] -use std::pin::Pin; - #[doc(hidden)] pub mod error; #[doc(hidden)] @@ -32,6 +19,19 @@ pub mod hook; #[doc(hidden)] pub mod setup; +mod collection; +pub use collection::*; + +mod once_exec; + +#[cfg(feature = "repl")] +mod repl_exec; +#[cfg(feature = "repl")] +pub use repl_exec::*; + +mod single_instance; +pub use single_instance::*; + mod config; pub use config::*; @@ -41,25 +41,6 @@ pub use flag::*; mod string_vec; pub use string_vec::*; -/// Global static reference to the current program instance -static THIS_PROGRAM: OnceLock<Option<Box<dyn std::any::Any + Send + Sync>>> = OnceLock::new(); - -/// Returns a reference to the current program instance, panics if not set. -pub fn this<C>() -> &'static Program<C> -where - C: ProgramCollect<Enum = C> + 'static, -{ - try_get_this_program().expect("Program not initialized") -} - -/// Returns a reference to the current program instance, if set. -fn try_get_this_program<C>() -> Option<&'static Program<C>> -where - C: ProgramCollect<Enum = C> + 'static, -{ - THIS_PROGRAM.get()?.as_ref()?.downcast_ref::<Program<C>>() -} - /// Program, used to define the behavior of the entire command-line program #[derive(Default)] pub struct Program<C> @@ -173,295 +154,6 @@ where } } -// Async program -#[cfg(feature = "async")] -impl<C> Program<C> -where - C: ProgramCollect<Enum = C>, -{ - async fn exec_wrapper<F, Fut>(self, f: F) -> Result<Fut::Output, ProgramPanic> - where - C: 'static + Send + Sync, - F: FnOnce(&'static Program<C>) -> Fut + Send + Sync, - Fut: Future + Send, - { - THIS_PROGRAM.get_or_init(|| Some(Box::new(self))); - let program = THIS_PROGRAM - .get() - .unwrap() - .as_ref() - .unwrap() - .downcast_ref::<Program<C>>() - .unwrap(); - - #[cfg(not(panic = "abort"))] - if program.stdout_setting.silence_panic { - std::panic::set_hook(Box::new(|_| {})); - } - - #[cfg(panic = "abort")] - return Ok(f(program)); - - #[cfg(not(panic = "abort"))] - match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| f(program))) { - Ok(fut) => Ok(fut.await), - Err(panic_info) => { - let panic_payload = ProgramPanic { - payload: panic_info, - }; - program.run_hook_exec_panic(&panic_payload); - Err(panic_payload) - } - } - } - - /// Run the command line program - pub async fn exec_without_render(mut self) -> Result<RenderResult, ProgramExecuteError> - where - C: 'static + Send + Sync, - { - // Run hooks - self.run_hook_on_begin(); - - self.args = self.args.iter().skip(1).cloned().collect(); - match self - .exec_wrapper(|p| async { crate::exec::exec(p).await.map_err(|e| e.into()) }) - .await - { - Ok(r) => r, - Err(e) => Err(ProgramExecuteError::Panic(e)), - } - } - - /// Run the command line program - pub async fn exec(self) -> i32 - where - C: 'static + Send + Sync, - { - let stdout_setting = self.stdout_setting.clone(); - let result = match self.exec_without_render().await { - Ok(r) => r, - Err(e) => match e { - ProgramExecuteError::DispatcherNotFound => { - eprintln!("Dispatcher not found"); - return 1; - } - ProgramExecuteError::RendererNotFound(renderer_name) => { - eprintln!("Renderer `{}` not found", renderer_name); - return 1; - } - ProgramExecuteError::Other(e) => { - eprintln!("{}", e); - return 1; - } - ProgramExecuteError::Panic(unwinded_error) => { - eprintln!("{}", unwinded_error); - return 1; - } - }, - }; - - // Render result - if stdout_setting.render_output && !result.is_empty() { - let exit_code = result.exit_code; - print!("{}", result); - - if let Err(e) = std::io::Write::flush(&mut std::io::stdout()) - && stdout_setting.error_output - { - eprintln!("{}", e); - 1 - } else { - exit_code - } - } else { - 0 - } - } - - /// Run the command line program, then exit - pub async fn exec_and_exit(self) - where - C: 'static + Send + Sync, - { - std::process::exit(self.exec().await) - } -} - -// Sync program -#[cfg(not(feature = "async"))] -impl<C> Program<C> -where - C: ProgramCollect<Enum = C>, -{ - fn exec_wrapper<F, R>(self, f: F) -> Result<R, ProgramPanic> - where - C: 'static + Send + Sync, - F: FnOnce(&'static Program<C>) -> R + Send + Sync, - { - THIS_PROGRAM.get_or_init(|| Some(Box::new(self))); - let program = THIS_PROGRAM - .get() - .unwrap() - .as_ref() - .unwrap() - .downcast_ref::<Program<C>>() - .unwrap(); - - #[cfg(not(panic = "abort"))] - if program.stdout_setting.silence_panic { - std::panic::set_hook(Box::new(|_| {})); - } - - #[cfg(panic = "abort")] - return Ok(f(program)); - - #[cfg(not(panic = "abort"))] - match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| f(program))) { - Ok(result) => Ok(result), - Err(panic_info) => { - let panic_payload = ProgramPanic { - payload: panic_info, - }; - program.run_hook_exec_panic(&panic_payload); - Err(panic_payload) - } - } - } - - /// Run the command line program - pub fn exec_without_render(mut self) -> Result<RenderResult, ProgramExecuteError> - where - C: 'static + Send + Sync, - { - // Run hooks - self.run_hook_on_begin(); - - self.args = self.args.iter().skip(1).cloned().collect(); - match self.exec_wrapper(|p| crate::exec::exec(p).map_err(|e| e.into())) { - Ok(r) => r, - Err(e) => Err(ProgramExecuteError::Panic(e)), - } - } - - /// Run the command line program - pub fn exec(self) -> i32 - where - C: 'static + Send + Sync, - { - let stdout_setting = self.stdout_setting.clone(); - let result = match self.exec_without_render() { - Ok(r) => r, - Err(e) => match e { - ProgramExecuteError::DispatcherNotFound => { - eprintln!("Dispatcher not found"); - return 1; - } - ProgramExecuteError::RendererNotFound(renderer_name) => { - eprintln!("Renderer `{}` not found", renderer_name); - return 1; - } - ProgramExecuteError::Other(e) => { - eprintln!("{}", e); - return 1; - } - ProgramExecuteError::Panic(unwinded_error) => { - eprintln!("{}", unwinded_error); - return 1; - } - }, - }; - - // Render result - if stdout_setting.render_output && !result.is_empty() { - let exit_code = result.exit_code; - print!("{}", result); - - if let Err(e) = std::io::Write::flush(&mut std::io::stdout()) - && stdout_setting.error_output - { - eprintln!("{}", e); - 1 - } else { - exit_code - } - } else { - 0 - } - } - - /// Run the command line program, then exit - pub fn exec_and_exit(self) - where - C: 'static + Send + Sync, - { - std::process::exit(self.exec()) - } -} - -/// Collected program context -/// -/// Note: It is recommended to use the `gen_program!()` macro from [mingling_macros](https://crates.io/crates/mingling_macros) to automatically create this type -pub trait ProgramCollect { - /// Enum type representing internal IDs for the program - type Enum; - type DispatcherNotFound: Groupped<Self::Enum>; - type RendererNotFound: Groupped<Self::Enum>; - type EmptyResult: Groupped<Self::Enum>; - - /// Use a prefix tree to quickly match arguments and dispatch to an Entry - #[cfg(feature = "dispatch_tree")] - fn dispatch_args_trie( - raw: &Vec<String>, - ) -> Result<AnyOutput<Self::Enum>, crate::error::ProgramInternalExecuteError>; - - /// Get all registered dispatcher names from the program - #[cfg(feature = "dispatch_tree")] - fn get_nodes() -> Vec<(String, &'static (dyn Dispatcher<Self::Enum> + Send + Sync))>; - - /// Build an [AnyOutput](./struct.AnyOutput.html) to indicate that a renderer was not found - fn build_renderer_not_found(member_id: Self::Enum) -> AnyOutput<Self::Enum>; - - /// Build an [AnyOutput](./struct.AnyOutput.html) to indicate that a dispatcher was not found - fn build_dispatcher_not_found(args: Vec<String>) -> AnyOutput<Self::Enum>; - - /// Build an [AnyOutput](./struct.AnyOutput.html) to indicate that the chain returned an empty result - fn build_empty_result() -> AnyOutput<Self::Enum>; - - /// Render the input [AnyOutput](./struct.AnyOutput.html) - fn render(any: AnyOutput<Self::Enum>, r: &mut RenderResult); - - /// Render help for Entry - fn render_help(any: AnyOutput<Self::Enum>, r: &mut RenderResult); - - /// Find a matching chain to continue execution based on the input [AnyOutput](./struct.AnyOutput.html), returning a new [AnyOutput](./struct.AnyOutput.html) - #[cfg(feature = "async")] - fn do_chain( - any: AnyOutput<Self::Enum>, - ) -> Pin<Box<dyn Future<Output = ChainProcess<Self::Enum>> + Send>>; - - /// Find a matching chain to continue execution based on the input [AnyOutput](./struct.AnyOutput.html), returning a new [AnyOutput](./struct.AnyOutput.html) - #[cfg(not(feature = "async"))] - fn do_chain(any: AnyOutput<Self::Enum>) -> ChainProcess<Self::Enum>; - - /// Match and execute specific completion logic based on any Entry - #[cfg(feature = "comp")] - fn do_comp(any: &AnyOutput<Self::Enum>, ctx: &ShellContext) -> Suggest; - - /// Whether the program has a renderer that can handle the current [AnyOutput](./struct.AnyOutput.html) - fn has_renderer(any: &AnyOutput<Self::Enum>) -> bool; - - /// Whether the program has a chain that can handle the current [AnyOutput](./struct.AnyOutput.html) - fn has_chain(any: &AnyOutput<Self::Enum>) -> bool; - - /// Perform general rendering and presentation of any type - #[cfg(feature = "general_renderer")] - fn general_render( - any: AnyOutput<Self::Enum>, - setting: &GeneralRendererSetting, - ) -> Result<RenderResult, GeneralRendererSerializeError>; -} - #[macro_export] #[doc(hidden)] macro_rules! __dispatch_program_renderers { diff --git a/mingling_core/src/program/collection.rs b/mingling_core/src/program/collection.rs new file mode 100644 index 0000000..dec2af6 --- /dev/null +++ b/mingling_core/src/program/collection.rs @@ -0,0 +1,76 @@ +#[cfg(feature = "async")] +use std::pin::Pin; + +#[cfg(feature = "dispatch_tree")] +use crate::Dispatcher; + +use crate::{AnyOutput, ChainProcess, Groupped, RenderResult}; + +#[cfg(feature = "general_renderer")] +use crate::{GeneralRendererSetting, error::GeneralRendererSerializeError}; + +#[cfg(feature = "comp")] +use crate::{ShellContext, Suggest}; + +/// Collected program context +/// +/// Note: It is recommended to use the `gen_program!()` macro from [mingling_macros](https://crates.io/crates/mingling_macros) to automatically create this type +pub trait ProgramCollect { + /// Enum type representing internal IDs for the program + type Enum; + type DispatcherNotFound: Groupped<Self::Enum>; + type RendererNotFound: Groupped<Self::Enum>; + type EmptyResult: Groupped<Self::Enum>; + + /// Use a prefix tree to quickly match arguments and dispatch to an Entry + #[cfg(feature = "dispatch_tree")] + fn dispatch_args_trie( + raw: &Vec<String>, + ) -> Result<AnyOutput<Self::Enum>, crate::error::ProgramInternalExecuteError>; + + /// Get all registered dispatcher names from the program + #[cfg(feature = "dispatch_tree")] + fn get_nodes() -> Vec<(String, &'static (dyn Dispatcher<Self::Enum> + Send + Sync))>; + + /// Build an [AnyOutput](./struct.AnyOutput.html) to indicate that a renderer was not found + fn build_renderer_not_found(member_id: Self::Enum) -> AnyOutput<Self::Enum>; + + /// Build an [AnyOutput](./struct.AnyOutput.html) to indicate that a dispatcher was not found + fn build_dispatcher_not_found(args: Vec<String>) -> AnyOutput<Self::Enum>; + + /// Build an [AnyOutput](./struct.AnyOutput.html) to indicate that the chain returned an empty result + fn build_empty_result() -> AnyOutput<Self::Enum>; + + /// Render the input [AnyOutput](./struct.AnyOutput.html) + fn render(any: AnyOutput<Self::Enum>, r: &mut RenderResult); + + /// Render help for Entry + fn render_help(any: AnyOutput<Self::Enum>, r: &mut RenderResult); + + /// Find a matching chain to continue execution based on the input [AnyOutput](./struct.AnyOutput.html), returning a new [AnyOutput](./struct.AnyOutput.html) + #[cfg(feature = "async")] + fn do_chain( + any: AnyOutput<Self::Enum>, + ) -> Pin<Box<dyn Future<Output = ChainProcess<Self::Enum>> + Send>>; + + /// Find a matching chain to continue execution based on the input [AnyOutput](./struct.AnyOutput.html), returning a new [AnyOutput](./struct.AnyOutput.html) + #[cfg(not(feature = "async"))] + fn do_chain(any: AnyOutput<Self::Enum>) -> ChainProcess<Self::Enum>; + + /// Match and execute specific completion logic based on any Entry + #[cfg(feature = "comp")] + fn do_comp(any: &AnyOutput<Self::Enum>, ctx: &ShellContext) -> Suggest; + + /// Whether the program has a renderer that can handle the current [AnyOutput](./struct.AnyOutput.html) + fn has_renderer(any: &AnyOutput<Self::Enum>) -> bool; + + /// Whether the program has a chain that can handle the current [AnyOutput](./struct.AnyOutput.html) + fn has_chain(any: &AnyOutput<Self::Enum>) -> bool; + + /// Perform general rendering and presentation of any type + #[cfg(feature = "general_renderer")] + fn general_render( + any: AnyOutput<Self::Enum>, + setting: &GeneralRendererSetting, + ) -> Result<RenderResult, GeneralRendererSerializeError>; +} diff --git a/mingling_core/src/program/once_exec.rs b/mingling_core/src/program/once_exec.rs new file mode 100644 index 0000000..ac985e2 --- /dev/null +++ b/mingling_core/src/program/once_exec.rs @@ -0,0 +1,235 @@ +use crate::THIS_PROGRAM; +use crate::{ + Program, ProgramCollect, RenderResult, + error::{ProgramExecuteError, ProgramPanic}, +}; + +// Async program +#[cfg(feature = "async")] +impl<C> Program<C> +where + C: ProgramCollect<Enum = C>, +{ + async fn exec_wrapper<F, Fut>(self, f: F) -> Result<Fut::Output, ProgramPanic> + where + C: 'static + Send + Sync, + F: FnOnce(&'static Program<C>) -> Fut + Send + Sync, + Fut: Future + Send, + { + THIS_PROGRAM.get_or_init(|| Some(Box::new(self))); + let program = THIS_PROGRAM + .get() + .unwrap() + .as_ref() + .unwrap() + .downcast_ref::<Program<C>>() + .unwrap(); + + #[cfg(not(panic = "abort"))] + if program.stdout_setting.silence_panic { + std::panic::set_hook(Box::new(|_| {})); + } + + #[cfg(panic = "abort")] + return Ok(f(program)); + + #[cfg(not(panic = "abort"))] + match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| f(program))) { + Ok(fut) => Ok(fut.await), + Err(panic_info) => { + let panic_payload = ProgramPanic { + payload: panic_info, + }; + program.run_hook_exec_panic(&panic_payload); + Err(panic_payload) + } + } + } + + /// Run the command line program + pub async fn exec_without_render(mut self) -> Result<RenderResult, ProgramExecuteError> + where + C: 'static + Send + Sync, + { + // Run hooks + self.run_hook_on_begin(); + + self.args = self.args.iter().skip(1).cloned().collect(); + match self + .exec_wrapper(|p| async { crate::exec::exec(p).await.map_err(|e| e.into()) }) + .await + { + Ok(r) => r, + Err(e) => Err(ProgramExecuteError::Panic(e)), + } + } + + /// Run the command line program + pub async fn exec(self) -> i32 + where + C: 'static + Send + Sync, + { + let stdout_setting = self.stdout_setting.clone(); + let result = match self.exec_without_render().await { + Ok(r) => r, + Err(e) => match e { + ProgramExecuteError::DispatcherNotFound => { + eprintln!("Dispatcher not found"); + return 1; + } + ProgramExecuteError::RendererNotFound(renderer_name) => { + eprintln!("Renderer `{}` not found", renderer_name); + return 1; + } + ProgramExecuteError::Other(e) => { + eprintln!("{}", e); + return 1; + } + ProgramExecuteError::Panic(unwinded_error) => { + eprintln!("{}", unwinded_error); + return 1; + } + }, + }; + + // Render result + if stdout_setting.render_output && !result.is_empty() { + let exit_code = result.exit_code; + print!("{}", result); + + if let Err(e) = std::io::Write::flush(&mut std::io::stdout()) + && stdout_setting.error_output + { + eprintln!("{}", e); + 1 + } else { + exit_code + } + } else { + 0 + } + } + + /// Run the command line program, then exit + pub async fn exec_and_exit(self) + where + C: 'static + Send + Sync, + { + std::process::exit(self.exec().await) + } +} + +// Sync program +#[cfg(not(feature = "async"))] +impl<C> Program<C> +where + C: ProgramCollect<Enum = C>, +{ + fn exec_wrapper<F, R>(self, f: F) -> Result<R, ProgramPanic> + where + C: 'static + Send + Sync, + F: FnOnce(&'static Program<C>) -> R + Send + Sync, + { + THIS_PROGRAM.get_or_init(|| Some(Box::new(self))); + let program = THIS_PROGRAM + .get() + .unwrap() + .as_ref() + .unwrap() + .downcast_ref::<Program<C>>() + .unwrap(); + + #[cfg(not(panic = "abort"))] + if program.stdout_setting.silence_panic { + std::panic::set_hook(Box::new(|_| {})); + } + + #[cfg(panic = "abort")] + return Ok(f(program)); + + #[cfg(not(panic = "abort"))] + match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| f(program))) { + Ok(result) => Ok(result), + Err(panic_info) => { + use crate::error::ProgramPanic; + + let panic_payload = ProgramPanic { + payload: panic_info, + }; + program.run_hook_exec_panic(&panic_payload); + Err(panic_payload) + } + } + } + + /// Run the command line program + pub fn exec_without_render(mut self) -> Result<RenderResult, ProgramExecuteError> + where + C: 'static + Send + Sync, + { + // Run hooks + self.run_hook_on_begin(); + + self.args = self.args.iter().skip(1).cloned().collect(); + match self.exec_wrapper(|p| crate::exec::exec(p).map_err(|e| e.into())) { + Ok(r) => r, + Err(e) => Err(ProgramExecuteError::Panic(e)), + } + } + + /// Run the command line program + pub fn exec(self) -> i32 + where + C: 'static + Send + Sync, + { + use crate::error::ProgramExecuteError; + + let stdout_setting = self.stdout_setting.clone(); + let result = match self.exec_without_render() { + Ok(r) => r, + Err(e) => match e { + ProgramExecuteError::DispatcherNotFound => { + eprintln!("Dispatcher not found"); + return 1; + } + ProgramExecuteError::RendererNotFound(renderer_name) => { + eprintln!("Renderer `{}` not found", renderer_name); + return 1; + } + ProgramExecuteError::Other(e) => { + eprintln!("{}", e); + return 1; + } + ProgramExecuteError::Panic(unwinded_error) => { + eprintln!("{}", unwinded_error); + return 1; + } + }, + }; + + // Render result + if stdout_setting.render_output && !result.is_empty() { + let exit_code = result.exit_code; + print!("{}", result); + + if let Err(e) = std::io::Write::flush(&mut std::io::stdout()) + && stdout_setting.error_output + { + eprintln!("{}", e); + 1 + } else { + exit_code + } + } else { + 0 + } + } + + /// Run the command line program, then exit + pub fn exec_and_exit(self) + where + C: 'static + Send + Sync, + { + std::process::exit(self.exec()) + } +} diff --git a/mingling_core/src/program/repl_exec.rs b/mingling_core/src/program/repl_exec.rs new file mode 100644 index 0000000..feeaad9 --- /dev/null +++ b/mingling_core/src/program/repl_exec.rs @@ -0,0 +1,12 @@ +use crate::{Program, ProgramCollect}; + +#[cfg(not(feature = "async"))] +impl<C> Program<C> +where + C: ProgramCollect<Enum = C>, +{ + pub fn exec_repl(self) {} +} + +#[cfg(feature = "async")] +impl<C> Program<C> where C: ProgramCollect<Enum = C> {} diff --git a/mingling_core/src/program/single_instance.rs b/mingling_core/src/program/single_instance.rs new file mode 100644 index 0000000..45d4d33 --- /dev/null +++ b/mingling_core/src/program/single_instance.rs @@ -0,0 +1,23 @@ +use std::sync::OnceLock; + +use crate::{Program, ProgramCollect}; + +/// Global static reference to the current program instance +pub(crate) static THIS_PROGRAM: OnceLock<Option<Box<dyn std::any::Any + Send + Sync>>> = + OnceLock::new(); + +/// Returns a reference to the current program instance, panics if not set. +pub fn this<C>() -> &'static Program<C> +where + C: ProgramCollect<Enum = C> + 'static, +{ + try_get_this_program().expect("Program not initialized") +} + +/// Returns a reference to the current program instance, if set. +fn try_get_this_program<C>() -> Option<&'static Program<C>> +where + C: ProgramCollect<Enum = C> + 'static, +{ + THIS_PROGRAM.get()?.as_ref()?.downcast_ref::<Program<C>>() +} |
