aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.toml16
-rw-r--r--examples/example-argument-parse/Cargo.lock83
-rw-r--r--examples/example-argument-parse/Cargo.toml10
-rw-r--r--examples/example-argument-parse/src/main.rs95
-rw-r--r--examples/example-async-support/Cargo.lock (renamed from examples/example-picker/Cargo.lock)8
-rw-r--r--examples/example-async-support/Cargo.toml15
-rw-r--r--examples/example-async-support/src/main.rs68
-rw-r--r--examples/example-async/Cargo.lock250
-rw-r--r--examples/example-async/Cargo.toml8
-rw-r--r--examples/example-async/src/main.rs50
-rw-r--r--examples/example-basic/Cargo.lock2
-rw-r--r--examples/example-basic/Cargo.toml2
-rw-r--r--examples/example-basic/src/main.rs79
-rw-r--r--examples/example-completion/Cargo.lock2
-rw-r--r--examples/example-completion/Cargo.toml24
-rw-r--r--examples/example-completion/build.rs14
-rw-r--r--examples/example-completion/src/main.rs205
-rw-r--r--examples/example-custom-pickable/Cargo.lock83
-rw-r--r--examples/example-custom-pickable/Cargo.toml11
-rw-r--r--examples/example-custom-pickable/src/main.rs125
-rw-r--r--examples/example-dispatch-tree/Cargo.lock10
-rw-r--r--examples/example-dispatch-tree/Cargo.toml8
-rw-r--r--examples/example-dispatch-tree/src/main.rs76
-rw-r--r--examples/example-enum-tag/Cargo.lock93
-rw-r--r--examples/example-enum-tag/Cargo.toml12
-rw-r--r--examples/example-enum-tag/src/main.rs102
-rw-r--r--examples/example-error-handling/Cargo.lock76
-rw-r--r--examples/example-error-handling/Cargo.toml7
-rw-r--r--examples/example-error-handling/src/main.rs97
-rw-r--r--examples/example-exit-code/src/main.rs38
-rw-r--r--examples/example-exitcode/Cargo.lock (renamed from examples/example-exit-code/Cargo.lock)2
-rw-r--r--examples/example-exitcode/Cargo.toml (renamed from examples/example-exit-code/Cargo.toml)2
-rw-r--r--examples/example-exitcode/src/main.rs62
-rw-r--r--examples/example-general-renderer/Cargo.lock54
-rw-r--r--examples/example-general-renderer/Cargo.toml15
-rw-r--r--examples/example-general-renderer/src/main.rs45
-rw-r--r--examples/example-help/Cargo.lock76
-rw-r--r--examples/example-help/Cargo.toml7
-rw-r--r--examples/example-help/src/main.rs41
-rw-r--r--examples/example-hook/Cargo.lock76
-rw-r--r--examples/example-hook/Cargo.toml7
-rw-r--r--examples/example-hook/src/main.rs71
-rw-r--r--examples/example-panic-unwind/Cargo.lock83
-rw-r--r--examples/example-panic-unwind/Cargo.toml16
-rw-r--r--examples/example-panic-unwind/src/main.rs56
-rw-r--r--examples/example-picker/Cargo.toml8
-rw-r--r--examples/example-picker/src/main.rs60
-rw-r--r--examples/example-repl-basic/Cargo.lock (renamed from examples/example-repl/Cargo.lock)4
-rw-r--r--examples/example-repl-basic/Cargo.toml11
-rw-r--r--examples/example-repl-basic/src/main.rs (renamed from examples/example-repl/src/main.rs)37
-rw-r--r--examples/example-repl/Cargo.toml8
-rw-r--r--examples/example-resources/Cargo.lock2
-rw-r--r--examples/example-resources/Cargo.toml7
-rw-r--r--examples/example-resources/src/main.rs73
-rw-r--r--examples/example-setup/Cargo.lock76
-rw-r--r--examples/example-setup/Cargo.toml7
-rw-r--r--examples/example-setup/src/main.rs33
-rw-r--r--examples/example-unit-test/Cargo.lock76
-rw-r--r--examples/example-unit-test/Cargo.toml7
-rw-r--r--examples/example-unit-test/src/main.rs122
-rw-r--r--examples/test-examples.toml180
-rw-r--r--mingling/src/example_docs.rs1616
-rw-r--r--mingling/src/lib.rs4
-rw-r--r--mingling_macros/src/entry.rs75
-rw-r--r--mingling_macros/src/lib.rs22
65 files changed, 3602 insertions, 1108 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 9e2240b..2548e8f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -2,15 +2,23 @@
resolver = "2"
members = ["mingling", "mingling_core", "mingling_macros", "mling"]
exclude = [
- "examples/example-async",
+ "examples/example-argument-parse",
+ "examples/example-async-support",
"examples/example-basic",
"examples/example-completion",
+ "examples/example-custom-pickable",
"examples/example-dispatch-tree",
- "examples/example-exit-code",
+ "examples/example-enum-tag",
+ "examples/example-error-handling",
+ "examples/example-exitcode",
"examples/example-general-renderer",
- "examples/example-picker",
- "examples/example-repl",
+ "examples/example-help",
+ "examples/example-hook",
+ "examples/example-panic-unwind",
+ "examples/example-repl-basic",
"examples/example-resources",
+ "examples/example-setup",
+ "examples/example-unit-test",
"dev_tools",
]
diff --git a/examples/example-argument-parse/Cargo.lock b/examples/example-argument-parse/Cargo.lock
new file mode 100644
index 0000000..b035436
--- /dev/null
+++ b/examples/example-argument-parse/Cargo.lock
@@ -0,0 +1,83 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "example-argument-parse"
+version = "0.1.0"
+dependencies = [
+ "mingling",
+]
+
+[[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.9"
+dependencies = [
+ "mingling_core",
+ "mingling_macros",
+ "size",
+]
+
+[[package]]
+name = "mingling_core"
+version = "0.1.9"
+dependencies = [
+ "just_fmt",
+]
+
+[[package]]
+name = "mingling_macros"
+version = "0.1.9"
+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 = "size"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b6709c7b6754dca1311b3c73e79fcce40dd414c782c66d88e8823030093b02b"
+
+[[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 = "unicode-ident"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
diff --git a/examples/example-argument-parse/Cargo.toml b/examples/example-argument-parse/Cargo.toml
new file mode 100644
index 0000000..3b06523
--- /dev/null
+++ b/examples/example-argument-parse/Cargo.toml
@@ -0,0 +1,10 @@
+[package]
+name = "example-argument-parse"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies.mingling]
+path = "../../mingling"
+
+# Enable `parser` features
+features = ["parser"]
diff --git a/examples/example-argument-parse/src/main.rs b/examples/example-argument-parse/src/main.rs
new file mode 100644
index 0000000..f63fdad
--- /dev/null
+++ b/examples/example-argument-parse/src/main.rs
@@ -0,0 +1,95 @@
+//! Example Argument Parse
+//!
+//! > This example demonstrates how to use the `parser` feature to parse user input
+//!
+//! Run:
+//! ```bash
+//! cargo run --manifest-path examples/example-argument-parse/Cargo.toml --quiet -- transfer README.md --size 32kib
+//! cargo run --manifest-path examples/example-argument-parse/Cargo.toml --quiet -- transfer src/ --dir
+//! cargo run --manifest-path examples/example-argument-parse/Cargo.toml --quiet -- strict-transfer README.md
+//! cargo run --manifest-path examples/example-argument-parse/Cargo.toml --quiet -- strict-transfer --dir
+//! ```
+//!
+//! Output:
+//! ```plaintext
+//! file: README.md (32768)
+//! dir: src/ (1048576)
+//! file: README.md (1048576)
+//! Error: name is not provided
+//! ```
+
+use mingling::{macros::route, prelude::*};
+
+dispatcher!("transfer", CMDTransfer => EntryTransfer);
+dispatcher!("strict-transfer", CMDStrictTransfer => EntryStrictTransfer);
+
+pack!(ResultFile = (bool, usize, String)); // (IsDir, Size, Name)
+
+#[chain]
+fn handle_transfer_parse(args: EntryTransfer) -> Next {
+ // --------- IMPORTANT ---------
+ // First parse flag arguments (like --dir/-D), then positional arguments
+ let result: ResultFile = args
+ // Name --dir --size 20mib
+ // ^^^^^^^^^^^^_ first
+ .pick::<bool>(["--dir", "-D"])
+ // Name --dir
+ // ^^^^^_ second (or `-D`)
+ .pick_or::<usize>("--size", 1024 * 1024_usize)
+ // Name
+ // ^^^^_ finally, pick positional arg
+ .pick::<String>(())
+ .after(|str| str.trim().replace(" ", ""))
+ // Unpack to tuple (is_dir, size, name)
+ .unpack()
+ // Convert into ResultFile
+ .into();
+ // --------- IMPORTANT ---------
+ result
+}
+
+pack!(ErrorNoNameProvided = ());
+
+#[chain]
+fn handle_strict_transfer_parse(args: EntryStrictTransfer) -> Next {
+ // --------- IMPORTANT ---------
+ // Strict parsing: error immediately if the name is not provided
+ let result: ResultFile = route! { // Use `route!` to wrap a Picker that contains `or_route`
+ args
+ .pick::<bool>(["--dir", "-D"])
+ .pick_or::<usize>("--size", 1024 * 1024_usize)
+ // Finally parse the positional argument; if not found, route to `ErrorNoNameProvided`
+ .pick_or_route::<String, _>((), ErrorNoNameProvided::default().to_chain())
+ .after(|str| str.trim().replace(" ", ""))
+ .unpack()
+ }
+ // Convert into ResultFile
+ .into();
+ // --------- IMPORTANT ---------
+ result.to_chain()
+}
+
+#[renderer]
+fn render_result_file(result: ResultFile) {
+ let (is_dir, size, name) = result.into();
+ r_println!(
+ "{}: {} ({})",
+ if is_dir { "dir" } else { "file" },
+ name,
+ size
+ )
+}
+
+#[renderer]
+fn render_error_no_name_provided(_: ErrorNoNameProvided) {
+ r_println!("Error: name is not provided")
+}
+
+gen_program!();
+
+fn main() {
+ let mut program = ThisProgram::new();
+ program.with_dispatcher(CMDTransfer);
+ program.with_dispatcher(CMDStrictTransfer);
+ program.exec_and_exit();
+}
diff --git a/examples/example-picker/Cargo.lock b/examples/example-async-support/Cargo.lock
index eada902..1c4d5b1 100644
--- a/examples/example-picker/Cargo.lock
+++ b/examples/example-async-support/Cargo.lock
@@ -3,8 +3,8 @@
version = 4
[[package]]
-name = "example-picker"
-version = "0.0.1"
+name = "example-async-support"
+version = "0.1.0"
dependencies = [
"mingling",
"tokio",
@@ -85,9 +85,9 @@ dependencies = [
[[package]]
name = "tokio"
-version = "1.52.1"
+version = "1.52.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6"
+checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe"
dependencies = [
"pin-project-lite",
"tokio-macros",
diff --git a/examples/example-async-support/Cargo.toml b/examples/example-async-support/Cargo.toml
new file mode 100644
index 0000000..3a66e52
--- /dev/null
+++ b/examples/example-async-support/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "example-async-support"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies.mingling]
+path = "../../mingling"
+
+# Enable `parser` features
+features = ["async", "parser"]
+
+# Import any async runtime, e.g. Tokio
+[dependencies.tokio]
+version = "1.52.3"
+features = ["macros", "rt", "rt-multi-thread", "time"]
diff --git a/examples/example-async-support/src/main.rs b/examples/example-async-support/src/main.rs
new file mode 100644
index 0000000..12b1b9c
--- /dev/null
+++ b/examples/example-async-support/src/main.rs
@@ -0,0 +1,68 @@
+//! Example Async Runtime Support
+//!
+//! > This example shows how to drive an async runtime using the `async` feature
+//!
+//! ## Note
+//!
+//! When the `async` feature is enabled, **Mingling** provides a different framework implementation,
+//! allowing you to use the `async` keyword directly within `#[chain]`.
+//!
+//! However, you will lose some capabilities:
+//!
+//! 1. `&mut` resource injection is not available in async chain functions
+//! 2. The program will not be able to use panic unwind functionality
+//!
+//! Run:
+//! ```bash
+//! cargo run --manifest-path examples/example-async-support/Cargo.toml --quiet -- download README.md
+//! ```
+//!
+//! Output:
+//! ```plaintext
+//! Download begin
+//! # (Will pause for 1 second here)
+//! "README.md" downloaded.
+//! ```
+
+use mingling::{hook::ProgramHook, prelude::*};
+
+#[tokio::main]
+async fn main() {
+ let mut program = ThisProgram::new();
+
+ program.with_dispatcher(CMDDownload);
+
+ // Add a hook to display when the download begins
+ program.with_hook(ProgramHook::empty().on_begin(|| println!("Download begin")));
+
+ // --------- IMPORTANT ---------
+ // The return values of `exec_*()` related functions have been replaced with Futures
+ program.exec_and_exit().await;
+ // --------- IMPORTANT ---------
+}
+
+dispatcher!("download", CMDDownload => EntryDownload);
+
+pack!(ResultDownloaded = String);
+
+// --------- IMPORTANT ---------
+#[chain]
+// vvvvv_ `async` keyword can be used directly here
+pub async fn handle_download(args: EntryDownload) -> Next {
+ let file_name = args.pick(()).unpack();
+ fake_download(file_name).await
+}
+
+#[renderer]
+// But renderers cannot use the `async` keyword
+pub fn render_downloaded(result: ResultDownloaded) {
+ r_println!("\"{}\" downloaded.", *result);
+}
+// --------- IMPORTANT ---------
+
+gen_program!();
+
+async fn fake_download(file_name: String) -> ResultDownloaded {
+ tokio::time::sleep(std::time::Duration::from_secs(1)).await;
+ ResultDownloaded::new(file_name)
+}
diff --git a/examples/example-async/Cargo.lock b/examples/example-async/Cargo.lock
deleted file mode 100644
index b7dd453..0000000
--- a/examples/example-async/Cargo.lock
+++ /dev/null
@@ -1,250 +0,0 @@
-# This file is automatically @generated by Cargo.
-# It is not intended for manual editing.
-version = 4
-
-[[package]]
-name = "bitflags"
-version = "2.11.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
-
-[[package]]
-name = "bytes"
-version = "1.11.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
-
-[[package]]
-name = "cfg-if"
-version = "1.0.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
-
-[[package]]
-name = "errno"
-version = "0.3.14"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
-dependencies = [
- "libc",
- "windows-sys",
-]
-
-[[package]]
-name = "example-async"
-version = "0.0.1"
-dependencies = [
- "mingling",
- "tokio",
-]
-
-[[package]]
-name = "just_fmt"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5454cda0d57db59778608d7a47bff5b16c6705598265869fb052b657f66cf05e"
-
-[[package]]
-name = "libc"
-version = "0.2.186"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
-
-[[package]]
-name = "lock_api"
-version = "0.4.14"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
-dependencies = [
- "scopeguard",
-]
-
-[[package]]
-name = "mingling"
-version = "0.1.9"
-dependencies = [
- "mingling_core",
- "mingling_macros",
-]
-
-[[package]]
-name = "mingling_core"
-version = "0.1.9"
-dependencies = [
- "just_fmt",
-]
-
-[[package]]
-name = "mingling_macros"
-version = "0.1.9"
-dependencies = [
- "just_fmt",
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
-name = "mio"
-version = "1.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1"
-dependencies = [
- "libc",
- "wasi",
- "windows-sys",
-]
-
-[[package]]
-name = "parking_lot"
-version = "0.12.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
-dependencies = [
- "lock_api",
- "parking_lot_core",
-]
-
-[[package]]
-name = "parking_lot_core"
-version = "0.9.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
-dependencies = [
- "cfg-if",
- "libc",
- "redox_syscall",
- "smallvec",
- "windows-link",
-]
-
-[[package]]
-name = "pin-project-lite"
-version = "0.2.17"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
-
-[[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 = "redox_syscall"
-version = "0.5.18"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
-dependencies = [
- "bitflags",
-]
-
-[[package]]
-name = "scopeguard"
-version = "1.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
-
-[[package]]
-name = "signal-hook-registry"
-version = "1.4.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
-dependencies = [
- "errno",
- "libc",
-]
-
-[[package]]
-name = "smallvec"
-version = "1.15.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
-
-[[package]]
-name = "socket2"
-version = "0.6.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e"
-dependencies = [
- "libc",
- "windows-sys",
-]
-
-[[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 = "tokio"
-version = "1.52.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6"
-dependencies = [
- "bytes",
- "libc",
- "mio",
- "parking_lot",
- "pin-project-lite",
- "signal-hook-registry",
- "socket2",
- "tokio-macros",
- "windows-sys",
-]
-
-[[package]]
-name = "tokio-macros"
-version = "2.7.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496"
-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"
-
-[[package]]
-name = "wasi"
-version = "0.11.1+wasi-snapshot-preview1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
-
-[[package]]
-name = "windows-link"
-version = "0.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
-
-[[package]]
-name = "windows-sys"
-version = "0.61.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
-dependencies = [
- "windows-link",
-]
diff --git a/examples/example-async/Cargo.toml b/examples/example-async/Cargo.toml
deleted file mode 100644
index 0089520..0000000
--- a/examples/example-async/Cargo.toml
+++ /dev/null
@@ -1,8 +0,0 @@
-[package]
-name = "example-async"
-version = "0.0.1"
-edition = "2024"
-
-[dependencies]
-tokio = { version = "1", features = ["full"] }
-mingling = { path = "../../mingling", features = ["async"] }
diff --git a/examples/example-async/src/main.rs b/examples/example-async/src/main.rs
deleted file mode 100644
index 7b0be38..0000000
--- a/examples/example-async/src/main.rs
+++ /dev/null
@@ -1,50 +0,0 @@
-//! `Mingling` Example - Async
-//!
-//! After enabling the `async` feature:
-//! 1. The `chain!` macro will support using **async** functions,
-//! 2. The `exec` function of `Program` will return a `Future` for you to use with an async runtime
-//!
-//! ## Enable Feature
-//! Enable the `async` feature for mingling in `Cargo.toml`
-//! ```toml
-//! [dependencies]
-//! mingling = { version = "...", features = ["async"] }
-//! ```
-//!
-//! # How to Run
-//! ```bash
-//! cargo run --manifest-path ./examples/example-async/Cargo.toml -- hello World
-//! ```
-
-use mingling::prelude::*;
-
-dispatcher!("hello", HelloCommand => HelloEntry);
-
-// Use Tokio async runtime
-#[tokio::main]
-async fn main() {
- let mut program = ThisProgram::new();
- program.with_dispatcher(HelloCommand);
-
- // Run program
- program.exec().await;
-}
-
-pack!(Hello = String);
-
-// You can freely use async / non-async functions to declare your Chain
-
-#[chain]
-// fn parse_name(prev: HelloEntry) -> Next {
-async fn parse_name(prev: HelloEntry) -> Next {
- let name = prev.first().cloned().unwrap_or_else(|| "World".to_string());
- Hello::new(name).to_render()
-}
-
-// For renderers, you can still only use synchronous functions
-#[renderer]
-fn render_hello_who(prev: Hello) {
- r_println!("Hello, {}!", *prev);
-}
-
-gen_program!();
diff --git a/examples/example-basic/Cargo.lock b/examples/example-basic/Cargo.lock
index de110d2..e8e6ba6 100644
--- a/examples/example-basic/Cargo.lock
+++ b/examples/example-basic/Cargo.lock
@@ -4,7 +4,7 @@ version = 4
[[package]]
name = "example-basic"
-version = "0.0.1"
+version = "0.1.0"
dependencies = [
"mingling",
]
diff --git a/examples/example-basic/Cargo.toml b/examples/example-basic/Cargo.toml
index 48c281a..c8504fc 100644
--- a/examples/example-basic/Cargo.toml
+++ b/examples/example-basic/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "example-basic"
-version = "0.0.1"
+version = "0.1.0"
edition = "2024"
[dependencies]
diff --git a/examples/example-basic/src/main.rs b/examples/example-basic/src/main.rs
index 33840fc..d741c3b 100644
--- a/examples/example-basic/src/main.rs
+++ b/examples/example-basic/src/main.rs
@@ -1,47 +1,70 @@
-//! `Mingling` Example - Basic
+//! Example The Basic Usage of Mingling
//!
-//! # How to Run
-//! ```bash
-//! cargo run --manifest-path ./examples/example-basic/Cargo.toml -- hello World
+//! Run:
+//! ```base
+//! cargo run --manifest-path examples/example-basic/Cargo.toml --quiet -- greet
+//! cargo run --manifest-path examples/example-basic/Cargo.toml --quiet -- greet Alice
+//! ```
+//!
+//! Output:
+//! ```plaintext
+//! Hello, World!
+//! Hello, Alice!
//! ```
+// Import commonly used Mingling modules
use mingling::prelude::*;
-// Define dispatcher `HelloCommand`, directing subcommand "hello" to `HelloEntry`
-dispatcher!("hello", HelloCommand => HelloEntry);
+// Define the `greet` subcommand
+// _____________________________ subcmd name, can be nested (e.g. "remote.add" "remote.rm")
+// / _____________________ dispatcher name
+// | / _________ entry, records raw arguments
+// | | / ^^^^^^^^^^^^^
+// vvvvv vvvvvvvv vvvvvvvvvv \_ equivalent to pack!(EntryGreet = Vec<String>)
+dispatcher!("greet", CMDGreet => EntryGreet);
fn main() {
- // Create program
+ // Create a new ThisProgram
let mut program = ThisProgram::new();
- // Add dispatcher `HelloCommand`
- program.with_dispatcher(HelloCommand);
+ // Add the CMDGreet dispatcher
+ program.with_dispatcher(CMDGreet);
- // Run program
- program.exec();
+ // Run the program, then exit the process
+ program.exec_and_exit();
}
-// Register wrapper type `Hello`, setting inner to `String`
-pack!(Hello = String);
+// Quickly wrap a type into a type recognizable by the current program
+// ____________________ Wrapped type name
+// / _______ Wrapped type inner value
+// | /
+// vvvvvvvvvv vvvvvv
+pack!(ResultName = String);
-// Register chain to `ThisProgram`, handling logic from `HelloEntry`
-#[chain]
-fn parse_name(prev: HelloEntry) -> Next {
- // Extract string from `HelloEntry` as argument
- let name = prev.first().cloned().unwrap_or_else(|| "World".to_string());
-
- // Build `Hello` type and route to renderer
- Hello::new(name).to_render()
+// Define the `handle_greet` chain for parsing input text
+// ____________________ Previous type:
+// / Mingling deduces types at runtime and routes them to this function
+// | _____ will be expanded to:
+// | / impl Into<mingling::ChainProcess<ThisProgram>>
+#[chain] // vvvvvvvvvv vvvv
+fn handle_greet(args: EntryGreet) -> Next {
+ let name: ResultName = args
+ .inner
+ .first()
+ .cloned()
+ .unwrap_or_else(|| "World".to_string())
+ .into();
+ name
}
-// Register renderer to `ThisProgram`, handling rendering of `Hello`
+// Define renderer `render_name`, used to render `ResultName`
#[renderer]
-fn render_hello_who(prev: Hello) {
- // Print message
- r_println!("Hello, {}!", *prev);
-
- // Program ends here
+fn render_name(name: ResultName) {
+ r_println!("Hello, {}!", *name);
}
-// Generate program, default is `ThisProgram`
+// Note: This macro generates the program entry point.
+// It must be placed at the end of the root module of the crate (>= mingling@0.1.8).
+// ^^^^^^ ^^^^^^^^^^^
+// For example: lib.rs, main.rs
gen_program!();
diff --git a/examples/example-completion/Cargo.lock b/examples/example-completion/Cargo.lock
index d027a14..ffc92a5 100644
--- a/examples/example-completion/Cargo.lock
+++ b/examples/example-completion/Cargo.lock
@@ -4,7 +4,7 @@ version = 4
[[package]]
name = "example-completion"
-version = "0.0.1"
+version = "0.1.0"
dependencies = [
"mingling",
]
diff --git a/examples/example-completion/Cargo.toml b/examples/example-completion/Cargo.toml
index 6a76d1f..4e1881a 100644
--- a/examples/example-completion/Cargo.toml
+++ b/examples/example-completion/Cargo.toml
@@ -1,7 +1,25 @@
[package]
name = "example-completion"
-version = "0.0.1"
+version = "0.1.0"
edition = "2024"
-[dependencies]
-mingling = { path = "../../mingling", features = ["comp", "parser"] }
+[dependencies.mingling]
+path = "../../mingling"
+
+features = [
+ # Enable `comp` features
+ "comp",
+ "parser",
+]
+
+[build-dependencies.mingling]
+path = "../../mingling"
+
+features = [
+ # Enable `comp` features
+ "comp",
+
+ # If you want to build completion scripts,
+ # enable `builds` features
+ "builds",
+]
diff --git a/examples/example-completion/build.rs b/examples/example-completion/build.rs
new file mode 100644
index 0000000..e1ffba1
--- /dev/null
+++ b/examples/example-completion/build.rs
@@ -0,0 +1,14 @@
+fn main() {
+ build_scripts();
+}
+
+/// Generate completion scripts
+fn build_scripts() {
+ // `env!("CARGO_PKG_NAME")` equals the crate name, which matches the binary name.
+ // If your binary name differs from the crate name, specify it explicitly.
+ mingling::build::build_comp_scripts(
+ // Your binary name:
+ env!("CARGO_PKG_NAME"),
+ )
+ .unwrap();
+}
diff --git a/examples/example-completion/src/main.rs b/examples/example-completion/src/main.rs
index 31528d1..7ca23b9 100644
--- a/examples/example-completion/src/main.rs
+++ b/examples/example-completion/src/main.rs
@@ -1,136 +1,129 @@
-//! `Mingling` Example - Completion
+//! Example Completion
//!
-//! # How to Deploy
-//! 1. Enable the `comp` feature
-//! ```toml
-//! [dependencies]
-//! mingling = { version = "...", features = [
-//! "comp", // Enable this feature
-//! "parser"
-//! ] }
-//! ```
+//! > This example demonstrates how to use **Mingling** to create fully dynamic command-line completions
//!
-//! 2. Add `mingling` as a build dependency, enabling the `builds` and `comp` features
-//! ```toml
-//! [build-dependencies]
-//! mingling = { version = "...", features = [
-//! "builds", // Enable this feature for build scripts
-//! "comp"
-//! ] }
-//! ```
+//! ## About Completion Scripts
+//!
+//! To make your completions work, you need to generate a completion script using Mingling's tools
+//!
+//! 1. Enable features
+//! You need to enable the `builds` and `comp` features for `mingling` in `[build-dependencies]`
+//!
+//! 2. Write `build.rs`
+//! Write the following in `build.rs`
//!
-//! 3. Write `build.rs` to generate completion scripts at compile time
-//! ```ignore
-//! use mingling::build::{build_comp_scripts, build_comp_scripts_with_bin_name};
+//! ```rust,ignore
//! fn main() {
-//! // Generate completion scripts for the current program, using the Cargo package name as the binary filename
-//! build_comp_scripts(env!("CARGO_PKG_NAME")).unwrap();
+//! build_scripts();
+//! }
//!
-//! // Or, explicitly specify the binary filename
-//! // build_comp_scripts("your_bin").unwrap();
+//! /// Generate completion scripts
+//! fn build_scripts() {
+//! // `env!("CARGO_PKG_NAME")` equals the crate name, which matches the binary name.
+//! // If your binary name differs from the crate name, specify it explicitly.
+//! mingling::build::build_comp_scripts(
+//! // Your binary name:
+//! env!("CARGO_PKG_NAME"),
+//! )
+//! .unwrap();
//! }
//! ```
//!
-//! 4. Write `main.rs`, adding completion logic for your command entry point
-//! 5. Execute `cargo install --path ./`, then run the corresponding completion script in your shell
-
-use mingling::prelude::*;
-use mingling::{
- macros::{suggest, suggest_enum},
- parser::{PickableEnum, Picker},
- EnumTag, Groupped, ShellContext, Suggest,
-};
-
-// Define dispatcher `FruitCommand`, directing subcommand "fruit" to `FruitEntry`
-dispatcher!("fruit", FruitCommand => FruitEntry);
+//! 3. Verify
+//! Build your project with `cargo build --release`. The completion scripts will be generated in `target/release/`
+//!
+//! Execute the script or have it be automatically sourced by your Shell
+//!
+//! Run:
+//! ```bash
+//! cargo run --manifest-path examples/example-completion/Cargo.toml --quiet -- greet Alice --repeat 3
+//! ```
+//!
+//! Output:
+//! ```plaintext
+//! Hello, Alice, Alice, Alice!
+//! ```
-#[completion(FruitEntry)]
-fn comp_fruit_command(ctx: &ShellContext) -> Suggest {
- if ctx.filling_argument_first("--name") {
- return suggest!();
- }
- if ctx.filling_argument_first("--type") {
- return suggest_enum!(FruitType);
- }
- if ctx.typing_argument() {
- return suggest! {
- "--name": "Fruit name",
- "--type": "Fruit type"
- }
- .strip_typed_argument(ctx);
- }
- return suggest!();
-}
+use mingling::{macros::suggest, prelude::*, ShellContext, Suggest};
fn main() {
let mut program = ThisProgram::new();
- program.with_dispatcher(CompletionDispatcher);
- program.with_dispatcher(FruitCommand);
- program.exec();
-}
-#[derive(Groupped)]
-struct FruitInfo {
- name: String,
- fruit_type: FruitType,
-}
+ program.with_dispatcher(CMDGreet);
-#[derive(Default, Debug, EnumTag)]
-enum FruitType {
- #[enum_desc("It's Apple")]
- #[enum_rename("apple")]
- FruitApple,
+ // --------- IMPORTANT ---------
+ // The `comp` feature makes `gen_program!()` generate a CompletionDispatcher automatically
+ // It adds a hidden `__comp` subcommand for communication with the completion script
+ program.with_dispatcher(crate::CompletionDispatcher);
+ // --------- IMPORTANT ---------
- #[enum_desc("It's Banana")]
- #[enum_rename("banana")]
- FruitBanana,
+ // TIP: Note that the completion script reads stdout,
+ // so make sure no output is produced before the CompletionDispatcher is dispatched.
+ program.exec_and_exit();
+}
- #[enum_desc("It's Cherry")]
- #[enum_rename("cherry")]
- FruitCherry,
+// --------- IMPORTANT ---------
+// __________________________________________ Entry point bound to completion behavior
+// / _________________________ Shell context for obtaining user input state
+// | / ________ Suggest, used to return completion results
+// vvvvvvvvvv | /
+#[completion(EntryGreet)] // vvvvvvvvvvvv vvvvvvv
+fn complete_greet_entry(ctx: &ShellContext) -> Suggest {
+ // When the previous word is `greet` (the current command being typed)
+ if ctx.previous_word == "greet" {
+ // Return suggestions
+ return suggest! {
+ "Bob": "Likes to pass messages",
+ "Alice": "Likes to receive messages",
+ "Hacker": "YOU",
+ "World"
+ };
+ }
- #[enum_desc("It's Date")]
- #[enum_rename("date")]
- FruitDate,
+ // When the user is typing `--repeat`
+ if ctx.filling_argument(["-r", "--repeat"]) {
+ return suggest! {}; // Don't suggest anything
+ }
- #[enum_desc("It's Elderberry")]
- #[enum_rename("elderberry")]
- FruitElderberry,
+ // When the user is typing `-`
+ if ctx.typing_argument() {
+ return suggest! {
+ "-r": "Number of repetitions",
+ "--repeat": "Number of repetitions",
+ }
+ // Remove arguments that have already been typed by the user
+ .strip_typed_argument(ctx);
+ }
- #[default]
- #[enum_rename("unknown")]
- Unknown,
+ // Otherwise, suggest nothing
+ suggest!()
+ // // You can also enable file completions using the following code,
+ // // which will invoke the Shell's default behavior
+ // Suggest::file_comp()
}
+// --------- IMPORTANT ---------
-impl PickableEnum for FruitType {}
+dispatcher!("greet", CMDGreet => EntryGreet);
+pack!(ResultName = (u8, String));
#[chain]
-fn parse_fruit_info(prev: FruitEntry) -> Next {
- let picker = Picker::from(prev.inner);
- let (fruit_name, fruit_type) = picker.pick("--name").pick("--type").unpack();
- let info = FruitInfo {
- name: fruit_name,
- fruit_type,
- };
- info.to_render()
+fn handle_greet(args: EntryGreet) -> Next {
+ let result: ResultName = args
+ .pick(["-r", "--repeat"])
+ .pick_or((), "World")
+ .unpack()
+ .into();
+ result
}
#[renderer]
-fn render_fruit(prev: FruitInfo) {
- match (prev.name.is_empty(), prev.fruit_type) {
- (true, FruitType::Unknown) => {
- r_println!("Fruit name is empty and type is unknown");
- }
- (true, fruit_type) => {
- r_println!("Fruit name is empty, Type: {:?}", fruit_type);
- }
- (false, FruitType::Unknown) => {
- r_println!("Fruit name: {}, Type is unknown", prev.name);
- }
- (false, fruit_type) => {
- r_println!("Fruit name: {}, Type: {:?}", prev.name, fruit_type);
- }
+fn render_name(result: ResultName) {
+ let (repeat, name) = result.inner;
+ let mut parts = Vec::with_capacity(repeat as usize);
+ for _ in 0..repeat {
+ parts.push(name.clone());
}
+ r_println!("Hello, {}!", parts.join(", "));
}
gen_program!();
diff --git a/examples/example-custom-pickable/Cargo.lock b/examples/example-custom-pickable/Cargo.lock
new file mode 100644
index 0000000..8ef90ec
--- /dev/null
+++ b/examples/example-custom-pickable/Cargo.lock
@@ -0,0 +1,83 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "example-custom-pickable"
+version = "0.1.0"
+dependencies = [
+ "mingling",
+]
+
+[[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.9"
+dependencies = [
+ "mingling_core",
+ "mingling_macros",
+ "size",
+]
+
+[[package]]
+name = "mingling_core"
+version = "0.1.9"
+dependencies = [
+ "just_fmt",
+]
+
+[[package]]
+name = "mingling_macros"
+version = "0.1.9"
+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 = "size"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b6709c7b6754dca1311b3c73e79fcce40dd414c782c66d88e8823030093b02b"
+
+[[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 = "unicode-ident"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
diff --git a/examples/example-custom-pickable/Cargo.toml b/examples/example-custom-pickable/Cargo.toml
new file mode 100644
index 0000000..ca97c4a
--- /dev/null
+++ b/examples/example-custom-pickable/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+name = "example-custom-pickable"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies.mingling]
+path = "../../mingling"
+
+features = [
+ "parser",
+]
diff --git a/examples/example-custom-pickable/src/main.rs b/examples/example-custom-pickable/src/main.rs
new file mode 100644
index 0000000..466ae43
--- /dev/null
+++ b/examples/example-custom-pickable/src/main.rs
@@ -0,0 +1,125 @@
+//! Example Custom Pickable
+//!
+//! > This example demonstrates how to use the Pickable trait to add parsing for your types
+//!
+//! Run:
+//! ```bash
+//! cargo run --manifest-path examples/example-custom-pickable/Cargo.toml --quiet -- connect 127.0.0.1:5012
+//! cargo run --manifest-path examples/example-custom-pickable/Cargo.toml --quiet -- connect 127.0.0.1
+//! ```
+//!
+//! Output:
+//! ```plaintext
+//! Connected to "127.0.0.1:5012"
+//! Failed to parse address
+//! ```
+
+use mingling::{Groupped, macros::route, parser::Pickable, prelude::*};
+
+// Define types that can be recognized by Mingling
+// ________________________ `Pickable` trait needs to implement Default
+// / ________ The Groupped derive macro registers an ID for this type
+// | / Mingling uses this ID to identify the type
+// vvvvvvv vvvvvvvv
+#[derive(Debug, Default, Clone, Groupped)]
+pub struct Address {
+ pub ip: [u8; 4],
+ pub port: u16,
+}
+
+// --------- IMPORTANT ---------
+impl Pickable for Address {
+ type Output = Address;
+ fn pick(args: &mut mingling::parser::Argument, flag: mingling::Flag) -> Option<Self::Output> {
+ // Extract the raw string from Argument using the Flag
+ let raw: String = args.pick_argument(flag)?.to_string();
+
+ // Use TryFrom to parse the address
+ Address::try_from(raw).ok()
+ }
+}
+// --------- IMPORTANT ---------
+
+dispatcher!("connect", CMDConnect => EntryConnect);
+pack!(ErrorParseAddressFailed = ());
+
+#[chain]
+fn handle_connect(prev: EntryConnect) -> Next {
+ let connect: Address =
+ route! { prev.pick_or_route((), ErrorParseAddressFailed::default().to_chain()).unpack() };
+ connect.to_chain()
+}
+
+#[renderer]
+fn render_address(addr: Address) {
+ r_println!("Connected to \"{}\"", addr.to_string());
+}
+
+#[renderer]
+fn render_error_parse_address_failed(_: ErrorParseAddressFailed) {
+ r_println!("Failed to parse address");
+}
+
+gen_program!();
+
+fn main() {
+ let mut program = ThisProgram::new();
+ program.with_dispatcher(CMDConnect);
+ program.exec_and_exit();
+}
+
+// Address conversion
+
+impl TryFrom<String> for Address {
+ type Error = String;
+
+ fn try_from(raw: String) -> Result<Self, Self::Error> {
+ // Expected format: "192.168.1.1:8080"
+ let parts: Vec<&str> = raw.split(':').collect();
+ if parts.len() != 2 {
+ return Err("Invalid format: expected 'IP:PORT'".to_string());
+ }
+
+ let ip_str = parts[0];
+ let port_str = parts[1];
+
+ // Parse IP address (4 octets separated by dots)
+ let ip_parts: Vec<&str> = ip_str.split('.').collect();
+ if ip_parts.len() != 4 {
+ return Err("Invalid IP address format".to_string());
+ }
+
+ let mut ip = [0u8; 4];
+ for (i, part) in ip_parts.iter().enumerate() {
+ ip[i] = part
+ .parse::<u8>()
+ .map_err(|_| format!("Invalid IP octet: {}", part))?;
+ }
+
+ // Parse port
+ let port = port_str
+ .parse::<u16>()
+ .map_err(|_| format!("Invalid port: {}", port_str))?;
+
+ Ok(Address { ip, port })
+ }
+}
+
+impl From<Address> for String {
+ fn from(addr: Address) -> String {
+ format!(
+ "{}.{}.{}.{}:{}",
+ addr.ip[0], addr.ip[1], addr.ip[2], addr.ip[3], addr.port
+ )
+ }
+}
+
+impl std::fmt::Display for Address {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(
+ f,
+ "{}.{}.{}.{}:{}",
+ self.ip[0], self.ip[1], self.ip[2], self.ip[3], self.port
+ )
+ }
+}
diff --git a/examples/example-dispatch-tree/Cargo.lock b/examples/example-dispatch-tree/Cargo.lock
index 4085ce5..4b6c7eb 100644
--- a/examples/example-dispatch-tree/Cargo.lock
+++ b/examples/example-dispatch-tree/Cargo.lock
@@ -16,15 +16,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5454cda0d57db59778608d7a47bff5b16c6705598265869fb052b657f66cf05e"
[[package]]
-name = "just_template"
-version = "0.1.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "db3edb658c34b10b69c4b3b58f7ba989cd09c82c0621dee1eef51843c2327225"
-dependencies = [
- "just_fmt",
-]
-
-[[package]]
name = "mingling"
version = "0.1.9"
dependencies = [
@@ -37,7 +28,6 @@ name = "mingling_core"
version = "0.1.9"
dependencies = [
"just_fmt",
- "just_template",
]
[[package]]
diff --git a/examples/example-dispatch-tree/Cargo.toml b/examples/example-dispatch-tree/Cargo.toml
index f1c6785..0c31ecb 100644
--- a/examples/example-dispatch-tree/Cargo.toml
+++ b/examples/example-dispatch-tree/Cargo.toml
@@ -3,5 +3,9 @@ name = "example-dispatch-tree"
version = "0.1.0"
edition = "2024"
-[dependencies]
-mingling = { path = "../../mingling", features = ["dispatch_tree", "comp"] }
+[dependencies.mingling]
+path = "../../mingling"
+
+features = [
+ "dispatch_tree",
+]
diff --git a/examples/example-dispatch-tree/src/main.rs b/examples/example-dispatch-tree/src/main.rs
index d8be32a..08714d1 100644
--- a/examples/example-dispatch-tree/src/main.rs
+++ b/examples/example-dispatch-tree/src/main.rs
@@ -1,47 +1,59 @@
-//! `Mingling` Example - Dispatch Tree
+//! Example Dispatch Tree
//!
-//! # How to Deploy
-//! 1. Enable the `dispatch_tree` feature (`comp` is optional)
-//! ```toml
-//! mingling = { version = "...", features = [
-//! "dispatch_tree", // Enable this feature
-//! "comp" // optional
-//! ] }
-//! ```
+//! > This example will introduce how to use `dispatch_tree`
+//! > to optimize your command line lookup efficiency
+//!
+//! When the number of commands in your project increases, you can use `dispatch_tree` to complete command registration at compile time.
+//! It will generate a trie for quickly finding related commands by prefix.
//!
-//! 2. Using `cargo expand`:
+//! Therefore, after enabling this feature,
+//! `Program` will no longer store a Dispatcher list internally, and the `with_dispatcher` function will not be compiled.
//!
+//! Run:
//! ```bash
-//! cargo expand --manifest-path examples/example-dispatch-tree/Cargo.toml > expanded.rs
-//! cat expanded.rs
+//! cargo run --manifest-path examples/example-dispatch-tree/Cargo.toml --quiet -- cmd5
//! ```
-
-#![allow(unused_mut)]
+//!
+//! Output:
+//! ```plaintext
+//! It's works!
+//! ```
+//!
use mingling::prelude::*;
+// --------- IMPORTANT ---------
+// You have a large number of subcommands
+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);
+dispatcher!("cmd3.sub2", CMD3Sub2 => Entry3Sub2);
+dispatcher!("cmd4.sub1.subsub1.deep", CMD4Deep => Entry4Deep);
+dispatcher!("cmd4.sub1.subsub2", CMD4SubSub2 => Entry4SubSub2);
+dispatcher!("cmd5", CMD5 => Entry5);
+dispatcher!("cmd5.extra", CMD5Extra => Entry5Extra);
+dispatcher!("nested.a.b.c", CMDA => EntryA);
+dispatcher!("nested.a.b.d", CMDB => EntryB);
+dispatcher!("nested.a.e", CMDC => EntryC);
+dispatcher!("nested.f", CMDD => EntryD);
+// --------- IMPORTANT ---------
+
fn main() {
- let mut program = ThisProgram::new();
+ let program = ThisProgram::new();
- // // After enabling `dispatch_tree`, this method will no longer exist
- // program.with_dispatcher(CommandGreet);
- //
- // // The `CompletionDispatcher` automatically generated by `comp` will also be imported automatically
- // program.with_dispatcher(CompletionDispatcher);
+ // --------- IMPORTANT ---------
+ // // You no longer need to use `with_dispatcher` anymore;
+ // // it'll be collected automatically once the `dispatch_tree` feature is enabled
+ // program.with_dispatcher(...);
- program.exec();
+ program.exec_and_exit()
}
-dispatcher!("greet", CommandGreet => EntryGreet);
-dispatcher!("help", CommandHelp => EntryHelp);
-dispatcher!("quit", CommandQuit => EntryQuit);
-dispatcher!("list", CommandList => EntryList);
-dispatcher!("status", CommandStatus => EntryStatus);
-dispatcher!("save", CommandSave => EntrySave);
-dispatcher!("load", CommandLoad => EntryLoad);
-dispatcher!("config", CommandConfig => EntryConfig);
-dispatcher!("run", CommandRun => EntryRun);
-dispatcher!("debug", CommandDebug => EntryDebug);
-dispatcher!("version", CommandVersion => EntryVersion);
+#[renderer]
+fn render_cmd5(_: Entry5) {
+ r_println!("It's works!");
+}
gen_program!();
diff --git a/examples/example-enum-tag/Cargo.lock b/examples/example-enum-tag/Cargo.lock
new file mode 100644
index 0000000..9839796
--- /dev/null
+++ b/examples/example-enum-tag/Cargo.lock
@@ -0,0 +1,93 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "example-enum-tag"
+version = "0.1.0"
+dependencies = [
+ "mingling",
+]
+
+[[package]]
+name = "just_fmt"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5454cda0d57db59778608d7a47bff5b16c6705598265869fb052b657f66cf05e"
+
+[[package]]
+name = "just_template"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db3edb658c34b10b69c4b3b58f7ba989cd09c82c0621dee1eef51843c2327225"
+dependencies = [
+ "just_fmt",
+]
+
+[[package]]
+name = "mingling"
+version = "0.1.9"
+dependencies = [
+ "mingling_core",
+ "mingling_macros",
+ "size",
+]
+
+[[package]]
+name = "mingling_core"
+version = "0.1.9"
+dependencies = [
+ "just_fmt",
+ "just_template",
+]
+
+[[package]]
+name = "mingling_macros"
+version = "0.1.9"
+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 = "size"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b6709c7b6754dca1311b3c73e79fcce40dd414c782c66d88e8823030093b02b"
+
+[[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 = "unicode-ident"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
diff --git a/examples/example-enum-tag/Cargo.toml b/examples/example-enum-tag/Cargo.toml
new file mode 100644
index 0000000..7a8e5d6
--- /dev/null
+++ b/examples/example-enum-tag/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "example-enum-tag"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies.mingling]
+path = "../../mingling"
+
+features = [
+ "comp",
+ "parser"
+]
diff --git a/examples/example-enum-tag/src/main.rs b/examples/example-enum-tag/src/main.rs
new file mode 100644
index 0000000..05419f7
--- /dev/null
+++ b/examples/example-enum-tag/src/main.rs
@@ -0,0 +1,102 @@
+//! Example Enum Tag
+//!
+//! > This example demonstrates how to use the `EnumTag` derive macro to tag enum variants with metadata,
+//! > which can be used for autocompletion and parsing
+//!
+//! Run:
+//! ```bash
+//! cargo run --manifest-path examples/example-enum-tag/Cargo.toml --quiet -- lang-select OCaml
+//! cargo run --manifest-path examples/example-enum-tag/Cargo.toml --quiet -- lang-select
+//! ```
+//!
+//! Output:
+//! ```plaintext
+//! Selected: OCaml (A representative functional programming language with strong type inference)
+//! Selected: Rust (A systems programming language focused on performance, safety, and concurrency)
+//! ```
+
+use mingling::{
+ EnumTag, Groupped, ShellContext, Suggest, macros::suggest_enum, parser::PickableEnum,
+ prelude::*,
+};
+
+// Define the enum and derive the EnumTag trait
+// ________ adds metadata to the enum, enabling it to:
+// / 1. Be used by the `suggest_enum!(Enum)` macro under the `comp` feature for autocompletion
+// vvvvvvv 2. Implement the `PickableEnum` trait
+#[derive(Debug, Default, EnumTag, Groupped)]
+pub enum ProgrammingLanguages {
+ #[enum_desc("An efficient and flexible compiled language widely used for system programming")]
+ C,
+
+ #[enum_rename("C++")]
+ #[enum_desc("A high-performance language extending C with object-oriented features")]
+ CPlusPlus,
+
+ #[enum_rename("C#")]
+ #[enum_desc("Microsoft's object-oriented programming language running on the .NET platform")]
+ Csharp,
+
+ #[enum_desc(
+ "A cross-platform object-oriented language widely used for enterprise application development"
+ )]
+ Java,
+
+ #[enum_desc(
+ "A dynamic scripting language for web development, supporting prototype chain inheritance"
+ )]
+ JavaScript,
+
+ #[enum_desc("A modern statically typed language running on the JVM, concise and safe")]
+ Kotlin,
+
+ #[enum_desc("A representative functional programming language with strong type inference")]
+ OCaml,
+
+ #[enum_desc("A general-purpose programming language with clean syntax, known for readability")]
+ Python,
+
+ #[enum_desc("An object-oriented scripting language, famous for its concise and elegant syntax")]
+ Ruby,
+
+ #[default]
+ #[enum_desc("A systems programming language focused on performance, safety, and concurrency")]
+ Rust,
+}
+
+// --------- IMPORTANT ---------
+// Implement the PickableEnum trait for ProgrammingLanguages,
+// so that `Picker` can parse this enum
+impl PickableEnum for ProgrammingLanguages {}
+// --------- IMPORTANT ---------
+
+dispatcher!("lang-select", CMDLanguageSelection => EntryLanguageSelection);
+
+#[chain]
+fn handle_language_selection(args: EntryLanguageSelection) -> Next {
+ // You can use Picker to directly parse ProgrammingLanguages
+ let lang: ProgrammingLanguages = args.pick(()).unpack();
+ lang
+}
+
+#[renderer]
+fn render_programming_language(lang: ProgrammingLanguages) {
+ // You can use `enum_info()` to get the name and description of the current enum
+ let (name, desc) = lang.enum_info();
+ r_println!("Selected: {} ({})", name, desc)
+}
+
+#[completion(EntryLanguageSelection)]
+fn complete_language_selection(_: &ShellContext) -> Suggest {
+ // Use `suggest_enum!` directly to generate enum suggestions
+ suggest_enum!(ProgrammingLanguages)
+}
+
+gen_program!();
+
+fn main() {
+ let mut program = ThisProgram::new();
+ program.with_dispatcher(CompletionDispatcher);
+ program.with_dispatcher(CMDLanguageSelection);
+ program.exec_and_exit();
+}
diff --git a/examples/example-error-handling/Cargo.lock b/examples/example-error-handling/Cargo.lock
new file mode 100644
index 0000000..d2deef4
--- /dev/null
+++ b/examples/example-error-handling/Cargo.lock
@@ -0,0 +1,76 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "example-error-handling"
+version = "0.1.0"
+dependencies = [
+ "mingling",
+]
+
+[[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.9"
+dependencies = [
+ "mingling_core",
+ "mingling_macros",
+]
+
+[[package]]
+name = "mingling_core"
+version = "0.1.9"
+dependencies = [
+ "just_fmt",
+]
+
+[[package]]
+name = "mingling_macros"
+version = "0.1.9"
+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 = "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 = "unicode-ident"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
diff --git a/examples/example-error-handling/Cargo.toml b/examples/example-error-handling/Cargo.toml
new file mode 100644
index 0000000..c6e7ea3
--- /dev/null
+++ b/examples/example-error-handling/Cargo.toml
@@ -0,0 +1,7 @@
+[package]
+name = "example-error-handling"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies]
+mingling = { path = "../../mingling" }
diff --git a/examples/example-error-handling/src/main.rs b/examples/example-error-handling/src/main.rs
new file mode 100644
index 0000000..d8db852
--- /dev/null
+++ b/examples/example-error-handling/src/main.rs
@@ -0,0 +1,97 @@
+//! Example Error Handling
+//!
+//! > This example demonstrates how to handle errors in Mingling, including custom error types and error rendering.
+//!
+//! Run:
+//! ```bash
+//! cargo run --manifest-path examples/example-error-handling/Cargo.toml --quiet -- hallo
+//! cargo run --manifest-path examples/example-error-handling/Cargo.toml --quiet -- hello
+//! cargo run --manifest-path examples/example-error-handling/Cargo.toml --quiet -- hello Alice
+//! cargo run --manifest-path examples/example-error-handling/Cargo.toml --quiet -- hello MyBestFriendAlice
+//! cargo run --manifest-path examples/example-error-handling/Cargo.toml --quiet -- hello Peter
+//! ```
+//!
+//! Output:
+//! ```plaintext
+//! Command not found: "hallo"
+//! No name provided
+//! Name not available
+//! Name too long: 17 > 10
+//! Hello, Peter
+//! ```
+
+use mingling::prelude::*;
+
+// In Mingling, instead of using ? to propagate errors upward,
+// errors are treated as branches that continue execution.
+
+dispatcher!("hello", CMDHello => EntryHello);
+
+// Define error types
+pack!(ErrorNoNameProvided = ());
+pack!(ErrorNameTooLong = u16);
+pack!(ErrorNameNotAvailable = ());
+
+// Define success type
+pack!(ResultName = String);
+
+// Pre-registered names
+static VEC_REGISTERED_NAMES: &[&str] = &["Alice", "Bob", "Charlie", "David", "Eve"];
+
+#[chain]
+fn handle_hello(args: EntryHello) -> Next {
+ let Some(name) = args.inner.first().cloned() else {
+ // If no name is provided, pass ErrorNoNameProvided
+ return ErrorNoNameProvided::default().to_render();
+ };
+
+ if name.len() > 10 {
+ // If the name is too long, pass ErrorNameTooLong
+ return ErrorNameTooLong::new(name.len() as u16).to_render();
+ }
+
+ if VEC_REGISTERED_NAMES.contains(&name.as_str()) {
+ // If the name already exists, pass ErrorNameNotAvailable
+ return ErrorNameNotAvailable::default().to_render();
+ }
+
+ // If the name is valid, pass ResultName
+ ResultName::new(name).to_render()
+}
+
+#[renderer]
+fn render_result_name(name: ResultName) {
+ r_println!("Hello, {}", *name);
+}
+
+#[renderer]
+fn render_error_no_name_provided(_: ErrorNoNameProvided) {
+ // Prompt when no name is provided
+ r_println!("No name provided");
+}
+
+#[renderer]
+fn render_error_name_not_available(_: ErrorNameNotAvailable) {
+ // Prompt when name is already taken
+ r_println!("Name not available");
+}
+
+#[renderer]
+fn render_error_name_too_long(len: ErrorNameTooLong) {
+ // Prompt when name is too long, showing actual length
+ r_println!("Name too long: {} > 10", *len);
+}
+
+#[renderer]
+fn render_dispatcher_not_found(err: DispatcherNotFound) {
+ // Prompt when command is not found, showing the input command
+ r_println!("Command not found: \"{}\"", err.inner.join(" "));
+}
+
+gen_program!();
+
+fn main() {
+ let mut program = ThisProgram::new();
+ program.with_dispatcher(CMDHello);
+ program.exec_and_exit();
+}
diff --git a/examples/example-exit-code/src/main.rs b/examples/example-exit-code/src/main.rs
deleted file mode 100644
index c9b3c92..0000000
--- a/examples/example-exit-code/src/main.rs
+++ /dev/null
@@ -1,38 +0,0 @@
-//! `Mingling` Example - Exit Code
-//!
-//! This example demonstrates how to modify the program's exit code using `ExitCodeSetup`.
-//! By default, the program exits with code 0. This example shows:
-//! 1. Using `dispatcher!` to define an error command,
-//! 2. Using `chain!` to handle errors and set a custom exit code via `ProgramExitCode`,
-//! 3. Using `renderer!` to print an error message.
-//!
-//! # How to Run
-//! ```bash
-//! cargo run --manifest-path ./examples/example-exit-code/Cargo.toml -- error
-//! ```
-
-use mingling::prelude::*;
-use mingling::{res::ExitCode, setup::ExitCodeSetup};
-
-fn main() {
- let mut program = ThisProgram::new();
- program.with_dispatcher(ErrorCommand);
- program.with_setup(ExitCodeSetup::<ThisProgram>::default());
- program.exec_and_exit();
-}
-
-dispatcher!("error", ErrorCommand => ErrorEntry);
-pack!(ResultError = ());
-
-#[chain]
-fn handle_error_entry(_prev: ErrorEntry, ec: &mut ExitCode) -> Next {
- ec.exit_code = 1;
- return ResultError::default();
-}
-
-#[renderer]
-fn render_error(_prev: ResultError, ec: &ExitCode) {
- r_println!("Exit with exit code: {}", ec.exit_code);
-}
-
-gen_program!();
diff --git a/examples/example-exit-code/Cargo.lock b/examples/example-exitcode/Cargo.lock
index 2ba3ffe..19f60a6 100644
--- a/examples/example-exit-code/Cargo.lock
+++ b/examples/example-exitcode/Cargo.lock
@@ -3,7 +3,7 @@
version = 4
[[package]]
-name = "example-exit-code"
+name = "example-exitcode"
version = "0.1.0"
dependencies = [
"mingling",
diff --git a/examples/example-exit-code/Cargo.toml b/examples/example-exitcode/Cargo.toml
index a6c9c7c..58c800b 100644
--- a/examples/example-exit-code/Cargo.toml
+++ b/examples/example-exitcode/Cargo.toml
@@ -1,5 +1,5 @@
[package]
-name = "example-exit-code"
+name = "example-exitcode"
version = "0.1.0"
edition = "2024"
diff --git a/examples/example-exitcode/src/main.rs b/examples/example-exitcode/src/main.rs
new file mode 100644
index 0000000..178fa78
--- /dev/null
+++ b/examples/example-exitcode/src/main.rs
@@ -0,0 +1,62 @@
+//! Example Error Handling
+//!
+//! > This example demonstrates how to handle errors in Mingling, including custom error types and error rendering.
+//!
+//! Run:
+//! ```bash
+//! cargo run --manifest-path examples/example-exitcode/Cargo.toml --quiet -- hello Alice
+//! cargo run --manifest-path examples/example-exitcode/Cargo.toml --quiet -- hello
+//! ```
+//!
+//! Output:
+//! ```plaintext
+//! Hello, Alice
+//! No name provided (with exit code 1)
+//! ```
+
+use mingling::{prelude::*, res::ExitCode, setup::ExitCodeSetup};
+
+fn main() {
+ let mut program = ThisProgram::new();
+
+ // --------- IMPORTANT ---------
+ // Register `ExitCodeSetup` for the program to enable exit codes
+ program.with_setup(ExitCodeSetup::default());
+ // --------- IMPORTANT ---------
+
+ program.with_dispatcher(CMDHello);
+ program.exec_and_exit();
+}
+
+dispatcher!("hello", CMDHello => EntryHello);
+
+pack!(ErrorNoNameProvided = ());
+pack!(ResultName = String);
+
+#[chain]
+fn handle_hello(args: EntryHello) -> Next {
+ let Some(name) = args.inner.first().cloned() else {
+ // If no name is provided, pass ErrorNoNameProvided
+ return ErrorNoNameProvided::default().to_render();
+ };
+
+ // If the name is valid, pass ResultName
+ ResultName::new(name).to_render()
+}
+
+#[renderer]
+fn render_result_name(name: ResultName) {
+ r_println!("Hello, {}", *name);
+}
+
+// Define renderer, render error message _____________ Inject exit code resource
+// /
+#[renderer] // vvvvvvvvvvvvv
+fn render_error_no_name_provided(_: ErrorNoNameProvided, ec: &mut ExitCode) {
+ ec.exit_code = 1;
+
+ // Prompt when no name is provided
+ r_println!("No name provided (with exit code 1)");
+}
+
+gen_program!();
diff --git a/examples/example-general-renderer/Cargo.lock b/examples/example-general-renderer/Cargo.lock
index f0f0eea..e8de59f 100644
--- a/examples/example-general-renderer/Cargo.lock
+++ b/examples/example-general-renderer/Cargo.lock
@@ -3,36 +3,14 @@
version = 4
[[package]]
-name = "equivalent"
-version = "1.0.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
-
-[[package]]
name = "example-general-renderer"
-version = "0.0.1"
+version = "0.1.0"
dependencies = [
"mingling",
"serde",
]
[[package]]
-name = "hashbrown"
-version = "0.17.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51"
-
-[[package]]
-name = "indexmap"
-version = "2.14.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
-dependencies = [
- "equivalent",
- "hashbrown",
-]
-
-[[package]]
name = "itoa"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -67,7 +45,6 @@ dependencies = [
"just_fmt",
"serde",
"serde_json",
- "serde_yaml",
]
[[package]]
@@ -99,12 +76,6 @@ dependencies = [
]
[[package]]
-name = "ryu"
-version = "1.0.23"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
-
-[[package]]
name = "serde"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -136,9 +107,9 @@ dependencies = [
[[package]]
name = "serde_json"
-version = "1.0.149"
+version = "1.0.150"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
+checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
dependencies = [
"itoa",
"memchr",
@@ -148,19 +119,6 @@ dependencies = [
]
[[package]]
-name = "serde_yaml"
-version = "0.9.34+deprecated"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
-dependencies = [
- "indexmap",
- "itoa",
- "ryu",
- "serde",
- "unsafe-libyaml",
-]
-
-[[package]]
name = "size"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -184,12 +142,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
-name = "unsafe-libyaml"
-version = "0.2.11"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
-
-[[package]]
name = "zmij"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/examples/example-general-renderer/Cargo.toml b/examples/example-general-renderer/Cargo.toml
index 4664e88..7fd4fd5 100644
--- a/examples/example-general-renderer/Cargo.toml
+++ b/examples/example-general-renderer/Cargo.toml
@@ -1,13 +1,14 @@
[package]
name = "example-general-renderer"
-version = "0.0.1"
+version = "0.1.0"
edition = "2024"
[dependencies]
-mingling = { path = "../../mingling", features = [
- "parser",
+serde = { version = "1.0.228", features = ["derive"] }
+
+[dependencies.mingling]
+path = "../../mingling"
+features = [
"general_renderer",
- "json_serde_fmt",
- "yaml_serde_fmt",
-] }
-serde = { version = "1", features = ["derive"] }
+ "parser",
+]
diff --git a/examples/example-general-renderer/src/main.rs b/examples/example-general-renderer/src/main.rs
index 64d8d00..5f74815 100644
--- a/examples/example-general-renderer/src/main.rs
+++ b/examples/example-general-renderer/src/main.rs
@@ -1,30 +1,15 @@
-//! `Mingling` Example - General Renderer
+//! Example General Renderer
//!
-//! ## Step1 - Enable Feature
-//! Enable the `general_renderer` feature for mingling in `Cargo.toml`
-//! ```toml
-//! [dependencies]
-//! mingling = { version = "...", features = ["general_renderer", "parser"] }
-//! ```
-//!
-//! ## Step2 - Add Dependencies
-//! Add `serde` dependency to `Cargo.toml` for serialization support
-//! ```toml
-//! [dependencies]
-//! serde = { version = "1", features = ["derive"] }
-//! ```
+//! > This example demonstrates how to use the `general_renderer` feature to render data into structures such as json / yaml
//!
-//! ## Step3 - Write Code
-//! Write the following content into `main.rs`
-//!
-//! ## Step4 - Build and Run
+//! Run
//! ```bash
-//! cargo run --manifest-path ./examples/example-general-renderer/Cargo.toml -- render Bob 22
-//! cargo run --manifest-path ./examples/example-general-renderer/Cargo.toml -- render Bob 22 --json
-//! cargo run --manifest-path ./examples/example-general-renderer/Cargo.toml -- render Bob 22 --yaml
+//! cargo run --manifest-path examples/example-general-renderer/Cargo.toml --quiet -- render Bob 22
+//! cargo run --manifest-path examples/example-general-renderer/Cargo.toml --quiet -- render Bob 22 --json
+//! cargo run --manifest-path examples/example-general-renderer/Cargo.toml --quiet -- render Bob 22 --yaml
//! ```
//!
-//! Will print:
+//! Output:
//! ```plain
//! Bob is 22 years old
//! {"member_name":"Bob","member_age":22}
@@ -33,7 +18,7 @@
//! ```
use mingling::prelude::*;
-use mingling::{parser::Picker, setup::GeneralRendererSetup, Groupped};
+use mingling::{Groupped, parser::Picker, setup::GeneralRendererSetup};
use serde::Serialize;
dispatcher!("render", RenderCommand => RenderCommandEntry);
@@ -46,7 +31,13 @@ fn main() {
program.exec();
}
-// Manually implement Info struct
+// --------- IMPORTANT ---------
+// For beautiful output structure, do not use `pack!` to wrap the types that need to be output.
+// Instead, manually implement
+// ____________________ Implement serde::Serialize
+// / _________ Implement mingling::Groupped
+// | / to ensure Mingling can recognize the type
+// vvvvvvvvv vvvvvvvv
#[derive(Serialize, Groupped)]
struct Info {
#[serde(rename = "member_name")]
@@ -54,6 +45,12 @@ struct Info {
#[serde(rename = "member_age")]
age: i32,
}
+// This will output: {"member_name":"name","member_age":32} structure
+
+// If using pack!(Info = (String, i32));
+// Output: {"inner":["name", 32]}
+
+// --------- IMPORTANT ---------
#[chain]
fn parse_render(prev: RenderCommandEntry) -> Next {
diff --git a/examples/example-help/Cargo.lock b/examples/example-help/Cargo.lock
new file mode 100644
index 0000000..5e16d19
--- /dev/null
+++ b/examples/example-help/Cargo.lock
@@ -0,0 +1,76 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "example-help"
+version = "0.1.0"
+dependencies = [
+ "mingling",
+]
+
+[[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.9"
+dependencies = [
+ "mingling_core",
+ "mingling_macros",
+]
+
+[[package]]
+name = "mingling_core"
+version = "0.1.9"
+dependencies = [
+ "just_fmt",
+]
+
+[[package]]
+name = "mingling_macros"
+version = "0.1.9"
+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 = "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 = "unicode-ident"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
diff --git a/examples/example-help/Cargo.toml b/examples/example-help/Cargo.toml
new file mode 100644
index 0000000..01e6801
--- /dev/null
+++ b/examples/example-help/Cargo.toml
@@ -0,0 +1,7 @@
+[package]
+name = "example-help"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies]
+mingling = { path = "../../mingling" }
diff --git a/examples/example-help/src/main.rs b/examples/example-help/src/main.rs
new file mode 100644
index 0000000..9567c49
--- /dev/null
+++ b/examples/example-help/src/main.rs
@@ -0,0 +1,41 @@
+//! Example Help
+//!
+//! > This example demonstrates how to use the `#[help]` macro to generate help information,
+//! > enabling `--help` to work
+//!
+//! Run
+//! ```bash
+//! cargo run --manifest-path examples/example-help/Cargo.toml --quiet -- greet --help
+//! ```
+//!
+//! Output:
+//! ```plain
+//! Usage: greet <NAME>
+//! ```
+
+use mingling::{macros::help, prelude::*, setup::BasicProgramSetup};
+
+dispatcher!("greet", CMDGreet => EntryGreet);
+
+// Define help _________ When `program.user_context.help` is `true`
+// / the command will not enter `#[chain]` / `#[renderer]`
+#[help] // vvvvvvvvvv but instead enter this `#[help]` function
+fn help_greet(_prev: EntryGreet) {
+ r_println!("Usage: greet <NAME>");
+}
+
+fn main() {
+ let mut program = ThisProgram::new();
+
+ // --------- IMPORTANT ---------
+ // Add `BasicProgramSetup` to the program
+ // to enable `--help`, `--quiet`, and other built-in features
+ program.with_setup(BasicProgramSetup);
+ // --------- IMPORTANT ---------
+
+ program.with_dispatcher(CMDGreet);
+
+ program.exec_and_exit();
+}
+
+gen_program!();
diff --git a/examples/example-hook/Cargo.lock b/examples/example-hook/Cargo.lock
new file mode 100644
index 0000000..b6717bb
--- /dev/null
+++ b/examples/example-hook/Cargo.lock
@@ -0,0 +1,76 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "example-hook"
+version = "0.1.0"
+dependencies = [
+ "mingling",
+]
+
+[[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.9"
+dependencies = [
+ "mingling_core",
+ "mingling_macros",
+]
+
+[[package]]
+name = "mingling_core"
+version = "0.1.9"
+dependencies = [
+ "just_fmt",
+]
+
+[[package]]
+name = "mingling_macros"
+version = "0.1.9"
+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 = "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 = "unicode-ident"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
diff --git a/examples/example-hook/Cargo.toml b/examples/example-hook/Cargo.toml
new file mode 100644
index 0000000..453fae6
--- /dev/null
+++ b/examples/example-hook/Cargo.toml
@@ -0,0 +1,7 @@
+[package]
+name = "example-hook"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies]
+mingling = { path = "../../mingling" }
diff --git a/examples/example-hook/src/main.rs b/examples/example-hook/src/main.rs
new file mode 100644
index 0000000..373c76d
--- /dev/null
+++ b/examples/example-hook/src/main.rs
@@ -0,0 +1,71 @@
+//! Example Hook
+//!
+//! > This example demonstrates how to use Mingling's hook system to obtain debugging information during program execution
+//!
+//! Run:
+//! ```base
+//! cargo run --manifest-path examples/example-hook/Cargo.toml --quiet -- greet Alice
+//! ```
+//!
+//! Output:
+//! ```plaintext
+//! [DEBUG] Program is begin
+//! [DEBUG] Pre dispatch: ["greet", "Alice"]
+//! [DEBUG] Post dispatch: EntryGreet
+//! [DEBUG] Pre chain: EntryGreet
+//! [DEBUG] Post chain: ResultName
+//! [DEBUG] Pre render: ResultName
+//! [DEBUG] Post render
+//! [DEBUG] Program end
+//! Hello, Alice!
+//! ```
+
+use mingling::{hook::ProgramHook, prelude::*};
+
+dispatcher!("greet", CMDGreet => EntryGreet);
+
+fn main() {
+ let mut program = ThisProgram::new();
+
+ // --------- IMPORTANT ---------
+ program.with_hook(
+ ProgramHook::<ThisProgram>::empty()
+ .on_begin(|| println!("[DEBUG] Program is begin"))
+ .on_pre_dispatch(|args| println!("[DEBUG] Pre dispatch: {:?}", args))
+ .on_post_dispatch(|c: &_| println!("[DEBUG] Post dispatch: {:?}", c))
+ .on_pre_chain(|c: &_, _| {
+ println!("[DEBUG] Pre chain: {}", c);
+ })
+ .on_post_chain(|any_output| println!("[DEBUG] Post chain: {}", any_output.member_id))
+ .on_finish(|| {
+ println!("[DEBUG] Loop end");
+ 0 // Override exit code
+ })
+ .on_pre_render(|c: &_, _| println!("[DEBUG] Pre render: {}", c))
+ .on_post_render(|_| println!("[DEBUG] Post render")),
+ );
+ // --------- IMPORTANT ---------
+
+ program.with_dispatcher(CMDGreet);
+ program.exec_and_exit();
+}
+
+pack!(ResultName = String);
+
+#[chain]
+fn handle_greet(args: EntryGreet) -> Next {
+ let name: ResultName = args
+ .inner
+ .first()
+ .cloned()
+ .unwrap_or_else(|| "World".to_string())
+ .into();
+ name
+}
+
+#[renderer]
+fn render_name(name: ResultName) {
+ r_println!("Hello, {}!", *name);
+}
+
+gen_program!();
diff --git a/examples/example-panic-unwind/Cargo.lock b/examples/example-panic-unwind/Cargo.lock
new file mode 100644
index 0000000..d6af75d
--- /dev/null
+++ b/examples/example-panic-unwind/Cargo.lock
@@ -0,0 +1,83 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "example-panic-unwind"
+version = "0.1.0"
+dependencies = [
+ "mingling",
+]
+
+[[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.9"
+dependencies = [
+ "mingling_core",
+ "mingling_macros",
+ "size",
+]
+
+[[package]]
+name = "mingling_core"
+version = "0.1.9"
+dependencies = [
+ "just_fmt",
+]
+
+[[package]]
+name = "mingling_macros"
+version = "0.1.9"
+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 = "size"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b6709c7b6754dca1311b3c73e79fcce40dd414c782c66d88e8823030093b02b"
+
+[[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 = "unicode-ident"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
diff --git a/examples/example-panic-unwind/Cargo.toml b/examples/example-panic-unwind/Cargo.toml
new file mode 100644
index 0000000..5d1c242
--- /dev/null
+++ b/examples/example-panic-unwind/Cargo.toml
@@ -0,0 +1,16 @@
+[package]
+name = "example-panic-unwind"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies.mingling]
+path = "../../mingling"
+features = ["parser"]
+
+# Enable panic unwinding in release builds
+[profile.release]
+panic = "unwind"
+
+# Enable panic unwinding in dev builds
+[profile.dev]
+panic = "unwind"
diff --git a/examples/example-panic-unwind/src/main.rs b/examples/example-panic-unwind/src/main.rs
new file mode 100644
index 0000000..18cf4d6
--- /dev/null
+++ b/examples/example-panic-unwind/src/main.rs
@@ -0,0 +1,56 @@
+//! Example Panic Unwind
+//!
+//! > This example introduces how to catch Panic in the Mingling program loop
+//!
+//! Run:
+//! ```bash
+//! cargo run --manifest-path examples/example-panic-unwind/Cargo.toml --quiet -- panic
+//! cargo run --manifest-path examples/example-panic-unwind/Cargo.toml --quiet -- panic OhMyGod
+//! ```
+//!
+//! Output:
+//! ```plaintext
+//! Program not panic
+//! Program panic: OhMyGod
+//! OhMyGod
+//! ```
+
+use mingling::{hook::ProgramHook, prelude::*};
+
+dispatcher!("panic", CMDPanic => EntryPanic);
+pack!(NotPanic = ());
+
+fn main() {
+ let mut program = ThisProgram::new();
+ program.with_dispatcher(CMDPanic);
+
+ // --------- IMPORTANT ---------
+ // Enable silence_panic to suppress automatic Panic output
+ program.stdout_setting.silence_panic = true;
+
+ // Define a hook to output &ProgramPanic when a Panic occurs
+ program
+ .with_hook(ProgramHook::empty().on_exec_panic(|info| println!("Program panic: {}", info)));
+ // --------- IMPORTANT ---------
+
+ program.exec();
+}
+
+#[chain]
+fn handle_panic(prev: EntryPanic) -> Next {
+ let panic_info = prev.pick::<Option<String>>(()).unpack();
+ match panic_info {
+ Some(s) => {
+ // Panic happens here, will be caught
+ panic!("{}", s)
+ }
+ None => NotPanic::default(),
+ }
+}
+
+#[renderer]
+fn render(_: NotPanic) {
+ r_println!("Program not panic");
+}
+
+gen_program!();
diff --git a/examples/example-picker/Cargo.toml b/examples/example-picker/Cargo.toml
deleted file mode 100644
index d8e9c86..0000000
--- a/examples/example-picker/Cargo.toml
+++ /dev/null
@@ -1,8 +0,0 @@
-[package]
-name = "example-picker"
-version = "0.0.1"
-edition = "2024"
-
-[dependencies]
-mingling = { path = "../../mingling", features = ["parser"] }
-tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros"] }
diff --git a/examples/example-picker/src/main.rs b/examples/example-picker/src/main.rs
deleted file mode 100644
index 651edb3..0000000
--- a/examples/example-picker/src/main.rs
+++ /dev/null
@@ -1,60 +0,0 @@
-//! `Mingling` Example - Picker
-//!
-//! ## Step1 - Enable Feature
-//! Enable the `parser` feature for mingling in `Cargo.toml`
-//! ```toml
-//! [dependencies]
-//! mingling = { version = "...", features = ["parser"] }
-//! ```
-//!
-//! ## Step2 - Write Code
-//! Write the following content into `main.rs`
-//!
-//! ## Step3 - Build and Run
-//! ```bash
-//! cargo run --manifest-path ./examples/example-picker/Cargo.toml -- pick Bob
-//! cargo run --manifest-path ./examples/example-picker/Cargo.toml -- pick Bob --age -15
-//! cargo run --manifest-path ./examples/example-picker/Cargo.toml -- pick --age 99
-//! ```
-
-use mingling::prelude::*;
-
-dispatcher!("pick", PickCommand => PickEntry);
-
-fn main() {
- let mut program = ThisProgram::new();
- program.with_dispatcher(PickCommand);
- program.exec();
-}
-
-pack!(NoNameProvided = ());
-pack!(ParsedPickInput = (i32, String));
-
-#[chain]
-fn parse(prev: PickEntry) -> Next {
- let picked = prev
- // First extract the named argument
- .pick_or("--age", 20)
- .after(|n: i32| n.clamp(0, 100))
- // Then sequentially extract the remaining arguments
- .pick_or_route((), NoNameProvided::default().to_render())
- .unpack();
-
- match picked {
- Ok(value) => ParsedPickInput::new(value).to_render(),
- Err(e) => e,
- }
-}
-
-#[renderer]
-fn render_parsed_pick_input(prev: ParsedPickInput) {
- let (age, name) = prev.inner;
- r_println!("Picked: name = {}, age = {}", name, age);
-}
-
-#[renderer]
-fn render_no_name_input(_prev: NoNameProvided) {
- r_println!("No name provided.");
-}
-
-gen_program!();
diff --git a/examples/example-repl/Cargo.lock b/examples/example-repl-basic/Cargo.lock
index adde389..854e482 100644
--- a/examples/example-repl/Cargo.lock
+++ b/examples/example-repl-basic/Cargo.lock
@@ -3,8 +3,8 @@
version = 4
[[package]]
-name = "example-repl"
-version = "0.0.1"
+name = "example-repl-basic"
+version = "0.1.0"
dependencies = [
"just_fmt",
"mingling",
diff --git a/examples/example-repl-basic/Cargo.toml b/examples/example-repl-basic/Cargo.toml
new file mode 100644
index 0000000..c358a73
--- /dev/null
+++ b/examples/example-repl-basic/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+name = "example-repl-basic"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies.mingling]
+path = "../../mingling"
+features = ["repl", "parser"]
+
+[dependencies]
+just_fmt = "0.1.2"
diff --git a/examples/example-repl/src/main.rs b/examples/example-repl-basic/src/main.rs
index 2d8d9b0..f02c2f8 100644
--- a/examples/example-repl/src/main.rs
+++ b/examples/example-repl-basic/src/main.rs
@@ -1,3 +1,12 @@
+//! Example REPL Basic
+//!
+//! > This example demonstrates how to develop a REPL program using the `repl` feature
+//!
+//! Run:
+//! ```bash
+//! cargo run --manifest-path examples/example-repl-basic/Cargo.toml --quiet
+//! ```
+
use mingling::{
REPL,
hook::ProgramHook,
@@ -9,11 +18,11 @@ use std::{env::current_dir, path::PathBuf};
// Resource to store the current directory
#[derive(Clone)]
-struct CurrentDir {
+struct ResCurrentDir {
dir: PathBuf,
}
-impl Default for CurrentDir {
+impl Default for ResCurrentDir {
fn default() -> Self {
Self {
dir: current_dir().unwrap(),
@@ -24,20 +33,26 @@ impl Default for CurrentDir {
fn main() {
let mut program = ThisProgram::new();
- // Add resource
- program.with_resource(CurrentDir::default());
+ // Resource
+ program.with_resource(ResCurrentDir::default());
- // Add dispatchers
+ // Dispatchers
program.with_dispatcher(ChangeDirectoryCommand);
program.with_dispatcher(ListCommand);
program.with_dispatcher(ExitCommand);
program.with_dispatcher(ClearCommand);
- // Add setups
+ // Setups
+ // Enable basic std::io::stdin().read_line(&mut input)
program.with_setup(BasicREPLReadlineSetup);
+
+ // Enable basic output, using println! after Renderer finishes drawing
program.with_setup(BasicREPLOutputSetup);
+
+ // Enable basic Prompt display, with custom display logic
program.with_setup(BasicREPLPromptSetup::func(|| {
- let res = this::<ThisProgram>().res::<CurrentDir>().unwrap();
+ // Get the ResCurrentDir resource from the program
+ let res = this::<ThisProgram>().res::<ResCurrentDir>().unwrap();
let dir_str: String = res.dir.to_string_lossy().into();
let prompt = format!(
"{}> ",
@@ -83,9 +98,11 @@ fn parse_cd_args(prev: ChangeDirectoryEntry) -> Next {
// Execute directory change
#[chain]
-fn handle_cd(prev: StateChangeDirectory, current_dir: &mut CurrentDir) -> Next {
+fn handle_cd(prev: StateChangeDirectory, current_dir: &mut ResCurrentDir) -> Next {
+ use just_fmt::fmt_path::fmt_path;
+
let join = prev.inner;
- let new_dir = just_fmt::fmt_path::fmt_path(current_dir.dir.join(join)).unwrap_or_default();
+ let new_dir = fmt_path(current_dir.dir.join(join)).unwrap_or_default();
// If the path is not found, route to error handling
if !new_dir.exists() {
@@ -98,7 +115,7 @@ fn handle_cd(prev: StateChangeDirectory, current_dir: &mut CurrentDir) -> Next {
// Get directory contents via the CurrentDir resource
#[chain]
-fn handle_ls(_prev: ListEntry, current_dir: &CurrentDir) -> Next {
+fn handle_ls(_prev: ListEntry, current_dir: &ResCurrentDir) -> Next {
let dir = &current_dir.dir;
let entries: Vec<String> = std::fs::read_dir(dir)
.into_iter()
diff --git a/examples/example-repl/Cargo.toml b/examples/example-repl/Cargo.toml
deleted file mode 100644
index 34b85e3..0000000
--- a/examples/example-repl/Cargo.toml
+++ /dev/null
@@ -1,8 +0,0 @@
-[package]
-name = "example-repl"
-version = "0.0.1"
-edition = "2024"
-
-[dependencies]
-mingling = { path = "../../mingling", features = ["repl", "parser"] }
-just_fmt = "0.1.2"
diff --git a/examples/example-resources/Cargo.lock b/examples/example-resources/Cargo.lock
index 63467d0..bab1a19 100644
--- a/examples/example-resources/Cargo.lock
+++ b/examples/example-resources/Cargo.lock
@@ -4,7 +4,7 @@ version = 4
[[package]]
name = "example-resources"
-version = "0.0.1"
+version = "0.1.0"
dependencies = [
"mingling",
]
diff --git a/examples/example-resources/Cargo.toml b/examples/example-resources/Cargo.toml
index ca783f3..2655654 100644
--- a/examples/example-resources/Cargo.toml
+++ b/examples/example-resources/Cargo.toml
@@ -1,7 +1,8 @@
[package]
name = "example-resources"
-version = "0.0.1"
+version = "0.1.0"
edition = "2024"
-[dependencies]
-mingling = { path = "../../mingling", features = ["parser"] }
+[dependencies.mingling]
+path = "../../mingling"
+features = ["parser"]
diff --git a/examples/example-resources/src/main.rs b/examples/example-resources/src/main.rs
index 9d8dde6..ae2c8f2 100644
--- a/examples/example-resources/src/main.rs
+++ b/examples/example-resources/src/main.rs
@@ -1,57 +1,64 @@
-//! `Mingling` Example - Global Resource Injection
+//! Example Resource Injection
//!
-//! This example demonstrates how to use global resource injection in `#[chain]` functions.
-//! You can inject both immutable (`&T`) and mutable (`&mut T`) references to global resources.
+//! > This example demonstrates how to read and write the program's global state using Mingling's resource system
//!
-//! # How to Run
+//! Run:
//! ```bash
-//! cargo run --manifest-path ./examples/example-resources/Cargo.toml -- setup
+//! cargo run --manifest-path examples/example-resources/Cargo.toml --quiet current
+//! cargo run --manifest-path examples/example-resources/Cargo.toml --quiet modify-current src
//! ```
+//!
+//! Output:
+//! ```plaintext
+//! Current directory: /home/alice/mingling
+//! Current directory: /home/alice/mingling/src
+//! ```
+
+use std::path::PathBuf;
use mingling::prelude::*;
-use std::{env::current_dir, path::PathBuf};
-// Define a resource for storing global state
+// Create resource
+// ______________ Resource needs to
+// / / implement the following two traits
+// vvvvvvv vvvvv
#[derive(Default, Clone)]
-pub struct MyResource {
+struct ResCurrentDir {
current_dir: PathBuf,
}
fn main() {
let mut program = ThisProgram::new();
- // Add the resource to the program
- program.with_resource(MyResource::default());
+ // --------- IMPORTANT ---------
+ // Use `with_resource` to inject a singleton into the program
+ program.with_resource(ResCurrentDir {
+ current_dir: std::env::current_dir().unwrap(),
+ });
+ // --------- IMPORTANT ---------
- program.with_dispatcher(SetupCommand);
+ program.with_dispatchers((CMDCurrent, CMDModifyCurrent));
program.exec_and_exit();
}
-dispatcher!("setup", SetupCommand => SetupEntry);
-pack!(StateRead = ());
-pack!(ResultCurrentDir = PathBuf);
-
-#[chain]
-fn setup(
- _prev: SetupEntry,
- resource: &mut MyResource, // Import the resource into `setup`
-) -> Next {
- // Set the global resource
- resource.current_dir = current_dir().unwrap();
-
- StateRead::default()
-}
+dispatcher!("current", CMDCurrent => EntryCurrent);
+dispatcher!("modify-current", CMDModifyCurrent => EntryModifyCurrent);
-#[chain]
-fn read(_prev: StateRead, resource: &MyResource) -> Next {
- // Read the global resource
- let current_dir = resource.current_dir.clone();
- ResultCurrentDir::new(current_dir).to_render()
+// Define chain for modifying current directory _________________ Injected muttable resource
+// /
+#[chain] // vvvvvvvvvvvvvvvvvv
+fn render_modify_current(args: EntryModifyCurrent, current_dir: &mut ResCurrentDir) -> Next {
+ current_dir.current_dir = current_dir
+ .current_dir
+ .join(args.pick::<String>(()).unpack());
+ EntryCurrent::default()
}
-#[renderer]
-fn render_current_dir(dir: ResultCurrentDir) {
- r_println!("Current dir: {}", dir.to_string_lossy())
+// Define renderer for output current path _____________ Injected resource
+// /
+#[renderer] // vvvvvvvvvvvvvv
+fn render_current(_: EntryCurrent, current_dir: &ResCurrentDir) {
+ r_println!("Current directory: {}", current_dir.current_dir.display());
}
gen_program!();
diff --git a/examples/example-setup/Cargo.lock b/examples/example-setup/Cargo.lock
new file mode 100644
index 0000000..6b8f0d4
--- /dev/null
+++ b/examples/example-setup/Cargo.lock
@@ -0,0 +1,76 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "example-setup"
+version = "0.1.0"
+dependencies = [
+ "mingling",
+]
+
+[[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.9"
+dependencies = [
+ "mingling_core",
+ "mingling_macros",
+]
+
+[[package]]
+name = "mingling_core"
+version = "0.1.9"
+dependencies = [
+ "just_fmt",
+]
+
+[[package]]
+name = "mingling_macros"
+version = "0.1.9"
+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 = "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 = "unicode-ident"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
diff --git a/examples/example-setup/Cargo.toml b/examples/example-setup/Cargo.toml
new file mode 100644
index 0000000..12364aa
--- /dev/null
+++ b/examples/example-setup/Cargo.toml
@@ -0,0 +1,7 @@
+[package]
+name = "example-setup"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies]
+mingling = { path = "../../mingling" }
diff --git a/examples/example-setup/src/main.rs b/examples/example-setup/src/main.rs
new file mode 100644
index 0000000..c445276
--- /dev/null
+++ b/examples/example-setup/src/main.rs
@@ -0,0 +1,33 @@
+//! Example Setup
+//!
+//! > This example demonstrates how to build a custom Setup for modular management of project components
+
+use mingling::{Program, macros::program_setup, prelude::*};
+
+fn main() {
+ let mut program = ThisProgram::new();
+
+ // --------- IMPORTANT ---------
+ // Introduce `CustomSetup` generated by `custom_setup`
+ program.with_setup(CustomSetup);
+ // --------- IMPORTANT ---------
+
+ program.exec_and_exit();
+}
+
+// --------- IMPORTANT ---------
+// Define `CustomSetup` (inferred from `custom_setup`)
+// Package part of the program construction logic into this type for modular management
+#[program_setup]
+fn custom_setup(program: &mut Program<ThisProgram>) {
+ program.with_dispatchers((CMD1, CMD2, CMD3, CMD4, CMD5));
+}
+// --------- IMPORTANT ---------
+
+dispatcher!("1", CMD1 => Entry1);
+dispatcher!("2", CMD2 => Entry2);
+dispatcher!("3", CMD3 => Entry3);
+dispatcher!("4", CMD4 => Entry4);
+dispatcher!("5", CMD5 => Entry5);
+
+gen_program!();
diff --git a/examples/example-unit-test/Cargo.lock b/examples/example-unit-test/Cargo.lock
new file mode 100644
index 0000000..712440e
--- /dev/null
+++ b/examples/example-unit-test/Cargo.lock
@@ -0,0 +1,76 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "example-unit-test"
+version = "0.1.0"
+dependencies = [
+ "mingling",
+]
+
+[[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.9"
+dependencies = [
+ "mingling_core",
+ "mingling_macros",
+]
+
+[[package]]
+name = "mingling_core"
+version = "0.1.9"
+dependencies = [
+ "just_fmt",
+]
+
+[[package]]
+name = "mingling_macros"
+version = "0.1.9"
+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 = "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 = "unicode-ident"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
diff --git a/examples/example-unit-test/Cargo.toml b/examples/example-unit-test/Cargo.toml
new file mode 100644
index 0000000..4a82503
--- /dev/null
+++ b/examples/example-unit-test/Cargo.toml
@@ -0,0 +1,7 @@
+[package]
+name = "example-unit-test"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies]
+mingling = { path = "../../mingling" }
diff --git a/examples/example-unit-test/src/main.rs b/examples/example-unit-test/src/main.rs
new file mode 100644
index 0000000..d0e1b90
--- /dev/null
+++ b/examples/example-unit-test/src/main.rs
@@ -0,0 +1,122 @@
+//! Example Unit Test
+//!
+//! > This example shows how to write unit tests for Chain and Renderer in Mingling
+//!
+//! ```bash
+//! cargo test --manifest-path examples/example-unit-test/Cargo.toml
+//! ```
+
+use mingling::prelude::*;
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use mingling::{assert_member_id, assert_render_result};
+
+ // --------- IMPORTANT ---------
+ #[test]
+ fn test_handle_hello() {
+ let hello_without_args = handle_hello(entry!()).into();
+ assert_render_result!(hello_without_args);
+ assert_member_id!(hello_without_args, ThisProgram::ErrorNoNameProvided);
+
+ let hello_with_registered_name = handle_hello(entry!("Alice")).into();
+ assert_render_result!(hello_with_registered_name);
+ assert_member_id!(
+ hello_with_registered_name,
+ ThisProgram::ErrorNameNotAvailable
+ );
+
+ let hello_with_long_name = handle_hello(entry!("It's a VeryLongName")).into();
+ assert_render_result!(hello_with_long_name);
+ assert_member_id!(hello_with_long_name, ThisProgram::ErrorNameTooLong);
+
+ let hello_with_valid_name = handle_hello(entry!("Peter")).into();
+ assert_render_result!(hello_with_valid_name);
+ }
+
+ #[test]
+ fn test_render_result_name() {
+ let r = render_result_name(ResultName::new("Peter".into()));
+ assert_eq!(r, "Hello, Peter!\n")
+ }
+
+ #[test]
+ fn test_render_error_no_name_provided() {
+ let r = render_error_no_name_provided(ErrorNoNameProvided::default());
+ assert_eq!(r, "No name provided\n")
+ }
+
+ #[test]
+ fn test_render_error_name_not_available() {
+ let r = render_error_name_not_available(ErrorNameNotAvailable::default());
+ assert_eq!(r, "Name not available\n")
+ }
+
+ #[test]
+ fn test_render_error_name_too_long() {
+ let r = render_error_name_too_long(ErrorNameTooLong::new(17));
+ assert_eq!(r, "Name too long: 17 > 10\n")
+ }
+ // --------- IMPORTANT ---------
+}
+
+dispatcher!("hello", CMDHello => EntryHello);
+
+pack!(ErrorNoNameProvided = ());
+pack!(ErrorNameTooLong = u16);
+pack!(ErrorNameNotAvailable = ());
+
+pack!(ResultName = String);
+
+static VEC_REGISTERED_NAMES: &[&str] = &["Alice", "Bob", "Charlie", "David", "Eve"];
+
+#[chain]
+fn handle_hello(args: EntryHello) -> Next {
+ let Some(name) = args.inner.first().cloned() else {
+ return ErrorNoNameProvided::default().to_render();
+ };
+
+ if name.len() > 10 {
+ return ErrorNameTooLong::new(name.len() as u16).to_render();
+ }
+
+ if VEC_REGISTERED_NAMES.contains(&name.as_str()) {
+ return ErrorNameNotAvailable::default().to_render();
+ }
+
+ ResultName::new(name).to_render()
+}
+
+#[renderer]
+fn render_result_name(name: ResultName) -> String {
+ r_println!("Hello, {}!", *name);
+}
+
+#[renderer]
+fn render_error_no_name_provided(_: ErrorNoNameProvided) -> String {
+ r_println!("No name provided");
+}
+
+#[renderer]
+fn render_error_name_not_available(_: ErrorNameNotAvailable) -> String {
+ r_println!("Name not available");
+}
+
+#[renderer]
+fn render_error_name_too_long(len: ErrorNameTooLong) -> String {
+ r_println!("Name too long: {} > 10", *len);
+}
+
+#[renderer]
+fn render_dispatcher_not_found(err: DispatcherNotFound) {
+ r_println!("Command not found: \"{}\"", err.inner.join(" "));
+}
+
+gen_program!();
+
+fn main() {
+ let mut program = ThisProgram::new();
+ program.with_dispatcher(CMDHello);
+ program.exec_and_exit();
+}
diff --git a/examples/test-examples.toml b/examples/test-examples.toml
index a63f566..68285fd 100644
--- a/examples/test-examples.toml
+++ b/examples/test-examples.toml
@@ -1,17 +1,112 @@
-[[test.example-async]]
-command = "hello World"
+[[test.example-basic]]
+command = "greet"
expect.exit-code = 0
expect.result = "Hello, World!"
[[test.example-basic]]
-command = "hello World"
+command = "greet Alice"
expect.exit-code = 0
-expect.result = "Hello, World!"
+expect.result = "Hello, Alice!"
+
+[[test.example-exitcode]]
+command = "hello Alice"
+expect.exit-code = 0
+expect.result = "Hello, Alice"
-[[test.example-exit-code]]
-command = "error"
+[[test.example-exitcode]]
+command = "hello"
expect.exit-code = 1
-expect.result = "Exit with exit code: 1"
+expect.result = "No name provided (with exit code 1)"
+
+[[test.example-error-handling]]
+command = "hello"
+expect.exit-code = 0
+expect.result = "No name provided"
+
+[[test.example-error-handling]]
+command = "hello MyBestFriendAlice"
+expect.exit-code = 0
+expect.result = "Name too long: 17 > 10"
+
+[[test.example-error-handling]]
+command = "hello Alice"
+expect.exit-code = 0
+expect.result = "Name not available"
+
+[[test.example-error-handling]]
+command = "hello Peter"
+expect.exit-code = 0
+expect.result = "Hello, Peter"
+
+[[test.example-error-handling]]
+command = "hallo"
+expect.exit-code = 0
+expect.result = "Command not found: \"hallo\""
+
+[[test.example-argument-parse]]
+command = "transfer"
+expect.exit-code = 0
+expect.result = "file: (1048576)"
+
+[[test.example-argument-parse]]
+command = "transfer --dir src"
+expect.exit-code = 0
+expect.result = "dir: src (1048576)"
+
+[[test.example-argument-parse]]
+command = "transfer --size 500 myfile.txt"
+expect.exit-code = 0
+expect.result = "file: myfile.txt (500)"
+
+[[test.example-argument-parse]]
+command = "strict-transfer README.md"
+expect.exit-code = 0
+expect.result = "file: README.md (1048576)"
+
+[[test.example-argument-parse]]
+command = "strict-transfer"
+expect.exit-code = 0
+expect.result = "Error: name is not provided"
+
+[[test.example-async-support]]
+command = "download readme.txt"
+expect.exit-code = 0
+expect.result = "\"readme.txt\" downloaded."
+
+[[test.example-custom-pickable]]
+command = "connect 192.168.1.1:8080"
+expect.exit-code = 0
+expect.result = "Connected to \"192.168.1.1:8080\""
+
+[[test.example-custom-pickable]]
+command = "connect"
+expect.exit-code = 0
+expect.result = "Failed to parse address"
+
+[[test.example-custom-pickable]]
+command = "connect invalid"
+expect.exit-code = 0
+expect.result = "Failed to parse address"
+
+[[test.example-dispatch-tree]]
+command = "cmd5"
+expect.exit-code = 0
+expect.result = "It's works!"
+
+[[test.example-enum-tag]]
+command = "lang-select"
+expect.exit-code = 0
+expect.result = "Selected: Rust"
+
+[[test.example-enum-tag]]
+command = "lang-select Python"
+expect.exit-code = 0
+expect.result = "Selected: Python"
+
+[[test.example-enum-tag]]
+command = "lang-select OCaml"
+expect.exit-code = 0
+expect.result = "Selected: OCaml"
[[test.example-general-renderer]]
command = "render Bob 22"
@@ -23,32 +118,67 @@ command = "render Bob 22 --json"
expect.exit-code = 0
expect.result = "{\"member_name\":\"Bob\",\"member_age\":22}"
-[[test.example-general-renderer]]
-command = "render Bob 22 --yaml"
+[[test.example-help]]
+command = "greet"
+expect.exit-code = 0
+expect.result = ""
+
+[[test.example-help]]
+command = "greet --help"
expect.exit-code = 0
-expect.result = "member_name: Bob\nmember_age: 22"
+expect.result = "Usage: greet <NAME>"
-[[test.example-picker]]
-command = "pick Bob"
+[[test.example-hook]]
+command = "greet"
expect.exit-code = 0
-expect.result = "Picked: name = Bob, age = 20"
+expect.result = "Hello, World!"
+
+[[test.example-hook]]
+command = "greet Alice"
+expect.exit-code = 0
+expect.result = "Hello, Alice!"
+
+[[test.example-panic-unwind]]
+command = "panic"
+expect.exit-code = 0
+expect.result = "Program not panic"
+
+[[test.example-panic-unwind]]
+command = "panic something_went_wrong"
+expect.exit-code = 0
+expect.result = "Program panic: something_went_wrong"
-[[test.example-picker]]
-command = "pick Bob --age -15"
+[[test.example-resources]]
+command = "current"
expect.exit-code = 0
-expect.result = "Picked: name = Bob, age = 0"
+expect.result = "Current directory:"
-[[test.example-picker]]
-command = "pick --age 99"
+[[test.example-setup]]
+command = "1"
expect.exit-code = 0
-expect.result = "No name provided."
+expect.result = ""
-[[test.example-picker]]
-command = "pick"
+[[test.example-setup]]
+command = "2"
expect.exit-code = 0
-expect.result = "No name provided."
+expect.result = ""
-[[test.example-picker]]
-command = "pick --age 150"
+[[test.example-setup]]
+command = "3"
expect.exit-code = 0
-expect.result = "No name provided."
+expect.result = ""
+
+[[test.example-setup]]
+command = "4"
+expect.exit-code = 0
+expect.result = ""
+
+[[test.example-setup]]
+command = "5"
+expect.exit-code = 0
+expect.result = ""
+
+[[test.example-completion]]
+command = "greet World --repeat 1"
+expect.exit-code = 0
+expect.result = "Hello, World!"
diff --git a/mingling/src/example_docs.rs b/mingling/src/example_docs.rs
index bb65280..ce65a84 100644
--- a/mingling/src/example_docs.rs
+++ b/mingling/src/example_docs.rs
@@ -1,83 +1,228 @@
// Auto generated
-/// `Mingling` Example - Async
+/// Example Argument Parse
///
-/// After enabling the `async` feature:
-/// 1. The `chain!` macro will support using **async** functions,
-/// 2. The `exec` function of `Program` will return a `Future` for you to use with an async runtime
+/// > This example demonstrates how to use the `parser` feature to parse user input
///
-/// ## Enable Feature
-/// Enable the `async` feature for mingling in `Cargo.toml`
-/// ```toml
-/// [dependencies]
-/// mingling = { version = "...", features = ["async"] }
+/// Run:
+/// ```bash
+/// cargo run --manifest-path examples/example-argument-parse/Cargo.toml --quiet -- transfer README.md --size 32kib
+/// cargo run --manifest-path examples/example-argument-parse/Cargo.toml --quiet -- transfer src/ --dir
+/// cargo run --manifest-path examples/example-argument-parse/Cargo.toml --quiet -- strict-transfer README.md
+/// cargo run --manifest-path examples/example-argument-parse/Cargo.toml --quiet -- strict-transfer --dir
/// ```
///
-/// # How to Run
-/// ```bash
-/// cargo run --manifest-path ./examples/example-async/Cargo.toml -- hello World
+/// Output:
+/// ```plaintext
+/// file: README.md (32768)
+/// dir: src/ (1048576)
+/// file: README.md (1048576)
+/// Error: name is not provided
/// ```
///
/// Cargo.toml
/// ```ignore
/// [package]
-/// name = "example-async"
-/// version = "0.0.1"
+/// name = "example-argument-parse"
+/// version = "0.1.0"
/// edition = "2024"
///
-/// [dependencies]
-/// tokio = { version = "1", features = ["full"] }
-/// mingling = { path = "../../mingling", features = ["async"] }
+/// [dependencies.mingling]
+/// path = "../../mingling"
+///
+/// # Enable `parser` features
+/// features = ["parser"]
/// ```
///
/// main.rs
/// ```ignore
-/// use mingling::prelude::*;
+/// use mingling::{macros::route, prelude::*};
+///
+/// dispatcher!("transfer", CMDTransfer => EntryTransfer);
+/// dispatcher!("strict-transfer", CMDStrictTransfer => EntryStrictTransfer);
+///
+/// pack!(ResultFile = (bool, usize, String)); // (IsDir, Size, Name)
+///
+/// #[chain]
+/// fn handle_transfer_parse(args: EntryTransfer) -> Next {
+/// // --------- IMPORTANT ---------
+/// // First parse flag arguments (like --dir/-D), then positional arguments
+/// let result: ResultFile = args
+/// // Name --dir --size 20mib
+/// // ^^^^^^^^^^^^_ first
+/// .pick::<bool>(["--dir", "-D"])
+/// // Name --dir
+/// // ^^^^^_ second (or `-D`)
+/// .pick_or::<usize>("--size", 1024 * 1024_usize)
+/// // Name
+/// // ^^^^_ finally, pick positional arg
+/// .pick::<String>(())
+/// .after(|str| str.trim().replace(" ", ""))
+/// // Unpack to tuple (is_dir, size, name)
+/// .unpack()
+/// // Convert into ResultFile
+/// .into();
+/// // --------- IMPORTANT ---------
+/// result
+/// }
+///
+/// pack!(ErrorNoNameProvided = ());
+///
+/// #[chain]
+/// fn handle_strict_transfer_parse(args: EntryStrictTransfer) -> Next {
+/// // --------- IMPORTANT ---------
+/// // Strict parsing: error immediately if the name is not provided
+/// let result: ResultFile = route! { // Use `route!` to wrap a Picker that contains `or_route`
+/// args
+/// .pick::<bool>(["--dir", "-D"])
+/// .pick_or::<usize>("--size", 1024 * 1024_usize)
+/// // Finally parse the positional argument; if not found, route to `ErrorNoNameProvided`
+/// .pick_or_route::<String, _>((), ErrorNoNameProvided::default().to_chain())
+/// .after(|str| str.trim().replace(" ", ""))
+/// .unpack()
+/// }
+/// // Convert into ResultFile
+/// .into();
+/// // --------- IMPORTANT ---------
+/// result.to_chain()
+/// }
+///
+/// #[renderer]
+/// fn render_result_file(result: ResultFile) {
+/// let (is_dir, size, name) = result.into();
+/// r_println!(
+/// "{}: {} ({})",
+/// if is_dir { "dir" } else { "file" },
+/// name,
+/// size
+/// )
+/// }
+///
+/// #[renderer]
+/// fn render_error_no_name_provided(_: ErrorNoNameProvided) {
+/// r_println!("Error: name is not provided")
+/// }
+///
+/// gen_program!();
+///
+/// fn main() {
+/// let mut program = ThisProgram::new();
+/// program.with_dispatcher(CMDTransfer);
+/// program.with_dispatcher(CMDStrictTransfer);
+/// program.exec_and_exit();
+/// }
+/// ```
+pub mod example_argument_parse {}
+/// Example Async Runtime Support
///
-/// dispatcher!("hello", HelloCommand => HelloEntry);
+/// > This example shows how to drive an async runtime using the `async` feature
+///
+/// ## Note
+///
+/// When the `async` feature is enabled, **Mingling** provides a different framework implementation,
+/// allowing you to use the `async` keyword directly within `#[chain]`.
+///
+/// However, you will lose some capabilities:
+///
+/// 1. `&mut` resource injection is not available in async chain functions
+/// 2. The program will not be able to use panic unwind functionality
+///
+/// Run:
+/// ```bash
+/// cargo run --manifest-path examples/example-async-support/Cargo.toml --quiet -- download README.md
+/// ```
+///
+/// Output:
+/// ```plaintext
+/// Download begin
+/// # (Will pause for 1 second here)
+/// "README.md" downloaded.
+/// ```
+///
+/// Cargo.toml
+/// ```ignore
+/// [package]
+/// name = "example-async-support"
+/// version = "0.1.0"
+/// edition = "2024"
+///
+/// [dependencies.mingling]
+/// path = "../../mingling"
+///
+/// # Enable `parser` features
+/// features = ["async", "parser"]
+///
+/// # Import any async runtime, e.g. Tokio
+/// [dependencies.tokio]
+/// version = "1.52.3"
+/// features = ["macros", "rt", "rt-multi-thread", "time"]
+/// ```
+///
+/// main.rs
+/// ```ignore
+/// use mingling::{hook::ProgramHook, prelude::*};
///
-/// // Use Tokio async runtime
/// #[tokio::main]
/// async fn main() {
/// let mut program = ThisProgram::new();
-/// program.with_dispatcher(HelloCommand);
///
-/// // Run program
-/// program.exec().await;
+/// program.with_dispatcher(CMDDownload);
+///
+/// // Add a hook to display when the download begins
+/// program.with_hook(ProgramHook::empty().on_begin(|| println!("Download begin")));
+///
+/// // --------- IMPORTANT ---------
+/// // The return values of `exec_*()` related functions have been replaced with Futures
+/// program.exec_and_exit().await;
+/// // --------- IMPORTANT ---------
/// }
///
-/// pack!(Hello = String);
+/// dispatcher!("download", CMDDownload => EntryDownload);
///
-/// // You can freely use async / non-async functions to declare your Chain
+/// pack!(ResultDownloaded = String);
///
+/// // --------- IMPORTANT ---------
/// #[chain]
-/// // fn parse_name(prev: HelloEntry) -> Next {
-/// async fn parse_name(prev: HelloEntry) -> Next {
-/// let name = prev.first().cloned().unwrap_or_else(|| "World".to_string());
-/// Hello::new(name).to_render()
+/// // vvvvv_ `async` keyword can be used directly here
+/// pub async fn handle_download(args: EntryDownload) -> Next {
+/// let file_name = args.pick(()).unpack();
+/// fake_download(file_name).await
/// }
///
-/// // For renderers, you can still only use synchronous functions
/// #[renderer]
-/// fn render_hello_who(prev: Hello) {
-/// r_println!("Hello, {}!", *prev);
+/// // But renderers cannot use the `async` keyword
+/// pub fn render_downloaded(result: ResultDownloaded) {
+/// r_println!("\"{}\" downloaded.", *result);
/// }
+/// // --------- IMPORTANT ---------
///
/// gen_program!();
+///
+/// async fn fake_download(file_name: String) -> ResultDownloaded {
+/// tokio::time::sleep(std::time::Duration::from_secs(1)).await;
+/// ResultDownloaded::new(file_name)
+/// }
/// ```
-pub mod example_async {}
-/// `Mingling` Example - Basic
+pub mod example_async_support {}
+/// Example The Basic Usage of Mingling
///
-/// # How to Run
-/// ```bash
-/// cargo run --manifest-path ./examples/example-basic/Cargo.toml -- hello World
+/// Run:
+/// ```base
+/// cargo run --manifest-path examples/example-basic/Cargo.toml --quiet -- greet
+/// cargo run --manifest-path examples/example-basic/Cargo.toml --quiet -- greet Alice
+/// ```
+///
+/// Output:
+/// ```plaintext
+/// Hello, World!
+/// Hello, Alice!
/// ```
///
/// Cargo.toml
/// ```ignore
/// [package]
/// name = "example-basic"
-/// version = "0.0.1"
+/// version = "0.1.0"
/// edition = "2024"
///
/// [dependencies]
@@ -86,215 +231,389 @@ pub mod example_async {}
///
/// main.rs
/// ```ignore
+/// // Import commonly used Mingling modules
/// use mingling::prelude::*;
///
-/// // Define dispatcher `HelloCommand`, directing subcommand "hello" to `HelloEntry`
-/// dispatcher!("hello", HelloCommand => HelloEntry);
+/// // Define the `greet` subcommand
+/// // _____________________________ subcmd name, can be nested (e.g. "remote.add" "remote.rm")
+/// // / _____________________ dispatcher name
+/// // | / _________ entry, records raw arguments
+/// // | | / ^^^^^^^^^^^^^
+/// // vvvvv vvvvvvvv vvvvvvvvvv \_ equivalent to pack!(EntryGreet = Vec<String>)
+/// dispatcher!("greet", CMDGreet => EntryGreet);
///
/// fn main() {
-/// // Create program
+/// // Create a new ThisProgram
/// let mut program = ThisProgram::new();
///
-/// // Add dispatcher `HelloCommand`
-/// program.with_dispatcher(HelloCommand);
+/// // Add the CMDGreet dispatcher
+/// program.with_dispatcher(CMDGreet);
///
-/// // Run program
-/// program.exec();
+/// // Run the program, then exit the process
+/// program.exec_and_exit();
/// }
///
-/// // Register wrapper type `Hello`, setting inner to `String`
-/// pack!(Hello = String);
-///
-/// // Register chain to `ThisProgram`, handling logic from `HelloEntry`
-/// #[chain]
-/// fn parse_name(prev: HelloEntry) -> Next {
-/// // Extract string from `HelloEntry` as argument
-/// let name = prev.first().cloned().unwrap_or_else(|| "World".to_string());
-///
-/// // Build `Hello` type and route to renderer
-/// Hello::new(name).to_render()
+/// // Quickly wrap a type into a type recognizable by the current program
+/// // ____________________ Wrapped type name
+/// // / _______ Wrapped type inner value
+/// // | /
+/// // vvvvvvvvvv vvvvvv
+/// pack!(ResultName = String);
+///
+/// // Define the `handle_greet` chain for parsing input text
+/// // ____________________ Previous type:
+/// // / Mingling deduces types at runtime and routes them to this function
+/// // | _____ will be expanded to:
+/// // | / impl Into<mingling::ChainProcess<ThisProgram>>
+/// #[chain] // vvvvvvvvvv vvvv
+/// fn handle_greet(args: EntryGreet) -> Next {
+/// let name: ResultName = args
+/// .inner
+/// .first()
+/// .cloned()
+/// .unwrap_or_else(|| "World".to_string())
+/// .into();
+/// name
/// }
///
-/// // Register renderer to `ThisProgram`, handling rendering of `Hello`
+/// // Define renderer `render_name`, used to render `ResultName`
/// #[renderer]
-/// fn render_hello_who(prev: Hello) {
-/// // Print message
-/// r_println!("Hello, {}!", *prev);
-///
-/// // Program ends here
+/// fn render_name(name: ResultName) {
+/// r_println!("Hello, {}!", *name);
/// }
///
-/// // Generate program, default is `ThisProgram`
+/// // Note: This macro generates the program entry point.
+/// // It must be placed at the end of the root module of the crate (>= mingling@0.1.8).
+/// // ^^^^^^ ^^^^^^^^^^^
+/// // For example: lib.rs, main.rs
/// gen_program!();
/// ```
pub mod example_basic {}
-/// `Mingling` Example - Completion
-///
-/// # How to Deploy
-/// 1. Enable the `comp` feature
-/// ```toml
-/// [dependencies]
-/// mingling = { version = "...", features = [
-/// "comp", // Enable this feature
-/// "parser"
-/// ] }
-/// ```
+/// Example Completion
///
-/// 2. Add `mingling` as a build dependency, enabling the `builds` and `comp` features
-/// ```toml
-/// [build-dependencies]
-/// mingling = { version = "...", features = [
-/// "builds", // Enable this feature for build scripts
-/// "comp"
-/// ] }
-/// ```
+/// > This example demonstrates how to use **Mingling** to create fully dynamic command-line completions
+///
+/// ## About Completion Scripts
///
-/// 3. Write `build.rs` to generate completion scripts at compile time
-/// ```ignore
-/// use mingling::build::{build_comp_scripts, build_comp_scripts_with_bin_name};
+/// To make your completions work, you need to generate a completion script using Mingling's tools
+///
+/// 1. Enable features
+/// You need to enable the `builds` and `comp` features for `mingling` in `[build-dependencies]`
+///
+/// 2. Write `build.rs`
+/// Write the following in `build.rs`
+///
+/// ```rust,ignore
/// fn main() {
-/// // Generate completion scripts for the current program, using the Cargo package name as the binary filename
-/// build_comp_scripts(env!("CARGO_PKG_NAME")).unwrap();
+/// build_scripts();
+/// }
///
-/// // Or, explicitly specify the binary filename
-/// // build_comp_scripts("your_bin").unwrap();
+/// /// Generate completion scripts
+/// fn build_scripts() {
+/// // `env!("CARGO_PKG_NAME")` equals the crate name, which matches the binary name.
+/// // If your binary name differs from the crate name, specify it explicitly.
+/// mingling::build::build_comp_scripts(
+/// // Your binary name:
+/// env!("CARGO_PKG_NAME"),
+/// )
+/// .unwrap();
/// }
/// ```
///
-/// 4. Write `main.rs`, adding completion logic for your command entry point
-/// 5. Execute `cargo install --path ./`, then run the corresponding completion script in your shell
+/// 3. Verify
+/// Build your project with `cargo build --release`. The completion scripts will be generated in `target/release/`
+///
+/// Execute the script or have it be automatically sourced by your Shell
+///
+/// Run:
+/// ```bash
+/// cargo run --manifest-path examples/example-completion/Cargo.toml --quiet -- greet Alice --repeat 3
+/// ```
+///
+/// Output:
+/// ```plaintext
+/// Hello, Alice, Alice, Alice!
+/// ```
///
/// Cargo.toml
/// ```ignore
/// [package]
/// name = "example-completion"
-/// version = "0.0.1"
+/// version = "0.1.0"
/// edition = "2024"
///
-/// [dependencies]
-/// mingling = { path = "../../mingling", features = ["comp", "parser"] }
+/// [dependencies.mingling]
+/// path = "../../mingling"
+///
+/// features = [
+/// # Enable `comp` features
+/// "comp",
+/// "parser",
+/// ]
+///
+/// [build-dependencies.mingling]
+/// path = "../../mingling"
+///
+/// features = [
+/// # Enable `comp` features
+/// "comp",
+///
+/// # If you want to build completion scripts,
+/// # enable `builds` features
+/// "builds",
+/// ]
/// ```
///
/// main.rs
/// ```ignore
-/// use mingling::prelude::*;
-/// use mingling::{
-/// macros::{suggest, suggest_enum},
-/// parser::{PickableEnum, Picker},
-/// EnumTag, Groupped, ShellContext, Suggest,
-/// };
+/// use mingling::{macros::suggest, prelude::*, ShellContext, Suggest};
+///
+/// fn main() {
+/// let mut program = ThisProgram::new();
///
-/// // Define dispatcher `FruitCommand`, directing subcommand "fruit" to `FruitEntry`
-/// dispatcher!("fruit", FruitCommand => FruitEntry);
+/// program.with_dispatcher(CMDGreet);
///
-/// #[completion(FruitEntry)]
-/// fn comp_fruit_command(ctx: &ShellContext) -> Suggest {
-/// if ctx.filling_argument_first("--name") {
-/// return suggest!();
+/// // --------- IMPORTANT ---------
+/// // The `comp` feature makes `gen_program!()` generate a CompletionDispatcher automatically
+/// // It adds a hidden `__comp` subcommand for communication with the completion script
+/// program.with_dispatcher(crate::CompletionDispatcher);
+/// // --------- IMPORTANT ---------
+///
+/// // TIP: Note that the completion script reads stdout,
+/// // so make sure no output is produced before the CompletionDispatcher is dispatched.
+/// program.exec_and_exit();
+/// }
+///
+/// // --------- IMPORTANT ---------
+/// // __________________________________________ Entry point bound to completion behavior
+/// // / _________________________ Shell context for obtaining user input state
+/// // | / ________ Suggest, used to return completion results
+/// // vvvvvvvvvv | /
+/// #[completion(EntryGreet)] // vvvvvvvvvvvv vvvvvvv
+/// fn complete_greet_entry(ctx: &ShellContext) -> Suggest {
+/// // When the previous word is `greet` (the current command being typed)
+/// if ctx.previous_word == "greet" {
+/// // Return suggestions
+/// return suggest! {
+/// "Bob": "Likes to pass messages",
+/// "Alice": "Likes to receive messages",
+/// "Hacker": "YOU",
+/// "World"
+/// };
/// }
-/// if ctx.filling_argument_first("--type") {
-/// return suggest_enum!(FruitType);
+///
+/// // When the user is typing `--repeat`
+/// if ctx.filling_argument(["-r", "--repeat"]) {
+/// return suggest! {}; // Don't suggest anything
/// }
+///
+/// // When the user is typing `-`
/// if ctx.typing_argument() {
/// return suggest! {
-/// "--name": "Fruit name",
-/// "--type": "Fruit type"
+/// "-r": "Number of repetitions",
+/// "--repeat": "Number of repetitions",
/// }
+/// // Remove arguments that have already been typed by the user
/// .strip_typed_argument(ctx);
/// }
-/// return suggest!();
+///
+/// // Otherwise, suggest nothing
+/// suggest!()
+/// // // You can also enable file completions using the following code,
+/// // // which will invoke the Shell's default behavior
+/// // Suggest::file_comp()
/// }
+/// // --------- IMPORTANT ---------
///
-/// fn main() {
-/// let mut program = ThisProgram::new();
-/// program.with_dispatcher(CompletionDispatcher);
-/// program.with_dispatcher(FruitCommand);
-/// program.exec();
+/// dispatcher!("greet", CMDGreet => EntryGreet);
+/// pack!(ResultName = (u8, String));
+///
+/// #[chain]
+/// fn handle_greet(args: EntryGreet) -> Next {
+/// let result: ResultName = args
+/// .pick(["-r", "--repeat"])
+/// .pick_or((), "World")
+/// .unpack()
+/// .into();
+/// result
/// }
///
-/// #[derive(Groupped)]
-/// struct FruitInfo {
-/// name: String,
-/// fruit_type: FruitType,
+/// #[renderer]
+/// fn render_name(result: ResultName) {
+/// let (repeat, name) = result.inner;
+/// let mut parts = Vec::with_capacity(repeat as usize);
+/// for _ in 0..repeat {
+/// parts.push(name.clone());
+/// }
+/// r_println!("Hello, {}!", parts.join(", "));
/// }
///
-/// #[derive(Default, Debug, EnumTag)]
-/// enum FruitType {
-/// #[enum_desc("It's Apple")]
-/// #[enum_rename("apple")]
-/// FruitApple,
+/// gen_program!();
+/// ```
+pub mod example_completion {}
+/// Example Custom Pickable
///
-/// #[enum_desc("It's Banana")]
-/// #[enum_rename("banana")]
-/// FruitBanana,
+/// > This example demonstrates how to use the Pickable trait to add parsing for your types
///
-/// #[enum_desc("It's Cherry")]
-/// #[enum_rename("cherry")]
-/// FruitCherry,
+/// Run:
+/// ```bash
+/// cargo run --manifest-path examples/example-custom-pickable/Cargo.toml --quiet -- connect 127.0.0.1:5012
+/// cargo run --manifest-path examples/example-custom-pickable/Cargo.toml --quiet -- connect 127.0.0.1
+/// ```
///
-/// #[enum_desc("It's Date")]
-/// #[enum_rename("date")]
-/// FruitDate,
+/// Output:
+/// ```plaintext
+/// Connected to "127.0.0.1:5012"
+/// Failed to parse address
+/// ```
///
-/// #[enum_desc("It's Elderberry")]
-/// #[enum_rename("elderberry")]
-/// FruitElderberry,
+/// Cargo.toml
+/// ```ignore
+/// [package]
+/// name = "example-custom-pickable"
+/// version = "0.1.0"
+/// edition = "2024"
///
-/// #[default]
-/// #[enum_rename("unknown")]
-/// Unknown,
+/// [dependencies.mingling]
+/// path = "../../mingling"
+///
+/// features = [
+/// "parser",
+/// ]
+/// ```
+///
+/// main.rs
+/// ```ignore
+/// use mingling::{Groupped, macros::route, parser::Pickable, prelude::*};
+///
+/// // Define types that can be recognized by Mingling
+/// // ________________________ `Pickable` trait needs to implement Default
+/// // / ________ The Groupped derive macro registers an ID for this type
+/// // | / Mingling uses this ID to identify the type
+/// // vvvvvvv vvvvvvvv
+/// #[derive(Debug, Default, Clone, Groupped)]
+/// pub struct Address {
+/// pub ip: [u8; 4],
+/// pub port: u16,
+/// }
+///
+/// // --------- IMPORTANT ---------
+/// impl Pickable for Address {
+/// type Output = Address;
+/// fn pick(args: &mut mingling::parser::Argument, flag: mingling::Flag) -> Option<Self::Output> {
+/// // Extract the raw string from Argument using the Flag
+/// let raw: String = args.pick_argument(flag)?.to_string();
+///
+/// // Use TryFrom to parse the address
+/// Address::try_from(raw).ok()
+/// }
/// }
+/// // --------- IMPORTANT ---------
///
-/// impl PickableEnum for FruitType {}
+/// dispatcher!("connect", CMDConnect => EntryConnect);
+/// pack!(ErrorParseAddressFailed = ());
///
/// #[chain]
-/// fn parse_fruit_info(prev: FruitEntry) -> Next {
-/// let picker = Picker::from(prev.inner);
-/// let (fruit_name, fruit_type) = picker.pick("--name").pick("--type").unpack();
-/// let info = FruitInfo {
-/// name: fruit_name,
-/// fruit_type,
-/// };
-/// info.to_render()
+/// fn handle_connect(prev: EntryConnect) -> Next {
+/// let connect: Address =
+/// route! { prev.pick_or_route((), ErrorParseAddressFailed::default().to_chain()).unpack() };
+/// connect.to_chain()
/// }
///
/// #[renderer]
-/// fn render_fruit(prev: FruitInfo) {
-/// match (prev.name.is_empty(), prev.fruit_type) {
-/// (true, FruitType::Unknown) => {
-/// r_println!("Fruit name is empty and type is unknown");
-/// }
-/// (true, fruit_type) => {
-/// r_println!("Fruit name is empty, Type: {:?}", fruit_type);
+/// fn render_address(addr: Address) {
+/// r_println!("Connected to \"{}\"", addr.to_string());
+/// }
+///
+/// #[renderer]
+/// fn render_error_parse_address_failed(_: ErrorParseAddressFailed) {
+/// r_println!("Failed to parse address");
+/// }
+///
+/// gen_program!();
+///
+/// fn main() {
+/// let mut program = ThisProgram::new();
+/// program.with_dispatcher(CMDConnect);
+/// program.exec_and_exit();
+/// }
+///
+/// // Address conversion
+///
+/// impl TryFrom<String> for Address {
+/// type Error = String;
+///
+/// fn try_from(raw: String) -> Result<Self, Self::Error> {
+/// // Expected format: "192.168.1.1:8080"
+/// let parts: Vec<&str> = raw.split(':').collect();
+/// if parts.len() != 2 {
+/// return Err("Invalid format: expected 'IP:PORT'".to_string());
/// }
-/// (false, FruitType::Unknown) => {
-/// r_println!("Fruit name: {}, Type is unknown", prev.name);
+///
+/// let ip_str = parts[0];
+/// let port_str = parts[1];
+///
+/// // Parse IP address (4 octets separated by dots)
+/// let ip_parts: Vec<&str> = ip_str.split('.').collect();
+/// if ip_parts.len() != 4 {
+/// return Err("Invalid IP address format".to_string());
/// }
-/// (false, fruit_type) => {
-/// r_println!("Fruit name: {}, Type: {:?}", prev.name, fruit_type);
+///
+/// let mut ip = [0u8; 4];
+/// for (i, part) in ip_parts.iter().enumerate() {
+/// ip[i] = part
+/// .parse::<u8>()
+/// .map_err(|_| format!("Invalid IP octet: {}", part))?;
/// }
+///
+/// // Parse port
+/// let port = port_str
+/// .parse::<u16>()
+/// .map_err(|_| format!("Invalid port: {}", port_str))?;
+///
+/// Ok(Address { ip, port })
/// }
/// }
///
-/// gen_program!();
+/// impl From<Address> for String {
+/// fn from(addr: Address) -> String {
+/// format!(
+/// "{}.{}.{}.{}:{}",
+/// addr.ip[0], addr.ip[1], addr.ip[2], addr.ip[3], addr.port
+/// )
+/// }
+/// }
+///
+/// impl std::fmt::Display for Address {
+/// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+/// write!(
+/// f,
+/// "{}.{}.{}.{}:{}",
+/// self.ip[0], self.ip[1], self.ip[2], self.ip[3], self.port
+/// )
+/// }
+/// }
/// ```
-pub mod example_completion {}
-/// `Mingling` Example - Dispatch Tree
-///
-/// # How to Deploy
-/// 1. Enable the `dispatch_tree` feature (`comp` is optional)
-/// ```toml
-/// mingling = { version = "...", features = [
-/// "dispatch_tree", // Enable this feature
-/// "comp" // optional
-/// ] }
-/// ```
+pub mod example_custom_pickable {}
+/// Example Dispatch Tree
+///
+/// > This example will introduce how to use `dispatch_tree`
+/// > to optimize your command line lookup efficiency
///
-/// 2. Using `cargo expand`:
+/// When the number of commands in your project increases, you can use `dispatch_tree` to complete command registration at compile time.
+/// It will generate a trie for quickly finding related commands by prefix.
///
+/// Therefore, after enabling this feature,
+/// `Program` will no longer store a Dispatcher list internally, and the `with_dispatcher` function will not be compiled.
+///
+/// Run:
/// ```bash
-/// cargo expand --manifest-path examples/example-dispatch-tree/Cargo.toml > expanded.rs
-/// cat expanded.rs
+/// cargo run --manifest-path examples/example-dispatch-tree/Cargo.toml --quiet -- cmd5
+/// ```
+///
+/// Output:
+/// ```plaintext
+/// It's works!
/// ```
///
/// Cargo.toml
@@ -304,123 +623,378 @@ pub mod example_completion {}
/// version = "0.1.0"
/// edition = "2024"
///
-/// [dependencies]
-/// mingling = { path = "../../mingling", features = ["dispatch_tree", "comp"] }
+/// [dependencies.mingling]
+/// path = "../../mingling"
+///
+/// features = [
+/// "dispatch_tree",
+/// ]
/// ```
///
/// main.rs
/// ```ignore
-/// #![allow(unused_mut)]
-///
/// use mingling::prelude::*;
///
+/// // --------- IMPORTANT ---------
+/// // You have a large number of subcommands
+/// 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);
+/// dispatcher!("cmd3.sub2", CMD3Sub2 => Entry3Sub2);
+/// dispatcher!("cmd4.sub1.subsub1.deep", CMD4Deep => Entry4Deep);
+/// dispatcher!("cmd4.sub1.subsub2", CMD4SubSub2 => Entry4SubSub2);
+/// dispatcher!("cmd5", CMD5 => Entry5);
+/// dispatcher!("cmd5.extra", CMD5Extra => Entry5Extra);
+/// dispatcher!("nested.a.b.c", CMDA => EntryA);
+/// dispatcher!("nested.a.b.d", CMDB => EntryB);
+/// dispatcher!("nested.a.e", CMDC => EntryC);
+/// dispatcher!("nested.f", CMDD => EntryD);
+/// // --------- IMPORTANT ---------
+///
/// fn main() {
-/// let mut program = ThisProgram::new();
+/// let program = ThisProgram::new();
///
-/// // // After enabling `dispatch_tree`, this method will no longer exist
-/// // program.with_dispatcher(CommandGreet);
-/// //
-/// // // The `CompletionDispatcher` automatically generated by `comp` will also be imported automatically
-/// // program.with_dispatcher(CompletionDispatcher);
+/// // --------- IMPORTANT ---------
+/// // // You no longer need to use `with_dispatcher` anymore;
+/// // // it'll be collected automatically once the `dispatch_tree` feature is enabled
+/// // program.with_dispatcher(...);
///
-/// program.exec();
+/// program.exec_and_exit()
/// }
///
-/// dispatcher!("greet", CommandGreet => EntryGreet);
-/// dispatcher!("help", CommandHelp => EntryHelp);
-/// dispatcher!("quit", CommandQuit => EntryQuit);
-/// dispatcher!("list", CommandList => EntryList);
-/// dispatcher!("status", CommandStatus => EntryStatus);
-/// dispatcher!("save", CommandSave => EntrySave);
-/// dispatcher!("load", CommandLoad => EntryLoad);
-/// dispatcher!("config", CommandConfig => EntryConfig);
-/// dispatcher!("run", CommandRun => EntryRun);
-/// dispatcher!("debug", CommandDebug => EntryDebug);
-/// dispatcher!("version", CommandVersion => EntryVersion);
+/// #[renderer]
+/// fn render_cmd5(_: Entry5) {
+/// r_println!("It's works!");
+/// }
///
/// gen_program!();
/// ```
pub mod example_dispatch_tree {}
-/// `Mingling` Example - Exit Code
+/// Example Enum Tag
///
-/// This example demonstrates how to modify the program's exit code using `ExitCodeSetup`.
-/// By default, the program exits with code 0. This example shows:
-/// 1. Using `dispatcher!` to define an error command,
-/// 2. Using `chain!` to handle errors and set a custom exit code via `ProgramExitCode`,
-/// 3. Using `renderer!` to print an error message.
+/// > This example demonstrates how to use the `EnumTag` derive macro to tag enum variants with metadata,
+/// > which can be used for autocompletion and parsing
///
-/// # How to Run
+/// Run:
/// ```bash
-/// cargo run --manifest-path ./examples/example-exit-code/Cargo.toml -- error
+/// cargo run --manifest-path examples/example-enum-tag/Cargo.toml --quiet -- lang-select OCaml
+/// cargo run --manifest-path examples/example-enum-tag/Cargo.toml --quiet -- lang-select
+/// ```
+///
+/// Output:
+/// ```plaintext
+/// Selected: OCaml (A representative functional programming language with strong type inference)
+/// Selected: Rust (A systems programming language focused on performance, safety, and concurrency)
/// ```
///
/// Cargo.toml
/// ```ignore
/// [package]
-/// name = "example-exit-code"
+/// name = "example-enum-tag"
/// version = "0.1.0"
/// edition = "2024"
///
-/// [dependencies]
-/// mingling = { path = "../../mingling" }
+/// [dependencies.mingling]
+/// path = "../../mingling"
+///
+/// features = [
+/// "comp",
+/// "parser"
+/// ]
/// ```
///
/// main.rs
/// ```ignore
-/// use mingling::prelude::*;
-/// use mingling::{res::ExitCode, setup::ExitCodeSetup};
+/// use mingling::{
+/// EnumTag, Groupped, ShellContext, Suggest, macros::suggest_enum, parser::PickableEnum,
+/// prelude::*,
+/// };
+///
+/// // Define the enum and derive the EnumTag trait
+/// // ________ adds metadata to the enum, enabling it to:
+/// // / 1. Be used by the `suggest_enum!(Enum)` macro under the `comp` feature for autocompletion
+/// // vvvvvvv 2. Implement the `PickableEnum` trait
+/// #[derive(Debug, Default, EnumTag, Groupped)]
+/// pub enum ProgrammingLanguages {
+/// #[enum_desc("An efficient and flexible compiled language widely used for system programming")]
+/// C,
+///
+/// #[enum_rename("C++")]
+/// #[enum_desc("A high-performance language extending C with object-oriented features")]
+/// CPlusPlus,
+///
+/// #[enum_rename("C#")]
+/// #[enum_desc("Microsoft's object-oriented programming language running on the .NET platform")]
+/// Csharp,
+///
+/// #[enum_desc(
+/// "A cross-platform object-oriented language widely used for enterprise application development"
+/// )]
+/// Java,
+///
+/// #[enum_desc(
+/// "A dynamic scripting language for web development, supporting prototype chain inheritance"
+/// )]
+/// JavaScript,
+///
+/// #[enum_desc("A modern statically typed language running on the JVM, concise and safe")]
+/// Kotlin,
+///
+/// #[enum_desc("A representative functional programming language with strong type inference")]
+/// OCaml,
+///
+/// #[enum_desc("A general-purpose programming language with clean syntax, known for readability")]
+/// Python,
+///
+/// #[enum_desc("An object-oriented scripting language, famous for its concise and elegant syntax")]
+/// Ruby,
+///
+/// #[default]
+/// #[enum_desc("A systems programming language focused on performance, safety, and concurrency")]
+/// Rust,
+/// }
+///
+/// // --------- IMPORTANT ---------
+/// // Implement the PickableEnum trait for ProgrammingLanguages,
+/// // so that `Picker` can parse this enum
+/// impl PickableEnum for ProgrammingLanguages {}
+/// // --------- IMPORTANT ---------
+///
+/// dispatcher!("lang-select", CMDLanguageSelection => EntryLanguageSelection);
+///
+/// #[chain]
+/// fn handle_language_selection(args: EntryLanguageSelection) -> Next {
+/// // You can use Picker to directly parse ProgrammingLanguages
+/// let lang: ProgrammingLanguages = args.pick(()).unpack();
+/// lang
+/// }
+///
+/// #[renderer]
+/// fn render_programming_language(lang: ProgrammingLanguages) {
+/// // You can use `enum_info()` to get the name and description of the current enum
+/// let (name, desc) = lang.enum_info();
+/// r_println!("Selected: {} ({})", name, desc)
+/// }
+///
+/// #[completion(EntryLanguageSelection)]
+/// fn complete_language_selection(_: &ShellContext) -> Suggest {
+/// // Use `suggest_enum!` directly to generate enum suggestions
+/// suggest_enum!(ProgrammingLanguages)
+/// }
+///
+/// gen_program!();
///
/// fn main() {
/// let mut program = ThisProgram::new();
-/// program.with_dispatcher(ErrorCommand);
-/// program.with_setup(ExitCodeSetup::<ThisProgram>::default());
+/// program.with_dispatcher(CompletionDispatcher);
+/// program.with_dispatcher(CMDLanguageSelection);
/// program.exec_and_exit();
/// }
+/// ```
+pub mod example_enum_tag {}
+/// Example Error Handling
///
-/// dispatcher!("error", ErrorCommand => ErrorEntry);
-/// pack!(ResultError = ());
+/// > This example demonstrates how to handle errors in Mingling, including custom error types and error rendering.
+///
+/// Run:
+/// ```bash
+/// cargo run --manifest-path examples/example-error-handling/Cargo.toml --quiet -- hallo
+/// cargo run --manifest-path examples/example-error-handling/Cargo.toml --quiet -- hello
+/// cargo run --manifest-path examples/example-error-handling/Cargo.toml --quiet -- hello Alice
+/// cargo run --manifest-path examples/example-error-handling/Cargo.toml --quiet -- hello MyBestFriendAlice
+/// cargo run --manifest-path examples/example-error-handling/Cargo.toml --quiet -- hello Peter
+/// ```
+///
+/// Output:
+/// ```plaintext
+/// Command not found: "hallo"
+/// No name provided
+/// Name not available
+/// Name too long: 17 > 10
+/// Hello, Peter
+/// ```
+///
+/// Cargo.toml
+/// ```ignore
+/// [package]
+/// name = "example-error-handling"
+/// version = "0.1.0"
+/// edition = "2024"
+///
+/// [dependencies]
+/// mingling = { path = "../../mingling" }
+/// ```
+///
+/// main.rs
+/// ```ignore
+/// use mingling::prelude::*;
+///
+/// // In Mingling, instead of using ? to propagate errors upward,
+/// // errors are treated as branches that continue execution.
+///
+/// dispatcher!("hello", CMDHello => EntryHello);
+///
+/// // Define error types
+/// pack!(ErrorNoNameProvided = ());
+/// pack!(ErrorNameTooLong = u16);
+/// pack!(ErrorNameNotAvailable = ());
+///
+/// // Define success type
+/// pack!(ResultName = String);
+///
+/// // Pre-registered names
+/// static VEC_REGISTERED_NAMES: &[&str] = &["Alice", "Bob", "Charlie", "David", "Eve"];
///
/// #[chain]
-/// fn handle_error_entry(_prev: ErrorEntry, ec: &mut ExitCode) -> Next {
-/// ec.exit_code = 1;
-/// return ResultError::default();
+/// fn handle_hello(args: EntryHello) -> Next {
+/// let Some(name) = args.inner.first().cloned() else {
+/// // If no name is provided, pass ErrorNoNameProvided
+/// return ErrorNoNameProvided::default().to_render();
+/// };
+///
+/// if name.len() > 10 {
+/// // If the name is too long, pass ErrorNameTooLong
+/// return ErrorNameTooLong::new(name.len() as u16).to_render();
+/// }
+///
+/// if VEC_REGISTERED_NAMES.contains(&name.as_str()) {
+/// // If the name already exists, pass ErrorNameNotAvailable
+/// return ErrorNameNotAvailable::default().to_render();
+/// }
+///
+/// // If the name is valid, pass ResultName
+/// ResultName::new(name).to_render()
+/// }
+///
+/// #[renderer]
+/// fn render_result_name(name: ResultName) {
+/// r_println!("Hello, {}", *name);
+/// }
+///
+/// #[renderer]
+/// fn render_error_no_name_provided(_: ErrorNoNameProvided) {
+/// // Prompt when no name is provided
+/// r_println!("No name provided");
/// }
///
/// #[renderer]
-/// fn render_error(_prev: ResultError, ec: &ExitCode) {
-/// r_println!("Exit with exit code: {}", ec.exit_code);
+/// fn render_error_name_not_available(_: ErrorNameNotAvailable) {
+/// // Prompt when name is already taken
+/// r_println!("Name not available");
+/// }
+///
+/// #[renderer]
+/// fn render_error_name_too_long(len: ErrorNameTooLong) {
+/// // Prompt when name is too long, showing actual length
+/// r_println!("Name too long: {} > 10", *len);
+/// }
+///
+/// #[renderer]
+/// fn render_dispatcher_not_found(err: DispatcherNotFound) {
+/// // Prompt when command is not found, showing the input command
+/// r_println!("Command not found: \"{}\"", err.inner.join(" "));
/// }
///
/// gen_program!();
+///
+/// fn main() {
+/// let mut program = ThisProgram::new();
+/// program.with_dispatcher(CMDHello);
+/// program.exec_and_exit();
+/// }
/// ```
-pub mod example_exit_code {}
-/// `Mingling` Example - General Renderer
+pub mod example_error_handling {}
+/// Example Error Handling
+///
+/// > This example demonstrates how to handle errors in Mingling, including custom error types and error rendering.
///
-/// ## Step1 - Enable Feature
-/// Enable the `general_renderer` feature for mingling in `Cargo.toml`
-/// ```toml
-/// [dependencies]
-/// mingling = { version = "...", features = ["general_renderer", "parser"] }
+/// Run:
+/// ```bash
+/// cargo run --manifest-path examples/example-exitcode/Cargo.toml --quiet -- hello Alice
+/// cargo run --manifest-path examples/example-exitcode/Cargo.toml --quiet -- hello
/// ```
///
-/// ## Step2 - Add Dependencies
-/// Add `serde` dependency to `Cargo.toml` for serialization support
-/// ```toml
-/// [dependencies]
-/// serde = { version = "1", features = ["derive"] }
+/// Output:
+/// ```plaintext
+/// Hello, Alice
+/// No name provided (with exit code 1)
/// ```
///
-/// ## Step3 - Write Code
-/// Write the following content into `main.rs`
+/// Cargo.toml
+/// ```ignore
+/// [package]
+/// name = "example-exitcode"
+/// version = "0.1.0"
+/// edition = "2024"
+///
+/// [dependencies]
+/// mingling = { path = "../../mingling" }
+/// ```
///
-/// ## Step4 - Build and Run
+/// main.rs
+/// ```ignore
+/// use mingling::{prelude::*, res::ExitCode, setup::ExitCodeSetup};
+///
+/// fn main() {
+/// let mut program = ThisProgram::new();
+///
+/// // --------- IMPORTANT ---------
+/// // Register `ExitCodeSetup` for the program to enable exit codes
+/// program.with_setup(ExitCodeSetup::default());
+/// // --------- IMPORTANT ---------
+///
+/// program.with_dispatcher(CMDHello);
+/// program.exec_and_exit();
+/// }
+///
+/// dispatcher!("hello", CMDHello => EntryHello);
+///
+/// pack!(ErrorNoNameProvided = ());
+/// pack!(ResultName = String);
+///
+/// #[chain]
+/// fn handle_hello(args: EntryHello) -> Next {
+/// let Some(name) = args.inner.first().cloned() else {
+/// // If no name is provided, pass ErrorNoNameProvided
+/// return ErrorNoNameProvided::default().to_render();
+/// };
+///
+/// // If the name is valid, pass ResultName
+/// ResultName::new(name).to_render()
+/// }
+///
+/// #[renderer]
+/// fn render_result_name(name: ResultName) {
+/// r_println!("Hello, {}", *name);
+/// }
+///
+/// // Define renderer, render error message _____________ Inject exit code resource
+/// // /
+/// #[renderer] // vvvvvvvvvvvvv
+/// fn render_error_no_name_provided(_: ErrorNoNameProvided, ec: &mut ExitCode) {
+/// ec.exit_code = 1;
+///
+/// // Prompt when no name is provided
+/// r_println!("No name provided (with exit code 1)");
+/// }
+///
+/// gen_program!();
+/// ```
+pub mod example_exitcode {}
+/// Example General Renderer
+///
+/// > This example demonstrates how to use the `general_renderer` feature to render data into structures such as json / yaml
+///
+/// Run
/// ```bash
-/// cargo run --manifest-path ./examples/example-general-renderer/Cargo.toml -- render Bob 22
-/// cargo run --manifest-path ./examples/example-general-renderer/Cargo.toml -- render Bob 22 --json
-/// cargo run --manifest-path ./examples/example-general-renderer/Cargo.toml -- render Bob 22 --yaml
+/// cargo run --manifest-path examples/example-general-renderer/Cargo.toml --quiet -- render Bob 22
+/// cargo run --manifest-path examples/example-general-renderer/Cargo.toml --quiet -- render Bob 22 --json
+/// cargo run --manifest-path examples/example-general-renderer/Cargo.toml --quiet -- render Bob 22 --yaml
/// ```
///
-/// Will print:
+/// Output:
/// ```plain
/// Bob is 22 years old
/// {"member_name":"Bob","member_age":22}
@@ -432,23 +1006,24 @@ pub mod example_exit_code {}
/// ```ignore
/// [package]
/// name = "example-general-renderer"
-/// version = "0.0.1"
+/// version = "0.1.0"
/// edition = "2024"
///
/// [dependencies]
-/// mingling = { path = "../../mingling", features = [
-/// "parser",
+/// serde = { version = "1.0.228", features = ["derive"] }
+///
+/// [dependencies.mingling]
+/// path = "../../mingling"
+/// features = [
/// "general_renderer",
-/// "json_serde_fmt",
-/// "yaml_serde_fmt",
-/// ] }
-/// serde = { version = "1", features = ["derive"] }
+/// "parser",
+/// ]
/// ```
///
/// main.rs
/// ```ignore
/// use mingling::prelude::*;
-/// use mingling::{parser::Picker, setup::GeneralRendererSetup, Groupped};
+/// use mingling::{Groupped, parser::Picker, setup::GeneralRendererSetup};
/// use serde::Serialize;
///
/// dispatcher!("render", RenderCommand => RenderCommandEntry);
@@ -461,7 +1036,13 @@ pub mod example_exit_code {}
/// program.exec();
/// }
///
-/// // Manually implement Info struct
+/// // --------- IMPORTANT ---------
+/// // For beautiful output structure, do not use `pack!` to wrap the types that need to be output.
+/// // Instead, manually implement
+/// // ____________________ Implement serde::Serialize
+/// // / _________ Implement mingling::Groupped
+/// // | / to ensure Mingling can recognize the type
+/// // vvvvvvvvv vvvvvvvv
/// #[derive(Serialize, Groupped)]
/// struct Info {
/// #[serde(rename = "member_name")]
@@ -469,6 +1050,12 @@ pub mod example_exit_code {}
/// #[serde(rename = "member_age")]
/// age: i32,
/// }
+/// // This will output: {"member_name":"name","member_age":32} structure
+///
+/// // If using pack!(Info = (String, i32));
+/// // Output: {"inner":["name", 32]}
+///
+/// // --------- IMPORTANT ---------
///
/// #[chain]
/// fn parse_render(prev: RenderCommandEntry) -> Next {
@@ -488,93 +1075,249 @@ pub mod example_exit_code {}
/// gen_program!();
/// ```
pub mod example_general_renderer {}
-/// `Mingling` Example - Picker
+/// Example Help
///
-/// ## Step1 - Enable Feature
-/// Enable the `parser` feature for mingling in `Cargo.toml`
-/// ```toml
-/// [dependencies]
-/// mingling = { version = "...", features = ["parser"] }
+/// > This example demonstrates how to use the `#[help]` macro to generate help information,
+/// > enabling `--help` to work
+///
+/// Run
+/// ```bash
+/// cargo run --manifest-path examples/example-help/Cargo.toml --quiet -- greet --help
/// ```
///
-/// ## Step2 - Write Code
-/// Write the following content into `main.rs`
+/// Output:
+/// ```plain
+/// Usage: greet <NAME>
+/// ```
///
-/// ## Step3 - Build and Run
-/// ```bash
-/// cargo run --manifest-path ./examples/example-picker/Cargo.toml -- pick Bob
-/// cargo run --manifest-path ./examples/example-picker/Cargo.toml -- pick Bob --age -15
-/// cargo run --manifest-path ./examples/example-picker/Cargo.toml -- pick --age 99
+/// Cargo.toml
+/// ```ignore
+/// [package]
+/// name = "example-help"
+/// version = "0.1.0"
+/// edition = "2024"
+///
+/// [dependencies]
+/// mingling = { path = "../../mingling" }
+/// ```
+///
+/// main.rs
+/// ```ignore
+/// use mingling::{macros::help, prelude::*, setup::BasicProgramSetup};
+///
+/// dispatcher!("greet", CMDGreet => EntryGreet);
+///
+/// // Define help _________ When `program.user_context.help` is `true`
+/// // / the command will not enter `#[chain]` / `#[renderer]`
+/// #[help] // vvvvvvvvvv but instead enter this `#[help]` function
+/// fn help_greet(_prev: EntryGreet) {
+/// r_println!("Usage: greet <NAME>");
+/// }
+///
+/// fn main() {
+/// let mut program = ThisProgram::new();
+///
+/// // --------- IMPORTANT ---------
+/// // Add `BasicProgramSetup` to the program
+/// // to enable `--help`, `--quiet`, and other built-in features
+/// program.with_setup(BasicProgramSetup);
+/// // --------- IMPORTANT ---------
+///
+/// program.with_dispatcher(CMDGreet);
+///
+/// program.exec_and_exit();
+/// }
+///
+/// gen_program!();
+/// ```
+pub mod example_help {}
+/// Example Hook
+///
+/// > This example demonstrates how to use Mingling's hook system to obtain debugging information during program execution
+///
+/// Run:
+/// ```base
+/// cargo run --manifest-path examples/example-hook/Cargo.toml --quiet -- greet Alice
+/// ```
+///
+/// Output:
+/// ```plaintext
+/// [DEBUG] Program is begin
+/// [DEBUG] Pre dispatch: ["greet", "Alice"]
+/// [DEBUG] Post dispatch: EntryGreet
+/// [DEBUG] Pre chain: EntryGreet
+/// [DEBUG] Post chain: ResultName
+/// [DEBUG] Pre render: ResultName
+/// [DEBUG] Post render
+/// [DEBUG] Program end
+/// Hello, Alice!
/// ```
///
/// Cargo.toml
/// ```ignore
/// [package]
-/// name = "example-picker"
-/// version = "0.0.1"
+/// name = "example-hook"
+/// version = "0.1.0"
/// edition = "2024"
///
/// [dependencies]
-/// mingling = { path = "../../mingling", features = ["parser"] }
-/// tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros"] }
+/// mingling = { path = "../../mingling" }
/// ```
///
/// main.rs
/// ```ignore
-/// use mingling::prelude::*;
+/// use mingling::{hook::ProgramHook, prelude::*};
///
-/// dispatcher!("pick", PickCommand => PickEntry);
+/// dispatcher!("greet", CMDGreet => EntryGreet);
///
/// fn main() {
/// let mut program = ThisProgram::new();
-/// program.with_dispatcher(PickCommand);
-/// program.exec();
+///
+/// // --------- IMPORTANT ---------
+/// program.with_hook(
+/// ProgramHook::<ThisProgram>::empty()
+/// .on_begin(|| println!("[DEBUG] Program is begin"))
+/// .on_pre_dispatch(|args| println!("[DEBUG] Pre dispatch: {:?}", args))
+/// .on_post_dispatch(|c: &_| println!("[DEBUG] Post dispatch: {:?}", c))
+/// .on_pre_chain(|c: &_, _| {
+/// println!("[DEBUG] Pre chain: {}", c);
+/// })
+/// .on_post_chain(|any_output| println!("[DEBUG] Post chain: {}", any_output.member_id))
+/// .on_finish(|| {
+/// println!("[DEBUG] Loop end");
+/// 0 // Override exit code
+/// })
+/// .on_pre_render(|c: &_, _| println!("[DEBUG] Pre render: {}", c))
+/// .on_post_render(|_| println!("[DEBUG] Post render")),
+/// );
+/// // --------- IMPORTANT ---------
+///
+/// program.with_dispatcher(CMDGreet);
+/// program.exec_and_exit();
/// }
///
-/// pack!(NoNameProvided = ());
-/// pack!(ParsedPickInput = (i32, String));
+/// pack!(ResultName = String);
///
/// #[chain]
-/// fn parse(prev: PickEntry) -> Next {
-/// let picked = prev
-/// // First extract the named argument
-/// .pick_or("--age", 20)
-/// .after(|n: i32| n.clamp(0, 100))
-/// // Then sequentially extract the remaining arguments
-/// .pick_or_route((), NoNameProvided::default().to_render())
-/// .unpack();
-///
-/// match picked {
-/// Ok(value) => ParsedPickInput::new(value).to_render(),
-/// Err(e) => e,
-/// }
+/// fn handle_greet(args: EntryGreet) -> Next {
+/// let name: ResultName = args
+/// .inner
+/// .first()
+/// .cloned()
+/// .unwrap_or_else(|| "World".to_string())
+/// .into();
+/// name
/// }
///
/// #[renderer]
-/// fn render_parsed_pick_input(prev: ParsedPickInput) {
-/// let (age, name) = prev.inner;
-/// r_println!("Picked: name = {}, age = {}", name, age);
+/// fn render_name(name: ResultName) {
+/// r_println!("Hello, {}!", *name);
+/// }
+///
+/// gen_program!();
+/// ```
+pub mod example_hook {}
+/// Example Panic Unwind
+///
+/// > This example introduces how to catch Panic in the Mingling program loop
+///
+/// Run:
+/// ```bash
+/// cargo run --manifest-path examples/example-panic-unwind/Cargo.toml --quiet -- panic
+/// cargo run --manifest-path examples/example-panic-unwind/Cargo.toml --quiet -- panic OhMyGod
+/// ```
+///
+/// Output:
+/// ```plaintext
+/// Program not panic
+/// Program panic: OhMyGod
+/// OhMyGod
+/// ```
+///
+/// Cargo.toml
+/// ```ignore
+/// [package]
+/// name = "example-panic-unwind"
+/// version = "0.1.0"
+/// edition = "2024"
+///
+/// [dependencies.mingling]
+/// path = "../../mingling"
+/// features = ["parser"]
+///
+/// # Enable panic unwinding in release builds
+/// [profile.release]
+/// panic = "unwind"
+///
+/// # Enable panic unwinding in dev builds
+/// [profile.dev]
+/// panic = "unwind"
+/// ```
+///
+/// main.rs
+/// ```ignore
+/// use mingling::{hook::ProgramHook, prelude::*};
+///
+/// dispatcher!("panic", CMDPanic => EntryPanic);
+/// pack!(NotPanic = ());
+///
+/// fn main() {
+/// let mut program = ThisProgram::new();
+/// program.with_dispatcher(CMDPanic);
+///
+/// // --------- IMPORTANT ---------
+/// // Enable silence_panic to suppress automatic Panic output
+/// program.stdout_setting.silence_panic = true;
+///
+/// // Define a hook to output &ProgramPanic when a Panic occurs
+/// program
+/// .with_hook(ProgramHook::empty().on_exec_panic(|info| println!("Program panic: {}", info)));
+/// // --------- IMPORTANT ---------
+///
+/// program.exec();
+/// }
+///
+/// #[chain]
+/// fn handle_panic(prev: EntryPanic) -> Next {
+/// let panic_info = prev.pick::<Option<String>>(()).unpack();
+/// match panic_info {
+/// Some(s) => {
+/// // Panic happens here, will be caught
+/// panic!("{}", s)
+/// }
+/// None => NotPanic::default(),
+/// }
/// }
///
/// #[renderer]
-/// fn render_no_name_input(_prev: NoNameProvided) {
-/// r_println!("No name provided.");
+/// fn render(_: NotPanic) {
+/// r_println!("Program not panic");
/// }
///
/// gen_program!();
/// ```
-pub mod example_picker {}
-
+pub mod example_panic_unwind {}
+/// Example REPL Basic
+///
+/// > This example demonstrates how to develop a REPL program using the `repl` feature
+///
+/// Run:
+/// ```bash
+/// cargo run --manifest-path examples/example-repl-basic/Cargo.toml --quiet
+/// ```
///
/// Cargo.toml
/// ```ignore
/// [package]
-/// name = "example-repl"
-/// version = "0.0.1"
+/// name = "example-repl-basic"
+/// version = "0.1.0"
/// edition = "2024"
///
+/// [dependencies.mingling]
+/// path = "../../mingling"
+/// features = ["repl", "parser"]
+///
/// [dependencies]
-/// mingling = { path = "../../mingling", features = ["repl", "parser"] }
/// just_fmt = "0.1.2"
/// ```
///
@@ -591,11 +1334,11 @@ pub mod example_picker {}
///
/// // Resource to store the current directory
/// #[derive(Clone)]
-/// struct CurrentDir {
+/// struct ResCurrentDir {
/// dir: PathBuf,
/// }
///
-/// impl Default for CurrentDir {
+/// impl Default for ResCurrentDir {
/// fn default() -> Self {
/// Self {
/// dir: current_dir().unwrap(),
@@ -606,20 +1349,26 @@ pub mod example_picker {}
/// fn main() {
/// let mut program = ThisProgram::new();
///
-/// // Add resource
-/// program.with_resource(CurrentDir::default());
+/// // Resource
+/// program.with_resource(ResCurrentDir::default());
///
-/// // Add dispatchers
+/// // Dispatchers
/// program.with_dispatcher(ChangeDirectoryCommand);
/// program.with_dispatcher(ListCommand);
/// program.with_dispatcher(ExitCommand);
/// program.with_dispatcher(ClearCommand);
///
-/// // Add setups
+/// // Setups
+/// // Enable basic std::io::stdin().read_line(&mut input)
/// program.with_setup(BasicREPLReadlineSetup);
+///
+/// // Enable basic output, using println! after Renderer finishes drawing
/// program.with_setup(BasicREPLOutputSetup);
+///
+/// // Enable basic Prompt display, with custom display logic
/// program.with_setup(BasicREPLPromptSetup::func(|| {
-/// let res = this::<ThisProgram>().res::<CurrentDir>().unwrap();
+/// // Get the ResCurrentDir resource from the program
+/// let res = this::<ThisProgram>().res::<ResCurrentDir>().unwrap();
/// let dir_str: String = res.dir.to_string_lossy().into();
/// let prompt = format!(
/// "{}> ",
@@ -665,9 +1414,11 @@ pub mod example_picker {}
///
/// // Execute directory change
/// #[chain]
-/// fn handle_cd(prev: StateChangeDirectory, current_dir: &mut CurrentDir) -> Next {
+/// fn handle_cd(prev: StateChangeDirectory, current_dir: &mut ResCurrentDir) -> Next {
+/// use just_fmt::fmt_path::fmt_path;
+///
/// let join = prev.inner;
-/// let new_dir = just_fmt::fmt_path::fmt_path(current_dir.dir.join(join)).unwrap_or_default();
+/// let new_dir = fmt_path(current_dir.dir.join(join)).unwrap_or_default();
///
/// // If the path is not found, route to error handling
/// if !new_dir.exists() {
@@ -680,7 +1431,7 @@ pub mod example_picker {}
///
/// // Get directory contents via the CurrentDir resource
/// #[chain]
-/// fn handle_ls(_prev: ListEntry, current_dir: &CurrentDir) -> Next {
+/// fn handle_ls(_prev: ListEntry, current_dir: &ResCurrentDir) -> Next {
/// let dir = &current_dir.dir;
/// let entries: Vec<String> = std::fs::read_dir(dir)
/// .into_iter()
@@ -738,76 +1489,269 @@ pub mod example_picker {}
///
/// gen_program!();
/// ```
-pub mod example_repl {}
-/// `Mingling` Example - Global Resource Injection
+pub mod example_repl_basic {}
+/// Example Resource Injection
///
-/// This example demonstrates how to use global resource injection in `#[chain]` functions.
-/// You can inject both immutable (`&T`) and mutable (`&mut T`) references to global resources.
+/// > This example demonstrates how to read and write the program's global state using Mingling's resource system
///
-/// # How to Run
+/// Run:
/// ```bash
-/// cargo run --manifest-path ./examples/example-resources/Cargo.toml -- setup
+/// cargo run --manifest-path examples/example-resources/Cargo.toml --quiet current
+/// cargo run --manifest-path examples/example-resources/Cargo.toml --quiet modify-current src
+/// ```
+///
+/// Output:
+/// ```plaintext
+/// Current directory: /home/alice/mingling
+/// Current directory: /home/alice/mingling/src
/// ```
///
/// Cargo.toml
/// ```ignore
/// [package]
/// name = "example-resources"
-/// version = "0.0.1"
+/// version = "0.1.0"
/// edition = "2024"
///
-/// [dependencies]
-/// mingling = { path = "../../mingling", features = ["parser"] }
+/// [dependencies.mingling]
+/// path = "../../mingling"
+/// features = ["parser"]
/// ```
///
/// main.rs
/// ```ignore
+/// use std::path::PathBuf;
+///
/// use mingling::prelude::*;
-/// use std::{env::current_dir, path::PathBuf};
///
-/// // Define a resource for storing global state
+/// // Create resource
+/// // ______________ Resource needs to
+/// // / / implement the following two traits
+/// // vvvvvvv vvvvv
/// #[derive(Default, Clone)]
-/// pub struct MyResource {
+/// struct ResCurrentDir {
/// current_dir: PathBuf,
/// }
///
/// fn main() {
/// let mut program = ThisProgram::new();
///
-/// // Add the resource to the program
-/// program.with_resource(MyResource::default());
+/// // --------- IMPORTANT ---------
+/// // Use `with_resource` to inject a singleton into the program
+/// program.with_resource(ResCurrentDir {
+/// current_dir: std::env::current_dir().unwrap(),
+/// });
+/// // --------- IMPORTANT ---------
///
-/// program.with_dispatcher(SetupCommand);
+/// program.with_dispatchers((CMDCurrent, CMDModifyCurrent));
/// program.exec_and_exit();
/// }
///
-/// dispatcher!("setup", SetupCommand => SetupEntry);
-/// pack!(StateRead = ());
-/// pack!(ResultCurrentDir = PathBuf);
+/// dispatcher!("current", CMDCurrent => EntryCurrent);
+/// dispatcher!("modify-current", CMDModifyCurrent => EntryModifyCurrent);
+///
+/// // Define chain for modifying current directory _________________ Injected muttable resource
+/// // /
+/// #[chain] // vvvvvvvvvvvvvvvvvv
+/// fn render_modify_current(args: EntryModifyCurrent, current_dir: &mut ResCurrentDir) -> Next {
+/// current_dir.current_dir = current_dir
+/// .current_dir
+/// .join(args.pick::<String>(()).unpack());
+/// EntryCurrent::default()
+/// }
///
-/// #[chain]
-/// fn setup(
-/// _prev: SetupEntry,
-/// resource: &mut MyResource, // Import the resource into `setup`
-/// ) -> Next {
-/// // Set the global resource
-/// resource.current_dir = current_dir().unwrap();
+/// // Define renderer for output current path _____________ Injected resource
+/// // /
+/// #[renderer] // vvvvvvvvvvvvvv
+/// fn render_current(_: EntryCurrent, current_dir: &ResCurrentDir) {
+/// r_println!("Current directory: {}", current_dir.current_dir.display());
+/// }
+///
+/// gen_program!();
+/// ```
+pub mod example_resources {}
+/// Example Setup
+///
+/// > This example demonstrates how to build a custom Setup for modular management of project components
+///
+/// Cargo.toml
+/// ```ignore
+/// [package]
+/// name = "example-setup"
+/// version = "0.1.0"
+/// edition = "2024"
+///
+/// [dependencies]
+/// mingling = { path = "../../mingling" }
+/// ```
+///
+/// main.rs
+/// ```ignore
+/// use mingling::{Program, macros::program_setup, prelude::*};
+///
+/// fn main() {
+/// let mut program = ThisProgram::new();
+///
+/// // --------- IMPORTANT ---------
+/// // Introduce `CustomSetup` generated by `custom_setup`
+/// program.with_setup(CustomSetup);
+/// // --------- IMPORTANT ---------
+///
+/// program.exec_and_exit();
+/// }
+///
+/// // --------- IMPORTANT ---------
+/// // Define `CustomSetup` (inferred from `custom_setup`)
+/// // Package part of the program construction logic into this type for modular management
+/// #[program_setup]
+/// fn custom_setup(program: &mut Program<ThisProgram>) {
+/// program.with_dispatchers((CMD1, CMD2, CMD3, CMD4, CMD5));
+/// }
+/// // --------- IMPORTANT ---------
+///
+/// dispatcher!("1", CMD1 => Entry1);
+/// dispatcher!("2", CMD2 => Entry2);
+/// dispatcher!("3", CMD3 => Entry3);
+/// dispatcher!("4", CMD4 => Entry4);
+/// dispatcher!("5", CMD5 => Entry5);
///
-/// StateRead::default()
+/// gen_program!();
+/// ```
+pub mod example_setup {}
+/// Example Unit Test
+///
+/// > This example shows how to write unit tests for Chain and Renderer in Mingling
+///
+/// ```bash
+/// cargo test --manifest-path examples/example-unit-test/Cargo.toml
+/// ```
+///
+/// Cargo.toml
+/// ```ignore
+/// [package]
+/// name = "example-unit-test"
+/// version = "0.1.0"
+/// edition = "2024"
+///
+/// [dependencies]
+/// mingling = { path = "../../mingling" }
+/// ```
+///
+/// main.rs
+/// ```ignore
+/// use mingling::prelude::*;
+///
+/// #[cfg(test)]
+/// mod tests {
+/// use super::*;
+/// use mingling::{assert_member_id, assert_render_result};
+///
+/// // --------- IMPORTANT ---------
+/// #[test]
+/// fn test_handle_hello() {
+/// let hello_without_args = handle_hello(entry!()).into();
+/// assert_render_result!(hello_without_args);
+/// assert_member_id!(hello_without_args, ThisProgram::ErrorNoNameProvided);
+///
+/// let hello_with_registered_name = handle_hello(entry!("Alice")).into();
+/// assert_render_result!(hello_with_registered_name);
+/// assert_member_id!(
+/// hello_with_registered_name,
+/// ThisProgram::ErrorNameNotAvailable
+/// );
+///
+/// let hello_with_long_name = handle_hello(entry!("It's a VeryLongName")).into();
+/// assert_render_result!(hello_with_long_name);
+/// assert_member_id!(hello_with_long_name, ThisProgram::ErrorNameTooLong);
+///
+/// let hello_with_valid_name = handle_hello(entry!("Peter")).into();
+/// assert_render_result!(hello_with_valid_name);
+/// }
+///
+/// #[test]
+/// fn test_render_result_name() {
+/// let r = render_result_name(ResultName::new("Peter".into()));
+/// assert_eq!(r, "Hello, Peter!\n")
+/// }
+///
+/// #[test]
+/// fn test_render_error_no_name_provided() {
+/// let r = render_error_no_name_provided(ErrorNoNameProvided::default());
+/// assert_eq!(r, "No name provided\n")
+/// }
+///
+/// #[test]
+/// fn test_render_error_name_not_available() {
+/// let r = render_error_name_not_available(ErrorNameNotAvailable::default());
+/// assert_eq!(r, "Name not available\n")
+/// }
+///
+/// #[test]
+/// fn test_render_error_name_too_long() {
+/// let r = render_error_name_too_long(ErrorNameTooLong::new(17));
+/// assert_eq!(r, "Name too long: 17 > 10\n")
+/// }
+/// // --------- IMPORTANT ---------
/// }
///
+/// dispatcher!("hello", CMDHello => EntryHello);
+///
+/// pack!(ErrorNoNameProvided = ());
+/// pack!(ErrorNameTooLong = u16);
+/// pack!(ErrorNameNotAvailable = ());
+///
+/// pack!(ResultName = String);
+///
+/// static VEC_REGISTERED_NAMES: &[&str] = &["Alice", "Bob", "Charlie", "David", "Eve"];
+///
/// #[chain]
-/// fn read(_prev: StateRead, resource: &MyResource) -> Next {
-/// // Read the global resource
-/// let current_dir = resource.current_dir.clone();
-/// ResultCurrentDir::new(current_dir).to_render()
+/// fn handle_hello(args: EntryHello) -> Next {
+/// let Some(name) = args.inner.first().cloned() else {
+/// return ErrorNoNameProvided::default().to_render();
+/// };
+///
+/// if name.len() > 10 {
+/// return ErrorNameTooLong::new(name.len() as u16).to_render();
+/// }
+///
+/// if VEC_REGISTERED_NAMES.contains(&name.as_str()) {
+/// return ErrorNameNotAvailable::default().to_render();
+/// }
+///
+/// ResultName::new(name).to_render()
+/// }
+///
+/// #[renderer]
+/// fn render_result_name(name: ResultName) -> String {
+/// r_println!("Hello, {}!", *name);
/// }
///
/// #[renderer]
-/// fn render_current_dir(dir: ResultCurrentDir) {
-/// r_println!("Current dir: {}", dir.to_string_lossy())
+/// fn render_error_no_name_provided(_: ErrorNoNameProvided) -> String {
+/// r_println!("No name provided");
+/// }
+///
+/// #[renderer]
+/// fn render_error_name_not_available(_: ErrorNameNotAvailable) -> String {
+/// r_println!("Name not available");
+/// }
+///
+/// #[renderer]
+/// fn render_error_name_too_long(len: ErrorNameTooLong) -> String {
+/// r_println!("Name too long: {} > 10", *len);
+/// }
+///
+/// #[renderer]
+/// fn render_dispatcher_not_found(err: DispatcherNotFound) {
+/// r_println!("Command not found: \"{}\"", err.inner.join(" "));
/// }
///
/// gen_program!();
+///
+/// fn main() {
+/// let mut program = ThisProgram::new();
+/// program.with_dispatcher(CMDHello);
+/// program.exec_and_exit();
+/// }
/// ```
-pub mod example_resources {}
+pub mod example_unit_test {}
diff --git a/mingling/src/lib.rs b/mingling/src/lib.rs
index 4c11b7c..583c89a 100644
--- a/mingling/src/lib.rs
+++ b/mingling/src/lib.rs
@@ -85,6 +85,8 @@ pub mod macros {
pub use mingling_macros::dispatcher_clap;
/// Used to create an empty result value for early return from a chain function
pub use mingling_macros::empty_result;
+ /// Creates a packed entry value from a list of string literals
+ pub use mingling_macros::entry;
/// Used to collect data and create a command-line context
pub use mingling_macros::gen_program;
/// Used to generate a struct implementing the `HelpRequest` trait via a method
@@ -216,6 +218,8 @@ pub mod prelude {
pub use crate::macros::dispatcher;
/// Re-export of the `empty_result` macro for creating an empty result value for early return.
pub use crate::macros::empty_result;
+ /// Re-export of the `entry` macro for creating packed entry values from string literals.
+ pub use crate::macros::entry;
/// Re-export of the `gen_program` macro for generating the program entry point.
pub use crate::macros::gen_program;
/// Re-export of the `pack` macro for creating wrapper types.
diff --git a/mingling_macros/src/entry.rs b/mingling_macros/src/entry.rs
new file mode 100644
index 0000000..6237e41
--- /dev/null
+++ b/mingling_macros/src/entry.rs
@@ -0,0 +1,75 @@
+use proc_macro::TokenStream;
+use quote::quote;
+use syn::{Ident, LitStr, parse_macro_input};
+
+enum EntryInput {
+ Typed { ident: Ident, strings: Vec<String> },
+ Untyped { strings: Vec<String> },
+}
+
+impl syn::parse::Parse for EntryInput {
+ fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
+ // entry!(EntryType, ["a", "b", "c"])
+ // entry!["a", "b", "c"] — comes in as just ["a", "b", "c"]
+ if input.peek(Ident) && input.peek2(syn::Token![,]) {
+ // entry!(EntryType, ["a", "b", "c"])
+ let ident: Ident = input.parse()?;
+ let _comma: syn::Token![,] = input.parse()?;
+ let content;
+ syn::bracketed!(content in input);
+ let strings = parse_strings(&content)?;
+ Ok(EntryInput::Typed { ident, strings })
+ } else {
+ // entry!["a", "b", "c"] — bare bracket content
+ let strings = parse_strings(input)?;
+ Ok(EntryInput::Untyped { strings })
+ }
+ }
+}
+
+fn parse_strings(input: &syn::parse::ParseBuffer) -> syn::Result<Vec<String>> {
+ let mut strings = Vec::new();
+ while !input.is_empty() {
+ let s: LitStr = input.parse()?;
+ strings.push(s.value());
+ if input.peek(syn::Token![,]) {
+ let _comma: syn::Token![,] = input.parse()?;
+ }
+ }
+ Ok(strings)
+}
+
+pub fn entry(input: TokenStream) -> TokenStream {
+ let parsed = parse_macro_input!(input as EntryInput);
+
+ let string_exprs = match &parsed {
+ EntryInput::Typed { .. } | EntryInput::Untyped { .. } => {
+ let strings = match &parsed {
+ EntryInput::Typed { strings, .. } => strings,
+ EntryInput::Untyped { strings } => strings,
+ };
+ strings
+ .iter()
+ .map(|s| {
+ let lit = syn::LitStr::new(s, proc_macro2::Span::call_site());
+ quote! { #lit.to_string() }
+ })
+ .collect::<Vec<_>>()
+ }
+ };
+
+ let expanded = match parsed {
+ EntryInput::Typed { ident, .. } => {
+ quote! {
+ #ident::new(vec![#(#string_exprs),*])
+ }
+ }
+ EntryInput::Untyped { .. } => {
+ quote! {
+ vec![#(#string_exprs),*].into()
+ }
+ }
+ };
+
+ expanded.into()
+}
diff --git a/mingling_macros/src/lib.rs b/mingling_macros/src/lib.rs
index 8c98ba3..57f37a1 100644
--- a/mingling_macros/src/lib.rs
+++ b/mingling_macros/src/lib.rs
@@ -33,6 +33,7 @@ mod dispatch_tree_gen;
mod dispatcher;
#[cfg(feature = "clap")]
mod dispatcher_clap;
+mod entry;
mod enum_tag;
mod groupped;
mod help;
@@ -776,6 +777,27 @@ pub fn dispatcher_clap(attr: TokenStream, item: TokenStream) -> TokenStream {
dispatcher_clap::dispatcher_clap_attr(attr, item)
}
+/// Creates a packed entry value from a list of string literals.
+///
+/// # Syntax
+///
+/// Two forms:
+///
+/// ```rust,ignore
+/// // With explicit type — expands to MyEntry::new(vec!["a".to_string(), ...])
+/// entry!(MyEntry, ["a", "b", "c"])
+///
+/// // Without type — use bracket syntax, expands to vec!["a".to_string(), ...].into()
+/// entry!["a", "b", "c"]
+/// ```
+///
+/// This is a convenience macro for constructing entry wrapper types (created
+/// via `pack!` or `dispatcher!`) with test data.
+#[proc_macro]
+pub fn entry(input: TokenStream) -> TokenStream {
+ entry::entry(input)
+}
+
/// Registers a help request mapping between an entry type and a help struct.
///
/// This macro is used internally by the `#[help]`(macro.help.html) attribute