aboutsummaryrefslogtreecommitdiff
path: root/examples
diff options
context:
space:
mode:
authorWeicao-CatilGrass <1992414357@qq.com>2026-05-23 23:41:04 +0800
committerWeicao-CatilGrass <1992414357@qq.com>2026-05-23 23:49:34 +0800
commit0a2ef958c0dca21d19e4ffc38ba5a7c4078e182a (patch)
treec82fc4242ed393b132ba514eb434d722e7d9c387 /examples
parentccab1940c019dfbfb7dfcbbe4cb927258933755f (diff)
Rework examples and add entry macro for testing
Diffstat (limited to 'examples')
-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
60 files changed, 2209 insertions, 768 deletions
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!"