diff options
| author | 魏曹先生 <1992414357@qq.com> | 2026-03-28 00:47:46 +0800 |
|---|---|---|
| committer | 魏曹先生 <1992414357@qq.com> | 2026-03-28 00:47:46 +0800 |
| commit | 7ce68cd11516bd7cf037ecea99a92aee7c31b2c3 (patch) | |
| tree | a3923ad41c91aa21fe169fd6b4b1bf8898a82589 /mingling | |
Add initial Mingling framework codebase
Diffstat (limited to 'mingling')
| -rw-r--r-- | mingling/Cargo.lock | 114 | ||||
| -rw-r--r-- | mingling/Cargo.toml | 17 | ||||
| -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 |
17 files changed, 602 insertions, 0 deletions
diff --git a/mingling/Cargo.lock b/mingling/Cargo.lock new file mode 100644 index 0000000..34edcd9 --- /dev/null +++ b/mingling/Cargo.lock @@ -0,0 +1,114 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "just_fmt" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5454cda0d57db59778608d7a47bff5b16c6705598265869fb052b657f66cf05e" + +[[package]] +name = "mingling" +version = "0.1.0" +dependencies = [ + "just_fmt", + "mingling_macros", + "serde", + "thiserror", +] + +[[package]] +name = "mingling_macros" +version = "0.1.0" +dependencies = [ + "just_fmt", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" diff --git a/mingling/Cargo.toml b/mingling/Cargo.toml new file mode 100644 index 0000000..5cd681a --- /dev/null +++ b/mingling/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "mingling" +version = "0.1.0" +edition = "2024" +authors = ["Weicao-CatilGrass"] +license-file = "LICENSE-MIT" + +[features] +default = ["macros"] +macros = ["mingling_macros"] +serde_renderer = ["dep:serde", "mingling_macros/serde"] + +[dependencies] +mingling_macros = { path = "../mingling_macros", optional = true } +just_fmt = "0.1.2" +serde = { version = "1.0", features = ["derive"], optional = true } +thiserror = "2" 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(); + } +} |
