diff options
Diffstat (limited to 'mingling/src')
| -rw-r--r-- | mingling/src/any.rs | 72 | ||||
| -rw-r--r-- | mingling/src/asset.rs | 4 | ||||
| -rw-r--r-- | mingling/src/asset/chain.rs | 8 | ||||
| -rw-r--r-- | mingling/src/asset/chain/error.rs | 13 | ||||
| -rw-r--r-- | mingling/src/asset/dispatcher.rs | 19 | ||||
| -rw-r--r-- | mingling/src/asset/node.rs | 54 | ||||
| -rw-r--r-- | mingling/src/asset/renderer.rs | 6 | ||||
| -rw-r--r-- | mingling/src/lib.rs | 33 | ||||
| -rw-r--r-- | mingling/src/program.rs | 35 | ||||
| -rw-r--r-- | mingling/src/program/config.rs | 33 | ||||
| -rw-r--r-- | mingling/src/program/flag.rs | 115 | ||||
| -rw-r--r-- | mingling/src/program/setup.rs | 15 | ||||
| -rw-r--r-- | mingling/src/program/setup/basic.rs | 25 | ||||
| -rw-r--r-- | mingling/src/renderer.rs | 1 | ||||
| -rw-r--r-- | mingling/src/renderer/render_result.rs | 38 |
15 files changed, 471 insertions, 0 deletions
diff --git a/mingling/src/any.rs b/mingling/src/any.rs new file mode 100644 index 0000000..a74cd54 --- /dev/null +++ b/mingling/src/any.rs @@ -0,0 +1,72 @@ +#[cfg(feature = "serde_renderer")] +use serde::Serialize; + +use crate::error::ChainProcessError; + +pub type ChainProcess = Result<AnyOutput, ChainProcessError>; + +#[derive(Debug)] +pub struct AnyOutput { + inner: Box<dyn std::any::Any + Send + 'static>, + type_id: std::any::TypeId, +} + +impl AnyOutput { + #[cfg(feature = "serde_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 = "serde_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 { + Ok(self) + } + + /// Route the output to the Renderer, ending execution + pub fn route_renderer(self) -> ChainProcess { + Err(ChainProcessError::Broken(self)) + } +} + +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 + } +} diff --git a/mingling/src/asset.rs b/mingling/src/asset.rs new file mode 100644 index 0000000..c2adf4e --- /dev/null +++ b/mingling/src/asset.rs @@ -0,0 +1,4 @@ +pub mod chain; +pub mod dispatcher; +pub mod node; +pub mod renderer; diff --git a/mingling/src/asset/chain.rs b/mingling/src/asset/chain.rs new file mode 100644 index 0000000..1ea1125 --- /dev/null +++ b/mingling/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/src/asset/chain/error.rs b/mingling/src/asset/chain/error.rs new file mode 100644 index 0000000..d4da4ac --- /dev/null +++ b/mingling/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/src/asset/dispatcher.rs b/mingling/src/asset/dispatcher.rs new file mode 100644 index 0000000..31623d3 --- /dev/null +++ b/mingling/src/asset/dispatcher.rs @@ -0,0 +1,19 @@ +use crate::{ChainProcess, Program, asset::node::Node}; + +pub use mingling_macros::Dispatcher; + +pub trait Dispatcher { + fn node(&self) -> Node; +} + +pub trait DispatcherChain { + fn begin(&self) -> ChainProcess; +} + +impl Program { + /// Adds a dispatcher to the program. + pub fn with_dispatcher<D: Dispatcher + 'static>(&mut self, dispatcher: D) { + let dispatcher = Box::new(dispatcher); + self.dispatcher.push(dispatcher); + } +} diff --git a/mingling/src/asset/node.rs b/mingling/src/asset/node.rs new file mode 100644 index 0000000..c8b7600 --- /dev/null +++ b/mingling/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/src/asset/renderer.rs b/mingling/src/asset/renderer.rs new file mode 100644 index 0000000..3852b55 --- /dev/null +++ b/mingling/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/src/lib.rs b/mingling/src/lib.rs new file mode 100644 index 0000000..c172559 --- /dev/null +++ b/mingling/src/lib.rs @@ -0,0 +1,33 @@ +mod any; +pub use crate::any::*; + +pub mod error { + pub use crate::asset::chain::error::*; +} + +mod program; +pub use crate::program::*; +pub mod setup { + pub use crate::program::setup::*; +} + +#[cfg(feature = "macros")] +#[allow(unused_imports)] +pub mod macros { + pub use mingling_macros::chain; + pub use mingling_macros::chain_struct; + pub use mingling_macros::node; + 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::*; +pub use mingling_macros::Dispatcher; diff --git a/mingling/src/program.rs b/mingling/src/program.rs new file mode 100644 index 0000000..e4bc3d0 --- /dev/null +++ b/mingling/src/program.rs @@ -0,0 +1,35 @@ +use crate::asset::dispatcher::Dispatcher; +use std::env; + +pub mod setup; + +mod config; +pub use config::*; + +mod flag; +pub use flag::*; + +#[derive(Default)] +pub struct Program { + pub(crate) args: Vec<String>, + pub(crate) dispatcher: Vec<Box<dyn Dispatcher>>, + + pub stdout_setting: ProgramStdoutSetting, + pub user_context: ProgramUserContext, +} + +impl Program { + /// Creates a new Program instance, initializing args from environment. + pub fn new() -> Self { + Program { + args: env::args().collect(), + dispatcher: Vec::new(), + ..Default::default() + } + } + + /// Run the command line program + pub async fn exec(self) { + todo!() + } +} diff --git a/mingling/src/program/config.rs b/mingling/src/program/config.rs new file mode 100644 index 0000000..93507e7 --- /dev/null +++ b/mingling/src/program/config.rs @@ -0,0 +1,33 @@ +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, + } + } +} + +pub struct ProgramUserContext { + /// View help information instead of running the command + pub help: bool, + + /// Skip user confirmation step + pub confirm: bool, +} + +impl Default for ProgramUserContext { + fn default() -> Self { + ProgramUserContext { + help: false, + confirm: false, + } + } +} diff --git a/mingling/src/program/flag.rs b/mingling/src/program/flag.rs new file mode 100644 index 0000000..0178ebe --- /dev/null +++ b/mingling/src/program/flag.rs @@ -0,0 +1,115 @@ +use crate::Program; + +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 Program { + /// 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), + 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), + 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; + } + } + } +} diff --git a/mingling/src/program/setup.rs b/mingling/src/program/setup.rs new file mode 100644 index 0000000..fdf7b04 --- /dev/null +++ b/mingling/src/program/setup.rs @@ -0,0 +1,15 @@ +use crate::program::Program; + +mod basic; +pub use basic::*; + +pub trait ProgramSetup { + fn setup(program: &mut Program); +} + +impl Program { + /// Load and execute init logic + pub fn with_setup<S: ProgramSetup + 'static>(&mut self, _setup: S) { + S::setup(self); + } +} diff --git a/mingling/src/program/setup/basic.rs b/mingling/src/program/setup/basic.rs new file mode 100644 index 0000000..0ea72a6 --- /dev/null +++ b/mingling/src/program/setup/basic.rs @@ -0,0 +1,25 @@ +use crate::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 ProgramSetup for BasicProgramSetup { + fn setup(program: &mut Program) { + 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/src/renderer.rs b/mingling/src/renderer.rs new file mode 100644 index 0000000..631092b --- /dev/null +++ b/mingling/src/renderer.rs @@ -0,0 +1 @@ +pub mod render_result; diff --git a/mingling/src/renderer/render_result.rs b/mingling/src/renderer/render_result.rs new file mode 100644 index 0000000..73c38e7 --- /dev/null +++ b/mingling/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(); + } +} |
