summaryrefslogtreecommitdiff
path: root/mingling/src/program
diff options
context:
space:
mode:
Diffstat (limited to 'mingling/src/program')
-rw-r--r--mingling/src/program/config.rs2
-rw-r--r--mingling/src/program/exec.rs130
-rw-r--r--mingling/src/program/exec/error.rs44
-rw-r--r--mingling/src/program/flag.rs41
-rw-r--r--mingling/src/program/hint.rs52
-rw-r--r--mingling/src/program/setup.rs16
-rw-r--r--mingling/src/program/setup/basic.rs12
7 files changed, 284 insertions, 13 deletions
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<C: ProgramCollect>(
+ program: Program<C>,
+) -> Result<RenderResult, ProgramInternalExecuteError> {
+ // Match user input
+ let matched: (Box<dyn Dispatcher>, Vec<String>) = 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<dyn Dispatcher> = Box::new(DispatcherNotFound);
+ (disp, program.args)
+ }
+ Err(e) => return Err(e),
+ };
+
+ // Entry point
+ let (dispatcher, args) = matched;
+ let mut current = match handle_chain_process::<C>(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>(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::<ProgramEnd>() || current.is::<NoChainFound>() {
+ break;
+ }
+ }
+
+ Ok(RenderResult::default())
+}
+
+/// Match user input against registered dispatchers and return the matched dispatcher and remaining arguments.
+fn match_user_input<C: ProgramCollect>(
+ program: &Program<C>,
+) -> Result<(&Box<dyn Dispatcher>, Vec<String>), 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<dyn Dispatcher>)> = 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<String> = 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<String> = program.args.iter().skip(prefix_len).cloned().collect();
+ Ok((matched_prefix.1, trimmed_args))
+ }
+ }
+}
+
+#[inline(always)]
+fn render<C: ProgramCollect>(any: AnyOutput) -> RenderResult {
+ let mut render_result = RenderResult::default();
+ C::render(any, &mut render_result);
+ render_result
+}
+
+fn handle_chain_process<C: ProgramCollect>(
+ process: ChainProcess,
+) -> Result<Next, ProgramInternalExecuteError> {
+ match process {
+ Ok(any) => Ok(Next::AnyOutput(any)),
+ Err(e) => match e {
+ ChainProcessError::Broken(any_output) => {
+ let render_result = render::<C>(any_output);
+ return Ok(Next::RenderResult(render_result));
+ }
+ _ => {
+ return Err(e.into());
+ }
+ },
+ }
+}
+
+// Get all registered dispatcher names from the program
+fn get_nodes<C: ProgramCollect>(program: &Program<C>) -> Vec<(String, &Box<dyn Dispatcher>)> {
+ program
+ .dispatcher
+ .iter()
+ .map(|disp| {
+ let node_str = disp
+ .node()
+ .to_string()
+ .split('.')
+ .collect::<Vec<_>>()
+ .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<ProgramInternalExecuteError> 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<ChainProcessError> 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<C> Program<C>
+where
+ C: ProgramCollect,
+{
/// Registers a global argument (with value) and its handler.
pub fn global_argument<F, A>(&mut self, arguments: A, do_fn: F)
where
- F: Fn(&mut Program, String),
+ F: Fn(&mut Program<C>, String),
A: Into<Flag>,
{
let flag = arguments.into();
@@ -100,7 +103,7 @@ impl Program {
/// Registers a global flag (boolean) and its handler.
pub fn global_flag<F, A>(&mut self, flag: A, do_fn: F)
where
- F: Fn(&mut Program),
+ F: Fn(&mut Program<C>),
A: Into<Flag>,
{
let flag = flag.into();
@@ -112,4 +115,34 @@ impl Program {
}
}
}
+
+ /// Extracts a global argument (with value) from arguments
+ pub fn pick_global_argument<F>(&mut self, flag: F) -> Option<String>
+ where
+ F: Into<Flag>,
+ {
+ 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<F>(&mut self, flag: F) -> bool
+ where
+ F: Into<Flag>,
+ {
+ 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<String>,
+}
+
+#[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<String>) -> ChainProcess {
+ AnyOutput::new(NoDispatcherFound { args }).route_renderer()
+ }
+
+ fn clone_dispatcher(&self) -> Box<dyn Dispatcher> {
+ 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<C: ProgramCollect> {
+ fn setup(&mut self, program: &mut Program<C>);
}
-impl Program {
+impl<C> Program<C>
+where
+ C: ProgramCollect,
+{
/// Load and execute init logic
- pub fn with_setup<S: ProgramSetup + 'static>(&mut self, _setup: S) {
- S::setup(self);
+ pub fn with_setup<S: ProgramSetup<C> + '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<C> ProgramSetup<C> for BasicProgramSetup
+where
+ C: ProgramCollect,
+{
+ fn setup(&mut self, program: &mut Program<C>) {
program.global_flag(["--quiet", "-q"], |p| {
p.stdout_setting.render_output = false;
p.stdout_setting.error_output = false;