use crate::{ AnyOutput, ChainProcess, RenderResult, asset::dispatcher::Dispatcher, error::ProgramExecuteError, }; use std::{env, fmt::Display, pin::Pin}; pub mod exec; pub mod setup; mod config; pub use config::*; mod flag; pub use flag::*; use tokio::io::AsyncWriteExt; #[derive(Default)] pub struct Program where C: ProgramCollect, G: Display, { pub(crate) collect: std::marker::PhantomData, pub(crate) group: std::marker::PhantomData, pub(crate) args: Vec, pub(crate) dispatcher: Vec>>, pub stdout_setting: ProgramStdoutSetting, pub user_context: ProgramUserContext, } impl Program where C: ProgramCollect, G: Display, { /// Creates a new Program instance, initializing args from environment. pub fn new() -> Self { Program { collect: std::marker::PhantomData, group: std::marker::PhantomData, args: env::args().collect(), dispatcher: Vec::new(), stdout_setting: Default::default(), user_context: Default::default(), } } /// Run the command line program pub async fn exec_without_render(mut self) -> Result { self.args = self.args.iter().skip(1).cloned().collect(); crate::exec::exec(self).await.map_err(|e| e.into()) } /// Run the command line program pub async fn exec(self) { 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) = tokio::io::stdout().flush().await && stdout_setting.error_output { eprintln!("{}", e); } } } } pub trait ProgramCollect { type Enum: Display; fn render(any: AnyOutput, r: &mut RenderResult); fn do_chain( any: AnyOutput, ) -> Pin> + Send>>; fn has_renderer(any: &AnyOutput) -> bool; fn has_chain(any: &AnyOutput) -> bool; } #[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)] 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), } } }; }