diff options
| -rw-r--r-- | mingling/src/lib.rs | 52 | ||||
| -rw-r--r-- | mingling_core/src/any.rs | 31 | ||||
| -rw-r--r-- | mingling_core/src/any/group.rs | 2 | ||||
| -rw-r--r-- | mingling_core/src/asset.rs | 4 | ||||
| -rw-r--r-- | mingling_core/src/asset/chain.rs | 5 | ||||
| -rw-r--r-- | mingling_core/src/asset/dispatcher.rs | 20 | ||||
| -rw-r--r-- | mingling_core/src/asset/node.rs | 5 | ||||
| -rw-r--r-- | mingling_core/src/asset/renderer.rs | 4 | ||||
| -rw-r--r-- | mingling_core/src/lib.rs | 43 | ||||
| -rw-r--r-- | mingling_core/src/markers.rs | 1 | ||||
| -rw-r--r-- | mingling_core/src/program.rs | 19 | ||||
| -rw-r--r-- | mingling_core/src/program/config.rs | 2 | ||||
| -rw-r--r-- | mingling_core/src/program/exec.rs | 1 | ||||
| -rw-r--r-- | mingling_core/src/program/flag.rs | 41 | ||||
| -rw-r--r-- | mingling_core/src/renderer/render_result.rs | 41 |
15 files changed, 257 insertions, 14 deletions
diff --git a/mingling/src/lib.rs b/mingling/src/lib.rs index 86e1152..741effb 100644 --- a/mingling/src/lib.rs +++ b/mingling/src/lib.rs @@ -1,3 +1,55 @@ +//! Mingling +//! +//! # Intro +//! `Mingling` is a Rust command-line framework. Its name comes from the Chinese Pinyin for "命令", which means "Command". +//! +//! # Use +//! +//! ```rust +//! use mingling::macros::{dispatcher, gen_program, r_println, renderer}; +//! +//! #[tokio::main] +//! async fn main() { +//! let mut program = DefaultProgram::new(); +//! program.with_dispatcher(HelloCommand); +//! +//! // Execute +//! program.exec().await; +//! } +//! +//! // Define command: "<bin> hello" +//! dispatcher!("hello", HelloCommand => HelloEntry); +//! +//! // Render HelloEntry +//! #[renderer] +//! fn render_hello_world(_prev: HelloEntry) { +//! r_println!("Hello, World!") +//! } +//! +//! // Fallbacks +//! #[renderer] +//! fn fallback_dispatcher_not_found(prev: DispatcherNotFound) { +//! r_println!("Dispatcher not found for command `{}`", prev.join(", ")) +//! } +//! +//! #[renderer] +//! fn fallback_renderer_not_found(prev: RendererNotFound) { +//! r_println!("Renderer not found `{}`", *prev) +//! } +//! +//! // Collect renderers and chains to generate DefaultProgram +//! gen_program!(); +//! ``` +//! +// Output: +//! +//! ``` +//! > mycmd hello +//! Hello, World! +//! > mycmd hallo +//! Dispatcher not found for command `hallo` +//! ``` + // Re-Export Core lib pub use mingling::*; pub use mingling_core as mingling; diff --git a/mingling_core/src/any.rs b/mingling_core/src/any.rs index dac0d44..d550ec7 100644 --- a/mingling_core/src/any.rs +++ b/mingling_core/src/any.rs @@ -6,8 +6,18 @@ use serde::Serialize; use crate::Groupped; use crate::error::ChainProcessError; +#[doc(hidden)] pub mod group; +/// Any type output +/// +/// Accepts any type that implements `Send + Groupped<G>` +/// After being passed into AnyOutput, it will be converted to `Box<dyn Any + Send + 'static>` +/// +/// Note: +/// - If an enum value that does not belong to this type is incorrectly specified, it will be **unsafely** unwrapped by the scheduler +/// - Under the `general_renderer` feature, the passed value must ensure it implements `serde::Serialize` +/// - It is recommended to use the `pack!` macro from [mingling_macros](https://crates.io/crates/mingling_macros) to create types that can be converted to `AnyOutput`, which guarantees runtime safety #[derive(Debug)] pub struct AnyOutput<G> where @@ -22,6 +32,7 @@ impl<G> AnyOutput<G> where G: Display, { + /// Create an AnyOutput from a `Send + Groupped<G> + Serialize` type #[cfg(feature = "general_renderer")] pub fn new<T>(value: T) -> Self where @@ -34,6 +45,7 @@ where } } + /// Create an AnyOutput from a `Send + Groupped<G>` type #[cfg(not(feature = "general_renderer"))] pub fn new<T>(value: T) -> Self where @@ -46,6 +58,7 @@ where } } + /// Downcast the AnyOutput to a concrete type 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()) @@ -54,6 +67,7 @@ where } } + /// Check if the inner value is of type T pub fn is<T: 'static>(&self) -> bool { self.type_id == std::any::TypeId::of::<T>() } @@ -89,6 +103,12 @@ where } } +/// Chain exec result type +/// +/// Stores `Ok` and `Err` types of execution results, used to notify the scheduler what to execute next +/// - Returns `Ok((`[`AnyOutput`](./struct.AnyOutput.html)`, `[`Next::Chain`](./enum.Next.html)`))` to continue execution with this type next +/// - Returns `Ok((`[`AnyOutput`](./struct.AnyOutput.html)`, `[`Next::Renderer`](./enum.Next.html)`))` to render this type next and output to the terminal +/// - Returns `Err(`[`ChainProcessError`](./error/enum.ChainProcessError.html)`]` to terminate the program directly pub enum ChainProcess<G> where G: Display, @@ -97,6 +117,10 @@ where Err(ChainProcessError), } +/// Indicates the next step after processing +/// +/// - `Chain`: Continue execution to the next chain +/// - `Renderer`: Send output to renderer and end execution pub enum Next { Chain, Renderer, @@ -106,14 +130,17 @@ impl<G> ChainProcess<G> where G: Display, { + /// Returns true if the result is Ok (has a next step) pub fn is_next(&self) -> bool { matches!(self, Self::Ok(_)) } + /// Returns true if the result is an error pub fn is_err(&self) -> bool { matches!(self, Self::Err(_)) } + /// Returns the next step if the result is Ok pub fn next(&self) -> Option<&Next> { match self { Self::Ok((_, next)) => Some(next), @@ -121,6 +148,7 @@ where } } + /// Returns the error if the result is Err pub fn err(&self) -> Option<&ChainProcessError> { match self { Self::Ok(_) => None, @@ -128,6 +156,7 @@ where } } + /// Unwraps the result, panics if it's an error pub fn unwrap(self) -> (AnyOutput<G>, Next) { match self { Self::Ok(tuple) => tuple, @@ -135,6 +164,7 @@ where } } + /// Returns the Ok value or a provided default pub fn unwrap_or(self, default: (AnyOutput<G>, Next)) -> (AnyOutput<G>, Next) { match self { Self::Ok(tuple) => tuple, @@ -142,6 +172,7 @@ where } } + /// Returns the Ok value or computes it from the error pub fn unwrap_or_else<F>(self, f: F) -> (AnyOutput<G>, Next) where F: FnOnce(ChainProcessError) -> (AnyOutput<G>, Next), diff --git a/mingling_core/src/any/group.rs b/mingling_core/src/any/group.rs index 29d6a48..04701f2 100644 --- a/mingling_core/src/any/group.rs +++ b/mingling_core/src/any/group.rs @@ -1,3 +1,5 @@ +/// Used to mark a type with a unique enum ID, assisting dynamic dispatch pub trait Groupped<Group> { + /// Returns the specific enum value representing its ID within that enum fn member_id() -> Group; } diff --git a/mingling_core/src/asset.rs b/mingling_core/src/asset.rs index c2adf4e..81aa3a6 100644 --- a/mingling_core/src/asset.rs +++ b/mingling_core/src/asset.rs @@ -1,4 +1,8 @@ +#[doc(hidden)] pub mod chain; +#[doc(hidden)] pub mod dispatcher; +#[doc(hidden)] pub mod node; +#[doc(hidden)] pub mod renderer; diff --git a/mingling_core/src/asset/chain.rs b/mingling_core/src/asset/chain.rs index 9f62228..70eda8c 100644 --- a/mingling_core/src/asset/chain.rs +++ b/mingling_core/src/asset/chain.rs @@ -2,12 +2,17 @@ use std::fmt::Display; use crate::ChainProcess; +#[doc(hidden)] pub mod error; +/// Takes over a type (G: Previous) and converts it to another [AnyOutput](./struct.AnyOutput.html) pub trait Chain<G> where G: Display, { + /// The previous type in the chain type Previous; + + /// Process the previous value and return a future that resolves to a [`ChainProcess<G>`](./enum.ChainProcess.html) fn proc(p: Self::Previous) -> impl Future<Output = ChainProcess<G>> + Send; } diff --git a/mingling_core/src/asset/dispatcher.rs b/mingling_core/src/asset/dispatcher.rs index 0863f16..86dfe7c 100644 --- a/mingling_core/src/asset/dispatcher.rs +++ b/mingling_core/src/asset/dispatcher.rs @@ -2,12 +2,22 @@ use std::fmt::Display; use crate::{ChainProcess, Program, asset::node::Node}; +/// Dispatches user input commands to specific [ChainProcess](./enum.ChainProcess.html) +/// +/// Note: If you are using [mingling_macros](https://crates.io/crates/mingling_macros), +/// you can use the `dispatcher!("node.subnode", CommandType => Entry)` macro to declare a `Dispatcher` pub trait Dispatcher<G> where G: Display, { + /// Returns a command node for matching user input fn node(&self) -> Node; + + /// Returns a [ChainProcess](./enum.ChainProcess.html) based on user input arguments, + /// to be sent to the specific invocation fn begin(&self, args: Vec<String>) -> ChainProcess<G>; + + /// Clones the current dispatcher for implementing the `Clone` trait fn clone_dispatcher(&self) -> Box<dyn Dispatcher<G>>; } @@ -39,6 +49,16 @@ impl<C: crate::program::ProgramCollect, G: Display> Program<C, G> { } } +/// A collection of dispatchers. +/// +/// This struct holds a vector of boxed `Dispatcher` trait objects, +/// allowing multiple dispatchers to be grouped together and passed +/// to the program via `Program::with_dispatchers`. +/// A collection of dispatchers. +/// +/// This struct holds a vector of boxed `Dispatcher` trait objects, +/// allowing multiple dispatchers to be grouped together and passed +/// to the program via `Program::with_dispatchers`. pub struct Dispatchers<G> { dispatcher: Vec<Box<dyn Dispatcher<G> + 'static>>, } diff --git a/mingling_core/src/asset/node.rs b/mingling_core/src/asset/node.rs index c8b7600..035d227 100644 --- a/mingling_core/src/asset/node.rs +++ b/mingling_core/src/asset/node.rs @@ -1,11 +1,16 @@ use just_fmt::kebab_case; +/// Represents a command node, used to match user-input command paths. +/// +/// The node consists of multiple parts, each separated by a dot (`.`), and automatically converted to kebab-case. +/// For example, the input string `"node.subnode"` will be converted to a node representation of `["node", "subnode"]`. #[derive(Debug, Default)] pub struct Node { node: Vec<String>, } impl Node { + /// Append a new part to the node path. pub fn join(self, node: impl Into<String>) -> Node { let mut new_node = self.node; new_node.push(node.into()); diff --git a/mingling_core/src/asset/renderer.rs b/mingling_core/src/asset/renderer.rs index 3852b55..de417a2 100644 --- a/mingling_core/src/asset/renderer.rs +++ b/mingling_core/src/asset/renderer.rs @@ -1,6 +1,10 @@ use crate::RenderResult; +/// Takes over a type (Self::Previous) and converts it to a [`RenderResult`](./struct.RenderResult.html) pub trait Renderer { + /// The previous type in the chain type Previous; + + /// Process the previous value and write the result into the provided [`RenderResult`](./struct.RenderResult.html) fn render(p: Self::Previous, r: &mut RenderResult); } diff --git a/mingling_core/src/lib.rs b/mingling_core/src/lib.rs index 3d2be11..bcad91d 100644 --- a/mingling_core/src/lib.rs +++ b/mingling_core/src/lib.rs @@ -1,28 +1,43 @@ +//! Mingling Core +//! +//! # Intro +//! This crate is the core implementation of `mingling`, containing the complete logic for command dispatching, execution, and rendering. +//! +//! # Note +//! It is not recommended to use [mingling_core](https://crates.io/crates/mingling_core) directly, as this will lose the code generation functionality of [mingling_macros](https://crates.io/crates/mingling_macros). +//! +//! Recommended to import [mingling](https://crates.io/crates/mingling) to use its features. + mod any; +mod asset; +mod markers; +mod program; +mod renderer; + pub use crate::any::group::*; pub use crate::any::*; -mod markers; -pub mod marker { - pub use crate::markers::group_process::*; -} +pub use crate::asset::chain::*; +pub use crate::asset::dispatcher::*; +pub use crate::asset::node::*; +pub use crate::asset::renderer::*; +/// All error types of `Mingling` pub mod error { pub use crate::asset::chain::error::*; pub use crate::exec::error::*; } -mod program; pub use crate::program::*; + +pub use crate::renderer::render_result::*; + +/// All marker types of `Mingling` that serve no practical purpose +pub mod marker { + pub use crate::markers::group_process::*; +} + +/// `Mingling`'s Program initialization system pub mod setup { pub use crate::program::setup::*; } - -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/markers.rs b/mingling_core/src/markers.rs index e95fb8a..151a0d4 100644 --- a/mingling_core/src/markers.rs +++ b/mingling_core/src/markers.rs @@ -1 +1,2 @@ +#[doc(hidden)] pub mod group_process; diff --git a/mingling_core/src/program.rs b/mingling_core/src/program.rs index ffba17e..5d81234 100644 --- a/mingling_core/src/program.rs +++ b/mingling_core/src/program.rs @@ -4,7 +4,9 @@ use crate::{ }; use std::{env, fmt::Display, pin::Pin}; +#[doc(hidden)] pub mod exec; +#[doc(hidden)] pub mod setup; mod config; @@ -14,6 +16,7 @@ mod flag; pub use flag::*; use tokio::io::AsyncWriteExt; +/// Program, used to define the behavior of the entire command-line program #[derive(Default)] pub struct Program<C, G> where @@ -86,15 +89,31 @@ where } } +/// Collected program context +/// +/// Note: It is recommended to use the `gen_program!()` macro from [mingling_macros](https://crates.io/crates/mingling_macros) to automatically create this type pub trait ProgramCollect { + /// Enum type representing internal IDs for the program type Enum: Display; + + /// Build an [AnyOutput](./struct.AnyOutput.html) to indicate that a renderer was not found fn build_renderer_not_found(member_id: Self::Enum) -> AnyOutput<Self::Enum>; + + /// Build an [AnyOutput](./struct.AnyOutput.html) to indicate that a dispatcher was not found fn build_dispatcher_not_found(args: Vec<String>) -> AnyOutput<Self::Enum>; + + /// Render the input [AnyOutput](./struct.AnyOutput.html) fn render(any: AnyOutput<Self::Enum>, r: &mut RenderResult); + + /// Find a matching chain to continue execution based on the input [AnyOutput](./struct.AnyOutput.html), returning a new [AnyOutput](./struct.AnyOutput.html) fn do_chain( any: AnyOutput<Self::Enum>, ) -> Pin<Box<dyn Future<Output = ChainProcess<Self::Enum>> + Send>>; + + /// Whether the program has a renderer that can handle the current [AnyOutput](./struct.AnyOutput.html) fn has_renderer(any: &AnyOutput<Self::Enum>) -> bool; + + /// Whether the program has a chain that can handle the current [AnyOutput](./struct.AnyOutput.html) fn has_chain(any: &AnyOutput<Self::Enum>) -> bool; } diff --git a/mingling_core/src/program/config.rs b/mingling_core/src/program/config.rs index 386b112..6ad0a38 100644 --- a/mingling_core/src/program/config.rs +++ b/mingling_core/src/program/config.rs @@ -1,3 +1,4 @@ +/// Program stdout settings #[derive(Debug, Clone)] pub struct ProgramStdoutSetting { /// Output error messages @@ -16,6 +17,7 @@ impl Default for ProgramStdoutSetting { } } +/// Program stdout settings #[derive(Debug, Clone, Default)] pub struct ProgramUserContext { /// View help information instead of running the command diff --git a/mingling_core/src/program/exec.rs b/mingling_core/src/program/exec.rs index 7de3723..9c80c4a 100644 --- a/mingling_core/src/program/exec.rs +++ b/mingling_core/src/program/exec.rs @@ -7,6 +7,7 @@ use crate::{ error::ProgramInternalExecuteError, }; +#[doc(hidden)] pub mod error; pub async fn exec<C, G>(program: Program<C, G>) -> Result<RenderResult, ProgramInternalExecuteError> diff --git a/mingling_core/src/program/flag.rs b/mingling_core/src/program/flag.rs index 84fae01..a520495 100644 --- a/mingling_core/src/program/flag.rs +++ b/mingling_core/src/program/flag.rs @@ -2,6 +2,45 @@ use std::fmt::Display; use crate::{Program, ProgramCollect}; +/// A wrapper for a collection of static string slices representing command-line flags or arguments. +/// +/// `Flag` is used to store one or more static string slices (e.g., `["-h", "--help"]`) that +/// represent command-line flags or arguments. It provides conversions from various input types +/// (like a single `&'static str`, a slice, or an array) and dereferences to a slice of strings +/// for easy iteration and access. +/// +/// # Examples +/// +/// ``` +/// use mingling_core::Flag; +/// +/// // Create a Flag from a single string slice +/// let flag1 = Flag::from("-h"); +/// assert_eq!(flag1.as_ref(), &["-h"]); +/// +/// // Create a Flag from a slice of string slices +/// let flag2 = Flag::from(&["-h", "--help"][..]); +/// assert_eq!(flag2.as_ref(), &["-h", "--help"]); +/// +/// // Create a Flag from an array +/// let flag3 = Flag::from(["-v", "--verbose"]); +/// assert_eq!(flag3.as_ref(), &["-v", "--verbose"]); +/// +/// // Create a Flag from a reference to an array +/// let arr = &["-f", "--file"]; +/// let flag4 = Flag::from(arr); +/// assert_eq!(flag4.as_ref(), &["-f", "--file"]); +/// +/// // Create an empty Flag from unit type +/// let flag5 = Flag::from(()); +/// assert_eq!(flag5.as_ref(), &[] as &[&str]); +/// +/// // Dereference to slice for iteration +/// let flag = Flag::from(["-a", "-b"]); +/// for arg in flag.iter() { +/// println!("Flag: {}", arg); +/// } +/// ``` pub struct Flag { vec: Vec<&'static str>, } @@ -57,6 +96,7 @@ impl std::ops::Deref for Flag { } #[macro_export] +#[doc(hidden)] macro_rules! special_flag { ($args:expr, $flag:expr) => {{ let flag = $flag; @@ -67,6 +107,7 @@ macro_rules! special_flag { } #[macro_export] +#[doc(hidden)] macro_rules! special_argument { ($args:expr, $flag:expr) => {{ let flag = $flag; diff --git a/mingling_core/src/renderer/render_result.rs b/mingling_core/src/renderer/render_result.rs index 73c38e7..d9da7b7 100644 --- a/mingling_core/src/renderer/render_result.rs +++ b/mingling_core/src/renderer/render_result.rs @@ -3,6 +3,7 @@ use std::{ ops::Deref, }; +/// Render result, containing the rendered text content. #[derive(Default, Debug, PartialEq)] pub struct RenderResult { render_text: String, @@ -23,15 +24,55 @@ impl Deref for RenderResult { } impl RenderResult { + /// Appends the given text to the rendered content. + /// + /// # Examples + /// + /// ``` + /// use mingling_core::RenderResult; + /// use std::ops::Deref; + /// + /// let mut result = RenderResult::default(); + /// result.print("Hello"); + /// result.print(", world!"); + /// assert_eq!(result.deref(), "Hello, world!"); + /// ``` pub fn print(&mut self, text: &str) { self.render_text.push_str(text); } + /// Appends the given text followed by a newline to the rendered content. + /// + /// # Examples + /// + /// ``` + /// use mingling_core::RenderResult; + /// use std::ops::Deref; + /// + /// let mut result = RenderResult::default(); + /// result.println("First line"); + /// result.println("Second line"); + /// assert_eq!(result.deref(), "First line\nSecond line\n"); + /// ``` pub fn println(&mut self, text: &str) { self.render_text.push_str(text); self.render_text.push('\n'); } + /// Clears all rendered content. + /// + /// # Examples + /// + /// ``` + /// use mingling_core::RenderResult; + /// use std::ops::Deref; + /// + /// let mut result = RenderResult::default(); + /// result.print("Some content"); + /// assert!(!result.is_empty()); + /// result.clear(); + /// assert!(result.is_empty()); + /// ``` pub fn clear(&mut self) { self.render_text.clear(); } |
