#[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, RenderResult, asset::dispatcher::Dispatcher, error::{ChainProcessError, ProgramExecuteError}, }; use std::{ collections::HashMap, fmt::Display, sync::{Arc, Mutex, OnceLock}, }; #[cfg(feature = "async")] use std::pin::Pin; #[doc(hidden)] pub mod exec; #[doc(hidden)] pub mod setup; mod config; pub use config::*; mod flag; pub use flag::*; mod string_vec; pub use string_vec::*; /// Global static reference to the current program instance static THIS_PROGRAM: OnceLock>> = OnceLock::new(); /// Returns a reference to the current program instance, panics if not set. pub fn this() -> &'static Program where C: ProgramCollect + 'static, { try_get_this_program().expect("Program not initialized") } /// Returns a reference to the current program instance, if set. fn try_get_this_program() -> Option<&'static Program> where C: ProgramCollect + 'static, { THIS_PROGRAM.get()?.as_ref()?.downcast_ref::>() } /// Program, used to define the behavior of the entire command-line program #[derive(Default)] pub struct Program where C: ProgramCollect, { pub(crate) collect: std::marker::PhantomData, pub(crate) args: Vec, pub(crate) dispatcher: Vec + Send + Sync>>, pub stdout_setting: ProgramStdoutSetting, pub user_context: ProgramUserContext, #[cfg(feature = "general_renderer")] pub general_renderer_name: GeneralRendererSetting, pub(crate) resources: GlobalResources, } impl Program where C: ProgramCollect, { /// Creates a new Program instance, initializing command-line arguments from the environment. pub fn new() -> Self { #[cfg(not(windows))] return Self::new_with_args(env::args().collect::>()); #[cfg(windows)] return Self::new_with_args({ std::env::args_os() .map(|arg| { use std::os::windows::ffi::OsStrExt; let wide: Vec = arg.encode_wide().collect(); String::from_utf16_lossy(&wide) }) .collect::>() }); } /// Creates a new Program instance with the provided command-line arguments. pub fn new_with_args(args: impl Into) -> Self { Program { collect: std::marker::PhantomData, args: args.into().into(), dispatcher: Vec::new(), stdout_setting: Default::default(), user_context: Default::default(), #[cfg(feature = "general_renderer")] general_renderer_name: GeneralRendererSetting::Disable, resources: Arc::new(Mutex::new(HashMap::new())), } } /// Returns a reference to the current program instance, if set. pub fn this_program() -> &'static Program where C: 'static, { THIS_PROGRAM .get() .unwrap() .as_ref() .unwrap() .downcast_ref::>() .unwrap() } /// Get all registered dispatcher names from the program pub fn get_nodes(&self) -> Vec<(String, &(dyn Dispatcher + Send + Sync))> { get_nodes(self) } /// Dynamically dispatch input arguments to registered entry types pub fn dispatch_args_dynamic( &self, args: impl Into, ) -> Result, ChainProcessError> { match exec::dispatch_args_dynamic(self, args.into().into()) { Ok(ok) => Ok(ok), Err(e) => Err(e.into()), } } } // Async program #[cfg(feature = "async")] impl Program where C: ProgramCollect, { /// Sets the current program instance and runs the provided async function. async fn set_instance_and_run(self, f: F) -> Fut::Output where C: 'static + Send + Sync, F: FnOnce(&'static Program) -> 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::>() .unwrap(); f(program).await } /// Run the command line program pub async fn exec_without_render(mut self) -> Result where C: 'static + Send + Sync, { self.args = self.args.iter().skip(1).cloned().collect(); self.set_instance_and_run(|p| async { crate::exec::exec(p).await.map_err(|e| e.into()) }) .await } /// Run the command line program pub async fn exec(self) 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; } ProgramExecuteError::RendererNotFound(renderer_name) => { eprintln!("Renderer `{}` not found", renderer_name); return; } ProgramExecuteError::Other(e) => { eprintln!("{}", e); return; } }, }; // Render result if stdout_setting.render_output && !result.is_empty() { print!("{}", result); if let Err(e) = std::io::Write::flush(&mut std::io::stdout()) && stdout_setting.error_output { eprintln!("{}", e); } } } } // Sync program #[cfg(not(feature = "async"))] impl Program where C: ProgramCollect, { /// Sets the current program instance and runs the provided function. fn set_instance_and_run(self, f: F) -> R where C: 'static + Send + Sync, F: FnOnce(&'static Program) -> R + Send + Sync, { THIS_PROGRAM.get_or_init(|| Some(Box::new(self))); let program = THIS_PROGRAM .get() .unwrap() .as_ref() .unwrap() .downcast_ref::>() .unwrap(); f(program) } /// Run the command line program pub fn exec_without_render(mut self) -> Result where C: 'static + Send + Sync, { self.args = self.args.iter().skip(1).cloned().collect(); self.set_instance_and_run(|p| crate::exec::exec(p).map_err(|e| e.into())) } /// Run the command line program pub fn exec(self) 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; } ProgramExecuteError::RendererNotFound(renderer_name) => { eprintln!("Renderer `{}` not found", renderer_name); return; } ProgramExecuteError::Other(e) => { eprintln!("{}", e); return; } }, }; // Render result if stdout_setting.render_output && !result.is_empty() { print!("{}", result); if let Err(e) = std::io::Write::flush(&mut std::io::stdout()) && stdout_setting.error_output { eprintln!("{}", e); } } } } /// 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: Display; /// Build an [AnyOutput](./struct.AnyOutput.html) to indicate that a renderer was not found fn build_renderer_not_found(member_id: Self::Enum) -> AnyOutput; /// Build an [AnyOutput](./struct.AnyOutput.html) to indicate that a dispatcher was not found fn build_dispatcher_not_found(args: Vec) -> AnyOutput; /// Render the input [AnyOutput](./struct.AnyOutput.html) fn render(any: AnyOutput, r: &mut RenderResult); /// Render help for Entry fn render_help(any: AnyOutput, 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, ) -> Pin> + 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) -> ChainProcess; /// Match and execute specific completion logic based on any Entry #[cfg(feature = "comp")] fn do_comp(any: &AnyOutput, ctx: &ShellContext) -> Suggest; /// Whether the program has a renderer that can handle the current [AnyOutput](./struct.AnyOutput.html) fn has_renderer(any: &AnyOutput) -> bool; /// Whether the program has a chain that can handle the current [AnyOutput](./struct.AnyOutput.html) fn has_chain(any: &AnyOutput) -> bool; /// Perform general rendering and presentation of any type #[cfg(feature = "general_renderer")] fn general_render( any: AnyOutput, setting: &GeneralRendererSetting, ) -> Result; } #[macro_export] #[doc(hidden)] macro_rules! __dispatch_program_renderers { ( $( $render_ty:ty => $prev_ty:ident, )* ) => { fn render(any: mingling::AnyOutput, r: &mut mingling::RenderResult) { match any.member_id { $( Self::$prev_ty => { // SAFETY: The `type_id` check ensures that `any` contains a value of type `$prev_ty`, // so downcasting to `$prev_ty` is safe. let value = unsafe { any.downcast::<$prev_ty>().unwrap_unchecked() }; <$render_ty as mingling::Renderer>::render(value, r); } )* _ => (), } } }; } #[macro_export] #[doc(hidden)] #[cfg(feature = "async")] macro_rules! __dispatch_program_chains { ( $( $chain_ty:ty => $chain_prev:ident, )* ) => { fn do_chain( any: mingling::AnyOutput, ) -> std::pin::Pin> + Send>> { match any.member_id { $( Self::$chain_prev => { // SAFETY: The `type_id` check ensures that `any` contains a value of type `$chain_prev`, // so downcasting to `$chain_prev` is safe. let value = unsafe { any.downcast::<$chain_prev>().unwrap_unchecked() }; let fut = async { <$chain_ty as mingling::Chain>::proc(value).await }; Box::pin(fut) } )* _ => panic!("No chain found for type id: {:?}", any.type_id), } } }; } #[macro_export] #[doc(hidden)] #[cfg(not(feature = "async"))] macro_rules! __dispatch_program_chains { ( $( $chain_ty:ty => $chain_prev:ident, )* ) => { fn do_chain( any: mingling::AnyOutput, ) -> mingling::ChainProcess { match any.member_id { $( Self::$chain_prev => { // SAFETY: The `type_id` check ensures that `any` contains a value of type `$chain_prev`, // so downcasting to `$chain_prev` is safe. let value = unsafe { any.downcast::<$chain_prev>().unwrap_unchecked() }; <$chain_ty as mingling::Chain>::proc(value) } )* _ => panic!("No chain found for type id: {:?}", any.type_id), } } }; } /// Get all registered dispatcher names from the program pub fn get_nodes>( program: &Program, ) -> Vec<(String, &(dyn Dispatcher + Send + Sync))> { program .dispatcher .iter() .map(|disp| { let node_str = disp .node() .to_string() .split('.') .collect::>() .join(" "); (node_str, &**disp) }) .collect() }