From db9afa0b06355028eafe3bc29fe0b2429ba8fd0a Mon Sep 17 00:00:00 2001 From: 魏曹先生 <1992414357@qq.com> Date: Sun, 29 Mar 2026 00:52:16 +0800 Subject: Completed the first preliminary usable version of the Mingling framework. --- mingling/src/program/config.rs | 2 + mingling/src/program/exec.rs | 130 ++++++++++++++++++++++++++++++++++++ mingling/src/program/exec/error.rs | 44 ++++++++++++ mingling/src/program/flag.rs | 41 ++++++++++-- mingling/src/program/hint.rs | 52 +++++++++++++++ mingling/src/program/setup.rs | 16 +++-- mingling/src/program/setup/basic.rs | 12 +++- 7 files changed, 284 insertions(+), 13 deletions(-) create mode 100644 mingling/src/program/exec.rs create mode 100644 mingling/src/program/exec/error.rs create mode 100644 mingling/src/program/hint.rs (limited to 'mingling/src/program') diff --git a/mingling/src/program/config.rs b/mingling/src/program/config.rs index 93507e7..ffcade3 100644 --- a/mingling/src/program/config.rs +++ b/mingling/src/program/config.rs @@ -1,3 +1,4 @@ +#[derive(Debug, Clone)] pub struct ProgramStdoutSetting { /// Output error messages pub error_output: bool, @@ -15,6 +16,7 @@ impl Default for ProgramStdoutSetting { } } +#[derive(Debug, Clone)] pub struct ProgramUserContext { /// View help information instead of running the command pub help: bool, diff --git a/mingling/src/program/exec.rs b/mingling/src/program/exec.rs new file mode 100644 index 0000000..99eb419 --- /dev/null +++ b/mingling/src/program/exec.rs @@ -0,0 +1,130 @@ +use crate::{ + AnyOutput, ChainProcess, Dispatcher, Program, ProgramCollect, RenderResult, + error::{ChainProcessError, ProgramInternalExecuteError}, + hint::{DispatcherNotFound, NoChainFound, ProgramEnd}, +}; + +pub mod error; + +pub async fn exec( + program: Program, +) -> Result { + // Match user input + let matched: (Box, Vec) = match match_user_input(&program) { + Ok(r) => (r.0.clone(), r.1), + Err(ProgramInternalExecuteError::DispatcherNotFound) => { + // If no Dispatcher is found, dispatch to the DispatcherNotFound Dispatcher + // to route it to the NoDispatcherFound struct + let disp: Box = Box::new(DispatcherNotFound); + (disp, program.args) + } + Err(e) => return Err(e), + }; + + // Entry point + let (dispatcher, args) = matched; + let mut current = match handle_chain_process::(dispatcher.begin(args)) { + Ok(Next::RenderResult(render_result)) => return Ok(render_result), + Ok(Next::AnyOutput(any)) => any, + Err(e) => return Err(e), + }; + + loop { + current = match handle_chain_process::(C::do_chain(current).await) { + Ok(Next::RenderResult(render_result)) => return Ok(render_result), + Ok(Next::AnyOutput(any)) => any, + Err(e) => return Err(e), + }; + if current.is::() || current.is::() { + break; + } + } + + Ok(RenderResult::default()) +} + +/// Match user input against registered dispatchers and return the matched dispatcher and remaining arguments. +fn match_user_input( + program: &Program, +) -> Result<(&Box, Vec), ProgramInternalExecuteError> { + let nodes = get_nodes(&program); + let command = format!("{} ", program.args.join(" ")); + + // Find all nodes that match the command prefix + let matching_nodes: Vec<&(String, &Box)> = 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 = program.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 = program.args.iter().skip(prefix_len).cloned().collect(); + Ok((matched_prefix.1, trimmed_args)) + } + } +} + +#[inline(always)] +fn render(any: AnyOutput) -> RenderResult { + let mut render_result = RenderResult::default(); + C::render(any, &mut render_result); + render_result +} + +fn handle_chain_process( + process: ChainProcess, +) -> Result { + match process { + Ok(any) => Ok(Next::AnyOutput(any)), + Err(e) => match e { + ChainProcessError::Broken(any_output) => { + let render_result = render::(any_output); + return Ok(Next::RenderResult(render_result)); + } + _ => { + return Err(e.into()); + } + }, + } +} + +// Get all registered dispatcher names from the program +fn get_nodes(program: &Program) -> Vec<(String, &Box)> { + program + .dispatcher + .iter() + .map(|disp| { + let node_str = disp + .node() + .to_string() + .split('.') + .collect::>() + .join(" "); + (node_str, disp) + }) + .collect() +} + +enum Next { + RenderResult(RenderResult), + AnyOutput(AnyOutput), +} diff --git a/mingling/src/program/exec/error.rs b/mingling/src/program/exec/error.rs new file mode 100644 index 0000000..0523ea5 --- /dev/null +++ b/mingling/src/program/exec/error.rs @@ -0,0 +1,44 @@ +use crate::error::ChainProcessError; + +#[derive(thiserror::Error, Debug)] +pub enum ProgramExecuteError { + #[error("No Dispatcher Found")] + DispatcherNotFound, + + #[error("Other error: {0}")] + Other(String), +} + +#[derive(thiserror::Error, Debug)] +pub enum ProgramInternalExecuteError { + #[error("No Dispatcher Found")] + DispatcherNotFound, + + #[error("Other error: {0}")] + Other(String), + + #[error("IO error: {0}")] + IO(#[from] std::io::Error), +} + +impl From for ProgramExecuteError { + fn from(value: ProgramInternalExecuteError) -> Self { + match value { + ProgramInternalExecuteError::DispatcherNotFound => { + ProgramExecuteError::DispatcherNotFound + } + ProgramInternalExecuteError::Other(s) => ProgramExecuteError::Other(s), + ProgramInternalExecuteError::IO(e) => ProgramExecuteError::Other(format!("{}", e)), + } + } +} + +impl From for ProgramInternalExecuteError { + fn from(value: ChainProcessError) -> Self { + match value { + ChainProcessError::Other(s) => ProgramInternalExecuteError::Other(s), + ChainProcessError::IO(error) => ProgramInternalExecuteError::IO(error), + ChainProcessError::Broken(_) => ProgramInternalExecuteError::Other(format!("Broken")), + } + } +} diff --git a/mingling/src/program/flag.rs b/mingling/src/program/flag.rs index 0178ebe..8372517 100644 --- a/mingling/src/program/flag.rs +++ b/mingling/src/program/flag.rs @@ -1,4 +1,4 @@ -use crate::Program; +use crate::{Program, ProgramCollect}; pub struct Flag { vec: Vec<&'static str>, @@ -80,11 +80,14 @@ macro_rules! special_argument { }}; } -impl Program { +impl Program +where + C: ProgramCollect, +{ /// Registers a global argument (with value) and its handler. pub fn global_argument(&mut self, arguments: A, do_fn: F) where - F: Fn(&mut Program, String), + F: Fn(&mut Program, String), A: Into, { let flag = arguments.into(); @@ -100,7 +103,7 @@ impl Program { /// Registers a global flag (boolean) and its handler. pub fn global_flag(&mut self, flag: A, do_fn: F) where - F: Fn(&mut Program), + F: Fn(&mut Program), A: Into, { let flag = flag.into(); @@ -112,4 +115,34 @@ impl Program { } } } + + /// Extracts a global argument (with value) from arguments + pub fn pick_global_argument(&mut self, flag: F) -> Option + where + F: Into, + { + let flag: Flag = flag.into(); + for argument in flag.iter() { + let value = special_argument!(self.args, argument); + if value.is_some() { + return value; + } + } + None + } + + /// Extracts global flags from arguments + pub fn pick_global_flag(&mut self, flag: F) -> bool + where + F: Into, + { + let flag: Flag = flag.into(); + for argument in flag.iter() { + let enabled = special_flag!(self.args, argument); + if enabled { + return enabled; + } + } + return false; + } } diff --git a/mingling/src/program/hint.rs b/mingling/src/program/hint.rs new file mode 100644 index 0000000..e9c510b --- /dev/null +++ b/mingling/src/program/hint.rs @@ -0,0 +1,52 @@ +use crate::{AnyOutput, ChainProcess, Dispatcher, Node}; + +/// Marker: Program End +/// +/// If a chain outputs ProgramEnd to the Chain, +/// the program will terminate directly. +/// +/// You can implement Renderer for ProgramEnd +/// to render relevant information after the program ends. +#[cfg_attr(feature = "serde_renderer", derive(serde::Serialize))] +pub struct ProgramEnd; + +/// Marker: Chain Not Found +/// +/// If a Chain or Dispatcher outputs NoChainFound to the Chain, +/// the program will terminate directly. +/// +/// You can implement Renderer for NoChainFound +/// to render relevant information when a Chain cannot be found. +#[cfg_attr(feature = "serde_renderer", derive(serde::Serialize))] +pub struct NoChainFound { + pub name: String, +} + +/// Marker: Dispatcher Not Found +/// +/// If a Dispatcher outputs NoDispatcherFound to the Chain, +/// the program will terminate directly. +/// +/// You can implement Renderer for NoDispatcherFound +/// to render relevant information when a Dispatcher cannot be found. +#[cfg_attr(feature = "serde_renderer", derive(serde::Serialize))] +pub struct NoDispatcherFound { + pub args: Vec, +} + +#[derive(Default)] +#[cfg_attr(feature = "serde_renderer", derive(serde::Serialize))] +pub struct DispatcherNotFound; +impl Dispatcher for DispatcherNotFound { + fn node(&self) -> crate::Node { + Node::default().join("_not_found") + } + + fn begin(&self, args: Vec) -> ChainProcess { + AnyOutput::new(NoDispatcherFound { args }).route_renderer() + } + + fn clone_dispatcher(&self) -> Box { + Box::new(DispatcherNotFound) + } +} diff --git a/mingling/src/program/setup.rs b/mingling/src/program/setup.rs index fdf7b04..e81247e 100644 --- a/mingling/src/program/setup.rs +++ b/mingling/src/program/setup.rs @@ -1,15 +1,19 @@ -use crate::program::Program; +use crate::{ProgramCollect, program::Program}; mod basic; pub use basic::*; -pub trait ProgramSetup { - fn setup(program: &mut Program); +pub trait ProgramSetup { + fn setup(&mut self, program: &mut Program); } -impl Program { +impl Program +where + C: ProgramCollect, +{ /// Load and execute init logic - pub fn with_setup(&mut self, _setup: S) { - S::setup(self); + pub fn with_setup + 'static>(&mut self, mut setup: S) -> S { + S::setup(&mut setup, self); + setup } } diff --git a/mingling/src/program/setup/basic.rs b/mingling/src/program/setup/basic.rs index 0ea72a6..43c14b9 100644 --- a/mingling/src/program/setup/basic.rs +++ b/mingling/src/program/setup/basic.rs @@ -1,4 +1,7 @@ -use crate::program::{Program, setup::ProgramSetup}; +use crate::{ + ProgramCollect, + program::{Program, setup::ProgramSetup}, +}; /// Performs basic program initialization: /// @@ -7,8 +10,11 @@ use crate::program::{Program, setup::ProgramSetup}; /// - Collects `--confirm` flag to skip user confirmation pub struct BasicProgramSetup; -impl ProgramSetup for BasicProgramSetup { - fn setup(program: &mut Program) { +impl ProgramSetup for BasicProgramSetup +where + C: ProgramCollect, +{ + fn setup(&mut self, program: &mut Program) { program.global_flag(["--quiet", "-q"], |p| { p.stdout_setting.render_output = false; p.stdout_setting.error_output = false; -- cgit