https://github.com/mingling-rs/mingling
Macro magician in your CLI.
https://github.com/mingling-rs/mingling
bash cli command-line command-line-interface completion crate fish framework library macros powershell proc-macro proc-macro-attribute proc-macro-derive repl rust shell zsh
Last synced: about 8 hours ago
JSON representation
Macro magician in your CLI.
- Host: GitHub
- URL: https://github.com/mingling-rs/mingling
- Owner: mingling-rs
- License: apache-2.0
- Created: 2026-03-29T07:05:01.000Z (3 months ago)
- Default Branch: main
- Last Pushed: 2026-07-01T21:06:59.000Z (about 10 hours ago)
- Last Synced: 2026-07-01T23:00:16.021Z (about 8 hours ago)
- Topics: bash, cli, command-line, command-line-interface, completion, crate, fish, framework, library, macros, powershell, proc-macro, proc-macro-attribute, proc-macro-derive, repl, rust, shell, zsh
- Language: Rust
- Homepage: https://mingling-rs.github.io/mingling/
- Size: 4.67 MB
- Stars: 3
- Watchers: 1
- Forks: 2
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Contributing: CONTRIBUTING.md
- License: LICENSE-APACHE
Awesome Lists containing this project
README
Mìng Lìng - 命令
/mɪŋ lɪŋ/
Macro magician in your CLI.
> [!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?
[`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"**, meaning **"Command"**.
### Mingling's Core Capabilities
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)
---
**💡 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 [EN](https://mingling-rs.github.io/mingling/docs/doc.html#/) [中文](https://mingling-rs.github.io/mingling/docs/_zh_CN/index.html#/)
Getting Started
Add Mingling to your `Cargo.toml`:
```toml
[dependencies.mingling]
version = "0.2.1"
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
```
Writing with Mingling
### 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` 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`.
- 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 writes it to the terminal.
```rust
pack!(ResultGreeting = String);
#[renderer]
fn render_greeting(greeting: ResultGreeting) {
r_println!("Hello, {}!", *greeting);
}
```
Inside a renderer, use `r_print!` / `r_println!` to write to the output buffer. This is not `println!` — it writes into Mingling's internal `RenderResult` buffer, which is flushed at the end of the pipeline.
You can write renderers for **any type** in your program, including error types:
```rust
use mingling::prelude::*;
#[renderer]
fn render_dispatcher_not_found(err: ErrorDispatcherNotFound) {
r_println!("Command not found: [{}]", err.join(" "));
}
```
---
### 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::(()) // positional: first string
.pick_or::(["-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::>(())
.pick_or::(["-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:
```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, setup::BasicProgramSetup};
dispatcher!("greet", CMDGreet => EntryGreet);
#[help]
fn help_greet(_prev: EntryGreet) {
r_println!("Usage: greet ");
r_println!("Greets the user with the given name.");
}
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
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) {
r_println!("No name provided");
}
#[renderer]
fn render_too_long(len: ErrorNameTooLong) {
r_println!("Name too long: {} > 10", *len);
}
```
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);
#[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!()
}
```
Resources can also be injected into `#[renderer]`:
```rust
dispatcher!("current", CMDCurrent => EntryCurrent);
#[derive(Default, Clone)]
struct ResCurrentDir {
current_dir: std::path::PathBuf,
}
#[renderer]
fn render_current(_: EntryCurrent, current_dir: &ResCurrentDir) {
r_println!("Current directory: {}", current_dir.current_dir.display());
}
```
---
### 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::Groupped;
#[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) {
r_print!("Hello, ");
for _ in 0..greet.repeat { r_print!("{}", greet.name); }
r_println!("!");
}
#[renderer]
fn render_parse_error(err: ErrorGreetParsed) {
r_println!("{}", *err);
}
```
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();
}
```
Mingling provides built-in REPL setups:
```rust
// Features: ["repl", "extra_macros"]
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:
#[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::::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;
dispatcher!("render", CMDRender => EntryRender);
#[derive(Default, StructuralData, Serialize, Groupped)]
struct ResultInfo {
name: String,
age: i32,
}
#[chain]
fn render_info(args: EntryRender) -> Next {
let (name, age) = args.pick::(()).pick::(()).unpack();
ResultInfo { name, age }.to_chain()
}
#[renderer]
fn render_info_result(info: ResultInfo) {
r_println!("{} is {} years old", info.name, info.age);
}
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:
```bash
$ myapp render Bob 22
Bob is 22 years old
$ myapp render Bob 22 --json
{"name":"Bob","age":22}
$ myapp render Bob 22 --yaml
name: Bob
age: 22
```
---
### 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::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)
}
#[renderer]
fn render_downloaded(result: ResultDownloaded) {
r_println!("\"{}\" downloaded.", *result);
}
```
> [!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
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) {
r_println!("Hello, {}!", *greeting);
}
gen_program!();
```
```bash
$ myapp greet
Hello, World!
$ myapp greet Alice
Hello, Alice!
```
🗺️ 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
- [x] [[0.1.5](https://docs.rs/mingling/0.1.5/mingling/)] [`core`] [`comp`] **Mingling** can dynamically invoke itself to provide completions for shells like `bash`, `zsh`, `fish`, and `pwsh`
- [x] [[0.1.6](https://docs.rs/mingling/0.1.6/mingling/)] [`core`] [`comp`] **Mingling** can gather more context for smarter completions
- [x] [[0.1.7](https://docs.rs/mingling/0.1.7/mingling/)] [`clap`] Provides a **Clap** compatibility layer, allowing **Mingling** to reuse its powerful parsing capabilities
- [x] [[0.1.7](https://docs.rs/mingling/0.1.7/mingling/)] [`core`] **Mingling** can intercept `-h` or `--help` flags to display custom help text for each subcommand
- [x] [[0.1.7](https://docs.rs/mingling/0.1.7/mingling/)] [`mling`] Provides a basic scaffolding tool (`mling`) for rapid development and debugging
- [x] [[0.1.8](https://docs.rs/mingling/0.1.8/mingling/)] [`core`] [`dispatch_tree`] Converts the subcommand list into a prefix tree to improve command matching speed
- [x] [[0.1.9](https://docs.rs/mingling/0.1.9/mingling/)] [`core`] [`dev_toolkits`] Provides debugging interfaces for developers to capture invocation information when issues arise (`InvokeStackDisplay`) (indirectly implemented via `ProgramHook`)
- [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"
- [ ] [**0.3.0**] [`macros`] `r_println!` in `#[chain]` support.
- [ ] [**0.5.0**] [`mling`] Helpdoc Maker
- [ ] [**0.7.0**] [`picker`] A more efficient and intelligent argument parser
- [ ] Milestone.3 "Unplanned"
- [ ] ...
🚫 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.
- **Colored Output**: To add color and styles (bold, italic, etc.) to terminal output, consider using crates like [`colored`](https://crates.io/crates/colored) or [`owo-colors`](https://crates.io/crates/owo-colors). You can integrate their types directly into your renderers.
- **I18n**: To translate your CLI application, the [`rust-i18n`](https://crates.io/crates/rust-i18n) crate provides a powerful internationalization solution that you can use in your command logic and renderers.
- **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`).
📄 Open Source License 📄
This project is licensed under the MIT License.
See [LICENSE-MIT](LICENSE-MIT) or [LICENSE-APACHE](LICENSE-APACHE) file for details.