summaryrefslogtreecommitdiff
path: root/mingling
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-03-28 00:47:46 +0800
committer魏曹先生 <1992414357@qq.com>2026-03-28 00:47:46 +0800
commit7ce68cd11516bd7cf037ecea99a92aee7c31b2c3 (patch)
treea3923ad41c91aa21fe169fd6b4b1bf8898a82589 /mingling
Add initial Mingling framework codebase
Diffstat (limited to 'mingling')
-rw-r--r--mingling/Cargo.lock114
-rw-r--r--mingling/Cargo.toml17
-rw-r--r--mingling/src/any.rs72
-rw-r--r--mingling/src/asset.rs4
-rw-r--r--mingling/src/asset/chain.rs8
-rw-r--r--mingling/src/asset/chain/error.rs13
-rw-r--r--mingling/src/asset/dispatcher.rs19
-rw-r--r--mingling/src/asset/node.rs54
-rw-r--r--mingling/src/asset/renderer.rs6
-rw-r--r--mingling/src/lib.rs33
-rw-r--r--mingling/src/program.rs35
-rw-r--r--mingling/src/program/config.rs33
-rw-r--r--mingling/src/program/flag.rs115
-rw-r--r--mingling/src/program/setup.rs15
-rw-r--r--mingling/src/program/setup/basic.rs25
-rw-r--r--mingling/src/renderer.rs1
-rw-r--r--mingling/src/renderer/render_result.rs38
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();
+ }
+}