diff options
Diffstat (limited to 'mingling_core/src')
| -rw-r--r-- | mingling_core/src/any.rs | 128 | ||||
| -rw-r--r-- | mingling_core/src/asset.rs | 4 | ||||
| -rw-r--r-- | mingling_core/src/asset/chain.rs | 8 | ||||
| -rw-r--r-- | mingling_core/src/asset/chain/error.rs | 13 | ||||
| -rw-r--r-- | mingling_core/src/asset/dispatcher.rs | 195 | ||||
| -rw-r--r-- | mingling_core/src/asset/node.rs | 54 | ||||
| -rw-r--r-- | mingling_core/src/asset/renderer.rs | 6 | ||||
| -rw-r--r-- | mingling_core/src/lib.rs | 39 | ||||
| -rw-r--r-- | mingling_core/src/program.rs | 132 | ||||
| -rw-r--r-- | mingling_core/src/program/config.rs | 26 | ||||
| -rw-r--r-- | mingling_core/src/program/exec.rs | 126 | ||||
| -rw-r--r-- | mingling_core/src/program/exec/error.rs | 46 | ||||
| -rw-r--r-- | mingling_core/src/program/flag.rs | 148 | ||||
| -rw-r--r-- | mingling_core/src/program/hint.rs | 62 | ||||
| -rw-r--r-- | mingling_core/src/program/setup.rs | 19 | ||||
| -rw-r--r-- | mingling_core/src/program/setup/basic.rs | 31 | ||||
| -rw-r--r-- | mingling_core/src/renderer.rs | 1 | ||||
| -rw-r--r-- | mingling_core/src/renderer/render_result.rs | 38 |
18 files changed, 1076 insertions, 0 deletions
diff --git a/mingling_core/src/any.rs b/mingling_core/src/any.rs new file mode 100644 index 0000000..1bce96a --- /dev/null +++ b/mingling_core/src/any.rs @@ -0,0 +1,128 @@ +#[cfg(feature = "general_renderer")] +use serde::Serialize; + +use crate::error::ChainProcessError; + +#[derive(Debug)] +pub struct AnyOutput { + inner: Box<dyn std::any::Any + Send + 'static>, + pub type_id: std::any::TypeId, +} + +impl AnyOutput { + #[cfg(feature = "general_renderer")] + pub fn new<T>(value: T) -> Self + where + T: Send + Serialize + 'static, + { + Self { + inner: Box::new(value), + type_id: std::any::TypeId::of::<T>(), + } + } + + #[cfg(not(feature = "general_renderer"))] + pub fn new<T>(value: T) -> Self + where + T: Send + 'static, + { + Self { + inner: Box::new(value), + type_id: std::any::TypeId::of::<T>(), + } + } + + pub fn downcast<T: 'static>(self) -> Result<T, Self> { + if self.type_id == std::any::TypeId::of::<T>() { + Ok(*self.inner.downcast::<T>().unwrap()) + } else { + Err(self) + } + } + + pub fn is<T: 'static>(&self) -> bool { + self.type_id == std::any::TypeId::of::<T>() + } + + /// Route the output to the next Chain + pub fn route_chain(self) -> ChainProcess { + ChainProcess::Ok((self, Next::Chain)) + } + + /// Route the output to the Renderer, ending execution + pub fn route_renderer(self) -> ChainProcess { + ChainProcess::Ok((self, Next::Renderer)) + } +} + +impl std::ops::Deref for AnyOutput { + type Target = dyn std::any::Any + Send + 'static; + + fn deref(&self) -> &Self::Target { + &*self.inner + } +} + +impl std::ops::DerefMut for AnyOutput { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut *self.inner + } +} + +pub enum ChainProcess { + Ok((AnyOutput, Next)), + Err(ChainProcessError), +} + +pub enum Next { + Chain, + Renderer, +} + +impl ChainProcess { + pub fn is_next(&self) -> bool { + matches!(self, Self::Ok(_)) + } + + pub fn is_err(&self) -> bool { + matches!(self, Self::Err(_)) + } + + pub fn next(&self) -> Option<&Next> { + match self { + Self::Ok((_, next)) => Some(next), + Self::Err(_) => None, + } + } + + pub fn err(&self) -> Option<&ChainProcessError> { + match self { + Self::Ok(_) => None, + Self::Err(err) => Some(err), + } + } + + pub fn unwrap(self) -> (AnyOutput, Next) { + match self { + Self::Ok(tuple) => tuple, + Self::Err(_) => panic!("called `ChainProcess2::unwrap()` on an `Error` value"), + } + } + + pub fn unwrap_or(self, default: (AnyOutput, Next)) -> (AnyOutput, Next) { + match self { + Self::Ok(tuple) => tuple, + Self::Err(_) => default, + } + } + + pub fn unwrap_or_else<F>(self, f: F) -> (AnyOutput, Next) + where + F: FnOnce(ChainProcessError) -> (AnyOutput, Next), + { + match self { + Self::Ok(tuple) => tuple, + Self::Err(err) => f(err), + } + } +} diff --git a/mingling_core/src/asset.rs b/mingling_core/src/asset.rs new file mode 100644 index 0000000..c2adf4e --- /dev/null +++ b/mingling_core/src/asset.rs @@ -0,0 +1,4 @@ +pub mod chain; +pub mod dispatcher; +pub mod node; +pub mod renderer; diff --git a/mingling_core/src/asset/chain.rs b/mingling_core/src/asset/chain.rs new file mode 100644 index 0000000..1ea1125 --- /dev/null +++ b/mingling_core/src/asset/chain.rs @@ -0,0 +1,8 @@ +use crate::ChainProcess; + +pub mod error; + +pub trait Chain { + type Previous; + fn proc(p: Self::Previous) -> impl Future<Output = ChainProcess> + Send; +} diff --git a/mingling_core/src/asset/chain/error.rs b/mingling_core/src/asset/chain/error.rs new file mode 100644 index 0000000..d4da4ac --- /dev/null +++ b/mingling_core/src/asset/chain/error.rs @@ -0,0 +1,13 @@ +use crate::AnyOutput; + +#[derive(thiserror::Error, Debug)] +pub enum ChainProcessError { + #[error("Other error: {0}")] + Other(String), + + #[error("IO error: {0}")] + IO(#[from] std::io::Error), + + #[error("Broken chain")] + Broken(AnyOutput), +} diff --git a/mingling_core/src/asset/dispatcher.rs b/mingling_core/src/asset/dispatcher.rs new file mode 100644 index 0000000..13e35f7 --- /dev/null +++ b/mingling_core/src/asset/dispatcher.rs @@ -0,0 +1,195 @@ +use crate::{ChainProcess, Program, asset::node::Node}; + +pub trait Dispatcher { + fn node(&self) -> Node; + fn begin(&self, args: Vec<String>) -> ChainProcess; + fn clone_dispatcher(&self) -> Box<dyn Dispatcher>; +} + +impl Clone for Box<dyn Dispatcher> { + fn clone(&self) -> Self { + self.clone_dispatcher() + } +} + +impl<C: crate::program::ProgramCollect> Program<C> { + /// Adds a dispatcher to the program. + pub fn with_dispatcher<D>(&mut self, dispatcher: D) + where + D: Into<Dispatchers>, + { + let dispatchers = dispatcher.into().dispatcher; + self.dispatcher.extend(dispatchers); + } +} + +pub struct Dispatchers { + dispatcher: Vec<Box<dyn Dispatcher + 'static>>, +} + +impl<D> From<D> for Dispatchers +where + D: Dispatcher + 'static, +{ + fn from(dispatcher: D) -> Self { + Self { + dispatcher: vec![Box::new(dispatcher)], + } + } +} + +impl From<Vec<Box<dyn Dispatcher>>> for Dispatchers { + fn from(dispatcher: Vec<Box<dyn Dispatcher>>) -> Self { + Self { dispatcher } + } +} + +impl From<Box<dyn Dispatcher>> for Dispatchers { + fn from(dispatcher: Box<dyn Dispatcher>) -> Self { + Self { + dispatcher: vec![dispatcher], + } + } +} + +impl<D> From<(D,)> for Dispatchers +where + D: Dispatcher + 'static, +{ + fn from(dispatcher: (D,)) -> Self { + Self { + dispatcher: vec![Box::new(dispatcher.0)], + } + } +} + +impl<D1, D2> From<(D1, D2)> for Dispatchers +where + D1: Dispatcher + 'static, + D2: Dispatcher + 'static, +{ + fn from(dispatchers: (D1, D2)) -> Self { + Self { + dispatcher: vec![Box::new(dispatchers.0), Box::new(dispatchers.1)], + } + } +} + +impl<D1, D2, D3> From<(D1, D2, D3)> for Dispatchers +where + D1: Dispatcher + 'static, + D2: Dispatcher + 'static, + D3: Dispatcher + 'static, +{ + fn from(dispatchers: (D1, D2, D3)) -> Self { + Self { + dispatcher: vec![ + Box::new(dispatchers.0), + Box::new(dispatchers.1), + Box::new(dispatchers.2), + ], + } + } +} + +impl<D1, D2, D3, D4> From<(D1, D2, D3, D4)> for Dispatchers +where + D1: Dispatcher + 'static, + D2: Dispatcher + 'static, + D3: Dispatcher + 'static, + D4: Dispatcher + 'static, +{ + fn from(dispatchers: (D1, D2, D3, D4)) -> Self { + Self { + dispatcher: vec![ + Box::new(dispatchers.0), + Box::new(dispatchers.1), + Box::new(dispatchers.2), + Box::new(dispatchers.3), + ], + } + } +} + +impl<D1, D2, D3, D4, D5> From<(D1, D2, D3, D4, D5)> for Dispatchers +where + D1: Dispatcher + 'static, + D2: Dispatcher + 'static, + D3: Dispatcher + 'static, + D4: Dispatcher + 'static, + D5: Dispatcher + 'static, +{ + fn from(dispatchers: (D1, D2, D3, D4, D5)) -> Self { + Self { + dispatcher: vec![ + Box::new(dispatchers.0), + Box::new(dispatchers.1), + Box::new(dispatchers.2), + Box::new(dispatchers.3), + Box::new(dispatchers.4), + ], + } + } +} + +impl<D1, D2, D3, D4, D5, D6> From<(D1, D2, D3, D4, D5, D6)> for Dispatchers +where + D1: Dispatcher + 'static, + D2: Dispatcher + 'static, + D3: Dispatcher + 'static, + D4: Dispatcher + 'static, + D5: Dispatcher + 'static, + D6: Dispatcher + 'static, +{ + fn from(dispatchers: (D1, D2, D3, D4, D5, D6)) -> Self { + Self { + dispatcher: vec![ + Box::new(dispatchers.0), + Box::new(dispatchers.1), + Box::new(dispatchers.2), + Box::new(dispatchers.3), + Box::new(dispatchers.4), + Box::new(dispatchers.5), + ], + } + } +} + +impl<D1, D2, D3, D4, D5, D6, D7> From<(D1, D2, D3, D4, D5, D6, D7)> for Dispatchers +where + D1: Dispatcher + 'static, + D2: Dispatcher + 'static, + D3: Dispatcher + 'static, + D4: Dispatcher + 'static, + D5: Dispatcher + 'static, + D6: Dispatcher + 'static, + D7: Dispatcher + 'static, +{ + fn from(dispatchers: (D1, D2, D3, D4, D5, D6, D7)) -> Self { + Self { + dispatcher: vec![ + Box::new(dispatchers.0), + Box::new(dispatchers.1), + Box::new(dispatchers.2), + Box::new(dispatchers.3), + Box::new(dispatchers.4), + Box::new(dispatchers.5), + Box::new(dispatchers.6), + ], + } + } +} + +impl std::ops::Deref for Dispatchers { + type Target = Vec<Box<dyn Dispatcher + 'static>>; + + fn deref(&self) -> &Self::Target { + &self.dispatcher + } +} + +impl From<Dispatchers> for Vec<Box<dyn Dispatcher + 'static>> { + fn from(val: Dispatchers) -> Self { + val.dispatcher + } +} diff --git a/mingling_core/src/asset/node.rs b/mingling_core/src/asset/node.rs new file mode 100644 index 0000000..c8b7600 --- /dev/null +++ b/mingling_core/src/asset/node.rs @@ -0,0 +1,54 @@ +use just_fmt::kebab_case; + +#[derive(Debug, Default)] +pub struct Node { + node: Vec<String>, +} + +impl Node { + pub fn join(self, node: impl Into<String>) -> Node { + let mut new_node = self.node; + new_node.push(node.into()); + Node { node: new_node } + } +} + +impl From<&str> for Node { + fn from(s: &str) -> Self { + let node = s.split('.').map(|part| kebab_case!(part)).collect(); + Node { node } + } +} + +impl From<String> for Node { + fn from(s: String) -> Self { + let node = s.split('.').map(|part| kebab_case!(part)).collect(); + Node { node } + } +} + +impl PartialEq for Node { + fn eq(&self, other: &Self) -> bool { + self.node == other.node + } +} + +impl Eq for Node {} + +impl PartialOrd for Node { + fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { + Some(self.cmp(other)) + } +} + +impl Ord for Node { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.node.cmp(&other.node) + } +} + +impl std::fmt::Display for Node { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.node.join(".")) + } +} diff --git a/mingling_core/src/asset/renderer.rs b/mingling_core/src/asset/renderer.rs new file mode 100644 index 0000000..3852b55 --- /dev/null +++ b/mingling_core/src/asset/renderer.rs @@ -0,0 +1,6 @@ +use crate::RenderResult; + +pub trait Renderer { + type Previous; + fn render(p: Self::Previous, r: &mut RenderResult); +} diff --git a/mingling_core/src/lib.rs b/mingling_core/src/lib.rs new file mode 100644 index 0000000..4d9bcc5 --- /dev/null +++ b/mingling_core/src/lib.rs @@ -0,0 +1,39 @@ +mod any; +pub use crate::any::*; + +pub mod error { + pub use crate::asset::chain::error::*; + pub use crate::exec::error::*; +} + +mod program; +pub use crate::program::*; +pub mod setup { + pub use crate::program::setup::*; +} +pub mod hint { + pub use crate::program::hint::*; +} + +#[cfg(feature = "macros")] +#[allow(unused_imports)] +pub mod macros { + pub use mingling_macros::chain; + pub use mingling_macros::chain_struct; + pub use mingling_macros::dispatcher; + pub use mingling_macros::dispatcher_render; + pub use mingling_macros::node; + pub use mingling_macros::program; + pub use mingling_macros::r_print; + pub use mingling_macros::r_println; + pub use mingling_macros::renderer; +} + +mod renderer; + +mod asset; +pub use crate::asset::chain::*; +pub use crate::asset::dispatcher::*; +pub use crate::asset::node::*; +pub use crate::asset::renderer::*; +pub use crate::renderer::render_result::*; diff --git a/mingling_core/src/program.rs b/mingling_core/src/program.rs new file mode 100644 index 0000000..3d8bc14 --- /dev/null +++ b/mingling_core/src/program.rs @@ -0,0 +1,132 @@ +use crate::{ + AnyOutput, ChainProcess, RenderResult, asset::dispatcher::Dispatcher, + error::ProgramExecuteError, +}; +use std::{env, pin::Pin}; + +pub mod exec; +pub mod hint; +pub mod setup; + +mod config; +pub use config::*; + +mod flag; +pub use flag::*; +use tokio::io::AsyncWriteExt; + +#[derive(Default)] +pub struct Program<C: ProgramCollect> { + pub(crate) collect: std::marker::PhantomData<C>, + + pub(crate) args: Vec<String>, + pub(crate) dispatcher: Vec<Box<dyn Dispatcher>>, + + pub stdout_setting: ProgramStdoutSetting, + pub user_context: ProgramUserContext, +} + +impl<C> Program<C> +where + C: ProgramCollect, +{ + /// Creates a new Program instance, initializing args from environment. + pub fn new() -> Self { + Program { + collect: 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<RenderResult, ProgramExecuteError> { + 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::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 { + fn render(any: AnyOutput, r: &mut RenderResult); + fn do_chain(any: AnyOutput) -> Pin<Box<dyn Future<Output = ChainProcess> + 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:ty, )* + ) => { + fn render(any: mingling::AnyOutput, r: &mut mingling::RenderResult) { + match any.type_id { + $( + id if id == std::any::TypeId::of::<$prev_ty>() => { + let value = any.downcast::<$prev_ty>().unwrap(); + <$render_ty as mingling::Renderer>::render(value, r); + } + )* + _ => (), + } + } + }; +} + +#[macro_export] +#[doc(hidden)] +macro_rules! __dispatch_program_chains { + ( + $( $chain_ty:ty => $chain_prev:ty, )* + ) => { + fn do_chain( + any: mingling::AnyOutput, + ) -> std::pin::Pin<Box<dyn Future<Output = mingling::ChainProcess> + Send>> { + match any.type_id { + $( + id if id == std::any::TypeId::of::<$chain_prev>() => { + let value = any.downcast::<$chain_prev>().unwrap(); + let fut = async { <$chain_ty as mingling::Chain>::proc(value).await }; + Box::pin(fut) + } + )* + _ => Box::pin(async move { + mingling::AnyOutput::new(mingling::hint::NoChainFound { + name: format!("{:?}", any.type_id).to_string(), + }) + .route_chain() + }), + } + } + }; +} diff --git a/mingling_core/src/program/config.rs b/mingling_core/src/program/config.rs new file mode 100644 index 0000000..386b112 --- /dev/null +++ b/mingling_core/src/program/config.rs @@ -0,0 +1,26 @@ +#[derive(Debug, Clone)] +pub struct ProgramStdoutSetting { + /// Output error messages + pub error_output: bool, + + /// Render results and output + pub render_output: bool, +} + +impl Default for ProgramStdoutSetting { + fn default() -> Self { + ProgramStdoutSetting { + error_output: true, + render_output: true, + } + } +} + +#[derive(Debug, Clone, Default)] +pub struct ProgramUserContext { + /// View help information instead of running the command + pub help: bool, + + /// Skip user confirmation step + pub confirm: bool, +} diff --git a/mingling_core/src/program/exec.rs b/mingling_core/src/program/exec.rs new file mode 100644 index 0000000..853bff1 --- /dev/null +++ b/mingling_core/src/program/exec.rs @@ -0,0 +1,126 @@ +#![allow(clippy::borrowed_box)] + +use crate::{ + AnyOutput, ChainProcess, Dispatcher, Next, Program, ProgramCollect, RenderResult, + error::ProgramInternalExecuteError, + hint::{DispatcherNotFound, RendererNotFound}, +}; + +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 dispatcher.begin(args) { + ChainProcess::Ok((any, Next::Renderer)) => return Ok(render::<C>(any)), + ChainProcess::Ok((any, Next::Chain)) => any, + ChainProcess::Err(e) => return Err(e.into()), + }; + + loop { + current = { + // If a chain exists, execute as a chain + if C::has_chain(¤t) { + match C::do_chain(current).await { + ChainProcess::Ok((any, Next::Renderer)) => return Ok(render::<C>(any)), + ChainProcess::Ok((any, Next::Chain)) => any, + ChainProcess::Err(e) => return Err(e.into()), + } + } + // If no chain exists, attempt to render + else if C::has_renderer(¤t) { + let mut render_result = RenderResult::default(); + C::render(current, &mut render_result); + return Ok(render_result); + } + // If no renderer exists, transfer to the RendererNotFound Dispatcher for execution + else { + let disp: Box<dyn Dispatcher> = Box::new(RendererNotFound); + + match disp.begin(vec![format!("{:?}", current.type_id)]) { + ChainProcess::Ok((any, Next::Renderer)) => return Ok(render::<C>(any)), + ChainProcess::Ok((any, Next::Chain)) => any, + ChainProcess::Err(e) => return Err(e.into()), + } + } + }; + } +} + +/// 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 +} + +// 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() +} diff --git a/mingling_core/src/program/exec/error.rs b/mingling_core/src/program/exec/error.rs new file mode 100644 index 0000000..fe66e22 --- /dev/null +++ b/mingling_core/src/program/exec/error.rs @@ -0,0 +1,46 @@ +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("Broken".to_string()) + } + } + } +} diff --git a/mingling_core/src/program/flag.rs b/mingling_core/src/program/flag.rs new file mode 100644 index 0000000..3a678be --- /dev/null +++ b/mingling_core/src/program/flag.rs @@ -0,0 +1,148 @@ +use crate::{Program, ProgramCollect}; + +pub struct Flag { + vec: Vec<&'static str>, +} + +impl From<&'static str> for Flag { + fn from(s: &'static str) -> Self { + Flag { vec: vec![s] } + } +} + +impl From<&'static [&'static str]> for Flag { + fn from(slice: &'static [&'static str]) -> Self { + Flag { + vec: slice.to_vec(), + } + } +} + +impl<const N: usize> From<[&'static str; N]> for Flag { + fn from(slice: [&'static str; N]) -> Self { + Flag { + vec: slice.to_vec(), + } + } +} + +impl<const N: usize> From<&'static [&'static str; N]> for Flag { + fn from(slice: &'static [&'static str; N]) -> Self { + Flag { + vec: slice.to_vec(), + } + } +} + +impl AsRef<[&'static str]> for Flag { + fn as_ref(&self) -> &[&'static str] { + &self.vec + } +} + +impl std::ops::Deref for Flag { + type Target = [&'static str]; + + fn deref(&self) -> &Self::Target { + &self.vec + } +} + +macro_rules! special_flag { + ($args:expr, $flag:expr) => {{ + let flag = $flag; + let found = $args.iter().any(|arg| arg == flag); + $args.retain(|arg| arg != flag); + found + }}; +} + +macro_rules! special_argument { + ($args:expr, $flag:expr) => {{ + let flag = $flag; + let mut value: Option<String> = None; + let mut i = 0; + while i < $args.len() { + if &$args[i] == flag { + if i + 1 < $args.len() { + value = Some($args[i + 1].clone()); + $args.remove(i + 1); + $args.remove(i); + } else { + value = None; + $args.remove(i); + } + break; + } + i += 1; + } + value + }}; +} + +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<C>, String), + A: Into<Flag>, + { + let flag = arguments.into(); + for argument in flag.iter() { + let value = special_argument!(self.args, argument); + if let Some(value) = value { + do_fn(self, value); + return; + } + } + } + + /// 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<C>), + A: Into<Flag>, + { + let flag = flag.into(); + for argument in flag.iter() { + let enabled = special_flag!(self.args, argument); + if enabled { + do_fn(self); + return; + } + } + } + + /// 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; + } + } + false + } +} diff --git a/mingling_core/src/program/hint.rs b/mingling_core/src/program/hint.rs new file mode 100644 index 0000000..6dbbac2 --- /dev/null +++ b/mingling_core/src/program/hint.rs @@ -0,0 +1,62 @@ +use crate::{AnyOutput, ChainProcess, Dispatcher, Node}; + +/// 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 = "general_renderer", derive(serde::Serialize))] +pub struct NoDispatcherFound { + pub args: Vec<String>, +} + +#[derive(Default)] +#[cfg_attr(feature = "general_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) + } +} + +/// Marker: Renderer Not Found +/// +/// If a Chain outputs NoRendererFound to the Chain, +/// the program will terminate directly. +/// +/// You can implement Renderer for NoRendererFound +/// to render relevant information when a Renderer cannot be found. +#[cfg_attr(feature = "general_renderer", derive(serde::Serialize))] +pub struct NoRendererFound { + pub type_to_render: String, +} + +#[derive(Default)] +#[cfg_attr(feature = "general_renderer", derive(serde::Serialize))] +pub struct RendererNotFound; +impl Dispatcher for RendererNotFound { + fn node(&self) -> crate::Node { + Node::default().join("_not_found") + } + + fn begin(&self, args: Vec<String>) -> ChainProcess { + AnyOutput::new(NoRendererFound { + type_to_render: args.first().unwrap().clone(), + }) + .route_renderer() + } + + fn clone_dispatcher(&self) -> Box<dyn Dispatcher> { + Box::new(RendererNotFound) + } +} diff --git a/mingling_core/src/program/setup.rs b/mingling_core/src/program/setup.rs new file mode 100644 index 0000000..e81247e --- /dev/null +++ b/mingling_core/src/program/setup.rs @@ -0,0 +1,19 @@ +use crate::{ProgramCollect, program::Program}; + +mod basic; +pub use basic::*; + +pub trait ProgramSetup<C: ProgramCollect> { + fn setup(&mut self, program: &mut Program<C>); +} + +impl<C> Program<C> +where + C: ProgramCollect, +{ + /// Load and execute init logic + pub fn with_setup<S: ProgramSetup<C> + 'static>(&mut self, mut setup: S) -> S { + S::setup(&mut setup, self); + setup + } +} diff --git a/mingling_core/src/program/setup/basic.rs b/mingling_core/src/program/setup/basic.rs new file mode 100644 index 0000000..43c14b9 --- /dev/null +++ b/mingling_core/src/program/setup/basic.rs @@ -0,0 +1,31 @@ +use crate::{ + ProgramCollect, + program::{Program, setup::ProgramSetup}, +}; + +/// Performs basic program initialization: +/// +/// - Collects `--quiet` flag to control message rendering +/// - Collects `--help` flag to enable help mode +/// - Collects `--confirm` flag to skip user confirmation +pub struct BasicProgramSetup; + +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; + }); + + program.global_flag(["--help", "-h"], |p| { + p.user_context.help = true; + }); + + program.global_flag(["--confirm", "-C"], |p| { + p.user_context.confirm = true; + }); + } +} diff --git a/mingling_core/src/renderer.rs b/mingling_core/src/renderer.rs new file mode 100644 index 0000000..631092b --- /dev/null +++ b/mingling_core/src/renderer.rs @@ -0,0 +1 @@ +pub mod render_result; diff --git a/mingling_core/src/renderer/render_result.rs b/mingling_core/src/renderer/render_result.rs new file mode 100644 index 0000000..73c38e7 --- /dev/null +++ b/mingling_core/src/renderer/render_result.rs @@ -0,0 +1,38 @@ +use std::{ + fmt::{Display, Formatter}, + ops::Deref, +}; + +#[derive(Default, Debug, PartialEq)] +pub struct RenderResult { + render_text: String, +} + +impl Display for RenderResult { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + writeln!(f, "{}", self.render_text.trim()) + } +} + +impl Deref for RenderResult { + type Target = str; + + fn deref(&self) -> &Self::Target { + &self.render_text + } +} + +impl RenderResult { + pub fn print(&mut self, text: &str) { + self.render_text.push_str(text); + } + + pub fn println(&mut self, text: &str) { + self.render_text.push_str(text); + self.render_text.push('\n'); + } + + pub fn clear(&mut self) { + self.render_text.clear(); + } +} |
