aboutsummaryrefslogtreecommitdiff
path: root/README.md
diff options
context:
space:
mode:
Diffstat (limited to 'README.md')
-rw-r--r--README.md893
1 files changed, 74 insertions, 819 deletions
diff --git a/README.md b/README.md
index 50c66c6..e24fb7e 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
<p align="center">
<a href="https://github.com/mingling-rs/mingling">
- <img alt="Mingling" src="https://github.com/mingling-rs/mingling/raw/main/docs/res/icon2.png" width="50%">
+ <img alt="Mingling" src="https://github.com/mingling-rs/mingling/raw/main/docs/res/icon2.png" width="30%">
</a>
</p>
<h1 align="center">Mìng Lìng - 命令</h1>
@@ -21,852 +21,100 @@
<img src="https://img.shields.io/github/actions/workflow/status/mingling-rs/mingling/ci.yml">
</p>
-> [!WARNING]
->
-> **Note**: Mingling is still under active development, and its API may change. Feel free to try it out and give us feedback!
-> **Hint**: This note will be removed in version `0.5.0`
+## What is Mingling?
-<h1 align="center">
- What is Mingling?
-</h1>
+[`Mingling`](https://github.com/mingling-rs/mingling) is a **state-driven and data-driven** CLI workflow orchestration framework built in Rust.
-[`Mingling`](https://github.com/mingling-rs/mingling) is a **proc-macro and type-system based** Rust CLI framework, suitable for developing complex command-line programs with numerous subcommands.
+💡 Its name comes from the Chinese pinyin **"Mìng Lìng"**, which means **"command"**.
-> Its name comes from the Chinese Pinyin **"Mìng Lìng"**, meaning **"Command"**.
+## WARNING
-### Mingling's Core Capabilities
+Mingling is currently usable at a basic level, but it is still under active development, so many APIs are not yet mature. Any changes to the public API will be documented in detail in the [Changelog](https://github.com/mingling-rs/mingling/blob/main/CHANGELOG.md).
-1. **Separation of Concerns, Clear Logic**: Mingling decouples logic by responsibility, helping you organize your CLI program more clearly.
- See example: [Example](https://github.com/mingling-rs/mingling/blob/main/examples/example-basic/src/main.rs)
-2. **"All Logic is Functions"**: Execution logic, rendering logic, completion logic, help logic — everything is a function. Just attach the corresponding attribute macro to bind them to your program.
-3. **Fully Dynamic Completion System**: With the `comp` feature, you can flexibly implement dynamic completion logic for any subcommand.
- See examples: [Example](https://github.com/mingling-rs/mingling/blob/main/examples/example-completion/src/main.rs)
-4. **Lightning-Fast Subcommand Dispatch**: With the `dispatch_tree` feature, Mingling hardens the subcommand structure into a prefix tree at **compile time**, enabling blazing-fast subcommand lookup.
- See examples: [Example](https://github.com/mingling-rs/mingling/blob/main/examples/example-dispatch-tree/src/main.rs)
-5. **Lightweight Dependencies, On-Demand Importing**: Minimal core dependencies keep builds fast; enhanced features are imported on demand through fine-grained feature flags.
-6. **Structured Output**: Enabling the `structural_renderer` feature adds support for flags like `--json` and `--yaml`, providing structured output capabilities.
- See examples: [Example](https://github.com/mingling-rs/mingling/blob/main/examples/example-structural-renderer/src/main.rs)
+Additionally, the project is currently developed by me alone ([Weicao-CatilGrass](https://github.com/Weicao-CatilGrass)). If you are interested in this project, I warmly welcome your [contributions](https://github.com/mingling-rs/mingling/blob/main/CONTRIBUTING.md). You can reach me directly via [Github Issue](https://github.com/mingling-rs/mingling/issues).
----
+## About Mingling's Design
-**💡 To learn more, check out the following links:**
-
-- 📖 [Mainpage](https://mingling-rs.github.io/mingling/)
-- 📖 [Examples](https://mingling-rs.github.io/mingling/docs/examples.html)
-- 📖 [docs.rs](https://docs.rs/mingling/latest/mingling/)
-- 📖 [Helpdoc](https://mingling-rs.github.io/mingling/docs/doc.html#/) Or [帮助文档](https://mingling-rs.github.io/mingling/docs/_zh_CN/index.html#/)
-- 🔍 [Devdoc](https://mingling-rs.github.io/mingling/docs/dev/)
-
-<h1 align="center">
- Getting Started
-</h1>
-
-Add Mingling to your `Cargo.toml`:
-
-```toml
-[dependencies.mingling]
-version = "0.3.0"
-features = []
-```
-
-Or use the github version
-
-```toml
-[dependencies.mingling]
-git = "https://github.com/mingling-rs/mingling.git"
-tag = "unreleased"
-features = []
-```
-
-Or use the [template project](https://github.com/mingling-rs/mingling-template):
-
-```bash
-cargo generate --git mingling-rs/mingling-template
-```
-
-<h1 align="center">
- Writing with Mingling
-</h1>
-
-### The Big Picture
-
-Mingling organizes your CLI program into three distinct phases:
-
-```
-User Input → [Dispatcher] → Entry → [Chain(s)] → Result → [Renderer] → Output
-```
-
-**Step1: Input** — The user's raw arguments flow in.
-**Step2: Dispatch** — A **Dispatcher** picks them up and wraps them into an **Entry** type.
-**Step3: Chain** — The entry is handed off to a **Chain** function, which processes it.
-**Step4: Render** — A **Renderer** takes that result and writes it to the terminal.
-
-> [!NOTE]
-> A Chain can produce a **State** type to be passed to the next Chain for further processing,
->
-> or it can produce a **Result** type to be handed off to a Renderer.
-
-Everything in this pipeline is a **plain Rust function** with an attribute macro on top.
-
-You never need to manually implement traits or construct boilerplate.
-
----
-
-### 1. Defining Commands — `dispatcher!`
-
-The entry point for every subcommand is the `dispatcher!` macro. It generates two structs for you: a **Dispatcher** (used to register the command with the program) and an **Entry** (a wrapper around `Vec<String>` that holds the raw arguments).
-
-```rust
-use mingling::prelude::*;
-
-// "command.name" Dispatcher EntryType
-// │ │ │
-dispatcher!("greet", CMDGreet => EntryGreet);
-
-// Nested subcommand: `remote add`
-dispatcher!("remote.add", CMDRemoteAdd => EntryRemoteAdd);
-```
-
-Then in `main()`, register the dispatcher with the program:
-
-```rust
-dispatcher!("greet", CMDGreet => EntryGreet);
-
-fn main() {
- let mut program = ThisProgram::new();
- program.with_dispatcher(CMDGreet);
- program.exec_and_exit();
-}
-```
-
-Mingling also supports an abbreviated form (with the `extra_macros` feature):
-
-```rust
-// Features: ["extra_macros"]
-
-// Auto-generates CMDGreet / EntryGreet from "greet"
-dispatcher!("greet");
-```
-
----
-
-### 2. The Chain — "#[chain]" — Where Logic Lives
-
-The `#[chain]` attribute turns a plain function into an execution step. Think of it as "the logic that transforms one typed value into another."
-
-```rust
-dispatcher!("greet", CMDGreet => EntryGreet);
-
-pack!(ResultGreeting = String);
-
-#[chain]
-fn handle_greet(args: EntryGreet) -> Next {
- let greeting = args
- .inner
- .first()
- .cloned()
- .unwrap_or_else(|| "World".to_string());
- ResultGreeting::new(greeting)
-}
-```
-
-Key points:
-
-- The return type is `Next` — a type alias for `ChainProcess<ThisProgram>`.
-- You chain results by calling `.to_chain()` on any `pack!`-ed type.
-- You can have **multiple chain functions** for the same command, each transforming the data further.
-- With the `async` feature, chain functions can be `async fn`.
-
----
-
-### 3. The Renderer — "#[renderer]" — How Output Works
-
-The `#[renderer]` attribute turns a function into an output handler. It receives the final result of a chain and returns a `RenderResult`.
-
-```rust
-use mingling::macros::pack;
-use mingling::prelude::*;
-use std::io::Write;
-
-pack!(ResultGreeting = String);
-
-#[renderer]
-fn render_greeting(greeting: ResultGreeting) -> RenderResult {
- let mut result = RenderResult::new();
- writeln!(result, "Hello, {}!", *greeting).ok();
- result
-}
-```
-
-Inside a renderer, create a `RenderResult`, write to it using `write!` / `writeln!` (from [`std::io::Write`](https://doc.rust-lang.org/std/io/trait.Write.html)), and return it. The output is captured in the buffer and flushed by the framework at the end of the pipeline.
-
-You can write renderers for **any type** in your program, including error types:
-
-```rust
-use mingling::prelude::*;
-use std::io::Write;
-
-#[renderer]
-fn render_dispatcher_not_found(err: ErrorDispatcherNotFound) -> RenderResult {
- let mut result = RenderResult::new();
- writeln!(result, "Command not found: [{}]", err.join(" ")).ok();
- result
-}
-```
-
----
-
-### 4. Parsing Arguments — The Picker
-
-Mingling provides a **Picker** for zero-cost argument extraction. You use `pick()` or `pick_or()` on an entry to extract typed values, then `unpack()` to get the final tuple.
-
-```rust
-// Features: ["parser"]
-
-use mingling::parser::Picker;
-
-dispatcher!("greet", CMDGreet => EntryGreet);
-pack!(ResultGreeting = String);
-
-#[chain]
-fn handle_greet(args: EntryGreet) -> Next {
- let (name, count) = Picker::new(args.inner)
- .pick::<String>(()) // positional: first string
- .pick_or::<u8>(["-r", "--repeat"], 1) // optional flag with default
- .unpack();
- ResultGreeting::new(format!("{} x{}", name, count))
-}
-```
-
-With the `parser` feature, the `AsPicker` trait provides a shorthand directly on entries:
-
-```rust
-// Features: ["parser"]
-
-dispatcher!("greet", CMDGreet => EntryGreet);
-pack!(ResultGreeting = String);
-
-#[chain]
-fn handle(args: EntryGreet) -> Next {
- let (name, count) = args
- .pick::<Option<String>>(())
- .pick_or::<u8>(["-r", "--repeat"], 1)
- .unpack();
- ResultGreeting::new(format!("{} x{}", name.unwrap_or_default(), count))
-}
-```
-
-For enums, derive `EnumTag` and implement `PickableEnum` to parse enum variants from strings:
+Mingling abstracts the behavior of a program's lifecycle into three phases: **Dispatch**, **Execution**, and **Rendering**. Each phase is connected by types — the output of the current phase becomes the input of the next phase. For example:
```rust
-// Features: ["parser", "extra_macros"]
-
-use mingling::{EnumTag, Groupped};
-use mingling::parser::PickableEnum;
-
-dispatcher!("lang.select", CMDLang => EntryLang);
-
-#[derive(Debug, Default, EnumTag, Groupped)]
-pub enum Language {
- #[default]
- Rust,
- #[enum_rename("C++")]
- CPlusPlus,
-}
-
-impl PickableEnum for Language {}
-
-#[chain]
-fn handle(args: EntryLang) -> Next {
- let lang: Language = args.pick(()).unpack();
- lang
-}
-```
-
----
-
-### 5. The Help System — "#[help]"
-
-Help is just another attribute macro. When the user passes `--help` or `-h`, the program skips the normal chain/render pipeline and routes directly to your `#[help]` function.
-
-Enable it by adding `BasicProgramSetup`:
-
-```rust
-use mingling::{macros::help, prelude::*, setup::BasicProgramSetup};
-use std::io::Write;
-
-dispatcher!("greet", CMDGreet => EntryGreet);
-
-#[help]
-fn help_greet(_prev: EntryGreet) -> RenderResult {
- let mut result = RenderResult::new();
- writeln!(result, "Usage: greet <NAME>").ok();
- writeln!(result, "Greets the user with the given name.").ok();
- result
-}
-
-fn main() {
- let mut program = ThisProgram::new();
- program.with_setup(BasicProgramSetup); // enables --help / -h
- program.with_dispatcher(CMDGreet);
- program.exec_and_exit();
-}
-
-gen_program!();
-```
-
-The flow is:
-
-- User types `greet --help`
-- `BasicProgramSetup` sets `program.user_context.help = true`
-- The dispatcher sees this flag and routes to the `#[help]` function instead of the `#[chain]`
-
----
-
-### 6. Completion — "#[completion]" — Dynamic Shell Completions
-
-With the `comp` feature, Mingling provides a fully dynamic completion system. You write a function that returns `Suggest` based on the current shell context, and Mingling generates the completion scripts for bash, zsh, fish, and pwsh.
-
-```rust
-// Features: ["comp", "extra_macros"]
-
-use mingling::{macros::suggest, ShellContext, Suggest};
-
-dispatcher!("greet", CMDGreet => EntryGreet);
-pack!(ResultName = (u8, String));
-
-#[completion(EntryGreet)]
-fn complete_greet(ctx: &ShellContext) -> Suggest {
- // Suggest positional arguments
- if ctx.previous_word == "greet" {
- return suggest! {
- "Alice": "Likes to receive messages",
- "Bob": "Likes to pass messages",
- "World"
- };
- }
-
- // Suggest flag arguments
- if ctx.typing_argument() {
- return suggest! {
- "-r": "Number of repetitions",
- "--repeat": "Number of repetitions",
- }
- .strip_typed_argument(ctx);
- }
-
- suggest!() // no suggestions
-}
-```
-
-You also need to register the built-in completion dispatcher:
-
-```rust
-// Features: ["comp"]
-
-fn main() {
- let mut program = ThisProgram::new();
- program.with_dispatcher(crate::CMDCompletion);
- program.exec_and_exit();
-}
-```
-
-In your `build.rs`, generate the shell scripts:
-
-```rust
-// BUILD TIME
-// Features: ["comp", "builds"]
-mingling::build::build_comp_scripts(env!("CARGO_PKG_NAME")).unwrap();
-```
-
-For enum-based completions, use `suggest_enum!`:
-
-```rust
-// Features: ["comp", "extra_macros"]
-
-use mingling::{ShellContext, Suggest};
-use mingling::macros::suggest_enum;
-use mingling::EnumTag;
-
-dispatcher!("lang.select", CMDLang => EntryLang);
-
-#[derive(EnumTag)]
-pub enum ProgrammingLanguages {
- Rust,
- Python,
- JavaScript,
-}
-
-#[completion(EntryLang)]
-fn complete_lang(_: &ShellContext) -> Suggest {
- suggest_enum!(ProgrammingLanguages)
-}
-```
-
----
-
-### 7. Error Handling
-
-Mingling doesn't use `?` operator propagation. Instead, errors are just **alternative results** that flow through the same chain/render pipeline. Create error types with `pack!` and route to them with `.to_render()`:
-
-```rust
-use mingling::macros::pack;
-use mingling::prelude::*;
-use std::io::Write;
-
-dispatcher!("hello", CMDHello => EntryHello);
-pack!(ResultName = String);
-pack!(ErrorNoNameProvided = ());
-pack!(ErrorNameTooLong = u16);
-
-#[chain]
-fn handle(args: EntryHello) -> Next {
- let Some(name) = args.inner.first().cloned() else {
- return ErrorNoNameProvided::default().to_render(); // ← early return to error renderer
- };
-
- if name.len() > 10 {
- return ErrorNameTooLong::new(name.len() as u16).to_render();
- }
-
- ResultName::new(name).to_render() // ← success path
-}
-
-#[renderer]
-fn render_no_name(_: ErrorNoNameProvided) -> RenderResult {
- let mut result = RenderResult::new();
- writeln!(result, "No name provided").ok();
- result
-}
-
-#[renderer]
-fn render_too_long(len: ErrorNameTooLong) -> RenderResult {
- let mut result = RenderResult::new();
- writeln!(result, "Name too long: {} > 10", *len).ok();
- result
-}
-```
-
-Two built-in fallback types are always available:
-
-- `ErrorDispatcherNotFound` — rendered when no dispatcher matches the input
-- `ErrorRendererNotFound` — rendered when no renderer is found for a result type
-
----
-
-### 8. Resource Injection
-
-Chain and renderer functions can accept **additional parameters** for the program's global state. Resources are singleton values registered with `program.with_resource(...)`.
-
-```rust
-// Features: ["parser", "extra_macros"]
-
-use std::path::PathBuf;
-
dispatcher!("current", CMDCurrent => EntryCurrent);
-dispatcher!("cd", CMDCd => EntryCd);
+pack!(StateNext = ());
-#[derive(Default, Clone)]
-struct ResCurrentDir {
- current_dir: PathBuf,
-}
-
-fn main() {
- let mut program = ThisProgram::new();
- program.with_resource(ResCurrentDir {
- current_dir: std::env::current_dir().unwrap(),
- });
- program.with_dispatcher(CMDCurrent);
- program.with_dispatcher(CMDCd);
- program.exec_and_exit();
-}
-
-// Read-only access (shared reference):
#[chain]
-fn show_current(_prev: EntryCurrent, current_dir: &ResCurrentDir) -> Next {
- println!("Current: {}", current_dir.current_dir.display());
- empty_result!()
-}
-
-// Mutable access:
-#[chain]
-fn change_dir(prev: EntryCd, current_dir: &mut ResCurrentDir) -> Next {
- let path: String = prev.pick(()).unpack();
- current_dir.current_dir = current_dir.current_dir.join(path);
- empty_result!()
+fn handle_current(_: EntryCurrent) -> StateNext {
+ // 1. The first phase outputs the StateNext value
+ StateNext::default() // ^^^^^^^^^
+} // |
+ // |
+ // 2. The second phase takes StateNext as input
+#[chain] // |
+fn handle_state_next(_: StateNext) {
+ todo!()
}
```
-Resources can also be injected into `#[renderer]`:
+See? `handle_current` and `handle_state_next` have no direct connection!
-```rust
-use mingling::prelude::*;
-use std::io::Write;
-
-dispatcher!("current", CMDCurrent => EntryCurrent);
-
-#[derive(Default, Clone)]
-struct ResCurrentDir {
- current_dir: std::path::PathBuf,
-}
-
-#[renderer]
-fn render_current(_: EntryCurrent, current_dir: &ResCurrentDir) -> RenderResult {
- let mut result = RenderResult::new();
- writeln!(result, "Current directory: {}", current_dir.current_dir.display()).ok();
- result
-}
-```
-
----
-
-### 9. Dispatch Tree — Compile-Time Command Trie
-
-As your program grows to dozens or hundreds of subcommands, linear dispatcher lookup becomes slow. Enable the `dispatch_tree` feature to convert the command structure into a **prefix tree (Trie)** at compile time.
-
-```rust
-// Features: ["dispatch_tree"]
-
-dispatcher!("cmd1", CMD1 => Entry1);
-dispatcher!("cmd2.sub1", CMD2Sub1 => Entry2Sub1);
-dispatcher!("cmd2.sub2", CMD2Sub2 => Entry2Sub2);
-dispatcher!("cmd3.sub1.leaf1", CMD3Sub1Leaf1 => Entry3Sub1Leaf1);
-dispatcher!("cmd3.sub1.leaf2", CMD3Sub1Leaf2 => Entry3Sub1Leaf2);
-// ... dozens more
-
-fn main() {
- let program = ThisProgram::new();
- // No more with_dispatcher calls — it's all compile-time!
- program.exec_and_exit();
-}
-```
-
-With `dispatch_tree` enabled:
-
-- Dispatchers are auto-collected at compile time
-- `Program` no longer stores a dispatcher list
-- `program.with_dispatcher(...)` is not compiled
-- Lookup is **O(n)** where _n_ is input length, not number of commands
-
----
-
-### 10. Clap Binding — Using Clap's Parser
-
-If you prefer clap's powerful argument parsing, use `#[dispatcher_clap]`. It generates a dispatcher from a `clap::Parser` struct.
-
-```rust
-// Features: ["clap"]
-// Dependencies:
-// clap = "4"
-
-use mingling::macros::dispatcher_clap;
-use mingling::prelude::*;
-use std::io::Write;
-
-#[derive(Default, clap::Parser, Groupped)]
-#[dispatcher_clap(
- "greet", CMDGreet,
- help = true, // auto-generate #[help] from clap
- error = ErrorGreetParsed, // capture parse errors as a renderable type
-)]
-pub struct EntryGreet {
- #[clap(default_value = "World")]
- name: String,
-
- #[arg(short, long, default_value_t = 1)]
- repeat: i32,
-}
-
-#[renderer]
-fn render_greet(greet: EntryGreet) -> RenderResult {
- let mut result = RenderResult::new();
- write!(result, "Hello, ").ok();
- for _ in 0..greet.repeat { write!(result, "{}", greet.name).ok(); }
- writeln!(result, "!").ok();
- result
-}
-
-#[renderer]
-fn render_parse_error(err: ErrorGreetParsed) -> RenderResult {
- let mut result = RenderResult::new();
- writeln!(result, "{}", *err).ok();
- result
-}
-```
-
-You can control how clap help is displayed:
-
-```rust
-// Features: ["clap"]
-
-dispatcher!("greet", CMDGreet => EntryGreet);
-
-fn main() {
- let mut program = ThisProgram::new();
- program.with_dispatcher(CMDGreet);
- program.stdout_setting.clap_help_print_behaviour =
- mingling::ClapHelpPrintBehaviour::WriteToRenderResult;
- // or: PrintDirectly — writes clap help straight to stdout
- program.exec_and_exit();
-}
-```
-
----
-
-### 11. REPL Mode
-
-With the `repl` feature, turn your CLI into an interactive shell with one method call:
-
-```rust
-// Features: ["repl"]
-
-fn main() {
- ThisProgram::new().exec_repl();
-}
-```
+They are bridged by `StateNext` and automatically linked by the framework.
-Mingling provides built-in REPL setups:
+You can use this approach to separate computation from result rendering, like this:
```rust
-// Features: ["repl", "extra_macros"]
+// Features: ["picker"]
+dispatcher!("calc", CMDCalculate => EntryCalculate);
+pack!(StateSumNumbers = Vec<i32>);
+pack!(ResultNumber = i32);
-use mingling::{
- res::ResREPL,
- setup::{BasicREPLReadlineSetup, BasicREPLOutputSetup, BasicREPLPromptSetup},
-};
-
-dispatcher!("cd", CMDCd => EntryCd);
-dispatcher!("exit", CMDExit => EntryExit);
-
-fn main() {
- let mut program = ThisProgram::new();
-
- program.with_dispatcher(CMDCd);
- program.with_dispatcher(CMDExit);
-
- // Enable line reading from stdin
- program.with_setup(BasicREPLReadlineSetup);
-
- // Enable output flushing after each render
- program.with_setup(BasicREPLOutputSetup);
-
- // Custom prompt
- program.with_setup(BasicREPLPromptSetup::func(|| "> ".to_string()));
-
- program.exec_repl(); // ← interactive loop
-}
-
-// Exit the REPL via the ResREPL resource:
+// Entry: parse arguments and pass state to the calculation step
#[chain]
-fn handle_exit(_prev: EntryExit, repl: &mut ResREPL) {
- repl.exit = true;
-}
-```
-
----
-
-### 12. Hooks — Observing the Pipeline
-
-Mingling provides a `ProgramHook` system for observing every stage of the execution pipeline. Useful for debugging, logging, or telemetry.
-
-```rust
-use mingling::{
- hook::{ProgramControlUnit, ProgramHook},
-};
-
-dispatcher!("greet", CMDGreet => EntryGreet);
-
-fn main() {
- let mut program = ThisProgram::new();
-
- program.with_hook(
- ProgramHook::<ThisProgram>::empty()
- .on_begin::<_, ()>(|_| println!("[DEBUG] Program is begin"))
- .on_pre_dispatch(|info| println!("[DEBUG] Pre dispatch: {}", info.arguments.join(" ")))
- .on_post_dispatch(|info| println!("[DEBUG] Post dispatch: {}", info.entry))
- .on_pre_chain(|info| {
- println!("[DEBUG] Pre chain: {}", info.input);
- })
- .on_post_chain(|info| println!("[DEBUG] Post chain: {}", info.output.member_id))
- .on_finish(|_| {
- println!("[DEBUG] Loop end");
- ProgramControlUnit::OverrideExitCode(0) // Override exit code
- })
- .on_pre_render(|info| println!("[DEBUG] Pre render: {}", info.input))
- .on_post_render(|_| println!("[DEBUG] Post render")),
- );
-
- program.with_dispatcher(CMDGreet);
- program.exec_and_exit();
-}
-```
-
----
-
-### 13. Structural Renderer — Structured Output (JSON/YAML)
-
-With the `structural_renderer` feature, users can add `--json` or `--yaml` flags to get structured output instead of human-readable text.
-
-```rust
-// Features: ["structural_renderer", "parser"]
-// Dependencies:
-// serde = "1"
-
-use mingling::{prelude::*, setup::StructuralRendererSetup};
-use mingling::Groupped;
-use mingling::StructuralData;
-use serde::Serialize;
-use std::io::Write;
-
-dispatcher!("render", CMDRender => EntryRender);
-
-#[derive(Default, StructuralData, Serialize, Groupped)]
-struct ResultInfo {
- name: String,
- age: i32,
+fn handle_calc(args: EntryCalculate) -> StateSumNumbers {
+ let numbers = args.pick(&arg![Vec<i32>]).unwrap();
+ StateSumNumbers::new(numbers)
}
+// Calculate: pass the result to the rendering step
#[chain]
-fn render_info(args: EntryRender) -> Next {
- let (name, age) = args.pick::<String>(()).pick::<i32>(()).unpack();
- ResultInfo { name, age }.to_chain()
+fn handle_state_sum_numbers(sum: StateSumNumbers) -> ResultNumber {
+ let numbers = sum.inner;
+ let total: i32 = numbers.iter().sum();
+ ResultNumber::new(total)
}
+// Renderer: return the render result and let the framework handle output
#[renderer]
-fn render_info_result(info: ResultInfo) -> RenderResult {
+fn render_number(number: ResultNumber) -> RenderResult {
let mut result = RenderResult::new();
- writeln!(result, "{} is {} years old", info.name, info.age).ok();
+ writeln!(result, "Number: {}", *number).ok();
result
}
-
-fn main() {
- let mut program = ThisProgram::new();
- program.with_setup(StructuralRendererSetup); // enables --json / --yaml
- program.with_dispatcher(CMDRender);
- let _ = program.exec();
-}
```
-Then users can do:
+Although this may make your program slightly more verbose, each step is a **pure function**, making it extremely easy to test!
-```bash
-$ myapp render Bob 22
-Bob is 22 years old
+## Getting Started
-$ myapp render Bob 22 --json
-{"name":"Bob","age":22}
+Add Mingling to your `Cargo.toml`:
-$ myapp render Bob 22 --yaml
-name: Bob
-age: 22
+```toml
+[dependencies.mingling]
+version = "0.3.0"
+features = []
```
----
-
-### 14. Async Support
-
-Enable the `async` feature to use `async fn` inside `#[chain]`:
-
-```rust
-// Features: ["async", "parser"]
-// Dependencies:
-// tokio = { version = "1", features = ["full"] }
-
-use std::io::Write;
-use std::time::Duration;
-
-dispatcher!("download", CMDDownload => EntryDownload);
-pack!(ResultDownloaded = String);
-
-#[chain]
-pub async fn handle_download(args: EntryDownload) -> Next {
- let file = args.pick(()).unpack();
- download_file(file).await
-}
-
-async fn download_file(name: String) -> ResultDownloaded {
- tokio::time::sleep(Duration::from_secs(1)).await;
- ResultDownloaded::new(name)
-}
+Or use the github version
-#[renderer]
-fn render_downloaded(result: ResultDownloaded) -> RenderResult {
- let mut r = RenderResult::new();
- writeln!(r, "\"{}\" downloaded.", *result).ok();
- r
-}
+```toml
+[dependencies.mingling]
+git = "https://github.com/mingling-rs/mingling.git"
+tag = "unreleased"
+features = []
```
> [!NOTE]
->
-> `#[renderer]` functions cannot be async. When `async` is enabled, `program.exec_and_exit().await` returns a Future.
-
----
-
-### 15. Wrapping Up — `gen_program!()`
-
-At the very end of your crate root (main.rs / lib.rs), call `gen_program!()` to generate the `ThisProgram` struct, the `Next` type alias, and all internal plumbing.
-
-```rust
-use mingling::macros::gen_program;
-
-gen_program!();
-```
-
-It must be placed **after** all your `dispatcher!`, `pack!`, `#[chain]`, `#[renderer]`, and `#[help]` declarations.
-
----
-
-### Putting It All Together
-
-Here's a complete, runnable program:
-
-```rust
-use mingling::macros::pack;
-use mingling::prelude::*;
-use std::io::Write;
+> To learn more, check out [Writing with Mingling](https://github.com/mingling-rs/mingling/blob/main/GETTING_STARTED.md)
-dispatcher!("greet", CMDGreet => EntryGreet);
-
-fn main() {
- let mut program = ThisProgram::new();
- program.with_dispatcher(CMDGreet);
- program.exec_and_exit();
-}
-
-pack!(ResultGreeting = String);
-
-#[chain]
-fn handle_greet(args: EntryGreet) -> Next {
- let greeting = args
- .inner
- .first()
- .cloned()
- .unwrap_or_else(|| "World".to_string());
- ResultGreeting::new(greeting)
-}
-
-#[renderer]
-fn render_greeting(greeting: ResultGreeting) -> RenderResult {
- let mut result = RenderResult::new();
- writeln!(result, "Hello, {}!", *greeting).ok();
- result
-}
-
-gen_program!();
-```
-
-```bash
-$ myapp greet
-Hello, World!
-
-$ myapp greet Alice
-Hello, Alice!
-```
-
-<h1 align="center">
- 🗺️ Roadmap 🗺️
-</h1>
+## Roadmap
- [x] Milestone.1 "MVP" 🎉
- [x] [[0.1.4](https://docs.rs/mingling/0.1.4/mingling/)] [`core`] [`structural_renderer`] **Mingling** can render data into serializable formats via `--json` and `--yaml` flags
@@ -880,19 +128,17 @@ Hello, Alice!
- [x] [[0.1.9](https://docs.rs/mingling/0.1.9/mingling/)] [`core`] [`repl`] Provides REPL capability (`program.exec_repl();`)
- [x] [[0.2.0](https://docs.rs/mingling/0.2.0/mingling/)] Complete documentation, tests, and examples
- [ ] Milestone.2 "More Comfortable Dev and User Experience"
- - [ ] [`mling` / `mingling-cli`]
- - [ ] **Mingling** Linter
- - [ ] **Mingling** Project Generator
- - [ ] **Mingling** Program Installer & Manager (For development)
- - [ ] Helpdoc Editor
- - [ ] [`picker`] A more efficient and intelligent argument parser
- - [x] [`macros`] Remove `r_print!` / `r_println!` macros
+ - [ ] [`mling` / `mingling-cli`]
+ - [ ] **Mingling** Linter
+ - [ ] **Mingling** Project Generator
+ - [ ] **Mingling** Program Installer & Manager (For development)
+ - [ ] Helpdoc Editor
+ - [ ] [`picker`] A more efficient and intelligent argument parser
+ - [x] [`macros`] Remove `r_print!` / `r_println!` macros
- [ ] Milestone.3 "Unplanned"
- - [ ] ...
+ - [ ] ...
-<h1 align="center">
- 🚫 Unplanned Features 🚫
-</h1>
+## Unplanned Features
While Mingling has several common CLI features that are **NOT PLANNED** to be directly included in the framework.
This is because the Rust ecosystem already has excellent and mature crates to handle these issues, and Mingling's design is intended to be used in combination with them.
@@ -902,10 +148,19 @@ This is because the Rust ecosystem already has excellent and mature crates to ha
- **Progress Bars**: To display progress indicators, the [`indicatif`](https://crates.io/crates/indicatif) crate is the standard choice.
- **TUI**: To build full-screen interactive terminal applications, it is recommended to use a framework like [`ratatui`](https://crates.io/crates/ratatui) (formerly `tui-rs`).
-<h1 align="center">
- 📄 Open Source License 📄
-</h1>
+## License
This project is licensed under the MIT License.
See [LICENSE-MIT](LICENSE-MIT) or [LICENSE-APACHE](LICENSE-APACHE) file for details.
+
+## Learn More
+
+**To learn more, check out the following links:**
+
+- 📦 Repo - [Github](https://github.com/mingling-rs/mingling) | [Gitee](https://gitee.com/mingling-rs/mingling) | [Origin](https://catilgrass.cn/mingling.git)
+- 🚪 Mainpage - [Github](https://mingling-rs.github.io/mingling/) | [crates.io](https://crates.io/crates/mingling)
+- 💡 Examples - [Github](https://mingling-rs.github.io/mingling/docs/examples.html)
+- 📖 Help Doc - [EN](https://mingling-rs.github.io/mingling/docs/doc.html#/) | [中文](https://mingling-rs.github.io/mingling/docs/_zh_CN/index.html#/)
+- 📖 API Doc - [docs.rs](https://docs.rs/mingling/latest/mingling/) | [latest](https://mingling-rs.github.io/mingling/docs/api-docs/mingling/)
+- 📖 Dev Doc - [Github](https://mingling-rs.github.io/mingling/docs/dev/)