#![allow(clippy::borrowed_box)] use crate::{ AnyOutput, ChainProcess, Dispatcher, NextProcess, Program, ProgramCollect, RenderResult, error::ProgramInternalExecuteError, }; #[doc(hidden)] pub mod error; #[cfg(feature = "async")] pub async fn exec( program: &'static Program, ) -> Result where C: ProgramCollect, { // Run hooks 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(¤t.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 { let mut render_result = render_help::(program, current); // Run hook render_result.exit_code = program.run_hook_finish(); return Ok(render_result); } loop { let final_exec = stop_next; current = { // If a chain exists, execute as a chain if C::has_chain(¤t) { // Run hook program.run_hook_pre_chain(¤t.member_id, current.inner.as_ref()); match C::do_chain(current).await { ChainProcess::Ok((any, NextProcess::Renderer)) => { let mut render_result = render::(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()); } } } // If no chain exists, attempt to render else if C::has_renderer(¤t) { // Run hook program.run_hook_pre_render(¤t.member_id, current.inner.as_ref()); let mut render_result = render::(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 final_exec && stop_next { break; } } 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(program: &'static Program) -> Result where C: ProgramCollect, { // Run hooks 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(¤t.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 { let mut render_result = render_help::(program, current); // Run hook render_result.exit_code = program.run_hook_finish(); return Ok(render_result); } loop { let final_exec = stop_next; current = { // If a chain exists, execute as a chain if C::has_chain(¤t) { // Run hook program.run_hook_pre_chain(¤t.member_id, current.inner.as_ref()); match C::do_chain(current) { ChainProcess::Ok((any, NextProcess::Renderer)) => { { let mut render_result = render::(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()); } } } // If no chain exists, attempt to render else if C::has_renderer(¤t) { // Run hook program.run_hook_pre_render(¤t.member_id, current.inner.as_ref()); let mut render_result = render::(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 final_exec && stop_next { break; } } 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 pub(crate) fn dispatch_args_dynamic( program: &'static Program, args: &Vec, ) -> Result, ProgramInternalExecuteError> where C: ProgramCollect, { let next = match match_user_input(program, args) { Ok((dispatcher, args)) => { // Entry point match dispatcher.begin(args) { ChainProcess::Ok((any, _)) => any, ChainProcess::Err(e) => return Err(e.into()), } } Err(ProgramInternalExecuteError::DispatcherNotFound) => { // No matching Dispatcher is found C::build_dispatcher_not_found(program.args.clone()) } Err(e) => return Err(e), }; Ok(next) } /// Match user input against registered dispatchers and return the matched dispatcher and remaining arguments. #[allow(clippy::type_complexity)] #[allow(clippy::ptr_arg)] pub(crate) fn match_user_input( program: &'static Program, args: &Vec, ) -> Result<(&'static (dyn Dispatcher + Send + Sync), Vec), ProgramInternalExecuteError> where C: ProgramCollect, { let nodes = program.get_nodes(); let command = format!("{} ", args.join(" ")); // Find all nodes that match the command prefix let matching_nodes: Vec<&(String, &(dyn Dispatcher + Send + Sync))> = nodes .iter() // Also add a space to the node string to ensure consistent matching logic .filter(|(node_str, _)| command.starts_with(&format!("{} ", node_str))) .collect(); match matching_nodes.len() { 0 => { // No matching node found Err(ProgramInternalExecuteError::DispatcherNotFound) } 1 => { let matched_prefix = matching_nodes[0]; let prefix_len = matched_prefix.0.split_whitespace().count(); let trimmed_args: Vec = args.iter().skip(prefix_len).cloned().collect(); Ok((matched_prefix.1, trimmed_args)) } _ => { // Multiple matching nodes found // Find the node with the longest length (most specific match) let matched_prefix = matching_nodes .iter() .max_by_key(|node| node.0.len()) .unwrap(); let prefix_len = matched_prefix.0.split_whitespace().count(); let trimmed_args: Vec = args.iter().skip(prefix_len).cloned().collect(); Ok((matched_prefix.1, trimmed_args)) } } } #[inline(always)] #[allow(unused_variables)] fn render>(program: &Program, any: AnyOutput) -> RenderResult { #[cfg(not(feature = "general_renderer"))] { let mut render_result = RenderResult::default(); C::render(any, &mut render_result); render_result } #[cfg(feature = "general_renderer")] { #[allow(unreachable_patterns)] match program.general_renderer_name { super::GeneralRendererSetting::Disable => { let mut render_result = RenderResult::default(); C::render(any, &mut render_result); render_result } _ => C::general_render(any, &program.general_renderer_name).unwrap(), } } } #[inline(always)] #[allow(unused_variables)] fn render_help>( program: &Program, entry: AnyOutput, ) -> RenderResult { #[cfg(not(feature = "general_renderer"))] { let mut render_result = RenderResult::default(); C::render_help(entry, &mut render_result); render_result } #[cfg(feature = "general_renderer")] { #[allow(unreachable_patterns)] match program.general_renderer_name { super::GeneralRendererSetting::Disable => { let mut render_result = RenderResult::default(); C::render_help(entry, &mut render_result); render_result } _ => RenderResult::default(), } } }