aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-08-17 04:05:41 +0800
committer魏曹先生 <1992414357@qq.com>2026-08-17 04:05:41 +0800
commitaa251efb87b561f62266628f06ad341253fbbdc5 (patch)
tree7d2cc2f34f3e4a7282d8d6f0ec0f5ae4bfc87002
parentc23c590330af83afb6e146bcd9b0a274b3689d22 (diff)
refactor!: remove legacy parser feature and migrate to picker
The legacy `parser` feature and its module tree (`mingling::parser`, `Argument`, `Picker`, `Pickable`, etc.) have been fully removed and replaced by the `picker` feature powered by `arg-picker`. BREAKING CHANGE: Remove `parser` feature and use `picker` instead. Migration guide: Replace `features = ["parser"]` with `features = ["picker"]` and update API usage per the provided table.
-rw-r--r--.run/src/bin/doc-nightly.ps12
-rwxr-xr-x.run/src/bin/doc-nightly.sh2
-rw-r--r--.run/src/bin/doc.ps12
-rwxr-xr-x.run/src/bin/doc.sh2
-rw-r--r--CHANGELOG.md61
-rw-r--r--Cargo.lock7
-rw-r--r--docs/_zh_CN/pages/3-define-a-chain.md2
-rw-r--r--docs/_zh_CN/pages/6-argument-parse-picker.md246
-rw-r--r--docs/_zh_CN/pages/other/features.md12
-rw-r--r--docs/dev/pages/abouts/code-verify-system.md4
-rw-r--r--docs/example-pages/examples.json30
-rw-r--r--docs/pages/1-getting-started.md2
-rw-r--r--docs/pages/3-define-a-chain.md2
-rw-r--r--docs/pages/6-argument-parse-picker.md243
-rw-r--r--docs/pages/other/features.md14
-rw-r--r--examples/example-argument-parse/Cargo.lock223
-rw-r--r--examples/example-argument-parse/Cargo.toml12
-rw-r--r--examples/example-argument-parse/page.toml10
-rw-r--r--examples/example-argument-parse/src/main.rs102
-rw-r--r--examples/example-argument-parse/test.toml29
-rw-r--r--examples/example-async-support/Cargo.lock25
-rw-r--r--examples/example-async-support/Cargo.toml4
-rw-r--r--examples/example-async-support/src/main.rs2
-rw-r--r--examples/example-completion/Cargo.lock25
-rw-r--r--examples/example-completion/Cargo.toml2
-rw-r--r--examples/example-completion/src/main.rs20
-rw-r--r--examples/example-custom-pickable/Cargo.lock223
-rw-r--r--examples/example-custom-pickable/Cargo.toml11
-rw-r--r--examples/example-custom-pickable/page.toml10
-rw-r--r--examples/example-custom-pickable/src/main.rs130
-rw-r--r--examples/example-custom-pickable/test.toml17
-rw-r--r--examples/example-enum-tag/Cargo.lock25
-rw-r--r--examples/example-enum-tag/Cargo.toml2
-rw-r--r--examples/example-enum-tag/src/main.rs40
-rw-r--r--examples/example-panic-unwind/Cargo.lock25
-rw-r--r--examples/example-panic-unwind/Cargo.toml2
-rw-r--r--examples/example-panic-unwind/src/main.rs2
-rw-r--r--examples/example-repl-basic/Cargo.lock25
-rw-r--r--examples/example-repl-basic/Cargo.toml2
-rw-r--r--examples/example-repl-basic/src/main.rs2
-rw-r--r--examples/example-resources/Cargo.lock25
-rw-r--r--examples/example-resources/Cargo.toml2
-rw-r--r--examples/example-resources/src/main.rs2
-rw-r--r--examples/example-structural-renderer/Cargo.lock25
-rw-r--r--examples/example-structural-renderer/Cargo.toml2
-rw-r--r--examples/example-structural-renderer/src/main.rs12
-rw-r--r--examples/full-todolist/Cargo.lock25
-rw-r--r--examples/full-todolist/Cargo.toml2
-rw-r--r--examples/full-todolist/src/main.rs15
-rw-r--r--mingling/Cargo.toml4
-rw-r--r--mingling/src/example_docs.rs367
-rw-r--r--mingling/src/features.rs11
-rw-r--r--mingling/src/lib.rs8
-rw-r--r--mingling/src/parser.rs12
-rw-r--r--mingling/src/parser/args.rs177
-rw-r--r--mingling/src/parser/picker.rs815
-rw-r--r--mingling/src/parser/picker/bools.rs143
-rw-r--r--mingling/src/parser/picker/builtin.rs113
-rw-r--r--mingling/src/parser/picker/path.rs145
-rw-r--r--mingling/src/parser/picker/path/rule.rs231
-rw-r--r--mingling/src/parser/test.rs731
-rw-r--r--mingling_core/tests/test-all/Cargo.lock25
-rw-r--r--mingling_core/tests/test-all/Cargo.toml2
-rw-r--r--mingling_core/tests/test-structural-renderer/Cargo.lock25
-rw-r--r--mingling_core/tests/test-structural-renderer/Cargo.toml2
65 files changed, 593 insertions, 3929 deletions
diff --git a/.run/src/bin/doc-nightly.ps1 b/.run/src/bin/doc-nightly.ps1
index 30d6aaf..58d7af4 100644
--- a/.run/src/bin/doc-nightly.ps1
+++ b/.run/src/bin/doc-nightly.ps1
@@ -1,6 +1,6 @@
cargo +nightly rustdoc `
--manifest-path mingling/Cargo.toml `
- --features docs_rs,core,macros,builds,structural_renderer,repl,comp,parser,picker,clap,extra_macros `
+ --features docs_rs,core,macros,builds,structural_renderer,repl,comp,picker,clap,extra_macros `
--open `
-- `
--cfg docsrs
diff --git a/.run/src/bin/doc-nightly.sh b/.run/src/bin/doc-nightly.sh
index 944f4b3..d16b6fc 100755
--- a/.run/src/bin/doc-nightly.sh
+++ b/.run/src/bin/doc-nightly.sh
@@ -2,7 +2,7 @@
cargo rustdoc \
--manifest-path mingling/Cargo.toml \
- --features docs_rs,core,macros,builds,structural_renderer,repl,comp,parser,picker,clap,extra_macros \
+ --features docs_rs,core,macros,builds,structural_renderer,repl,comp,picker,clap,extra_macros \
--open \
-- \
--cfg docsrs
diff --git a/.run/src/bin/doc.ps1 b/.run/src/bin/doc.ps1
index 731168c..d400f76 100644
--- a/.run/src/bin/doc.ps1
+++ b/.run/src/bin/doc.ps1
@@ -1,5 +1,5 @@
$env:RUSTDOCFLAGS="--html-in-header mingling/arborium-header.html"; cargo doc `
--manifest-path mingling/Cargo.toml `
--no-deps `
- --features docs_rs,core,macros,builds,structural_renderer,repl,comp,parser,picker,clap,extra_macros,pathf `
+ --features docs_rs,core,macros,builds,structural_renderer,repl,comp,picker,clap,extra_macros,pathf `
--open
diff --git a/.run/src/bin/doc.sh b/.run/src/bin/doc.sh
index d6181d3..4229853 100755
--- a/.run/src/bin/doc.sh
+++ b/.run/src/bin/doc.sh
@@ -3,5 +3,5 @@
RUSTDOCFLAGS="--html-in-header mingling/arborium-header.html" cargo doc \
--manifest-path mingling/Cargo.toml \
--no-deps \
- --features docs_rs,core,macros,builds,structural_renderer,repl,comp,parser,picker,clap,extra_macros,pathf \
+ --features docs_rs,core,macros,builds,structural_renderer,repl,comp,picker,clap,extra_macros,pathf \
--open
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7094c3c..75f1576 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -202,6 +202,67 @@ None
_No behavioral changes to command matching — the semantics of dot-separated command paths, kebab-case normalization, and "longest registered prefix wins" are all preserved by the compiled-in string-based dispatch trie / linear list. The removal is purely an API simplification: the `Node` intermediate abstraction and the dispatcher-boxing machinery are gone._
+4. **[`feat:parser`]** **[BREAKING REMOVAL]** Removed the legacy `parser` feature and its entire module tree — `mingling::parser` (with `Argument`, `Picker`, `Pickable`, `PickableEnum`, `AsPicker`, `Yes`, `True`, `PathCheckRule`, `PathsChecker`, `PathChecker`, and the built-in `size`-based `usize` size parsing). The `Parser` has been fully superseded by the `picker` feature, which uses the standalone `arg-picker` crate. The `size` crate dependency (only used by the parser's `usize` size-string parsing) has also been removed from `mingling/Cargo.toml`.
+
+ ### What changed
+
+ The `parser` feature provided an internal argument-parsing module (`mingling::parser`) with a fluent `Picker` API for extracting typed values from `Vec<String>` command-line arguments. This functionality has been entirely replaced by the `picker` feature (`arg-picker` crate), so the legacy built-in parser is now dead code and has been removed.
+
+ **Removed public modules and types:**
+
+ - **`mingling::parser` module** — Entire module removed (`mingling/src/parser.rs` and the whole `mingling/src/parser/` directory), including:
+ - **`Argument`** — The struct wrapping `Vec<String>` with `pick_argument`, `pick_arguments`, `pick_flag`, `dump_remains`, and `strip_all_flags` methods.
+ - **`Picker`** — The fluent builder struct with `pick`, `pick_or`, `pick_or_route`, `require`, and `operate_args` methods.
+ - **`Pickable`** — The trait defining `pick(&mut Argument, Flag) -> Option<Self::Output>`.
+ - **`PickableEnum`** — The marker trait for `EnumTag`-implementing enums providing blanket `Pickable` impls.
+ - **`AsPicker`** — The blanket trait implementing `pick`/`pick_or`/`pick_or_route` for all `Into<Vec<String>>` types.
+ - **`Pick1`–`Pick12` / `PickWithRoute1`–`PickWithRoute12`** — Builder structs for chained picks, with `after`, `after_or_route`, `unpack`, `unpack_directly`, and `operate_args`.
+ - **`Yes`** / **`True`** — Explicit boolean-like enums with `is_yes`/`is_no` and `is_true`/`is_false` helpers.
+ - **`PathCheckRule`** / **`PathsChecker`** / **`PathChecker`** — Path validation helpers (`must_file`, `must_dir`, `must_exist`, etc.).
+ - **Built-in `Pickable` impls** — For `String`, `Vec<String>`, all integer/float types, `bool`, `usize` (special size-string parsing like `"25MiB"`), `Vec<usize>`, `Vec<PathBuf>`, `PathBuf`, `Argument`, and `Option<T>`.
+ - The `usize` size-string parsing (e.g. `"25mib"` → `25 * 1024 * 1024`) used the external `size` crate, which has been removed from the dependency tree.
+
+ **Other changes:**
+
+ - **`mingling::features::MINGLING_PARSER`** constant — Removed from `mingling/src/features.rs`.
+ - **`mingling::prelude::AsPicker`** re-export — Removed from the prelude.
+ - **Deleted examples** — Removed `example-argument-parse` and `example-custom-pickable` (and their entries in `docs/example-pages/examples.json`), as they only demonstrated the legacy `parser` API.
+ - **Docs updated** — `docs/pages/6-argument-parse-picker.md` (and `docs/_zh_CN` and `docs/dev` copies) now document the `picker` feature API; the `parser` section of `docs/pages/other/features.md` was removed.
+ - **`mingling/Cargo.toml`** — Removed `parser = ["dep:size"]` from `[features]`, removed `size = { version = "0.5", optional = true }` from `[dependencies]`, and removed `"parser"` from the dev-dependency and example feature lists.
+ - **`mingling/src/lib.rs`** — Removed `#[cfg(feature = "parser")] pub mod parser;` and the prelude's `#[cfg(feature = "parser")] pub use crate::parser::AsPicker;`.
+ - **`mingling/src/parser/` directory** — Entirely deleted (including `args.rs`, `picker.rs`, `picker/builtin.rs`, `picker/bools.rs`, `picker/path.rs`, `picker/path/rule.rs`, and `test.rs`).
+ - **`Cargo.lock`** and per-example `Cargo.lock` files — Removed the `size` package entry and added `arg-picker` / `arg-picker-macros` where the `picker` feature is enabled.
+
+ **Migration guide:**
+
+ - **Replace the `parser` feature with `picker`** in `Cargo.toml`:
+ ```toml
+ # Old:
+ features = ["parser"]
+ # New:
+ features = ["picker"]
+ ```
+ - **Replace API usage** with the `arg-picker` equivalents from the `picker` feature:
+ | Legacy `parser` API | New `picker` API |
+ | ------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
+ | `prev.pick(())` | `prev.pick_or_default(&arg![T])` (or `prev.pick(&arg![T])`) |
+ | `prev.pick(flag)` | `prev.pick(&arg![name: T, 'n'])` |
+ | `prev.pick_or(flag, default)` | `prev.pick_or(&arg![name: T, 'n'], \|\| default)` |
+ | `prev.pick_or_route(flag, route)` + `.unpack()` | `prev.pick_or_route(&arg![T], \|\| route.to_chain())` + `.to_result()` + `route!` |
+ | `args.pick_argument(flag)` | Use `arg_picker::picker::PickerArg` / the `arg!` macro |
+ | `impl Pickable for T { ... }` | `impl SinglePickable for T { fn pick_single(str: Option<&str>) -> PickerArgResult<Self> { ... } }` |
+ | `impl PickableEnum for T {}` on an `EnumTag` enum | Implement `SinglePickable` manually with a `match` |
+ | `.after(...)` | `.post(...)` |
+ | `.unpack()` | `.unwrap()` |
+ | `usize` size-string parsing (`"25MiB"`) | Not directly supported by `arg-picker`; parse manually (e.g., via `size` directly or a custom `SinglePickable`) |
+ | `PathCheckRule` / `PathsChecker` / `PathChecker` | Use the new path wrapper types in `mingling::picker::value` (`FilePath`, `DirPath`, `NoPath`, `RecursiveFiles`, etc.) |
+ | `Yes` / `True` | Use `mingling::picker::value::Flag` (flag-based) and explicit value checks for confirmations |
+ - **Remove any `use mingling::parser::...;` imports.** All `parser` items are gone. Use the `picker` feature's module (`mingling::picker`, `arg_picker::prelude::arg`, etc.).
+ - **If you used the `usize` size-string parsing** (e.g., `preved.pick::<usize>("--size").unpack()` with inputs like `"25mib"`), implement a custom `SinglePickable` or use the `size` crate directly.
+ - **If you used `AsPicker`** on `Vec<String>` / `&[String]`, use `arg_picker::picker::IntoPicker` (or `EntryPicker`, for program entry types) instead.
+
+ _Behavioral note:_ the `picked` values, flag parsing semantics, and the "longest registered prefix wins" matching rule are all preserved by the `picker` feature's `arg-picker` API. The removal is purely a dead-code cleanup: the legacy built-in parser has been fully superseded by `picker`, and downstream code must migrate to the new API names described above.
+
---
## Contents
diff --git a/Cargo.lock b/Cargo.lock
index 74611d3..b5116a3 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -277,7 +277,6 @@ dependencies = [
"mingling_core",
"mingling_macros",
"serde",
- "size",
"tokio",
]
@@ -556,12 +555,6 @@ dependencies = [
]
[[package]]
-name = "size"
-version = "0.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1b6709c7b6754dca1311b3c73e79fcce40dd414c782c66d88e8823030093b02b"
-
-[[package]]
name = "smallvec"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/docs/_zh_CN/pages/3-define-a-chain.md b/docs/_zh_CN/pages/3-define-a-chain.md
index bf30b91..c8d8a24 100644
--- a/docs/_zh_CN/pages/3-define-a-chain.md
+++ b/docs/_zh_CN/pages/3-define-a-chain.md
@@ -92,7 +92,7 @@ fn handle_greet(args: EntryGreet) -> Next {
}
```
-如果你启用了 `parser` 特性,还可以用 `Picker` 做更灵活的参数提取,不过那是后话了。
+如果你启用了 `picker` 特性,还可以用 `Picker` 做更灵活的参数提取,不过那是后话了。
## 组合起来
diff --git a/docs/_zh_CN/pages/6-argument-parse-picker.md b/docs/_zh_CN/pages/6-argument-parse-picker.md
index 33f1f0d..7944d0a 100644
--- a/docs/_zh_CN/pages/6-argument-parse-picker.md
+++ b/docs/_zh_CN/pages/6-argument-parse-picker.md
@@ -19,34 +19,38 @@ let name = args.first().cloned().unwrap_or_else(|| "World".to_string());
```toml
# Cargo.toml
[dependencies.mingling]
-features = ["parser"]
+features = ["picker"]
```
好了,让我们看看 `Picker` 的写法:
```rust
-// Features: ["parser"]
+// Features: ["picker"]
@@@dispatcher!("greet", EntryGreet);
@@@pack!(ResultName = String);
#[chain]
fn handle_greet_entry(prev: EntryGreet) -> Next {
- let name = prev.pick_or((), "World").unpack();
+ let name = prev
+ .pick_or(&arg![String], || "World".to_string())
+ .unwrap();
ResultName::new(name).into()
}
```
-`AsPicker` 为所有可以转换为 `Vec<String>` 的类型实现了 `pick`、`pick_or`、`pick_or_route` 函数:它们可以语义化地从字符串列表中 **拾取 (Pick)** 参数,并转换为结构化数据。
+`EntryPicker` 为所有入口类型实现了 `pick`、`pick_or`、`pick_or_default` 和 `pick_or_route` 函数:它们可以通过 `arg!` 宏声明要拾取的内容,语义化地从字符串列表中 **拾取 (Pick)** 参数,并转换为结构化数据。
对于上述示例中的代码:
```rust
-// Features: ["parser"]
+// Features: ["picker"]
@@@dispatcher!("greet", EntryGreet);
@@@pack!(ResultName = String);
@@@#[chain]
@@@fn handle_greet_entry(prev: EntryGreet) -> Next {
-let name = prev.pick_or((), "World").unpack();
+let name = prev
+ .pick_or(&arg![String], || "World".to_string())
+ .unwrap();
@@@ResultName::new(name).into()
@@@}
```
@@ -54,75 +58,79 @@ let name = prev.pick_or((), "World").unpack();
它的语义为:
```rust
-// Features: ["parser"]
+// Features: ["picker"]
@@@dispatcher!("greet", EntryGreet);
@@@pack!(ResultName = String);
@@@#[chain]
@@@fn handle_greet_entry(prev: EntryGreet) {
@@@let name: String =
- prev.pick_or((), "World").unpack();
-// ~~~~ ~~~~~~~ ~~ ~~~~~~~ ~~~~~~~~
-// | | | | |_ 解包为 String
-// | | | |__________ 默认值为 "World"
-// | | |______________ 取出第一个位置参数(不指定标志)
-// | |______________________ 拾取或使用默认
-// |___________________________ 从前一个输入中
+ prev.pick_or(&arg![String], || "World".to_string()).unwrap();
+// ~~~~ ~~~~~~~ ~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~
+// | | | | |_ 解包为 String
+// | | | |__________________________ 默认值为 "World"
+// | | |________________________________________ 取出第一个位置参数(声明为 String)
+// | |________________________________________________ 拾取或使用默认
+// |_____________________________________________________ 从前一个输入中
@@@}
```
## 解析标志参数
-若你的程序需要解析标志参数(例如 `greet --name Alice`),可以使用如下方式
+若你的程序需要解析标志参数(例如 `greet --name Alice`),可以在 `arg!` 中声明一个具名标志:
```rust
-// Features: ["parser"]
+// Features: ["picker"]
@@@dispatcher!("greet", EntryGreet);
@@@pack!(ResultName = String);
#[chain]
fn handle_greet_entry(prev: EntryGreet) -> Next {
- let name = prev.pick_or(["--name", "-n"], "World").unpack();
+ let name = prev
+ .pick_or(&arg![name: String, 'n'], || "World".to_string())
+ .unwrap();
ResultName::new(name).into()
}
```
+`arg!` 宏会从字段名推导长标志名(`--name`),`'n'` 则添加短别名(`-n`)。
+
同理,它的语义为:
```rust
-// Features: ["parser"]
+// Features: ["picker"]
@@@dispatcher!("greet", EntryGreet);
@@@pack!(ResultName = String);
@@@#[chain]
@@@fn handle_greet_entry(prev: EntryGreet) {
@@@let name: String =
- prev.pick_or(["--name", "-n"], "World").unpack();
-// ~~~~ ~~~~~~~ ~~~~~~~~~~~~~~~~ ~~~~~~~ ~~~~~~~~
-// | | | | |_ 解包为 String
-// | | | |__________ 默认值为 "World"
-// | | |____________________________ 取出 "--name" 或 "-n" 后面的参数
-// | |____________________________________ 拾取或使用默认
-// |_________________________________________ 从前一个输入中
+ prev.pick_or(&arg![name: String, 'n'], || "World".to_string()).unwrap();
+// ~~~~ ~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~ ~~~~~~
+// | | | | |_ 解包为 String
+// | | | |________________________ 默认值为 "World"
+// | | |___________________________________________________ 取出 "--name" 或 "-n" 后面的参数
+// | |___________________________________________________________ 拾取或使用默认
+// |________________________________________________________________ 从前一个输入中
@@@}
```
-## 关于 `.unpack()`
+## 关于 `.unwrap()` 与 `route!`
-你可能注意到了,`Picker` 在命令解析的最后,会执行一个 `.unpack()` 函数,它的作用是将前面解析出来的结果,转换为结构化信息。
+你可能注意到了,`Picker` 在命令解析的最后,会执行一个 `.unwrap()`(或 `route!`)函数,它的作用是将前面解析出来的结果,转换为结构化信息。
-对于只拾取了一次的数据来说,`.unpack()` 会返回单个数据,而对于多次拾取,`Picker` 则会返回元组:
+对于只拾取了一次的数据来说,`.unwrap()` 会返回单个数据,而对于多次拾取,`Picker` 则会返回元组:
```rust
-// Features: ["parser"]
+// Features: ["picker"]
@@@dispatcher!("test", EntryTest);
@@@pack!(ResultInfo = (String, u8, u32));
#[chain]
fn handle_test_entry(prev: EntryTest) -> Next {
let (name, age, id) = prev
- .pick::<String>(["--name", "-n"])
- .pick::<u8>(["--age", "-a"])
- .pick::<u32>(["--id", "-I"])
- .unpack();
+ .pick_or_default(&arg![name: String, 'n'])
+ .pick_or_default(&arg![age: u8, 'a'])
+ .pick_or_default(&arg![id: u32, 'I'])
+ .unwrap();
ResultInfo::new((name, age, id)).into()
}
@@ -138,7 +146,7 @@ fn handle_test_entry(prev: EntryTest) -> Next {
先来看一个简单示例
```rust
-// Features: ["parser", "extras"]
+// Features: ["picker", "extras"]
@@@use mingling::macros::buffer;
@@@use mingling::macros::route;
@@@dispatcher!("greet", EntryGreet);
@@ -147,12 +155,13 @@ fn handle_test_entry(prev: EntryTest) -> Next {
#[chain]
fn handle_greet_entry(prev: EntryGreet) -> Next {
- let pick_result = prev
- .pick_or_route(["--name", "-n"], ErrorNoName::default())
- .unpack();
-
- // 使用 route! 宏展开 pick_result
- let name = route!(pick_result);
+ // 使用 route! 宏展开 Result<Value, Route>
+ let name = route!(
+ prev.pick_or_route(&arg![name: String, 'n'], || {
+ ErrorNoName::default().to_chain()
+ })
+ .to_result()
+ );
ResultName::new(name).into()
}
@@ -162,18 +171,18 @@ fn render_greet(result: ResultName) {
}
```
-若使用 `pick_or_route`,写法会变得相对复杂:因为 `.unpack()` 不再直接返回参数,而是 `Result<Value, Route>`。
+若使用 `pick_or_route`,`.to_result()` 不再直接返回参数,而是 `Result<Value, Route>`。
不过 **Mingling** 的 `extras` 特性提供了简化展开的宏 `route!`,它不复杂,只是省略了一部分样板代码:
```rust
-// Features: ["parser", "extras"]
+// Features: ["picker", "extras"]
@@@ pack!(ErrorFail = ());
@@@ use mingling::macros::route;
+@@@ use mingling::picker::IntoPicker;
@@@ fn func() -> mingling::ChainProcess<ThisProgram> {
@@@ let args: Vec<String> = vec![];
-@@@ let pick_result = args.pick_or_route::<String, _>((), ErrorFail::new(())).unpack();
-let name = route!(pick_result);
+let name = route!(args.pick_or_route(&arg![String], || ErrorFail::new(()).to_chain()).to_result());
@@@ mingling::macros::empty_result!()
@@@ }
```
@@ -181,14 +190,14 @@ let name = route!(pick_result);
它展开为:
```rust
-// Features: ["parser", "extras"]
+// Features: ["picker", "extras"]
@@@ pack!(ErrorFail = ());
+@@@ use mingling::picker::IntoPicker;
@@@ fn func() -> mingling::ChainProcess<ThisProgram> {
@@@ let args: Vec<String> = vec![];
-@@@ let pick_result = args.pick_or_route::<String, _>((), ErrorFail::new(())).unpack();
-let name = match pick_result {
+let name = match args.pick_or_route(&arg![String], || ErrorFail::new(()).to_chain()).to_result() {
Ok(r) => r,
- Err(e) => return e.to_chain(),
+ Err(e) => return e,
};
@@@ mingling::macros::empty_result!()
@@@ }
@@ -196,122 +205,59 @@ let name = match pick_result {
## 提取值的后处理
-在您使用 `pick` 提取了用户输入后,可以使用 `after` 立刻处理该参数
+在您使用 `pick` 提取了用户输入后,可以使用 `post` 立刻处理该参数
-````rust
-// Features: ["parser"]
+```rust
+// Features: ["picker"]
@@@dispatcher!("greet", EntryGreet);
@@@pack!(ResultName = String);
#[chain]
fn handle_greet_entry(prev: EntryGreet) -> Next {
let name = prev
- .pick_or(["--name", "-n"], "World")
+ .pick_or(&arg![name: String, 'n'], || "World".to_string())
// 在提取出 --name 后,立刻格式化
- .after(|name: String| {
+ .post(|name: String| {
name.replace(['-', '_', '.'], " ")
.to_lowercase()
.trim()
.to_string()
})
- .unpack();
-
- ResultName::new(name).into()
-}
-```
-
-同样,你可以使用 `after_or_route` 来处理输入参数的格式错误
-
-```rust
-// Features: ["parser", "extras"]
-@@@use mingling::macros::buffer;
-@@@use mingling::macros::route;
-@@@dispatcher!("greet", EntryGreet);
-@@@pack!(ResultName = String);
-@@@pack!(ErrorNameTooLong = usize);
-
-#[chain]
-fn handle_greet_entry(prev: EntryGreet) -> Next {
- let pick_result = prev
- .pick_or(["--name", "-n"], "World")
- .after_or_route(|name: &String| {
- if name.len() < 32 {
- Ok(name.clone())
- } else {
- Err(ErrorNameTooLong::new(name.len()))
- }
- })
- .unpack();
- let name = route!(pick_result);
+ .unwrap();
ResultName::new(name).into()
}
-
-#[renderer(buffer)]
-fn render_name_too_long(prev: ErrorNameTooLong) {
- let len = *prev;
- r_println!("Error: name too long (length: {} > 32)", len);
-}
-
-#[renderer(buffer)]
-fn render_name(prev: ResultName) {
- r_println!("Hello, {}!", *prev);
-}
```
## 布尔值解析
-`Picker` 当然也可以解析布尔类型,但是布尔类型分为显式和隐式模式:
-
-| 模式 | 格式 |
-| ---- | ----------------------------------- |
-| 隐式 | `--confirmed` |
-| 显式 | `--confirm true` 或 `--confirm yes` |
-
-- 使用 `.pick::<bool>(flag)` 时,采用隐式解析:只要标志存在即为 `true`
-- 使用 `.pick::<Yes>(flag)` 或 `.pick::<True>(flag)` 时,采用显式解析
-
-一般来说使用隐式解析即可,但在处理重要的确认行为时,显式逻辑更符合语义。
+`Picker` 将布尔值解析为**标志**:标志存在即为 `true`。
```rust
-// Features: ["parser"]
-@@@use mingling::parser::Yes;
+// Features: ["picker"]
+@@@use mingling::picker::value::Flag;
@@@dispatcher!("test", EntryTest);
@@@pack!(ResultDone = ());
#[chain]
fn handle_entry(prev: EntryTest) -> Next {
-@@@ let prev1 = prev.clone();
- let _confirmed: bool = prev.pick::<Yes>(()).unpack().is_yes();
-@@@ let prev = prev1;
- let _confirm: bool = prev.pick::<bool>(["--confirm", "-C"]).unpack();
+ // `--confirm` / `-C` 存在 → true
+ let _confirm: bool = *prev.pick(&arg![confirm: Flag, 'C']).unwrap();
ResultDone::default().to_render()
}
```
-## 特殊用法:`usize` 解析
-
-**Mingling** 为 `usize` 提供了一个特殊的用法:解析类似 `25G`、`32mib` 等字样
+> [!NOTE]
+> 对于重要的确认行为,如果精确的布尔语义很关键,请将标志与显式的值检查配合使用。
-```rust
-// Features: ["parser"]
-
-#[test]
-fn parse_size() {
- let vec = vec!["--size".to_string(), "25mib".to_string()];
- let size: usize = vec.pick(["--size", "-S"]).unpack();
- assert_eq!(size, 25 * 1024 * 1024);
-}
-```
-
## 自定义可解析类型
-你可以使用 `Pickable` trait 使你的类型支持被 `Picker` 解析,这也是 `Picker` 拓展性的来源
+你可以使用 `SinglePickable` trait 使你的类型支持被 `Picker` 解析,这也是 `Picker` 拓展性的来源
```rust
-// Features: ["parser"]
+// Features: ["picker"]
@@@use mingling::macros::buffer;
-@@@use mingling::parser::{Pickable, Argument};
+@@@use mingling::picker::{PickerArgResult, SinglePickable};
@@@use mingling::Flag;
#[derive(Default, Clone)]
pub struct Address {
@@ -319,14 +265,18 @@ pub struct Address {
port: u16,
}
-impl Pickable for Address {
- type Output = Self;
- fn pick(args: &mut Argument, flag: Flag) -> Option<Self::Output> {
- let raw = args.pick_argument(flag)?;
+impl SinglePickable for Address {
+ fn pick_single(str: Option<&str>) -> PickerArgResult<Self> {
+ let Some(raw) = str else {
+ return PickerArgResult::NotFound;
+ };
let parts: Vec<&str> = raw.split(':').collect();
- let ip = parts.first()?.to_string();
- let port: u16 = parts.get(1)?.parse().ok()?;
- Some(Address { ip, port })
+ let ip = parts.first().copied().unwrap_or_default().to_string();
+ let port: u16 = match parts.get(1).and_then(|p| p.parse().ok()) {
+ Some(p) => p,
+ None => return PickerArgResult::NotFound,
+ };
+ PickerArgResult::Parsed(Address { ip, port })
}
}
@@@dispatcher!("connect", EntryConnect);
@@ -334,7 +284,7 @@ impl Pickable for Address {
#[chain]
fn handle_connect_entry(prev: EntryConnect) -> Next {
- let address: Address = prev.pick("--addr").unpack();
+ let address: Address = prev.pick_or_default(&arg![Address]).unwrap();
ResultConnected::new(address).into()
}
@@ -351,14 +301,14 @@ fn render_connected(addr: ResultConnected) {
Connected: IP: 127.0.0.1 PORT: 8080
```
-## 自动为枚举实现 Pickable
+## 为枚举实现 Pickable
-要为枚举类型实现 `Pickable`,只需该枚举实现了 `EnumTag`,然后为其实现 `PickableEnum` 即可
+要让枚举支持 `Picker` 解析,可以手写 `SinglePickable`,用 match 匹配输入:
```rust
-// Features: ["parser"]
+// Features: ["picker"]
@@@use mingling::macros::buffer;
-@@@use mingling::parser::PickableEnum;
+@@@use mingling::picker::{PickerArgResult, SinglePickable};
@@@use mingling::EnumTag;
#[derive(Debug, Default, EnumTag)]
pub enum Fruits {
@@ -368,13 +318,26 @@ pub enum Fruits {
Orange,
}
-impl PickableEnum for Fruits {}
+impl SinglePickable for Fruits {
+ fn pick_single(str: Option<&str>) -> PickerArgResult<Self> {
+ let Some(str) = str else {
+ return PickerArgResult::NotFound;
+ };
+ let fruit = match str.to_lowercase().as_str() {
+ "apple" => Self::Apple,
+ "banana" => Self::Banana,
+ "orange" => Self::Orange,
+ _ => return PickerArgResult::NotFound,
+ };
+ PickerArgResult::Parsed(fruit)
+ }
+}
@@@dispatcher!("eat", EntryEat);
@@@pack!(ResultFruit = Fruits);
#[chain]
fn handle_eat_entry(prev: EntryEat) -> Next {
- let fruit: Fruits = prev.pick("--fruit").unpack();
+ let fruit: Fruits = prev.pick_or_default(&arg![Fruits]).unwrap();
ResultFruit::new(fruit).into()
}
@@ -389,4 +352,3 @@ fn render_fruit(prev: ResultFruit) {
<p align="center" style="font-size: 0.85em; color: gray;">
Written by @Weicao-CatilGrass
</p>
-````
diff --git a/docs/_zh_CN/pages/other/features.md b/docs/_zh_CN/pages/other/features.md
index 4913945..30231ce 100644
--- a/docs/_zh_CN/pages/other/features.md
+++ b/docs/_zh_CN/pages/other/features.md
@@ -330,24 +330,12 @@ analyze_and_build_type_mapping().unwrap();
详见 [示例](https://mingling-rs.github.io/mingling/docs/example-viewer.html?name=example-pathfinder)
-## 特性 `parser`
-
-**介绍:**
-
-启用参数解析器模块,提供参数解析功能。
-
-开启后可以使用 `Picker` 进行简易的参数提取,支持 `pick()` 和 `pick_or()` 等方法。
-
-详见 [示例](https://mingling-rs.github.io/mingling/docs/example-viewer.html?name=example-argument-parse)
-
## 特性 `picker`
**介绍:**
引入依赖 `arg-picker`,为 Mingling 提供更高级的参数解析能力。
-它可以与 `parser`、`clap` 特性共存,但建议不要和 `parser` 特性同时启用,因为两者的 API 极为相似。
-
`picker` 是独立于 Mingling 的参数解析器,不依赖 `mingling_core` 的内置参数提取 API。
详见 [示例](https://mingling-rs.github.io/mingling/docs/example-viewer.html?name=example-argument-picker)
diff --git a/docs/dev/pages/abouts/code-verify-system.md b/docs/dev/pages/abouts/code-verify-system.md
index 61b66e8..929b337 100644
--- a/docs/dev/pages/abouts/code-verify-system.md
+++ b/docs/dev/pages/abouts/code-verify-system.md
@@ -209,7 +209,7 @@ Use `@@@` for:
````markdown
```rust
-// Features: ["parser"]
+// Features: ["picker"]
// Dependencies:
// serde = "1"
@@ -237,6 +237,6 @@ mingling::macros::gen_program!();
```toml
[dependencies]
-mingling = { path = "../../mingling", features = ["parser"] }
+mingling = { path = "../../mingling", features = ["picker"] }
serde = { version = "1", features = ["derive"] }
```
diff --git a/docs/example-pages/examples.json b/docs/example-pages/examples.json
index 31336f1..8d6eaa6 100644
--- a/docs/example-pages/examples.json
+++ b/docs/example-pages/examples.json
@@ -16,21 +16,6 @@
]
},
{
- "id": "example-argument-parse",
- "name": "Argument Parse",
- "icon": "📋",
- "category": "parsing",
- "desc": "Shows how to use Mingling's `parser` feature with a `Picker` to extract and validate typed arguments from the command line.\n",
- "tags": [
- "pick",
- "Pickable"
- ],
- "files": [
- "src/main.rs",
- "Cargo.toml"
- ]
- },
- {
"id": "example-argument-picker",
"name": "Argument Picker",
"icon": "📋",
@@ -144,21 +129,6 @@
]
},
{
- "id": "example-custom-pickable",
- "name": "Custom Pickable",
- "icon": "🎯",
- "category": "parsing",
- "desc": "Shows how to implement the `Pickable` trait on custom types for seamless extraction from CLI arguments via Picker.\n",
- "tags": [
- "Pickable",
- "custom"
- ],
- "files": [
- "src/main.rs",
- "Cargo.toml"
- ]
- },
- {
"id": "example-dispatch-tree",
"name": "Dispatch Tree",
"icon": "🌳",
diff --git a/docs/pages/1-getting-started.md b/docs/pages/1-getting-started.md
index 0131ec1..94f507e 100644
--- a/docs/pages/1-getting-started.md
+++ b/docs/pages/1-getting-started.md
@@ -27,7 +27,7 @@ Some features **directly affect the entire lifecycle behavior**, so you need to
[dependencies.mingling]
version = "0.5.0"
features = [
- "parser",
+ "picker",
"comp",
]
```
diff --git a/docs/pages/3-define-a-chain.md b/docs/pages/3-define-a-chain.md
index dca299e..72ade94 100644
--- a/docs/pages/3-define-a-chain.md
+++ b/docs/pages/3-define-a-chain.md
@@ -92,7 +92,7 @@ fn handle_greet(args: EntryGreet) -> Next {
}
```
-If you enable the `parser` feature, you can also use `Picker` for more flexible param extraction — but that's a topic for later.
+If you enable the `picker` feature, you can also use `Picker` for more flexible param extraction — but that's a topic for later.
## Putting It Together
diff --git a/docs/pages/6-argument-parse-picker.md b/docs/pages/6-argument-parse-picker.md
index 9da56d5..da0fff1 100644
--- a/docs/pages/6-argument-parse-picker.md
+++ b/docs/pages/6-argument-parse-picker.md
@@ -19,34 +19,38 @@ To enable `Picker`, update your `Cargo.toml`:
```toml
# Cargo.toml
[dependencies.mingling]
-features = ["parser"]
+features = ["picker"]
```
Now let's see how `Picker` is written:
```rust
-// Features: ["parser"]
+// Features: ["picker"]
@@@dispatcher!("greet", EntryGreet);
@@@pack!(ResultName = String);
#[chain]
fn handle_greet_entry(prev: EntryGreet) -> Next {
- let name = prev.pick_or((), "World").unpack();
+ let name = prev
+ .pick_or(&arg![String], || "World".to_string())
+ .unwrap();
ResultName::new(name).into()
}
```
-`AsPicker` implements `pick`, `pick_or`, and `pick_or_route` for all types convertible to `Vec<String>`. These functions semantically **pick** params from the string list and convert them into structured data.
+`EntryPicker` implements `pick`, `pick_or`, `pick_or_default`, and `pick_or_route` for all entry types. These functions semantically **pick** params from the string list and convert them into structured data, using the `arg!` macro to declare what to pick.
For the code above:
```rust
-// Features: ["parser"]
+// Features: ["picker"]
@@@dispatcher!("greet", EntryGreet);
@@@pack!(ResultName = String);
@@@#[chain]
@@@fn handle_greet_entry(prev: EntryGreet) -> Next {
-let name = prev.pick_or((), "World").unpack();
+let name = prev
+ .pick_or(&arg![String], || "World".to_string())
+ .unwrap();
@@@ResultName::new(name).into()
@@@}
```
@@ -54,75 +58,79 @@ let name = prev.pick_or((), "World").unpack();
Its semantics are:
```rust
-// Features: ["parser"]
+// Features: ["picker"]
@@@dispatcher!("greet", EntryGreet);
@@@pack!(ResultName = String);
@@@#[chain]
@@@fn handle_greet_entry(prev: EntryGreet) {
@@@let name: String =
- prev.pick_or((), "World").unpack();
-// ~~~~ ~~~~~~~ ~~ ~~~~~~~ ~~~~~~~~
-// | | | | |_ unpack as String
-// | | | |__________ default value "World"
-// | | |______________ pick the first positional arg (no flag)
-// | |______________________ pick or use default
-// |___________________________ from the previous input
+ prev.pick_or(&arg![String], || "World".to_string()).unwrap();
+// ~~~~ ~~~~~~~ ~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~
+// | | | | |_ unwrap to String
+// | | | |__________________________ default value "World"
+// | | |________________________________________ pick the first positional arg (declared as `String`)
+// | |________________________________________________ pick or use default
+// |_____________________________________________________ from the previous input
@@@}
```
## Parsing Flag Arguments
-If your program needs to parse flag arguments (e.g. `greet --name Alice`), do this:
+If your program needs to parse flag arguments (e.g. `greet --name Alice`), declare a named flag in `arg!`:
```rust
-// Features: ["parser"]
+// Features: ["picker"]
@@@dispatcher!("greet", EntryGreet);
@@@pack!(ResultName = String);
#[chain]
fn handle_greet_entry(prev: EntryGreet) -> Next {
- let name = prev.pick_or(["--name", "-n"], "World").unpack();
+ let name = prev
+ .pick_or(&arg![name: String, 'n'], || "World".to_string())
+ .unwrap();
ResultName::new(name).into()
}
```
+The `arg!` macro derives the long flag name (`--name`) from the field name, and `'n'` adds the short alias (`-n`).
+
Its semantics:
```rust
-// Features: ["parser"]
+// Features: ["picker"]
@@@dispatcher!("greet", EntryGreet);
@@@pack!(ResultName = String);
@@@#[chain]
@@@fn handle_greet_entry(prev: EntryGreet) {
@@@let name: String =
- prev.pick_or(["--name", "-n"], "World").unpack();
-// ~~~~ ~~~~~~~ ~~~~~~~~~~~~~~~~ ~~~~~~~ ~~~~~~~~
-// | | | | |_ unpack as String
-// | | | |__________ default value "World"
-// | | |____________________________ pick the value after "--name" or "-n"
-// | |____________________________________ pick or use default
-// |_________________________________________ from the previous input
+ prev.pick_or(&arg![name: String, 'n'], || "World".to_string()).unwrap();
+// ~~~~ ~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~
+// | | | | |_ unwrap to String
+// | | | |________________________ default value "World"
+// | | |___________________________________________________ pick the value after "--name" or "-n"
+// | |___________________________________________________________ pick or use default
+// |________________________________________________________________ from the previous input
@@@}
```
-## About `.unpack()`
+## About `.unwrap()` and `route!`
-You may have noticed that `Picker` calls `.unpack()` at the end of parsing. It converts the collected results into structured info.
+You may have noticed that `Picker` calls `.unwrap()` (or `route!`) at the end of parsing. It converts the collected results into structured info.
-For a single pick, `.unpack()` returns the value directly; for multiple picks, it returns a tuple:
+For a single pick, `.unwrap()` returns the value directly; for multiple picks, it returns a tuple:
```rust
-// Features: ["parser"]
+// Features: ["picker"]
@@@dispatcher!("test", EntryTest);
@@@pack!(ResultInfo = (String, u8, u32));
#[chain]
fn handle_test_entry(prev: EntryTest) -> Next {
let (name, age, id) = prev
- .pick::<String>(["--name", "-n"])
- .pick::<u8>(["--age", "-a"])
- .pick::<u32>(["--id", "-I"])
- .unpack();
+ .pick_or_default(&arg![name: String, 'n'])
+ .pick_or_default(&arg![age: u8, 'a'])
+ .pick_or_default(&arg![id: u32, 'I'])
+ .unwrap();
ResultInfo::new((name, age, id)).into()
}
@@ -138,7 +146,7 @@ As the saying goes: "never trust your users." To handle missing required params,
Here's a simple example:
```rust
-// Features: ["parser", "extras"]
+// Features: ["picker", "extras"]
@@@use mingling::macros::buffer;
@@@use mingling::macros::route;
@@@dispatcher!("greet", EntryGreet);
@@ -147,12 +155,13 @@ Here's a simple example:
#[chain]
fn handle_greet_entry(prev: EntryGreet) -> Next {
- let pick_result = prev
- .pick_or_route(["--name", "-n"], ErrorNoName::default())
- .unpack();
-
- // Use route! macro to expand pick_result
- let name = route!(pick_result);
+ // Use route! macro to expand the Result<Value, Route>
+ let name = route!(
+ prev.pick_or_route(&arg![name: String, 'n'], || {
+ ErrorNoName::default().to_chain()
+ })
+ .to_result()
+ );
ResultName::new(name).into()
}
@@ -162,18 +171,18 @@ fn render_greet(result: ResultName) {
}
```
-With `pick_or_route`, the code becomes more involved: `.unpack()` no longer returns the value directly, but `Result<Value, Route>`.
+With `pick_or_route`, `.to_result()` no longer returns the value directly, but `Result<Value, Route>`.
However, **Mingling**'s `extras` feature provides the `route!` macro for simplified expansion. It's not complex — it just reduces boilerplate:
```rust
-// Features: ["parser", "extras"]
+// Features: ["picker", "extras"]
@@@ pack!(ErrorFail = ());
@@@ use mingling::macros::route;
+@@@ use mingling::picker::IntoPicker;
@@@ fn func() -> mingling::ChainProcess<ThisProgram> {
@@@ let args: Vec<String> = vec![];
-@@@ let pick_result = args.pick_or_route::<String, _>((), ErrorFail::new(())).unpack();
-let name = route!(pick_result);
+let name = route!(args.pick_or_route(&arg![String], || ErrorFail::new(()).to_chain()).to_result());
@@@ mingling::macros::empty_result!()
@@@ }
```
@@ -181,14 +190,14 @@ let name = route!(pick_result);
It expands to:
```rust
-// Features: ["parser", "extras"]
+// Features: ["picker", "extras"]
@@@ pack!(ErrorFail = ());
+@@@ use mingling::picker::IntoPicker;
@@@ fn func() -> mingling::ChainProcess<ThisProgram> {
@@@ let args: Vec<String> = vec![];
-@@@ let pick_result = args.pick_or_route::<String, _>((), ErrorFail::new(())).unpack();
-let name = match pick_result {
+let name = match args.pick_or_route(&arg![String], || ErrorFail::new(()).to_chain()).to_result() {
Ok(r) => r,
- Err(e) => return e.to_chain(),
+ Err(e) => return e,
};
@@@ mingling::macros::empty_result!()
@@@ }
@@ -196,122 +205,59 @@ let name = match pick_result {
## Post-processing Extracted Values
-After picking user input with `pick`, you can use `after` to process it immediately:
+After picking user input with `pick`, you can use `post` to process it immediately:
```rust
-// Features: ["parser"]
+// Features: ["picker"]
@@@dispatcher!("greet", EntryGreet);
@@@pack!(ResultName = String);
#[chain]
fn handle_greet_entry(prev: EntryGreet) -> Next {
let name = prev
- .pick_or(["--name", "-n"], "World")
+ .pick_or(&arg![name: String, 'n'], || "World".to_string())
// Format immediately after picking --name
- .after(|name: String| {
+ .post(|name: String| {
name.replace(['-', '_', '.'], " ")
.to_lowercase()
.trim()
.to_string()
})
- .unpack();
+ .unwrap();
ResultName::new(name).into()
}
```
-Similarly, you can use `after_or_route` to handle input format errors:
-
-```rust
-// Features: ["parser", "extras"]
-@@@use mingling::macros::buffer;
-@@@use mingling::macros::route;
-@@@dispatcher!("greet", EntryGreet);
-@@@pack!(ResultName = String);
-@@@pack!(ErrorNameTooLong = usize);
-
-#[chain]
-fn handle_greet_entry(prev: EntryGreet) -> Next {
- let pick_result = prev
- .pick_or(["--name", "-n"], "World")
- .after_or_route(|name: &String| {
- if name.len() < 32 {
- Ok(name.clone())
- } else {
- Err(ErrorNameTooLong::new(name.len()))
- }
- })
- .unpack();
- let name = route!(pick_result);
-
- ResultName::new(name).into()
-}
-
-#[renderer(buffer)]
-fn render_name_too_long(prev: ErrorNameTooLong) {
- let len = *prev;
- r_println!("Error: name too long (length: {} > 32)", len);
-}
-
-#[renderer(buffer)]
-fn render_name(prev: ResultName) {
- r_println!("Hello, {}!", *prev);
-}
-```
-
## Boolean Parsing
-`Picker` can also parse booleans, in two modes:
-
-| Mode | Format |
-| -------- | ----------------------------------- |
-| Implicit | `--confirmed` |
-| Explicit | `--confirm true` or `--confirm yes` |
-
-- `.pick::<bool>(flag)` uses implicit mode: the flag being present means `true`
-- `.pick::<Yes>(flag)` or `.pick::<True>(flag)` uses explicit mode
-
-Implicit mode is generally sufficient, but for important confirmations, explicit logic is more idiomatic.
+`Picker` parses booleans as **flags**: the flag being present means `true`.
```rust
-// Features: ["parser"]
-@@@use mingling::parser::Yes;
+// Features: ["picker"]
+@@@use mingling::picker::value::Flag;
@@@dispatcher!("test", EntryTest);
@@@pack!(ResultDone = ());
#[chain]
fn handle_entry(prev: EntryTest) -> Next {
-@@@ let prev1 = prev.clone();
- let _confirmed: bool = prev.pick::<Yes>(()).unpack().is_yes();
-@@@ let prev = prev1;
- let _confirm: bool = prev.pick::<bool>(["--confirm", "-C"]).unpack();
+ // `--confirm` / `-C` present → true
+ let _confirm: bool = *prev.pick(&arg![confirm: Flag, 'C']).unwrap();
ResultDone::default().to_render()
}
```
-## Special Usage: `usize` Parsing
-
-**Mingling** provides a special `usize` feature: parsing strings like `25G`, `32mib`, etc.
+> [!NOTE]
+> For important confirmations, pair the flag with an explicit value check if the exact boolean semantics matter.
-```rust
-// Features: ["parser"]
-
-#[test]
-fn parse_size() {
- let vec = vec!["--size".to_string(), "25mib".to_string()];
- let size: usize = vec.pick(["--size", "-S"]).unpack();
- assert_eq!(size, 25 * 1024 * 1024);
-}
-```
-
## Custom Pickable Types
-You can make your types pickable by `Picker` using the `Pickable` trait — this is where `Picker`'s extensibility comes from.
+You can make your types pickable by `Picker` using the `SinglePickable` trait — this is where `Picker`'s extensibility comes from.
```rust
-// Features: ["parser"]
+// Features: ["picker"]
@@@use mingling::macros::buffer;
-@@@use mingling::parser::{Pickable, Argument};
+@@@use mingling::picker::{PickerArgResult, SinglePickable};
@@@use mingling::Flag;
#[derive(Default, Clone)]
pub struct Address {
@@ -319,14 +265,18 @@ pub struct Address {
port: u16,
}
-impl Pickable for Address {
- type Output = Self;
- fn pick(args: &mut Argument, flag: Flag) -> Option<Self::Output> {
- let raw = args.pick_argument(flag)?;
+impl SinglePickable for Address {
+ fn pick_single(str: Option<&str>) -> PickerArgResult<Self> {
+ let Some(raw) = str else {
+ return PickerArgResult::NotFound;
+ };
let parts: Vec<&str> = raw.split(':').collect();
- let ip = parts.first()?.to_string();
- let port: u16 = parts.get(1)?.parse().ok()?;
- Some(Address { ip, port })
+ let ip = parts.first().copied().unwrap_or_default().to_string();
+ let port: u16 = match parts.get(1).and_then(|p| p.parse().ok()) {
+ Some(p) => p,
+ None => return PickerArgResult::NotFound,
+ };
+ PickerArgResult::Parsed(Address { ip, port })
}
}
@@@dispatcher!("connect", EntryConnect);
@@ -334,7 +284,7 @@ impl Pickable for Address {
#[chain]
fn handle_connect_entry(prev: EntryConnect) -> Next {
- let address: Address = prev.pick("--addr").unpack();
+ let address: Address = prev.pick_or_default(&arg![Address]).unwrap();
ResultConnected::new(address).into()
}
@@ -351,14 +301,14 @@ Output:
Connected: IP: 127.0.0.1 PORT: 8080
```
-## Auto-implementing Pickable for Enums
+## Implementing Pickable for Enums
-To make an enum `Pickable`, just implement `EnumTag` on it, then implement `PickableEnum`:
+To make an enum pickable, implement `SinglePickable` manually with a match on the input:
```rust
-// Features: ["parser"]
+// Features: ["picker"]
@@@use mingling::macros::buffer;
-@@@use mingling::parser::PickableEnum;
+@@@use mingling::picker::{PickerArgResult, SinglePickable};
@@@use mingling::EnumTag;
#[derive(Debug, Default, EnumTag)]
pub enum Fruits {
@@ -368,13 +318,26 @@ pub enum Fruits {
Orange,
}
-impl PickableEnum for Fruits {}
+impl SinglePickable for Fruits {
+ fn pick_single(str: Option<&str>) -> PickerArgResult<Self> {
+ let Some(str) = str else {
+ return PickerArgResult::NotFound;
+ };
+ let fruit = match str.to_lowercase().as_str() {
+ "apple" => Self::Apple,
+ "banana" => Self::Banana,
+ "orange" => Self::Orange,
+ _ => return PickerArgResult::NotFound,
+ };
+ PickerArgResult::Parsed(fruit)
+ }
+}
@@@dispatcher!("eat", EntryEat);
@@@pack!(ResultFruit = Fruits);
#[chain]
fn handle_eat_entry(prev: EntryEat) -> Next {
- let fruit: Fruits = prev.pick("--fruit").unpack();
+ let fruit: Fruits = prev.pick_or_default(&arg![Fruits]).unwrap();
ResultFruit::new(fruit).into()
}
diff --git a/docs/pages/other/features.md b/docs/pages/other/features.md
index 4552d30..3779390 100644
--- a/docs/pages/other/features.md
+++ b/docs/pages/other/features.md
@@ -330,23 +330,11 @@ analyze_and_build_type_mapping().unwrap();
See [example](https://mingling-rs.github.io/mingling/docs/example-viewer.html?name=example-pathfinder)
-## Feature `parser`
-
-**Description:**
-
-Enables the argument parser module, providing argument parsing functionality.
-
-When enabled, you can use `Picker` for simple argument extraction, supporting methods like `pick()` and `pick_or()`.
-
-See [example](https://mingling-rs.github.io/mingling/docs/example-viewer.html?name=example-argument-parse)
-
## Feature `picker`
**Description:**
-Introduces the `arg-picker` dependency, providing more advanced argument parsing capabilities for Mingling.
-
-It can coexist with the `parser` and `clap` features, but it is recommended not to enable it alongside the `parser` feature, as their APIs are very similar.
+Introduces the `arg-picker` dependency, providing advanced argument parsing capabilities for Mingling.
`picker` is an argument parser independent of Mingling and does not rely on the built-in argument extraction API of `mingling_core`.
diff --git a/examples/example-argument-parse/Cargo.lock b/examples/example-argument-parse/Cargo.lock
deleted file mode 100644
index 146e227..0000000
--- a/examples/example-argument-parse/Cargo.lock
+++ /dev/null
@@ -1,223 +0,0 @@
-# This file is automatically @generated by Cargo.
-# It is not intended for manual editing.
-version = 4
-
-[[package]]
-name = "equivalent"
-version = "1.0.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
-
-[[package]]
-name = "example-argument-parse"
-version = "0.1.0"
-dependencies = [
- "mingling",
-]
-
-[[package]]
-name = "hashbrown"
-version = "0.17.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
-
-[[package]]
-name = "indexmap"
-version = "2.14.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
-dependencies = [
- "equivalent",
- "hashbrown",
-]
-
-[[package]]
-name = "just_fmt"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6170dccbc3ea15dfb7f2da964097f814aba1dd8f746d4ffc56f33245c38e6d96"
-
-[[package]]
-name = "memchr"
-version = "2.8.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
-
-[[package]]
-name = "might_be_async"
-version = "0.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eca871cea620b07bd2e6da0c883891a25bead698c43a9ab64b0fd663a7a78d5f"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.118",
- "toml",
-]
-
-[[package]]
-name = "mingling"
-version = "0.5.0"
-dependencies = [
- "mingling_core",
- "mingling_macros",
- "size",
-]
-
-[[package]]
-name = "mingling_core"
-version = "0.5.0"
-dependencies = [
- "just_fmt",
- "might_be_async",
-]
-
-[[package]]
-name = "mingling_macros"
-version = "0.5.0"
-dependencies = [
- "just_fmt",
- "proc-macro2",
- "quote",
- "syn 2.0.118",
-]
-
-[[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.46"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368"
-dependencies = [
- "proc-macro2",
-]
-
-[[package]]
-name = "serde"
-version = "1.0.229"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
-dependencies = [
- "serde_core",
-]
-
-[[package]]
-name = "serde_core"
-version = "1.0.229"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
-dependencies = [
- "serde_derive",
-]
-
-[[package]]
-name = "serde_derive"
-version = "1.0.229"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 3.0.3",
-]
-
-[[package]]
-name = "serde_spanned"
-version = "0.6.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
-dependencies = [
- "serde",
-]
-
-[[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.118"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422"
-dependencies = [
- "proc-macro2",
- "quote",
- "unicode-ident",
-]
-
-[[package]]
-name = "syn"
-version = "3.0.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3"
-dependencies = [
- "proc-macro2",
- "quote",
- "unicode-ident",
-]
-
-[[package]]
-name = "toml"
-version = "0.8.23"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
-dependencies = [
- "serde",
- "serde_spanned",
- "toml_datetime",
- "toml_edit",
-]
-
-[[package]]
-name = "toml_datetime"
-version = "0.6.11"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
-dependencies = [
- "serde",
-]
-
-[[package]]
-name = "toml_edit"
-version = "0.22.27"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
-dependencies = [
- "indexmap",
- "serde",
- "serde_spanned",
- "toml_datetime",
- "toml_write",
- "winnow",
-]
-
-[[package]]
-name = "toml_write"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
-
-[[package]]
-name = "unicode-ident"
-version = "1.0.24"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
-
-[[package]]
-name = "winnow"
-version = "0.7.15"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945"
-dependencies = [
- "memchr",
-]
diff --git a/examples/example-argument-parse/Cargo.toml b/examples/example-argument-parse/Cargo.toml
deleted file mode 100644
index e08adcd..0000000
--- a/examples/example-argument-parse/Cargo.toml
+++ /dev/null
@@ -1,12 +0,0 @@
-[package]
-name = "example-argument-parse"
-version = "0.1.0"
-edition = "2024"
-
-[dependencies.mingling]
-path = "../../mingling"
-
-# Enable `parser` features
-features = ["parser", "extras"]
-
-[workspace]
diff --git a/examples/example-argument-parse/page.toml b/examples/example-argument-parse/page.toml
deleted file mode 100644
index fc4d646..0000000
--- a/examples/example-argument-parse/page.toml
+++ /dev/null
@@ -1,10 +0,0 @@
-[example]
-id = "example-argument-parse"
-name = "Argument Parse"
-icon = "📋"
-category = "parsing"
-desc = """
-Shows how to use Mingling's `parser` feature with a `Picker` to extract and validate typed arguments from the command line.
-"""
-tags = ["pick", "Pickable"]
-files = ["src/main.rs", "Cargo.toml"]
diff --git a/examples/example-argument-parse/src/main.rs b/examples/example-argument-parse/src/main.rs
deleted file mode 100644
index d45c4af..0000000
--- a/examples/example-argument-parse/src/main.rs
+++ /dev/null
@@ -1,102 +0,0 @@
-//! 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::*};
-use std::io::Write;
-
-dispatcher!("transfer", EntryTransfer);
-dispatcher!("strict-transfer", 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.into()
-}
-
-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())
- .after(|str| str.trim().replace(' ', ""))
- .unpack()
- }
- // Convert into ResultFile
- .into();
- // --------- IMPORTANT ---------
- result.to_chain()
-}
-
-/// Renders the parsed transfer result (file/dir, size, name).
-#[renderer]
-fn render_result_file(result: ResultFile) -> RenderResult {
- let (is_dir, size, name) = result.into();
- let mut result = RenderResult::new();
- writeln!(
- result,
- "{}: {} ({})",
- if is_dir { "dir" } else { "file" },
- name,
- size
- )
- .ok();
- result
-}
-
-/// Renders the error when no name is provided.
-#[renderer]
-fn render_error_no_name_provided(_: ErrorNoNameProvided) -> RenderResult {
- let mut result = RenderResult::new();
- writeln!(result, "Error: name is not provided").ok();
- result
-}
-
-gen_program!();
-
-fn main() {
- let program = ThisProgram::new();
- program.exec_and_exit();
-}
diff --git a/examples/example-argument-parse/test.toml b/examples/example-argument-parse/test.toml
deleted file mode 100644
index a405bd9..0000000
--- a/examples/example-argument-parse/test.toml
+++ /dev/null
@@ -1,29 +0,0 @@
-[[runs]]
-input = [ "transfer" ]
-
-expect.exit-code = 0
-expect.result = "file: (1048576)"
-
-[[runs]]
-input = [ "transfer", "--dir", "src" ]
-
-expect.exit-code = 0
-expect.result = "dir: src (1048576)"
-
-[[runs]]
-input = [ "transfer", "--size", "500", "myfile.txt" ]
-
-expect.exit-code = 0
-expect.result = "file: myfile.txt (500)"
-
-[[runs]]
-input = [ "strict-transfer", "README.md" ]
-
-expect.exit-code = 0
-expect.result = "file: README.md (1048576)"
-
-[[runs]]
-input = [ "strict-transfer" ]
-
-expect.exit-code = 0
-expect.result = "Error: name is not provided"
diff --git a/examples/example-async-support/Cargo.lock b/examples/example-async-support/Cargo.lock
index a2751be..f122f6d 100644
--- a/examples/example-async-support/Cargo.lock
+++ b/examples/example-async-support/Cargo.lock
@@ -3,6 +3,23 @@
version = 4
[[package]]
+name = "arg-picker"
+version = "0.2.0"
+dependencies = [
+ "arg-picker-macros",
+ "just_fmt",
+]
+
+[[package]]
+name = "arg-picker-macros"
+version = "0.2.0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.118",
+]
+
+[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -60,9 +77,9 @@ dependencies = [
name = "mingling"
version = "0.5.0"
dependencies = [
+ "arg-picker",
"mingling_core",
"mingling_macros",
- "size",
]
[[package]]
@@ -146,12 +163,6 @@ dependencies = [
]
[[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.118"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/examples/example-async-support/Cargo.toml b/examples/example-async-support/Cargo.toml
index d1b952d..a0d2a75 100644
--- a/examples/example-async-support/Cargo.toml
+++ b/examples/example-async-support/Cargo.toml
@@ -6,8 +6,8 @@ edition = "2024"
[dependencies.mingling]
path = "../../mingling"
-# Enable `parser` features
-features = ["async", "parser"]
+# Enable `picker` features
+features = ["async", "picker"]
# Import any async runtime, e.g. Tokio
[dependencies.tokio]
diff --git a/examples/example-async-support/src/main.rs b/examples/example-async-support/src/main.rs
index ac1bcc1..090602a 100644
--- a/examples/example-async-support/src/main.rs
+++ b/examples/example-async-support/src/main.rs
@@ -47,7 +47,7 @@ pack!(ResultDownloaded = String);
#[chain]
// vvvvv_ `async` keyword can be used directly here
pub async fn handle_download(args: EntryDownload) -> Next {
- let file_name = args.pick(()).unpack();
+ let file_name = args.pick_or_default(&arg![String]).unwrap();
fake_download(file_name).await.into()
}
diff --git a/examples/example-completion/Cargo.lock b/examples/example-completion/Cargo.lock
index 97401e7..d4b2f33 100644
--- a/examples/example-completion/Cargo.lock
+++ b/examples/example-completion/Cargo.lock
@@ -3,6 +3,23 @@
version = 4
[[package]]
+name = "arg-picker"
+version = "0.2.0"
+dependencies = [
+ "arg-picker-macros",
+ "just_fmt 0.2.0",
+]
+
+[[package]]
+name = "arg-picker-macros"
+version = "0.2.0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.118",
+]
+
+[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -86,9 +103,9 @@ dependencies = [
name = "mingling"
version = "0.5.0"
dependencies = [
+ "arg-picker",
"mingling_core",
"mingling_macros",
- "size",
]
[[package]]
@@ -167,12 +184,6 @@ dependencies = [
]
[[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.118"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/examples/example-completion/Cargo.toml b/examples/example-completion/Cargo.toml
index 00c7f8c..9884f4e 100644
--- a/examples/example-completion/Cargo.toml
+++ b/examples/example-completion/Cargo.toml
@@ -9,7 +9,7 @@ path = "../../mingling"
features = [
# Enable `comp` features
"comp",
- "parser",
+ "picker",
]
[build-dependencies.mingling]
diff --git a/examples/example-completion/src/main.rs b/examples/example-completion/src/main.rs
index 7de41f6..d363697 100644
--- a/examples/example-completion/src/main.rs
+++ b/examples/example-completion/src/main.rs
@@ -74,18 +74,22 @@ fn complete_greet_entry(ctx: &ShellContext) -> Suggest {
}
// When the user is typing `--repeat`
- if ctx.filling_argument(["-r", "--repeat"]) {
+ if ctx.previous_word == "-r" || ctx.previous_word == "--repeat" {
return suggest! {}; // Don't suggest anything
}
// When the user is typing `-`
- if ctx.typing_argument() {
- return suggest! {
+ if ctx.current_word.starts_with('-') {
+ // Remove arguments that have already been typed by the user
+ let typed: Vec<&str> = ctx.all_words.iter().map(String::as_str).collect();
+ let mut set = suggest! {
"-r": "Number of repetitions",
"--repeat": "Number of repetitions",
+ };
+ if let Suggest::Suggest(items) = &mut set {
+ items.retain(|item| !typed.contains(&item.suggest().as_str()));
}
- // Remove arguments that have already been typed by the user
- .strip_typed_argument(ctx);
+ return set;
}
// Otherwise, suggest nothing
@@ -102,9 +106,9 @@ pack!(ResultName = (u8, String));
#[chain]
fn handle_greet(args: EntryGreet) -> Next {
let result: ResultName = args
- .pick_or(["-r", "--repeat"], 1)
- .pick_or((), "World")
- .unpack()
+ .pick_or(&arg![repeat: u8, 'r'], || 1)
+ .pick_or(&arg![String], || "World".to_string())
+ .unwrap()
.into();
result.into()
}
diff --git a/examples/example-custom-pickable/Cargo.lock b/examples/example-custom-pickable/Cargo.lock
deleted file mode 100644
index 3aa6236..0000000
--- a/examples/example-custom-pickable/Cargo.lock
+++ /dev/null
@@ -1,223 +0,0 @@
-# This file is automatically @generated by Cargo.
-# It is not intended for manual editing.
-version = 4
-
-[[package]]
-name = "equivalent"
-version = "1.0.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
-
-[[package]]
-name = "example-custom-pickable"
-version = "0.1.0"
-dependencies = [
- "mingling",
-]
-
-[[package]]
-name = "hashbrown"
-version = "0.17.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
-
-[[package]]
-name = "indexmap"
-version = "2.14.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
-dependencies = [
- "equivalent",
- "hashbrown",
-]
-
-[[package]]
-name = "just_fmt"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6170dccbc3ea15dfb7f2da964097f814aba1dd8f746d4ffc56f33245c38e6d96"
-
-[[package]]
-name = "memchr"
-version = "2.8.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
-
-[[package]]
-name = "might_be_async"
-version = "0.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eca871cea620b07bd2e6da0c883891a25bead698c43a9ab64b0fd663a7a78d5f"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.118",
- "toml",
-]
-
-[[package]]
-name = "mingling"
-version = "0.5.0"
-dependencies = [
- "mingling_core",
- "mingling_macros",
- "size",
-]
-
-[[package]]
-name = "mingling_core"
-version = "0.5.0"
-dependencies = [
- "just_fmt",
- "might_be_async",
-]
-
-[[package]]
-name = "mingling_macros"
-version = "0.5.0"
-dependencies = [
- "just_fmt",
- "proc-macro2",
- "quote",
- "syn 2.0.118",
-]
-
-[[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.46"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368"
-dependencies = [
- "proc-macro2",
-]
-
-[[package]]
-name = "serde"
-version = "1.0.229"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
-dependencies = [
- "serde_core",
-]
-
-[[package]]
-name = "serde_core"
-version = "1.0.229"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
-dependencies = [
- "serde_derive",
-]
-
-[[package]]
-name = "serde_derive"
-version = "1.0.229"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 3.0.3",
-]
-
-[[package]]
-name = "serde_spanned"
-version = "0.6.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
-dependencies = [
- "serde",
-]
-
-[[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.118"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422"
-dependencies = [
- "proc-macro2",
- "quote",
- "unicode-ident",
-]
-
-[[package]]
-name = "syn"
-version = "3.0.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3"
-dependencies = [
- "proc-macro2",
- "quote",
- "unicode-ident",
-]
-
-[[package]]
-name = "toml"
-version = "0.8.23"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
-dependencies = [
- "serde",
- "serde_spanned",
- "toml_datetime",
- "toml_edit",
-]
-
-[[package]]
-name = "toml_datetime"
-version = "0.6.11"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
-dependencies = [
- "serde",
-]
-
-[[package]]
-name = "toml_edit"
-version = "0.22.27"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
-dependencies = [
- "indexmap",
- "serde",
- "serde_spanned",
- "toml_datetime",
- "toml_write",
- "winnow",
-]
-
-[[package]]
-name = "toml_write"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
-
-[[package]]
-name = "unicode-ident"
-version = "1.0.24"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
-
-[[package]]
-name = "winnow"
-version = "0.7.15"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945"
-dependencies = [
- "memchr",
-]
diff --git a/examples/example-custom-pickable/Cargo.toml b/examples/example-custom-pickable/Cargo.toml
deleted file mode 100644
index 9324bd6..0000000
--- a/examples/example-custom-pickable/Cargo.toml
+++ /dev/null
@@ -1,11 +0,0 @@
-[package]
-name = "example-custom-pickable"
-version = "0.1.0"
-edition = "2024"
-
-[dependencies.mingling]
-path = "../../mingling"
-
-features = ["parser", "extras"]
-
-[workspace]
diff --git a/examples/example-custom-pickable/page.toml b/examples/example-custom-pickable/page.toml
deleted file mode 100644
index 1c3bfa0..0000000
--- a/examples/example-custom-pickable/page.toml
+++ /dev/null
@@ -1,10 +0,0 @@
-[example]
-id = "example-custom-pickable"
-name = "Custom Pickable"
-icon = "🎯"
-category = "parsing"
-desc = """
-Shows how to implement the `Pickable` trait on custom types for seamless extraction from CLI arguments via Picker.
-"""
-tags = ["Pickable", "custom"]
-files = ["src/main.rs", "Cargo.toml"]
diff --git a/examples/example-custom-pickable/src/main.rs b/examples/example-custom-pickable/src/main.rs
deleted file mode 100644
index 8265b09..0000000
--- a/examples/example-custom-pickable/src/main.rs
+++ /dev/null
@@ -1,130 +0,0 @@
-//! 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::{macros::route, parser::Pickable, prelude::*, Grouped};
-use std::io::Write;
-
-// Define types that can be recognized by Mingling
-// ________________________ `Pickable` trait needs to implement Default
-// / ________ The Grouped derive macro registers an ID for this type
-// | / Mingling uses this ID to identify the type
-// vvvvvvv vvvvvvv
-#[derive(Debug, Default, Clone, Grouped)]
-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)?.clone();
-
- // Use TryFrom to parse the address
- Address::try_from(raw).ok()
- }
-}
-// --------- IMPORTANT ---------
-
-dispatcher!("connect", EntryConnect);
-pack!(ErrorParseAddressFailed = ());
-
-#[chain]
-fn handle_connect(prev: EntryConnect) -> Next {
- let connect: Address =
- route! { prev.pick_or_route((), ErrorParseAddressFailed::default()).unpack() };
- connect.to_chain()
-}
-
-/// Renders the connected address.
-#[renderer]
-pub fn render_address(addr: Address) -> RenderResult {
- let mut render_result = RenderResult::new();
- write!(render_result, "Connected to \"{}\"", addr).ok();
- render_result
-}
-
-/// Renders the error message when address parsing fails.
-#[renderer]
-pub fn render_error_parse_address_failed(_: ErrorParseAddressFailed) -> RenderResult {
- let mut render_result = RenderResult::new();
- write!(render_result, "Failed to parse address").ok();
- render_result
-}
-
-gen_program!();
-
-fn main() {
- ThisProgram::new().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-custom-pickable/test.toml b/examples/example-custom-pickable/test.toml
deleted file mode 100644
index 8fc20eb..0000000
--- a/examples/example-custom-pickable/test.toml
+++ /dev/null
@@ -1,17 +0,0 @@
-[[runs]]
-input = [ "connect", "192.168.1.1:8080" ]
-
-expect.exit-code = 0
-expect.result = "Connected to \"192.168.1.1:8080\""
-
-[[runs]]
-input = [ "connect" ]
-
-expect.exit-code = 0
-expect.result = "Failed to parse address"
-
-[[runs]]
-input = [ "connect", "invalid" ]
-
-expect.exit-code = 0
-expect.result = "Failed to parse address"
diff --git a/examples/example-enum-tag/Cargo.lock b/examples/example-enum-tag/Cargo.lock
index 868af79..492d708 100644
--- a/examples/example-enum-tag/Cargo.lock
+++ b/examples/example-enum-tag/Cargo.lock
@@ -3,6 +3,23 @@
version = 4
[[package]]
+name = "arg-picker"
+version = "0.2.0"
+dependencies = [
+ "arg-picker-macros",
+ "just_fmt 0.2.0",
+]
+
+[[package]]
+name = "arg-picker-macros"
+version = "0.2.0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.118",
+]
+
+[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -86,9 +103,9 @@ dependencies = [
name = "mingling"
version = "0.5.0"
dependencies = [
+ "arg-picker",
"mingling_core",
"mingling_macros",
- "size",
]
[[package]]
@@ -167,12 +184,6 @@ dependencies = [
]
[[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.118"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/examples/example-enum-tag/Cargo.toml b/examples/example-enum-tag/Cargo.toml
index 9c0a0ff..489f401 100644
--- a/examples/example-enum-tag/Cargo.toml
+++ b/examples/example-enum-tag/Cargo.toml
@@ -8,7 +8,7 @@ path = "../../mingling"
features = [
"comp",
- "parser"
+ "picker"
]
[workspace]
diff --git a/examples/example-enum-tag/src/main.rs b/examples/example-enum-tag/src/main.rs
index 2966036..c7fb502 100644
--- a/examples/example-enum-tag/src/main.rs
+++ b/examples/example-enum-tag/src/main.rs
@@ -16,8 +16,10 @@
//! ```
use mingling::{
- macros::suggest_enum, parser::PickableEnum, prelude::*, EnumTag, Grouped, ShellContext,
- Suggest,
+ EnumTag, Grouped, ShellContext, Suggest,
+ macros::suggest_enum,
+ picker::{PickerArgResult, SinglePickable},
+ prelude::*,
};
use std::io::Write;
@@ -57,9 +59,7 @@ pub enum ProgrammingLanguages {
#[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"
- )]
+ #[enum_desc("An object-oriented scripting language, famous for its concise and elegant syntax")]
Ruby,
#[default]
@@ -68,9 +68,31 @@ pub enum ProgrammingLanguages {
}
// --------- IMPORTANT ---------
-// Implement the PickableEnum trait for ProgrammingLanguages,
-// so that `Picker` can parse this enum
-impl PickableEnum for ProgrammingLanguages {}
+// NOTE: Due to the migration from the legacy `parser` to `picker`, the `EnumTag` -> `Picker` path
+// is not yet complete, so a manual implementation is used for now.
+// Once that path is complete, `#[derive(EnumTag)]` can automatically implement `SinglePickable`,
+// replacing this manual implementation.
+impl SinglePickable for ProgrammingLanguages {
+ fn pick_single(str: Option<&str>) -> PickerArgResult<Self> {
+ let Some(str) = str else {
+ return PickerArgResult::NotFound;
+ };
+ let lang = match str.to_lowercase().as_str() {
+ "c" => Self::C,
+ "c++" | "cpp" => Self::CPlusPlus,
+ "c#" | "csharp" => Self::Csharp,
+ "java" => Self::Java,
+ "javascript" | "js" => Self::JavaScript,
+ "kotlin" => Self::Kotlin,
+ "ocaml" => Self::OCaml,
+ "python" => Self::Python,
+ "ruby" => Self::Ruby,
+ "rust" => Self::Rust,
+ _ => return PickerArgResult::NotFound,
+ };
+ PickerArgResult::Parsed(lang)
+ }
+}
// --------- IMPORTANT ---------
dispatcher!("lang-select", EntryLanguageSelection);
@@ -78,7 +100,7 @@ dispatcher!("lang-select", EntryLanguageSelection);
#[chain]
fn handle_language_selection(args: EntryLanguageSelection) -> Next {
// You can use Picker to directly parse ProgrammingLanguages
- let lang: ProgrammingLanguages = args.pick(()).unpack();
+ let lang: ProgrammingLanguages = args.pick_or_default(&arg![ProgrammingLanguages]).unwrap();
lang.into()
}
diff --git a/examples/example-panic-unwind/Cargo.lock b/examples/example-panic-unwind/Cargo.lock
index ec5492d..20671f5 100644
--- a/examples/example-panic-unwind/Cargo.lock
+++ b/examples/example-panic-unwind/Cargo.lock
@@ -3,6 +3,23 @@
version = 4
[[package]]
+name = "arg-picker"
+version = "0.2.0"
+dependencies = [
+ "arg-picker-macros",
+ "just_fmt",
+]
+
+[[package]]
+name = "arg-picker-macros"
+version = "0.2.0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.118",
+]
+
+[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -59,9 +76,9 @@ dependencies = [
name = "mingling"
version = "0.5.0"
dependencies = [
+ "arg-picker",
"mingling_core",
"mingling_macros",
- "size",
]
[[package]]
@@ -139,12 +156,6 @@ dependencies = [
]
[[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.118"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/examples/example-panic-unwind/Cargo.toml b/examples/example-panic-unwind/Cargo.toml
index 559bdb9..463a288 100644
--- a/examples/example-panic-unwind/Cargo.toml
+++ b/examples/example-panic-unwind/Cargo.toml
@@ -5,7 +5,7 @@ edition = "2024"
[dependencies.mingling]
path = "../../mingling"
-features = ["parser"]
+features = ["picker"]
# Enable panic unwinding in release builds
[profile.release]
diff --git a/examples/example-panic-unwind/src/main.rs b/examples/example-panic-unwind/src/main.rs
index e1aa15c..59adf07 100644
--- a/examples/example-panic-unwind/src/main.rs
+++ b/examples/example-panic-unwind/src/main.rs
@@ -41,7 +41,7 @@ fn main() {
#[chain]
fn handle_panic(prev: EntryPanic) -> Next {
- let panic_info = prev.pick::<Option<String>>(()).unpack();
+ let panic_info = prev.pick_or_default(&arg![Option<String>]).unwrap();
match panic_info {
Some(s) => {
// Panic happens here, will be caught
diff --git a/examples/example-repl-basic/Cargo.lock b/examples/example-repl-basic/Cargo.lock
index e85083d..9764a85 100644
--- a/examples/example-repl-basic/Cargo.lock
+++ b/examples/example-repl-basic/Cargo.lock
@@ -3,6 +3,23 @@
version = 4
[[package]]
+name = "arg-picker"
+version = "0.2.0"
+dependencies = [
+ "arg-picker-macros",
+ "just_fmt 0.2.0",
+]
+
+[[package]]
+name = "arg-picker-macros"
+version = "0.2.0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.118",
+]
+
+[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -66,9 +83,9 @@ dependencies = [
name = "mingling"
version = "0.5.0"
dependencies = [
+ "arg-picker",
"mingling_core",
"mingling_macros",
- "size",
]
[[package]]
@@ -146,12 +163,6 @@ dependencies = [
]
[[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.118"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/examples/example-repl-basic/Cargo.toml b/examples/example-repl-basic/Cargo.toml
index 3289939..7f2e463 100644
--- a/examples/example-repl-basic/Cargo.toml
+++ b/examples/example-repl-basic/Cargo.toml
@@ -5,7 +5,7 @@ edition = "2024"
[dependencies.mingling]
path = "../../mingling"
-features = ["repl", "parser", "extras"]
+features = ["repl", "picker", "extras"]
[dependencies]
just_fmt = "0.1.2"
diff --git a/examples/example-repl-basic/src/main.rs b/examples/example-repl-basic/src/main.rs
index 325619e..d0fad6d 100644
--- a/examples/example-repl-basic/src/main.rs
+++ b/examples/example-repl-basic/src/main.rs
@@ -87,7 +87,7 @@ pack!(ResultList = Vec<String>);
// Parse cd command arguments
#[chain]
fn parse_cd_args(prev: EntryCd) -> Next {
- let join = prev.pick(()).unpack();
+ let join = prev.pick_or_default(&arg![String]).unwrap();
StateChangeDirectory::new(join).into()
}
diff --git a/examples/example-resources/Cargo.lock b/examples/example-resources/Cargo.lock
index 9623e25..bfc04f0 100644
--- a/examples/example-resources/Cargo.lock
+++ b/examples/example-resources/Cargo.lock
@@ -3,6 +3,23 @@
version = 4
[[package]]
+name = "arg-picker"
+version = "0.2.0"
+dependencies = [
+ "arg-picker-macros",
+ "just_fmt",
+]
+
+[[package]]
+name = "arg-picker-macros"
+version = "0.2.0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.118",
+]
+
+[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -59,9 +76,9 @@ dependencies = [
name = "mingling"
version = "0.5.0"
dependencies = [
+ "arg-picker",
"mingling_core",
"mingling_macros",
- "size",
]
[[package]]
@@ -139,12 +156,6 @@ dependencies = [
]
[[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.118"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/examples/example-resources/Cargo.toml b/examples/example-resources/Cargo.toml
index faf24e4..72a530e 100644
--- a/examples/example-resources/Cargo.toml
+++ b/examples/example-resources/Cargo.toml
@@ -5,6 +5,6 @@ edition = "2024"
[dependencies.mingling]
path = "../../mingling"
-features = ["parser"]
+features = ["picker"]
[workspace]
diff --git a/examples/example-resources/src/main.rs b/examples/example-resources/src/main.rs
index bb07d74..4d792b3 100644
--- a/examples/example-resources/src/main.rs
+++ b/examples/example-resources/src/main.rs
@@ -49,7 +49,7 @@ dispatcher!("modify-current", EntryModifyCurrent);
fn render_modify_current(args: EntryModifyCurrent, current_dir: &mut ResCurrentDir) -> Next {
current_dir.current_dir = current_dir
.current_dir
- .join(args.pick::<String>(()).unpack());
+ .join(args.pick_or_default(&arg![String]).unwrap());
EntryCurrent::default().into()
}
diff --git a/examples/example-structural-renderer/Cargo.lock b/examples/example-structural-renderer/Cargo.lock
index ac4cbba..cf2cfe3 100644
--- a/examples/example-structural-renderer/Cargo.lock
+++ b/examples/example-structural-renderer/Cargo.lock
@@ -3,6 +3,23 @@
version = 4
[[package]]
+name = "arg-picker"
+version = "0.2.0"
+dependencies = [
+ "arg-picker-macros",
+ "just_fmt",
+]
+
+[[package]]
+name = "arg-picker-macros"
+version = "0.2.0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -66,10 +83,10 @@ dependencies = [
name = "mingling"
version = "0.5.0"
dependencies = [
+ "arg-picker",
"mingling_core",
"mingling_macros",
"serde",
- "size",
]
[[package]]
@@ -183,12 +200,6 @@ dependencies = [
]
[[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.118"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/examples/example-structural-renderer/Cargo.toml b/examples/example-structural-renderer/Cargo.toml
index e29e9c3..2851303 100644
--- a/examples/example-structural-renderer/Cargo.toml
+++ b/examples/example-structural-renderer/Cargo.toml
@@ -11,7 +11,7 @@ path = "../../mingling"
features = [
"structural_renderer",
"yaml_serde_fmt",
- "parser",
+ "picker",
]
[workspace]
diff --git a/examples/example-structural-renderer/src/main.rs b/examples/example-structural-renderer/src/main.rs
index eca87fc..a1bddbd 100644
--- a/examples/example-structural-renderer/src/main.rs
+++ b/examples/example-structural-renderer/src/main.rs
@@ -17,8 +17,8 @@
//! member_age: 22
//! ```
-use mingling::prelude::*;
-use mingling::{parser::Picker, setup::StructuralRendererSetup, Grouped, StructuralData};
+use mingling::setup::picker::StructuralRendererSetup;
+use mingling::{Grouped, StructuralData, prelude::*};
use serde::Serialize;
use std::io::Write;
@@ -55,10 +55,10 @@ struct Info {
#[chain]
fn parse_render(prev: EntryRender) -> Next {
- let (name, age) = Picker::new(prev.inner)
- .pick::<String>(())
- .pick::<i32>(())
- .unpack();
+ let (name, age) = prev
+ .pick_or_default(&arg![String])
+ .pick_or_default(&arg![i32])
+ .unwrap();
Info { name, age }.to_render()
}
diff --git a/examples/full-todolist/Cargo.lock b/examples/full-todolist/Cargo.lock
index 4fc964d..ad9f475 100644
--- a/examples/full-todolist/Cargo.lock
+++ b/examples/full-todolist/Cargo.lock
@@ -3,6 +3,23 @@
version = 4
[[package]]
+name = "arg-picker"
+version = "0.2.0"
+dependencies = [
+ "arg-picker-macros",
+ "just_fmt",
+]
+
+[[package]]
+name = "arg-picker-macros"
+version = "0.2.0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -67,10 +84,10 @@ dependencies = [
name = "mingling"
version = "0.5.0"
dependencies = [
+ "arg-picker",
"mingling_core",
"mingling_macros",
"serde",
- "size",
]
[[package]]
@@ -164,12 +181,6 @@ dependencies = [
]
[[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.118"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/examples/full-todolist/Cargo.toml b/examples/full-todolist/Cargo.toml
index 9d6f1a7..fb3d3be 100644
--- a/examples/full-todolist/Cargo.toml
+++ b/examples/full-todolist/Cargo.toml
@@ -10,7 +10,7 @@ serde_json = "1.0.150"
[dependencies.mingling]
path = "../../mingling"
features = [
- "parser",
+ "picker",
"extras",
"structural_renderer",
]
diff --git a/examples/full-todolist/src/main.rs b/examples/full-todolist/src/main.rs
index c0df1df..eea417a 100644
--- a/examples/full-todolist/src/main.rs
+++ b/examples/full-todolist/src/main.rs
@@ -9,9 +9,10 @@
use mingling::{
LazyInit, LazyRes,
macros::route,
+ picker::value::Flag,
prelude::*,
res::ResExitCode,
- setup::{ExitCodeSetup, HelpFlagSetup, StructuralRendererSetup},
+ setup::{ExitCodeSetup, picker::HelpFlagSetup, picker::StructuralRendererSetup},
};
use std::io::Write;
@@ -51,7 +52,7 @@ fn main() {
// Setups
program.with_setup(ExitCodeSetup::default());
program.with_setup(StructuralRendererSetup);
- program.with_setup(HelpFlagSetup::new(["--help", "-h"]));
+ program.with_setup(HelpFlagSetup::new(&arg![help: Flag, 'h']));
// Flags
let all = program.pick_global_flag(["-A", "--all"]);
@@ -74,9 +75,10 @@ fn main() {
#[chain]
fn handle_add(args: EntryAdd) -> Next {
let task: String = route! {
- args
- .pick_or_route((), ErrorNoTaskDescriptionProvided::new(()))
- .unpack()
+ args.pick_or_route(&arg![String], || {
+ ErrorNoTaskDescriptionProvided::new(()).to_chain()
+ })
+ .to_result()
};
StateAddTodo::new(task).to_chain()
}
@@ -115,7 +117,8 @@ fn handle_list(_args: EntryList, todolist: &mut LazyRes<ResTodoList>) -> Next {
#[chain]
fn handle_complete(args: EntryComplete) -> Next {
let index: i32 = route! {
- args.pick_or_route((), ErrorNoIndexProvided::new(())).unpack()
+ args.pick_or_route(&arg![i32], || ErrorNoIndexProvided::new(()).to_chain())
+ .to_result()
};
StateCompleteTodo::new(index).to_chain()
}
diff --git a/mingling/Cargo.toml b/mingling/Cargo.toml
index 1de0f57..6130b1b 100644
--- a/mingling/Cargo.toml
+++ b/mingling/Cargo.toml
@@ -16,7 +16,6 @@ tokio.workspace = true
mingling = { path = ".", features = [
"comp",
"structural_renderer",
- "parser",
"repl",
] }
@@ -30,7 +29,6 @@ features = [
"structural_renderer",
"repl",
"comp",
- "parser",
"picker",
"clap",
"extras",
@@ -63,7 +61,6 @@ clap = ["mingling_core/clap", "mingling_macros/clap"]
dispatch_tree = ["mingling_macros/dispatch_tree"]
repl = ["mingling_core/repl", "mingling_macros/repl"]
comp = ["mingling_core/comp", "mingling_macros/comp"]
-parser = ["dep:size"]
picker = ["mingling_core/picker", "dep:arg-picker", "arg-picker/mingling_support"]
pathf = ["mingling_core/pathf", "mingling_macros/pathf"]
@@ -112,4 +109,3 @@ mingling_core = { workspace = true, optional = true }
mingling_macros = { workspace = true, optional = true }
arg-picker = { workspace = true, optional = true }
serde = { workspace = true, optional = true }
-size = { version = "0.5", optional = true }
diff --git a/mingling/src/example_docs.rs b/mingling/src/example_docs.rs
index 5688793..9406ddc 100644
--- a/mingling/src/example_docs.rs
+++ b/mingling/src/example_docs.rs
@@ -1,127 +1,5 @@
// Auto generated
-/// 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
-/// ```
-///
-/// Source code (./Cargo.toml)
-/// ```toml
-/// [package]
-/// name = "example-argument-parse"
-/// version = "0.1.0"
-/// edition = "2024"
-///
-/// [dependencies.mingling]
-/// path = "../../mingling"
-///
-/// # Enable `parser` features
-/// features = ["parser", "extras"]
-///
-/// [workspace]
-/// ```
-///
-/// Source code (./src/main.rs)
-/// ```ignore
-/// use mingling::{macros::route, prelude::*};
-/// use std::io::Write;
-///
-/// dispatcher!("transfer", EntryTransfer);
-/// dispatcher!("strict-transfer", 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.into()
-/// }
-///
-/// 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())
-/// .after(|str| str.trim().replace(' ', ""))
-/// .unpack()
-/// }
-/// // Convert into ResultFile
-/// .into();
-/// // --------- IMPORTANT ---------
-/// result.to_chain()
-/// }
-///
-/// /// Renders the parsed transfer result (file/dir, size, name).
-/// #[renderer]
-/// fn render_result_file(result: ResultFile) -> RenderResult {
-/// let (is_dir, size, name) = result.into();
-/// let mut result = RenderResult::new();
-/// writeln!(
-/// result,
-/// "{}: {} ({})",
-/// if is_dir { "dir" } else { "file" },
-/// name,
-/// size
-/// )
-/// .ok();
-/// result
-/// }
-///
-/// /// Renders the error when no name is provided.
-/// #[renderer]
-/// fn render_error_no_name_provided(_: ErrorNoNameProvided) -> RenderResult {
-/// let mut result = RenderResult::new();
-/// writeln!(result, "Error: name is not provided").ok();
-/// result
-/// }
-///
-/// gen_program!();
-///
-/// fn main() {
-/// let program = ThisProgram::new();
-/// program.exec_and_exit();
-/// }
-/// ```
-pub mod example_argument_parse {}
/// Example Argument Picker
///
/// > Demonstrates how to use Mingling's `picker` feature and `Picker` to extract typed arguments from the command line.
@@ -401,8 +279,8 @@ pub mod example_argument_picker {}
/// [dependencies.mingling]
/// path = "../../mingling"
///
-/// # Enable `parser` features
-/// features = ["async", "parser"]
+/// # Enable `picker` features
+/// features = ["async", "picker"]
///
/// # Import any async runtime, e.g. Tokio
/// [dependencies.tokio]
@@ -438,7 +316,7 @@ pub mod example_argument_picker {}
/// #[chain]
/// // vvvvv_ `async` keyword can be used directly here
/// pub async fn handle_download(args: EntryDownload) -> Next {
-/// let file_name = args.pick(()).unpack();
+/// let file_name = args.pick_or_default(&arg![String]).unwrap();
/// fake_download(file_name).await.into()
/// }
///
@@ -963,7 +841,7 @@ pub mod example_command_macro {}
/// features = [
/// # Enable `comp` features
/// "comp",
-/// "parser",
+/// "picker",
/// ]
///
/// [build-dependencies.mingling]
@@ -1013,18 +891,22 @@ pub mod example_command_macro {}
/// }
///
/// // When the user is typing `--repeat`
-/// if ctx.filling_argument(["-r", "--repeat"]) {
+/// if ctx.previous_word == "-r" || ctx.previous_word == "--repeat" {
/// return suggest! {}; // Don't suggest anything
/// }
///
/// // When the user is typing `-`
-/// if ctx.typing_argument() {
-/// return suggest! {
+/// if ctx.current_word.starts_with('-') {
+/// // Remove arguments that have already been typed by the user
+/// let typed: Vec<&str> = ctx.all_words.iter().map(String::as_str).collect();
+/// let mut set = suggest! {
/// "-r": "Number of repetitions",
/// "--repeat": "Number of repetitions",
+/// };
+/// if let Suggest::Suggest(items) = &mut set {
+/// items.retain(|item| !typed.contains(&item.suggest().as_str()));
/// }
-/// // Remove arguments that have already been typed by the user
-/// .strip_typed_argument(ctx);
+/// return set;
/// }
///
/// // Otherwise, suggest nothing
@@ -1041,9 +923,9 @@ pub mod example_command_macro {}
/// #[chain]
/// fn handle_greet(args: EntryGreet) -> Next {
/// let result: ResultName = args
-/// .pick_or(["-r", "--repeat"], 1)
-/// .pick_or((), "World")
-/// .unpack()
+/// .pick_or(&arg![repeat: u8, 'r'], || 1)
+/// .pick_or(&arg![String], || "World".to_string())
+/// .unwrap()
/// .into();
/// result.into()
/// }
@@ -1064,155 +946,6 @@ pub mod example_command_macro {}
/// gen_program!();
/// ```
pub mod example_completion {}
-/// 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
-/// ```
-///
-/// Source code (./Cargo.toml)
-/// ```toml
-/// [package]
-/// name = "example-custom-pickable"
-/// version = "0.1.0"
-/// edition = "2024"
-///
-/// [dependencies.mingling]
-/// path = "../../mingling"
-///
-/// features = ["parser", "extras"]
-///
-/// [workspace]
-/// ```
-///
-/// Source code (./src/main.rs)
-/// ```ignore
-/// use mingling::{macros::route, parser::Pickable, prelude::*, Grouped};
-/// use std::io::Write;
-///
-/// // Define types that can be recognized by Mingling
-/// // ________________________ `Pickable` trait needs to implement Default
-/// // / ________ The Grouped derive macro registers an ID for this type
-/// // | / Mingling uses this ID to identify the type
-/// // vvvvvvv vvvvvvv
-/// #[derive(Debug, Default, Clone, Grouped)]
-/// 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)?.clone();
-///
-/// // Use TryFrom to parse the address
-/// Address::try_from(raw).ok()
-/// }
-/// }
-/// // --------- IMPORTANT ---------
-///
-/// dispatcher!("connect", EntryConnect);
-/// pack!(ErrorParseAddressFailed = ());
-///
-/// #[chain]
-/// fn handle_connect(prev: EntryConnect) -> Next {
-/// let connect: Address =
-/// route! { prev.pick_or_route((), ErrorParseAddressFailed::default()).unpack() };
-/// connect.to_chain()
-/// }
-///
-/// /// Renders the connected address.
-/// #[renderer]
-/// pub fn render_address(addr: Address) -> RenderResult {
-/// let mut render_result = RenderResult::new();
-/// write!(render_result, "Connected to \"{}\"", addr).ok();
-/// render_result
-/// }
-///
-/// /// Renders the error message when address parsing fails.
-/// #[renderer]
-/// pub fn render_error_parse_address_failed(_: ErrorParseAddressFailed) -> RenderResult {
-/// let mut render_result = RenderResult::new();
-/// write!(render_result, "Failed to parse address").ok();
-/// render_result
-/// }
-///
-/// gen_program!();
-///
-/// fn main() {
-/// ThisProgram::new().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
-/// )
-/// }
-/// }
-/// ```
-pub mod example_custom_pickable {}
/// Example Dispatch Tree
///
/// > This example will introduce how to use `dispatch_tree`
@@ -1317,7 +1050,7 @@ pub mod example_dispatch_tree {}
///
/// features = [
/// "comp",
-/// "parser"
+/// "picker"
/// ]
///
/// [workspace]
@@ -1326,8 +1059,10 @@ pub mod example_dispatch_tree {}
/// Source code (./src/main.rs)
/// ```ignore
/// use mingling::{
-/// macros::suggest_enum, parser::PickableEnum, prelude::*, EnumTag, Grouped, ShellContext,
-/// Suggest,
+/// EnumTag, Grouped, ShellContext, Suggest,
+/// macros::suggest_enum,
+/// picker::{PickerArgResult, SinglePickable},
+/// prelude::*,
/// };
/// use std::io::Write;
///
@@ -1367,9 +1102,7 @@ pub mod example_dispatch_tree {}
/// #[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"
-/// )]
+/// #[enum_desc("An object-oriented scripting language, famous for its concise and elegant syntax")]
/// Ruby,
///
/// #[default]
@@ -1378,9 +1111,31 @@ pub mod example_dispatch_tree {}
/// }
///
/// // --------- IMPORTANT ---------
-/// // Implement the PickableEnum trait for ProgrammingLanguages,
-/// // so that `Picker` can parse this enum
-/// impl PickableEnum for ProgrammingLanguages {}
+/// // NOTE: Due to the migration from the legacy `parser` to `picker`, the `EnumTag` -> `Picker` path
+/// // is not yet complete, so a manual implementation is used for now.
+/// // Once that path is complete, `#[derive(EnumTag)]` can automatically implement `SinglePickable`,
+/// // replacing this manual implementation.
+/// impl SinglePickable for ProgrammingLanguages {
+/// fn pick_single(str: Option<&str>) -> PickerArgResult<Self> {
+/// let Some(str) = str else {
+/// return PickerArgResult::NotFound;
+/// };
+/// let lang = match str.to_lowercase().as_str() {
+/// "c" => Self::C,
+/// "c++" | "cpp" => Self::CPlusPlus,
+/// "c#" | "csharp" => Self::Csharp,
+/// "java" => Self::Java,
+/// "javascript" | "js" => Self::JavaScript,
+/// "kotlin" => Self::Kotlin,
+/// "ocaml" => Self::OCaml,
+/// "python" => Self::Python,
+/// "ruby" => Self::Ruby,
+/// "rust" => Self::Rust,
+/// _ => return PickerArgResult::NotFound,
+/// };
+/// PickerArgResult::Parsed(lang)
+/// }
+/// }
/// // --------- IMPORTANT ---------
///
/// dispatcher!("lang-select", EntryLanguageSelection);
@@ -1388,7 +1143,7 @@ pub mod example_dispatch_tree {}
/// #[chain]
/// fn handle_language_selection(args: EntryLanguageSelection) -> Next {
/// // You can use Picker to directly parse ProgrammingLanguages
-/// let lang: ProgrammingLanguages = args.pick(()).unpack();
+/// let lang: ProgrammingLanguages = args.pick_or_default(&arg![ProgrammingLanguages]).unwrap();
/// lang.into()
/// }
///
@@ -2396,7 +2151,7 @@ pub mod example_pack_err {}
///
/// [dependencies.mingling]
/// path = "../../mingling"
-/// features = ["parser"]
+/// features = ["picker"]
///
/// # Enable panic unwinding in release builds
/// [profile.release]
@@ -2437,7 +2192,7 @@ pub mod example_pack_err {}
///
/// #[chain]
/// fn handle_panic(prev: EntryPanic) -> Next {
-/// let panic_info = prev.pick::<Option<String>>(()).unpack();
+/// let panic_info = prev.pick_or_default(&arg![Option<String>]).unwrap();
/// match panic_info {
/// Some(s) => {
/// // Panic happens here, will be caught
@@ -2536,7 +2291,7 @@ pub mod example_pathfinder {}
///
/// [dependencies.mingling]
/// path = "../../mingling"
-/// features = ["repl", "parser", "extras"]
+/// features = ["repl", "picker", "extras"]
///
/// [dependencies]
/// just_fmt = "0.1.2"
@@ -2626,7 +2381,7 @@ pub mod example_pathfinder {}
/// // Parse cd command arguments
/// #[chain]
/// fn parse_cd_args(prev: EntryCd) -> Next {
-/// let join = prev.pick(()).unpack();
+/// let join = prev.pick_or_default(&arg![String]).unwrap();
/// StateChangeDirectory::new(join).into()
/// }
///
@@ -2745,7 +2500,7 @@ pub mod example_repl_basic {}
///
/// [dependencies.mingling]
/// path = "../../mingling"
-/// features = ["parser"]
+/// features = ["picker"]
///
/// [workspace]
/// ```
@@ -2787,7 +2542,7 @@ pub mod example_repl_basic {}
/// fn render_modify_current(args: EntryModifyCurrent, current_dir: &mut ResCurrentDir) -> Next {
/// current_dir.current_dir = current_dir
/// .current_dir
-/// .join(args.pick::<String>(()).unpack());
+/// .join(args.pick_or_default(&arg![String]).unwrap());
/// EntryCurrent::default().into()
/// }
///
@@ -2940,7 +2695,7 @@ pub mod example_setup {}
/// features = [
/// "structural_renderer",
/// "yaml_serde_fmt",
-/// "parser",
+/// "picker",
/// ]
///
/// [workspace]
@@ -2948,8 +2703,8 @@ pub mod example_setup {}
///
/// Source code (./src/main.rs)
/// ```ignore
-/// use mingling::prelude::*;
-/// use mingling::{parser::Picker, setup::StructuralRendererSetup, Grouped, StructuralData};
+/// use mingling::setup::picker::StructuralRendererSetup;
+/// use mingling::{Grouped, StructuralData, prelude::*};
/// use serde::Serialize;
/// use std::io::Write;
///
@@ -2986,10 +2741,10 @@ pub mod example_setup {}
///
/// #[chain]
/// fn parse_render(prev: EntryRender) -> Next {
-/// let (name, age) = Picker::new(prev.inner)
-/// .pick::<String>(())
-/// .pick::<i32>(())
-/// .unpack();
+/// let (name, age) = prev
+/// .pick_or_default(&arg![String])
+/// .pick_or_default(&arg![i32])
+/// .unwrap();
/// Info { name, age }.to_render()
/// }
///
diff --git a/mingling/src/features.rs b/mingling/src/features.rs
index 2925f03..9445328 100644
--- a/mingling/src/features.rs
+++ b/mingling/src/features.rs
@@ -229,17 +229,6 @@ pub const MINGLING_NIGHTLY: bool = false;
#[cfg(feature = "nightly")]
#[allow(unused)]
pub const MINGLING_NIGHTLY: bool = true;
-/// Whether the `parser` feature is enabled
-/// Current: `disabled`
-#[cfg(not(feature = "parser"))]
-#[allow(unused)]
-pub const MINGLING_PARSER: bool = false;
-
-/// Whether the `parser` feature is enabled
-/// Current: `enabled`
-#[cfg(feature = "parser")]
-#[allow(unused)]
-pub const MINGLING_PARSER: bool = true;
/// Whether the `pathf` feature is enabled
/// Current: `disabled`
#[cfg(not(feature = "pathf"))]
diff --git a/mingling/src/lib.rs b/mingling/src/lib.rs
index 3707289..2596a9c 100644
--- a/mingling/src/lib.rs
+++ b/mingling/src/lib.rs
@@ -36,10 +36,6 @@ pub use mingling::*;
#[cfg(feature = "core")]
pub use mingling_core as mingling;
-/// `Mingling` argument parser (Built-in)
-#[cfg(feature = "parser")]
-pub mod parser;
-
/// `Mingling` argument parser (Picker2)
#[cfg(feature = "picker")]
pub mod picker;
@@ -231,11 +227,9 @@ pub mod prelude {
pub use mingling_macros::r_println;
#[cfg(all(feature = "macros", feature = "comp"))]
+ #[cfg(feature = "comp")]
pub use crate::macros::completion;
- #[cfg(feature = "parser")]
- pub use crate::parser::AsPicker;
-
#[cfg(feature = "picker")]
pub use arg_picker::prelude::arg;
diff --git a/mingling/src/parser.rs b/mingling/src/parser.rs
deleted file mode 100644
index 97124ca..0000000
--- a/mingling/src/parser.rs
+++ /dev/null
@@ -1,12 +0,0 @@
-// Doc Not Optimize
-mod args;
-pub use crate::parser::args::*;
-
-mod picker;
-pub use crate::parser::picker::*;
-
-pub use crate::parser::picker::bools::*;
-pub use crate::parser::picker::path::*;
-
-#[cfg(test)]
-mod test;
diff --git a/mingling/src/parser/args.rs b/mingling/src/parser/args.rs
deleted file mode 100644
index c7139c4..0000000
--- a/mingling/src/parser/args.rs
+++ /dev/null
@@ -1,177 +0,0 @@
-// Doc Not Optimize
-use std::mem::replace;
-
-use mingling_core::{Flag, special_argument, special_arguments, special_flag};
-
-/// User input arguments
-#[derive(Debug, Default, Clone)]
-pub struct Argument {
- vec: Vec<String>,
-}
-
-impl From<Vec<&str>> for Argument {
- fn from(vec: Vec<&str>) -> Self {
- Self {
- vec: vec
- .into_iter()
- .map(std::string::ToString::to_string)
- .collect(),
- }
- }
-}
-
-impl From<&'static str> for Argument {
- fn from(s: &'static str) -> Self {
- Self {
- vec: vec![s.to_string()],
- }
- }
-}
-
-impl From<&'static [&'static str]> for Argument {
- fn from(slice: &'static [&'static str]) -> Self {
- Self {
- vec: slice.iter().map(|&s| s.to_string()).collect(),
- }
- }
-}
-
-impl<const N: usize> From<[&'static str; N]> for Argument {
- fn from(slice: [&'static str; N]) -> Self {
- Self {
- vec: slice.iter().map(|&s| s.to_string()).collect(),
- }
- }
-}
-
-impl<const N: usize> From<&'static [&'static str; N]> for Argument {
- fn from(slice: &'static [&'static str; N]) -> Self {
- Self {
- vec: slice.iter().map(|&s| s.to_string()).collect(),
- }
- }
-}
-
-impl From<Vec<String>> for Argument {
- fn from(vec: Vec<String>) -> Self {
- Self { vec }
- }
-}
-
-impl AsRef<[String]> for Argument {
- fn as_ref(&self) -> &[String] {
- &self.vec
- }
-}
-
-impl std::ops::Deref for Argument {
- type Target = Vec<String>;
-
- fn deref(&self) -> &Self::Target {
- &self.vec
- }
-}
-
-impl std::ops::DerefMut for Argument {
- fn deref_mut(&mut self) -> &mut Self::Target {
- &mut self.vec
- }
-}
-
-impl Argument {
- /// Picks a single argument with the given flag
- pub fn pick_argument<F>(&mut self, flag: F) -> Option<String>
- where
- F: Into<Flag>,
- {
- if self.is_empty() {
- return None;
- }
-
- let flag: Flag = flag.into();
- if flag.is_empty() {
- // No flag
- return Some(self.vec.remove(0));
- }
- // Has any flag
- for argument in flag.iter() {
- let value = special_argument!(self.vec, argument);
- if value.is_some() {
- return value;
- }
- }
- None
- }
-
- /// Picks arguments with the given flag
- pub fn pick_arguments<F>(&mut self, flag: F) -> Vec<String>
- where
- F: Into<Flag>,
- {
- let mut str_result = Vec::new();
-
- if self.is_empty() {
- return str_result;
- }
-
- let flag: Flag = flag.into();
- if flag.is_empty() {
- let value = special_arguments!(self.vec, "");
- str_result.extend(value);
- } else {
- for argument in flag.iter() {
- let value = special_arguments!(self.vec, argument);
- str_result.extend(value);
- }
- }
-
- str_result
- }
-
- /// Picks a flag with the given flag
- pub fn pick_flag<F>(&mut self, flag: F) -> bool
- where
- F: Into<Flag>,
- {
- if self.is_empty() {
- return false;
- }
-
- let flag: Flag = flag.into();
- if flag.is_empty() {
- let first = self.vec.remove(0);
- let first_lower = first.to_lowercase();
- let trimmed = first_lower.trim();
- let result = match trimmed {
- "y" | "yes" | "true" | "1" => return true,
- "n" | "no" | "false" | "0" => return false,
- _ => false,
- };
- return result;
- }
- // Has any flag
- for argument in flag.iter() {
- let enabled = special_flag!(self.vec, argument);
- if enabled {
- return enabled;
- }
- }
- false
- }
-
- /// Dump all remaining arguments
- pub const fn dump_remains(&mut self) -> Vec<String> {
- let new = Vec::new();
- replace(&mut self.vec, new)
- }
-
- /// Removes all arguments that start with a dash ('-')
- ///
- /// This method filters out all command-line style flags from the arguments,
- /// returning a new `Argument` instance containing only non-flag arguments.
- #[must_use]
- pub fn strip_all_flags(mut self) -> Self {
- self.vec.retain(|f| !f.starts_with('-'));
- self
- }
-}
diff --git a/mingling/src/parser/picker.rs b/mingling/src/parser/picker.rs
deleted file mode 100644
index 2f43e8c..0000000
--- a/mingling/src/parser/picker.rs
+++ /dev/null
@@ -1,815 +0,0 @@
-// Doc Not Optimize
-use crate::parser::Argument;
-use mingling_core::{EnumTag, Flag};
-
-#[doc(hidden)]
-pub mod builtin;
-
-#[doc(hidden)]
-pub mod bools;
-
-#[doc(hidden)]
-pub mod path;
-
-/// A builder for extracting values from command-line arguments.
-///
-/// The `Picker` struct holds parsed arguments and provides a fluent interface
-/// to extract values associated with specific flags.
-#[derive(Default)]
-pub struct Picker {
- /// The parsed command-line arguments.
- pub args: Argument,
-}
-
-impl Picker {
- /// Creates a new `Picker` from a value that can be converted into `Argument`.
- pub fn new(args: impl Into<Argument>) -> Self {
- Self { args: args.into() }
- }
-
- /// Extracts a value for the given flag and returns a `Pick1` builder (no route).
- ///
- /// The extracted type `TNext` must implement `Pickable` and `Default`.
- /// If the flag is not present, the default value for `TNext` is used.
- pub fn pick<TNext>(mut self, val: impl Into<Flag>) -> Pick1<TNext>
- where
- TNext: Pickable<Output = TNext> + Default,
- {
- let v = TNext::pick(&mut self.args, val.into()).unwrap_or_default();
- Pick1 {
- args: self.args,
- val_1: v,
- }
- }
-
- /// Extracts a value for the given flag, returning the provided default value if not present,
- /// and returns a `Pick1` builder (no route).
- ///
- /// The extracted type `TNext` must implement `Pickable`.
- /// If the flag is not present, the provided `or` value is used.
- pub fn pick_or<TNext>(mut self, val: impl Into<Flag>, or: impl Into<TNext>) -> Pick1<TNext>
- where
- TNext: Pickable<Output = TNext>,
- {
- let v = TNext::pick(&mut self.args, val.into()).unwrap_or_else(|| or.into());
- Pick1 {
- args: self.args,
- val_1: v,
- }
- }
-
- /// Extracts a value for the given flag, storing the provided route if the flag is not present,
- /// and returns a `PickWithRoute1` builder (with route).
- ///
- /// The extracted type `TNext` must implement `Pickable` and `Default`.
- /// If the flag is not present, the default value for `TNext` is used and the provided `route`
- /// is stored in the returned builder for later error handling.
- pub fn pick_or_route<TNext, R>(
- mut self,
- val: impl Into<Flag>,
- route: R,
- ) -> PickWithRoute1<TNext, R>
- where
- TNext: Pickable<Output = TNext> + Default,
- {
- let Some(v) = TNext::pick(&mut self.args, val.into()) else {
- return PickWithRoute1 {
- args: self.args,
- val_1: TNext::default(),
- route: Some(route),
- };
- };
- PickWithRoute1 {
- args: self.args,
- val_1: v,
- route: None,
- }
- }
-
- /// Extracts a value for the given flag, returning `None` if the flag is not present,
- /// and returns an `Option<Pick1<TNext>>` builder (no route).
- ///
- /// The extracted type `TNext` must implement `Pickable`.
- /// If the flag is not present, `None` is returned.
- pub fn require<TNext>(mut self, val: impl Into<Flag>) -> Option<Pick1<TNext>>
- where
- TNext: Pickable<Output = TNext>,
- {
- let v = TNext::pick(&mut self.args, val.into());
- match v {
- Some(s) => Some(Pick1 {
- args: self.args,
- val_1: s,
- }),
- None => None,
- }
- }
-
- /// Applies an operation to the parsed arguments and returns the modified `Picker`.
- ///
- /// Takes a closure that receives the current `Argument` and returns a new `Argument`.
- /// The returned `Argument` replaces the original arguments in the builder.
- /// This method can be used to modify or transform the parsed arguments before extracting values.
- #[must_use]
- pub fn operate_args<F: FnOnce(Argument) -> Argument>(mut self, operation: F) -> Self {
- self.args = operation(self.args);
- self
- }
-}
-
-impl<T: Into<Argument>> From<T> for Picker {
- fn from(value: T) -> Self {
- Self::new(value)
- }
-}
-
-/// Extracts values from command-line arguments
-///
-/// The `Pickable` trait defines how to extract the value of a specific flag from parsed arguments
-pub trait Pickable {
- /// The output type produced by the extraction operation, must implement the `Default` trait
- type Output: Default;
-
- /// Extracts the value associated with the given flag from the provided arguments
- ///
- /// If the flag exists and the value can be successfully extracted, returns `Some(Output)`;
- /// otherwise returns `None`
- fn pick(args: &mut Argument, flag: Flag) -> Option<Self::Output>;
-}
-
-// Non-routed Pick structs (no R parameter, no route field)
-
-/// Internal macro: generates the struct definition and common methods
-/// (after, `after_or_route`, `operate_args`) for non-routed Pick structs.
-macro_rules! define_pick_struct {
- ($n:ident $final:ident $final_val:ident $route_self:ident $($T:ident $val:ident),+ $(,)?) => {
- #[doc(hidden)]
- pub struct $n<$($T,)+>
- where
- $($T: Pickable,)+
- {
- #[allow(dead_code)]
- args: Argument,
- $(pub $val: $T,)+
- }
-
- impl<$($T,)+> $n<$($T,)+>
- where
- $($T: Pickable,)+
- {
- /// Applies a transformation to the last extracted value.
- ///
- /// Takes a closure that receives the last extracted value and returns a new value of the same type.
- /// The transformed value replaces the original value in the builder.
- /// This method can be used to modify or validate the extracted value before final unpacking.
- #[must_use]
- pub fn after<F>(mut self, mut edit: F) -> Self
- where
- F: FnMut($final) -> $final,
- {
- self.$final_val = edit(self.$final_val);
- self
- }
-
- /// Applies a transformation to the last extracted value, storing a route if the transformation fails.
- ///
- /// Takes a closure that receives a reference to the last extracted value and returns a `Result`.
- /// If the closure returns `Ok(new_value)`, the new value replaces the original value in the builder.
- /// If the closure returns `Err(route)`, the provided `route` is stored in the builder for later error handling.
- /// If a route was already stored from a previous `pick_or_route` call, the existing route is preserved.
- #[must_use]
- pub fn after_or_route<F, R>(mut self, mut edit: F) -> $route_self<$($T,)+ R>
- where
- F: FnMut(&$final) -> Result<$final, R>,
- {
- match edit(&self.$final_val) {
- Ok(new_value) => {
- self.$final_val = new_value;
- $route_self {
- args: self.args,
- $($val: self.$val,)+
- route: None,
- }
- }
- Err(err_route) => {
- $route_self {
- args: self.args,
- $($val: self.$val,)+
- route: Some(err_route),
- }
- }
- }
- }
-
- /// Applies an operation to the parsed arguments and returns the modified builder.
- ///
- /// Takes a closure that receives the current `Argument` and returns a new `Argument`.
- /// The returned `Argument` replaces the original arguments in the builder.
- /// This method can be used to modify or transform the parsed arguments before extracting values.
- #[must_use]
- pub fn operate_args<F: FnOnce(Argument) -> Argument>(mut self, operation: F) -> Self {
- self.args = operation(self.args);
- self
- }
- }
- };
-}
-
-// Pick1 special case (single value)
-
-define_pick_struct! { Pick1 T1 val_1 PickWithRoute1 T1 val_1 }
-
-impl<T1> From<Pick1<T1>> for (T1,)
-where
- T1: Pickable,
-{
- fn from(pick: Pick1<T1>) -> Self {
- (pick.val_1,)
- }
-}
-
-impl<T1> Pick1<T1>
-where
- T1: Pickable,
-{
- /// Unpacks the builder into the extracted value.
- ///
- /// Always returns the value directly since there is no route.
- pub fn unpack(self) -> T1 {
- self.val_1
- }
-}
-
-// Pick2 .. Pick12
-
-macro_rules! impl_pick_from_tuple {
- ($n:ident $($T:ident $val:ident),+) => {
- impl<$($T,)+> From<$n<$($T,)+>> for ($($T,)+)
- where
- $($T: Pickable,)+
- {
- fn from(pick: $n<$($T,)+>) -> Self {
- ($(pick.$val,)+)
- }
- }
- };
-}
-
-macro_rules! impl_pick_unpack_tuple {
- ($n:ident $($T:ident $val:ident),+) => {
- impl<$($T,)+> $n<$($T,)+>
- where
- $($T: Pickable,)+
- {
- /// Unpacks the builder into a tuple of extracted values.
- ///
- /// Always returns the tuple directly since there is no route.
- pub fn unpack(self) -> ($($T,)+) {
- ($(self.$val,)+)
- }
- }
- };
-}
-
-define_pick_struct! { Pick2 T2 val_2 PickWithRoute2 T1 val_1, T2 val_2 }
-impl_pick_from_tuple! { Pick2 T1 val_1, T2 val_2 }
-impl_pick_unpack_tuple! { Pick2 T1 val_1, T2 val_2 }
-
-define_pick_struct! { Pick3 T3 val_3 PickWithRoute3 T1 val_1, T2 val_2, T3 val_3 }
-impl_pick_from_tuple! { Pick3 T1 val_1, T2 val_2, T3 val_3 }
-impl_pick_unpack_tuple! { Pick3 T1 val_1, T2 val_2, T3 val_3 }
-
-define_pick_struct! { Pick4 T4 val_4 PickWithRoute4 T1 val_1, T2 val_2, T3 val_3, T4 val_4 }
-impl_pick_from_tuple! { Pick4 T1 val_1, T2 val_2, T3 val_3, T4 val_4 }
-impl_pick_unpack_tuple! { Pick4 T1 val_1, T2 val_2, T3 val_3, T4 val_4 }
-
-define_pick_struct! { Pick5 T5 val_5 PickWithRoute5 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5 }
-impl_pick_from_tuple! { Pick5 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5 }
-impl_pick_unpack_tuple! { Pick5 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5 }
-
-define_pick_struct! { Pick6 T6 val_6 PickWithRoute6 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6 }
-impl_pick_from_tuple! { Pick6 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6 }
-impl_pick_unpack_tuple! { Pick6 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6 }
-
-define_pick_struct! { Pick7 T7 val_7 PickWithRoute7 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7 }
-impl_pick_from_tuple! { Pick7 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7 }
-impl_pick_unpack_tuple! { Pick7 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7 }
-
-define_pick_struct! { Pick8 T8 val_8 PickWithRoute8 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8 }
-impl_pick_from_tuple! { Pick8 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8 }
-impl_pick_unpack_tuple! { Pick8 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8 }
-
-define_pick_struct! { Pick9 T9 val_9 PickWithRoute9 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9 }
-impl_pick_from_tuple! { Pick9 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9 }
-impl_pick_unpack_tuple! { Pick9 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9 }
-
-define_pick_struct! { Pick10 T10 val_10 PickWithRoute10 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9, T10 val_10 }
-impl_pick_from_tuple! { Pick10 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9, T10 val_10 }
-impl_pick_unpack_tuple! { Pick10 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9, T10 val_10 }
-
-define_pick_struct! { Pick11 T11 val_11 PickWithRoute11 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9, T10 val_10, T11 val_11 }
-impl_pick_from_tuple! { Pick11 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9, T10 val_10, T11 val_11 }
-impl_pick_unpack_tuple! { Pick11 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9, T10 val_10, T11 val_11 }
-
-define_pick_struct! { Pick12 T12 val_12 PickWithRoute12 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9, T10 val_10, T11 val_11, T12 val_12 }
-impl_pick_from_tuple! { Pick12 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9, T10 val_10, T11 val_11, T12 val_12 }
-impl_pick_unpack_tuple! { Pick12 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9, T10 val_10, T11 val_11, T12 val_12 }
-
-// Non-routed Pick chaining methods (pick, pick_or, pick_or_route, require)
-
-#[doc(hidden)]
-macro_rules! impl_pick_next {
- ($n:ident $next:ident $next_val:ident $route_next:ident $($T:ident $val:ident),+) => {
- impl<$($T,)+> $n<$($T,)+>
- where
- $($T: Pickable,)+
- {
- /// Extracts a value for the given flag and returns a `PickN` builder (no route).
- pub fn pick<TNext>(mut self, val: impl Into<mingling_core::Flag>) -> $next<$($T,)+ TNext>
- where
- TNext: Pickable<Output = TNext> + Default,
- {
- let v = TNext::pick(&mut self.args, val.into()).unwrap_or_default();
- $next {
- args: self.args,
- $($val: self.$val,)+
- $next_val: v,
- }
- }
-
- /// Extracts a value for the given flag, returning the provided default value if not present,
- /// and returns a `PickN` builder (no route).
- pub fn pick_or<TNext>(mut self, val: impl Into<mingling_core::Flag>, or: impl Into<TNext>) -> $next<$($T,)+ TNext>
- where
- TNext: Pickable<Output = TNext>,
- {
- let v = TNext::pick(&mut self.args, val.into()).unwrap_or_else(|| or.into());
- $next {
- args: self.args,
- $($val: self.$val,)+
- $next_val: v,
- }
- }
-
- /// Extracts a value for the given flag, storing the provided route if the flag is not present,
- /// and returns a `PickWithRouteN` builder (with route).
- pub fn pick_or_route<TNext, R>(
- mut self,
- val: impl Into<mingling_core::Flag>,
- route: R,
- ) -> $route_next<$($T,)+ TNext, R>
- where
- TNext: Pickable<Output = TNext> + Default,
- {
- let Some(v) = TNext::pick(&mut self.args, val.into()) else {
- return $route_next {
- args: self.args,
- $($val: self.$val,)+
- $next_val: TNext::default(),
- route: Some(route),
- };
- };
- $route_next {
- args: self.args,
- $($val: self.$val,)+
- $next_val: v,
- route: None,
- }
- }
-
- /// Extracts a value for the given flag, returning `None` if the flag is not present,
- /// and returns an `Option<PickN<TNext>>` builder (no route).
- pub fn require<TNext>(mut self, val: impl Into<mingling_core::Flag>) -> Option<$next<$($T,)+ TNext>>
- where
- TNext: Pickable<Output = TNext>,
- {
- let v = TNext::pick(&mut self.args, val.into());
- match v {
- Some(s) => Some($next {
- args: self.args,
- $($val: self.$val,)+
- $next_val: s,
- }),
- None => None,
- }
- }
- }
- };
-}
-
-impl_pick_next! { Pick1 Pick2 val_2 PickWithRoute2 T1 val_1 }
-impl_pick_next! { Pick2 Pick3 val_3 PickWithRoute3 T1 val_1, T2 val_2 }
-impl_pick_next! { Pick3 Pick4 val_4 PickWithRoute4 T1 val_1, T2 val_2, T3 val_3 }
-impl_pick_next! { Pick4 Pick5 val_5 PickWithRoute5 T1 val_1, T2 val_2, T3 val_3, T4 val_4 }
-impl_pick_next! { Pick5 Pick6 val_6 PickWithRoute6 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5 }
-impl_pick_next! { Pick6 Pick7 val_7 PickWithRoute7 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6 }
-impl_pick_next! { Pick7 Pick8 val_8 PickWithRoute8 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7 }
-impl_pick_next! { Pick8 Pick9 val_9 PickWithRoute9 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8 }
-impl_pick_next! { Pick9 Pick10 val_10 PickWithRoute10 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9 }
-impl_pick_next! { Pick10 Pick11 val_11 PickWithRoute11 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9, T10 val_10 }
-impl_pick_next! { Pick11 Pick12 val_12 PickWithRoute12 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9, T10 val_10, T11 val_11 }
-
-// Routed PickWithRoute structs (with R parameter, route field)
-
-/// Internal macro: generates the routed struct definition and common methods
-/// (after, `after_or_route`, `operate_args`) for `PickWithRoute` structs.
-macro_rules! define_pick_with_route_struct {
- ($n:ident $final:ident $final_val:ident $($T:ident $val:ident),+) => {
- #[doc(hidden)]
- pub struct $n<$($T,)+ R>
- where
- $($T: Pickable,)+
- {
- #[allow(dead_code)]
- args: Argument,
- $(pub $val: $T,)+
- route: Option<R>,
- }
-
- impl<$($T,)+ R> $n<$($T,)+ R>
- where
- $($T: Pickable,)+
- {
- /// Applies a transformation to the last extracted value.
- ///
- /// Takes a closure that receives the last extracted value and returns a new value of the same type.
- /// The transformed value replaces the original value in the builder.
- /// This method can be used to modify or validate the extracted value before final unpacking.
- #[must_use]
- pub fn after<F>(mut self, mut edit: F) -> Self
- where
- F: FnMut($final) -> $final,
- {
- self.$final_val = edit(self.$final_val);
- self
- }
-
- /// Applies a transformation to the last extracted value, storing a route if the transformation fails.
- ///
- /// Takes a closure that receives a reference to the last extracted value and returns a `Result`.
- /// If the closure returns `Ok(new_value)`, the new value replaces the original value in the builder.
- /// If the closure returns `Err(route)`, the provided `route` is stored in the builder for later error handling.
- /// If a route was already stored from a previous `pick_or_route` call, the existing route is preserved.
- #[must_use]
- pub fn after_or_route<F>(mut self, mut edit: F) -> Self
- where
- F: FnMut(&$final) -> Result<$final, R>,
- {
- let value = &self.$final_val;
- match edit(value) {
- Ok(new_value) => {
- self.$final_val = new_value;
- }
- Err(err_route) => {
- let new_route = match self.route {
- Some(existing_route) => Some(existing_route),
- None => Some(err_route),
- };
- self.route = new_route;
- }
- }
- self
- }
-
- /// Applies an operation to the parsed arguments and returns the modified builder.
- ///
- /// Takes a closure that receives the current `Argument` and returns a new `Argument`.
- /// The returned `Argument` replaces the original arguments in the builder.
- /// This method can be used to modify or transform the parsed arguments before extracting values.
- #[must_use]
- pub fn operate_args<F: FnOnce(Argument) -> Argument>(mut self, operation: F) -> Self {
- self.args = operation(self.args);
- self
- }
- }
- };
-}
-
-/// Internal macro: generates `From` impl for routed `PickWithRouteN` into a tuple.
-macro_rules! impl_pick_with_route_from_tuple {
- ($n:ident $($T:ident $val:ident),+) => {
- impl<$($T,)+ R> From<$n<$($T,)+ R>> for ($($T,)+)
- where
- $($T: Pickable,)+
- {
- fn from(pick: $n<$($T,)+ R>) -> Self {
- ($(pick.$val,)+)
- }
- }
- };
-}
-
-/// Internal macro: generates `unpack` and `unpack_directly` for routed `PickWithRouteN` (N >= 2).
-macro_rules! impl_pick_with_route_unpack_tuple {
- ($n:ident $($T:ident $val:ident),+) => {
- impl<$($T,)+ R> $n<$($T,)+ R>
- where
- $($T: Pickable,)+
- {
- /// Unpacks the builder into a tuple of extracted values.
- ///
- /// Returns `Ok((T1, T2, ...))` if no route was stored.
- /// Returns `Err(R)` if a route was stored via `pick_or_route` or `after_or_route`.
- ///
- /// # Errors
- ///
- /// Returns `Err(R)` if a route was stored via `pick_or_route` or `after_or_route`.
- pub fn unpack(self) -> Result<($($T,)+), R> {
- match self.route {
- Some(route) => Err(route),
- None => Ok(($(self.$val,)+)),
- }
- }
-
- /// Unpacks the builder into a tuple of extracted values.
- ///
- /// Returns the tuple of extracted values regardless of route state.
- #[must_use]
- pub fn unpack_directly(self) -> ($($T,)+) {
- ($(self.$val,)+)
- }
- }
- };
-}
-
-// PickWithRoute1 special case (single value)
-
-define_pick_with_route_struct! { PickWithRoute1 T1 val_1 T1 val_1 }
-
-impl<T1, R> From<PickWithRoute1<T1, R>> for (T1,)
-where
- T1: Pickable,
-{
- fn from(pick: PickWithRoute1<T1, R>) -> Self {
- (pick.val_1,)
- }
-}
-
-impl<T1, R> PickWithRoute1<T1, R>
-where
- T1: Pickable,
-{
- /// Unpacks the builder into the extracted value.
- ///
- /// Returns `Ok(T1)` if no route was stored.
- /// Returns `Err(R)` if a route was stored via `pick_or_route` or `after_or_route`.
- ///
- /// # Errors
- ///
- /// Returns `Err(R)` if a route was stored via `pick_or_route` or `after_or_route`.
- pub fn unpack(self) -> Result<T1, R> {
- match self.route {
- Some(route) => Err(route),
- None => Ok(self.val_1),
- }
- }
-
- /// Unpacks the builder into the extracted value.
- ///
- /// Returns the extracted value regardless of route state.
- #[must_use]
- pub fn unpack_directly(self) -> T1 {
- self.val_1
- }
-}
-
-// PickWithRoute2 .. PickWithRoute12
-
-define_pick_with_route_struct! { PickWithRoute2 T2 val_2 T1 val_1, T2 val_2 }
-impl_pick_with_route_from_tuple! { PickWithRoute2 T1 val_1, T2 val_2 }
-impl_pick_with_route_unpack_tuple! { PickWithRoute2 T1 val_1, T2 val_2 }
-
-define_pick_with_route_struct! { PickWithRoute3 T3 val_3 T1 val_1, T2 val_2, T3 val_3 }
-impl_pick_with_route_from_tuple! { PickWithRoute3 T1 val_1, T2 val_2, T3 val_3 }
-impl_pick_with_route_unpack_tuple! { PickWithRoute3 T1 val_1, T2 val_2, T3 val_3 }
-
-define_pick_with_route_struct! { PickWithRoute4 T4 val_4 T1 val_1, T2 val_2, T3 val_3, T4 val_4 }
-impl_pick_with_route_from_tuple! { PickWithRoute4 T1 val_1, T2 val_2, T3 val_3, T4 val_4 }
-impl_pick_with_route_unpack_tuple! { PickWithRoute4 T1 val_1, T2 val_2, T3 val_3, T4 val_4 }
-
-define_pick_with_route_struct! { PickWithRoute5 T5 val_5 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5 }
-impl_pick_with_route_from_tuple! { PickWithRoute5 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5 }
-impl_pick_with_route_unpack_tuple! { PickWithRoute5 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5 }
-
-define_pick_with_route_struct! { PickWithRoute6 T6 val_6 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6 }
-impl_pick_with_route_from_tuple! { PickWithRoute6 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6 }
-impl_pick_with_route_unpack_tuple! { PickWithRoute6 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6 }
-
-define_pick_with_route_struct! { PickWithRoute7 T7 val_7 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7 }
-impl_pick_with_route_from_tuple! { PickWithRoute7 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7 }
-impl_pick_with_route_unpack_tuple! { PickWithRoute7 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7 }
-
-define_pick_with_route_struct! { PickWithRoute8 T8 val_8 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8 }
-impl_pick_with_route_from_tuple! { PickWithRoute8 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8 }
-impl_pick_with_route_unpack_tuple! { PickWithRoute8 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8 }
-
-define_pick_with_route_struct! { PickWithRoute9 T9 val_9 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9 }
-impl_pick_with_route_from_tuple! { PickWithRoute9 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9 }
-impl_pick_with_route_unpack_tuple! { PickWithRoute9 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9 }
-
-define_pick_with_route_struct! { PickWithRoute10 T10 val_10 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9, T10 val_10 }
-impl_pick_with_route_from_tuple! { PickWithRoute10 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9, T10 val_10 }
-impl_pick_with_route_unpack_tuple! { PickWithRoute10 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9, T10 val_10 }
-
-define_pick_with_route_struct! { PickWithRoute11 T11 val_11 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9, T10 val_10, T11 val_11 }
-impl_pick_with_route_from_tuple! { PickWithRoute11 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9, T10 val_10, T11 val_11 }
-impl_pick_with_route_unpack_tuple! { PickWithRoute11 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9, T10 val_10, T11 val_11 }
-
-define_pick_with_route_struct! { PickWithRoute12 T12 val_12 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9, T10 val_10, T11 val_11, T12 val_12 }
-impl_pick_with_route_from_tuple! { PickWithRoute12 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9, T10 val_10, T11 val_11, T12 val_12 }
-impl_pick_with_route_unpack_tuple! { PickWithRoute12 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9, T10 val_10, T11 val_11, T12 val_12 }
-
-// Routed PickWithRoute chaining methods (pick, pick_or, pick_or_route, require)
-
-#[doc(hidden)]
-macro_rules! impl_pick_with_route_next {
- ($n:ident $next:ident $next_val:ident $($T:ident $val:ident),+) => {
- impl<$($T,)+ R> $n<$($T,)+ R>
- where
- $($T: Pickable,)+
- {
- /// Extracts a value for the given flag and returns a `PickWithRouteN` builder.
- pub fn pick<TNext>(mut self, val: impl Into<mingling_core::Flag>) -> $next<$($T,)+ TNext, R>
- where
- TNext: Pickable<Output = TNext> + Default,
- {
- let v = TNext::pick(&mut self.args, val.into()).unwrap_or_default();
- $next {
- args: self.args,
- $($val: self.$val,)+
- $next_val: v,
- route: self.route,
- }
- }
-
- /// Extracts a value for the given flag, returning the provided default value if not present,
- /// and returns a `PickWithRouteN` builder.
- pub fn pick_or<TNext>(mut self, val: impl Into<mingling_core::Flag>, or: impl Into<TNext>) -> $next<$($T,)+ TNext, R>
- where
- TNext: Pickable<Output = TNext>,
- {
- let v = TNext::pick(&mut self.args, val.into()).unwrap_or_else(|| or.into());
- $next {
- args: self.args,
- $($val: self.$val,)+
- $next_val: v,
- route: self.route,
- }
- }
-
- /// Extracts a value for the given flag, storing the provided route if the flag is not present,
- /// and returns a `PickWithRouteN` builder.
- ///
- /// If a route was already stored from a previous `pick_or_route` or `after_or_route` call,
- /// the existing route is preserved and the new `route` parameter is ignored.
- #[allow(clippy::manual_let_else)]
- pub fn pick_or_route<TNext>(mut self, val: impl Into<mingling_core::Flag>, route: R) -> $next<$($T,)+ TNext, R>
- where
- TNext: Pickable<Output = TNext> + Default,
- {
- let v = match TNext::pick(&mut self.args, val.into()) {
- Some(value) => value,
- None => {
- let new_route = match self.route {
- Some(existing_route) => Some(existing_route),
- None => Some(route),
- };
- return $next {
- args: self.args,
- $($val: self.$val,)+
- $next_val: TNext::default(),
- route: new_route,
- };
- }
- };
- $next {
- args: self.args,
- $($val: self.$val,)+
- $next_val: v,
- route: self.route,
- }
- }
-
- /// Extracts a value for the given flag, returning `None` if the flag is not present,
- /// and returns an `Option<PickWithRouteN>` builder.
- pub fn require<TNext>(mut self, val: impl Into<mingling_core::Flag>) -> Option<$next<$($T,)+ TNext, R>>
- where
- TNext: Pickable<Output = TNext>,
- {
- let v = TNext::pick(&mut self.args, val.into());
- match v {
- Some(s) => Some($next {
- args: self.args,
- $($val: self.$val,)+
- $next_val: s,
- route: self.route,
- }),
- None => None,
- }
- }
- }
- };
-}
-
-impl_pick_with_route_next! { PickWithRoute1 PickWithRoute2 val_2 T1 val_1 }
-impl_pick_with_route_next! { PickWithRoute2 PickWithRoute3 val_3 T1 val_1, T2 val_2 }
-impl_pick_with_route_next! { PickWithRoute3 PickWithRoute4 val_4 T1 val_1, T2 val_2, T3 val_3 }
-impl_pick_with_route_next! { PickWithRoute4 PickWithRoute5 val_5 T1 val_1, T2 val_2, T3 val_3, T4 val_4 }
-impl_pick_with_route_next! { PickWithRoute5 PickWithRoute6 val_6 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5 }
-impl_pick_with_route_next! { PickWithRoute6 PickWithRoute7 val_7 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6 }
-impl_pick_with_route_next! { PickWithRoute7 PickWithRoute8 val_8 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7 }
-impl_pick_with_route_next! { PickWithRoute8 PickWithRoute9 val_9 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8 }
-impl_pick_with_route_next! { PickWithRoute9 PickWithRoute10 val_10 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9 }
-impl_pick_with_route_next! { PickWithRoute10 PickWithRoute11 val_11 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9, T10 val_10 }
-impl_pick_with_route_next! { PickWithRoute11 PickWithRoute12 val_12 T1 val_1, T2 val_2, T3 val_3, T4 val_4, T5 val_5, T6 val_6, T7 val_7, T8 val_8, T9 val_9, T10 val_10, T11 val_11 }
-
-/// Trait for types that can be used with `Pickable` to extract enum values from command-line arguments.
-///
-/// This trait combines `EnumTag` (for building an enum variant from a string name) and `Default`
-/// (for providing a fallback value when the flag is not present).
-///
-/// Types implementing this trait can be used with `Picker::pick`, `Picker::pick_or_route`, and
-/// the chaining `.pick()` methods to extract and parse enum values from command-line arguments.
-pub trait PickableEnum: EnumTag + Default {}
-
-impl<T> Pickable for T
-where
- T: PickableEnum,
-{
- type Output = T;
-
- fn pick(args: &mut Argument, flag: Flag) -> Option<Self::Output> {
- let name = args.pick_argument(flag)?;
- T::build_enum(name)
- }
-}
-
-/// Trait for types that can be converted into a `Picker` to extract values from command-line arguments.
-///
-/// This trait provides a convenient way to convert a value (such as `Vec<String>`, `&[String]`, etc.)
-/// into a `Picker` and immediately start extracting values associated with specific flags.
-pub trait AsPicker
-where
- Self: Into<Vec<String>>,
-{
- /// Converts the value into a `Picker` by first converting it into a `Vec<String>`.
- fn to_picker(self) -> Picker
- where
- Self: Sized,
- Vec<String>: From<Self>,
- {
- let vec: Vec<String> = self.into();
- Picker { args: vec.into() }
- }
-
- /// Extracts a value for the given flag and returns a `Pick1` builder (no route).
- ///
- /// The extracted type `TNext` must implement `Pickable` and `Default`.
- /// If the flag is not present, the default value for `TNext` is used.
- fn pick<TNext>(self, val: impl Into<Flag>) -> Pick1<TNext>
- where
- Self: Sized,
- TNext: Pickable<Output = TNext> + Default,
- {
- let vec: Vec<String> = self.into();
- let picker: Picker = vec.into();
- picker.pick(val)
- }
-
- /// Extracts a value for the given flag, returning the provided default value if not present,
- /// and returns a `Pick1` builder (no route).
- ///
- /// The extracted type `TNext` must implement `Pickable`.
- /// If the flag is not present, the provided `or` value is used.
- fn pick_or<TNext>(self, val: impl Into<Flag>, or: impl Into<TNext>) -> Pick1<TNext>
- where
- TNext: Pickable<Output = TNext>,
- {
- let vec: Vec<String> = self.into();
- let picker: Picker = vec.into();
- picker.pick_or(val, or)
- }
-
- /// Extracts a value for the given flag, storing the provided route if the flag is not present,
- /// and returns a `PickWithRoute1` builder (with route).
- ///
- /// The extracted type `TNext` must implement `Pickable` and `Default`.
- /// If the flag is not present, the default value for `TNext` is used and the provided `route`
- /// is stored in the returned builder for later error handling.
- fn pick_or_route<TNext, R>(self, val: impl Into<Flag>, route: R) -> PickWithRoute1<TNext, R>
- where
- TNext: Pickable<Output = TNext> + Default,
- {
- let vec: Vec<String> = self.into();
- let picker: Picker = vec.into();
- picker.pick_or_route(val, route)
- }
-}
-
-// Implement AsPicker for any type that can be converted into a Vec<String>
-impl<T> AsPicker for T
-where
- T: Sized,
- Vec<String>: From<T>,
-{
-}
diff --git a/mingling/src/parser/picker/bools.rs b/mingling/src/parser/picker/bools.rs
deleted file mode 100644
index bc9fd90..0000000
--- a/mingling/src/parser/picker/bools.rs
+++ /dev/null
@@ -1,143 +0,0 @@
-// Doc Not Optimize
-use crate::parser::Pickable;
-
-/// Represents a boolean-like value with `Yes` and `No` variants.
-///
-/// `Yes` can be parsed from command-line arguments using positive keywords such as `"y"` or `"yes"`,
-/// and defaults to `No`.
-#[derive(Debug, Default)]
-#[repr(u8)]
-pub enum Yes {
- /// The affirmative/positive variant.
- Yes,
- /// The negative/default variant.
- #[default]
- No,
-}
-
-impl From<bool> for Yes {
- fn from(b: bool) -> Self {
- if b { Self::Yes } else { Self::No }
- }
-}
-
-impl From<Yes> for bool {
- fn from(val: Yes) -> Self {
- match val {
- Yes::Yes => true,
- Yes::No => false,
- }
- }
-}
-
-impl std::ops::Deref for Yes {
- type Target = bool;
-
- fn deref(&self) -> &Self::Target {
- static TRUE: bool = true;
- static FALSE: bool = false;
- match self {
- Self::Yes => &TRUE,
- Self::No => &FALSE,
- }
- }
-}
-
-impl Yes {
- #[must_use]
- pub const fn is_yes(&self) -> bool {
- matches!(self, Self::Yes)
- }
-
- #[must_use]
- pub const fn is_no(&self) -> bool {
- matches!(self, Self::No)
- }
-}
-
-impl Pickable for Yes {
- type Output = Self;
-
- fn pick(args: &mut crate::parser::Argument, flag: mingling_core::Flag) -> Option<Self::Output> {
- let value = pick_bool(args, flag, &["y", "yes"]);
- Some(value.into())
- }
-}
-
-/// Represents a boolean-like value with `True` and `False` variants.
-///
-/// `True` can be parsed from command-line arguments using positive keywords such as `"t"` or `"true"`,
-/// and defaults to `False`.
-#[derive(Debug, Default)]
-#[repr(u8)]
-pub enum True {
- /// The affirmative/positive variant.
- True,
- /// The negative/default variant.
- #[default]
- False,
-}
-
-impl From<bool> for True {
- fn from(b: bool) -> Self {
- if b { Self::True } else { Self::False }
- }
-}
-
-impl From<True> for bool {
- fn from(val: True) -> Self {
- match val {
- True::True => true,
- True::False => false,
- }
- }
-}
-
-impl std::ops::Deref for True {
- type Target = bool;
-
- fn deref(&self) -> &Self::Target {
- static TRUE: bool = true;
- static FALSE: bool = false;
- match self {
- Self::True => &TRUE,
- Self::False => &FALSE,
- }
- }
-}
-
-impl True {
- #[must_use]
- pub const fn is_true(&self) -> bool {
- matches!(self, Self::True)
- }
-
- #[must_use]
- pub const fn is_false(&self) -> bool {
- matches!(self, Self::False)
- }
-}
-
-impl Pickable for True {
- type Output = Self;
-
- fn pick(args: &mut crate::parser::Argument, flag: mingling_core::Flag) -> Option<Self::Output> {
- let value = pick_bool(args, flag, &["true", "t"]);
- Some(value.into())
- }
-}
-
-fn pick_bool(
- args: &mut crate::parser::Argument,
- flag: mingling_core::Flag,
- positive: &[&str],
-) -> bool {
- let content = args.pick_argument(flag);
- content.map_or_else(
- || false,
- |content| {
- let s = content.as_str();
- positive.contains(&s)
- },
- )
-}
diff --git a/mingling/src/parser/picker/builtin.rs b/mingling/src/parser/picker/builtin.rs
deleted file mode 100644
index 6f67c78..0000000
--- a/mingling/src/parser/picker/builtin.rs
+++ /dev/null
@@ -1,113 +0,0 @@
-// Doc Not Optimize
-use size::Size;
-
-use crate::parser::{Argument, Pickable};
-
-impl Pickable for String {
- type Output = Self;
-
- fn pick(args: &mut crate::parser::Argument, flag: mingling_core::Flag) -> Option<Self::Output> {
- args.pick_argument(flag)
- }
-}
-
-impl Pickable for Vec<String> {
- type Output = Self;
-
- fn pick(args: &mut crate::parser::Argument, flag: mingling_core::Flag) -> Option<Self::Output> {
- Some(args.pick_arguments(flag))
- }
-}
-
-macro_rules! impl_pickable_for_number {
- ($($t:ty),*) => {
- $(
- impl Pickable for $t {
- type Output = $t;
-
- fn pick(args: &mut crate::parser::Argument, flag: mingling_core::Flag) -> Option<Self::Output> {
- let picked = args.pick_argument(flag)?;
- picked.parse().ok()
- }
- }
-
- impl Pickable for Vec<$t> {
- type Output = Vec<$t>;
-
- fn pick(args: &mut crate::parser::Argument, flag: mingling_core::Flag) -> Option<Self::Output> {
- let picked_vec = args.pick_arguments(flag);
- let mut result = Vec::new();
- for picked in picked_vec {
- if let Ok(parsed) = picked.parse() {
- result.push(parsed);
- } else {
- return None;
- }
- }
- Some(result)
- }
- }
- )*
- };
-}
-
-impl_pickable_for_number!(i8, i16, i32, i64, i128, u8, u16, u32, u64, u128, f32, f64);
-
-impl Pickable for bool {
- type Output = Self;
-
- fn pick(args: &mut crate::parser::Argument, flag: mingling_core::Flag) -> Option<Self::Output> {
- Some(args.pick_flag(flag))
- }
-}
-
-/// Special: parses a size string (e.g. "10MB") into a `usize` representing the number of bytes.
-impl Pickable for usize {
- type Output = Self;
-
- fn pick(args: &mut crate::parser::Argument, flag: mingling_core::Flag) -> Option<Self::Output> {
- let picked = args.pick_argument(flag)?;
- let size_parse = Size::from_str(picked.as_str());
- size_parse.map_or(None, |size| Self::try_from(size.bytes()).ok())
- }
-}
-
-/// Special: parses a comma-separated list of size strings (e.g. "10MB,20KB") into a `Vec<usize>`.
-impl Pickable for Vec<usize> {
- type Output = Self;
-
- fn pick(args: &mut crate::parser::Argument, flag: mingling_core::Flag) -> Option<Self::Output> {
- let picked_vec = args.pick_arguments(flag);
- let mut result = Self::new();
- for picked in picked_vec {
- let size_parse = Size::from_str(picked.as_str());
- match size_parse {
- Ok(size) => result.push(usize::try_from(size.bytes()).unwrap_or(usize::MAX)),
- Err(_) => return None,
- }
- }
- Some(result)
- }
-}
-
-/// Special: dumps the remaining arguments into an `Argument` struct.
-impl Pickable for Argument {
- type Output = Self;
-
- fn pick(
- args: &mut crate::parser::Argument,
- _flag: mingling_core::Flag,
- ) -> Option<Self::Output> {
- Some(args.dump_remains().into())
- }
-}
-
-/// Special: parses a single value of type `T` using the `Pickable` implementation for `T`, and wraps it in an `Option`.
-impl<T: Pickable<Output = T> + Default> Pickable for Option<T> {
- type Output = Self;
-
- fn pick(args: &mut Argument, flag: mingling_core::Flag) -> Option<Self::Output> {
- let r = T::pick(args, flag);
- Some(r)
- }
-}
diff --git a/mingling/src/parser/picker/path.rs b/mingling/src/parser/picker/path.rs
deleted file mode 100644
index 1caecfa..0000000
--- a/mingling/src/parser/picker/path.rs
+++ /dev/null
@@ -1,145 +0,0 @@
-// Doc Not Optimize
-use std::path::{Path, PathBuf};
-
-use crate::parser::Pickable;
-
-mod rule;
-pub use rule::*;
-
-impl Pickable for Vec<PathBuf> {
- type Output = Self;
-
- fn pick(args: &mut crate::parser::Argument, flag: mingling_core::Flag) -> Option<Self::Output> {
- let raw: Vec<String> = args.pick_arguments(flag);
- let paths = raw.into_iter().map(PathBuf::from).collect();
- Some(paths)
- }
-}
-
-impl Pickable for PathBuf {
- type Output = Self;
-
- fn pick(args: &mut crate::parser::Argument, flag: mingling_core::Flag) -> Option<Self::Output> {
- let raw: String = args.pick_argument(flag)?;
- Some(Self::from(raw))
- }
-}
-
-/// Provides path checking methods for [`Vec<PathBuf>`]
-///
-/// This trait automatically provides implementations for `Into<Vec<PathBuf>>`
-pub trait PathsChecker {
- /// Check if all paths in the list satisfy the rule
- fn is_all_passed(&self, rule: &PathCheckRule) -> bool
- where
- Self: Into<Vec<PathBuf>> + Clone,
- {
- check_paths(self.clone(), rule).is_ok()
- }
-
- /// Classify paths into (Passed, Stripped)
- ///
- /// Passed means paths that satisfy the rule, Stripped means paths that do not.
- fn classify(self, rule: &PathCheckRule) -> (Vec<PathBuf>, Vec<PathBuf>)
- where
- Self: Into<Vec<PathBuf>>,
- {
- let paths = self.into();
- let mut passed = Vec::new();
- let mut stripped = Vec::new();
- for path in paths {
- if check_path(&path, rule).is_ok() {
- passed.push(path);
- } else {
- stripped.push(path);
- }
- }
- (passed, stripped)
- }
-
- /// Return paths that satisfy the rule
- fn passed(self, rule: &PathCheckRule) -> Vec<PathBuf>
- where
- Self: Into<Vec<PathBuf>>,
- {
- self.classify(rule).0
- }
-
- /// Return paths that do not satisfy the rule
- fn stripped(self, rule: &PathCheckRule) -> Vec<PathBuf>
- where
- Self: Into<Vec<PathBuf>>,
- {
- self.classify(rule).1
- }
-}
-
-/// Provides path checking methods for [`PathBuf`]
-///
-/// This trait automatically provides implementations for `Into<PathBuf>`
-pub trait PathChecker {
- fn is_passed(&self, rule: &PathCheckRule) -> bool
- where
- Self: Into<PathBuf> + Clone,
- {
- check_path(self.clone(), rule).is_ok()
- }
-}
-
-impl<T: Into<Vec<PathBuf>>> PathsChecker for T {}
-impl<T: Into<PathBuf>> PathChecker for T {}
-
-fn check_paths(path: impl Into<Vec<PathBuf>>, rule: &PathCheckRule) -> Result<(), ()> {
- let paths = path.into();
- for p in &paths {
- check_exist(p, rule)?;
- check_type(p, rule)?;
- }
-
- Ok(())
-}
-
-fn check_path(path: impl Into<PathBuf>, rule: &PathCheckRule) -> Result<(), ()> {
- let p = path.into();
- check_exist(&p, rule)?;
- check_type(&p, rule)?;
-
- Ok(())
-}
-
-fn check_exist(path: &Path, rule: &PathCheckRule) -> Result<(), ()> {
- let Some(exist_check) = &rule.exist_check else {
- return Ok(());
- };
-
- match exist_check {
- PathExistCheck::Exists => bool_to_result(path.exists()),
- PathExistCheck::NotExists => bool_to_result(!path.exists()),
- }
-}
-
-fn check_type(path: &Path, rule: &PathCheckRule) -> Result<(), ()> {
- let Some(type_check) = &rule.type_check else {
- return Ok(());
- };
-
- let is_dir = path.is_dir();
- let is_file = path.is_file();
- let is_symlink = path.is_symlink();
-
- if type_check.allow_dir && is_dir {
- return Ok(());
- }
- if type_check.allow_file && is_file {
- return Ok(());
- }
- if type_check.allow_symlink && is_symlink {
- return Ok(());
- }
-
- Err(())
-}
-
-const fn bool_to_result(b: bool) -> Result<(), ()> {
- if b { Ok(()) } else { Err(()) }
-}
diff --git a/mingling/src/parser/picker/path/rule.rs b/mingling/src/parser/picker/path/rule.rs
deleted file mode 100644
index 5256f35..0000000
--- a/mingling/src/parser/picker/path/rule.rs
+++ /dev/null
@@ -1,231 +0,0 @@
-// Doc Not Optimize
-/// Path check rule
-#[derive(Default)]
-pub struct PathCheckRule {
- pub exist_check: Option<PathExistCheck>,
- pub type_check: Option<PathTypeCheck>,
-}
-
-/// Path existence check
-pub enum PathExistCheck {
- Exists,
- NotExists,
-}
-
-/// Path type check
-pub struct PathTypeCheck {
- /// Whether the path is allowed to be a file
- pub allow_file: bool,
-
- /// Whether the path is allowed to be a directory
- pub allow_dir: bool,
-
- /// Whether the path is allowed to be a symlink
- pub allow_symlink: bool,
-}
-
-impl PathCheckRule {
- /// Creates a new `PathCheckRule` with default values
- #[must_use]
- pub const fn new() -> Self {
- Self {
- exist_check: None,
- type_check: None,
- }
- }
-
- /// Allows the path to be a file
- #[must_use]
- pub const fn allow_file(self) -> Self {
- match self.type_check {
- Some(type_check) => Self {
- type_check: Some(PathTypeCheck {
- allow_file: true,
- allow_dir: type_check.allow_dir,
- allow_symlink: type_check.allow_symlink,
- }),
- ..self
- },
- None => Self {
- type_check: Some(PathTypeCheck {
- allow_file: true,
- allow_dir: false,
- allow_symlink: false,
- }),
- ..self
- },
- }
- }
-
- /// Allows the path to be a directory
- #[must_use]
- pub const fn allow_dir(self) -> Self {
- match self.type_check {
- Some(type_check) => Self {
- type_check: Some(PathTypeCheck {
- allow_file: type_check.allow_file,
- allow_dir: true,
- allow_symlink: type_check.allow_symlink,
- }),
- ..self
- },
- None => Self {
- type_check: Some(PathTypeCheck {
- allow_file: false,
- allow_dir: true,
- allow_symlink: false,
- }),
- ..self
- },
- }
- }
-
- /// Allows the path to be a symlink
- #[must_use]
- pub const fn allow_symlink(self) -> Self {
- match self.type_check {
- Some(type_check) => Self {
- type_check: Some(PathTypeCheck {
- allow_file: type_check.allow_file,
- allow_dir: type_check.allow_dir,
- allow_symlink: true,
- }),
- ..self
- },
- None => Self {
- type_check: Some(PathTypeCheck {
- allow_file: false,
- allow_dir: false,
- allow_symlink: true,
- }),
- ..self
- },
- }
- }
-
- /// Denies the path from being a file
- #[must_use]
- pub const fn deny_file(self) -> Self {
- match self.type_check {
- Some(type_check) => Self {
- type_check: Some(PathTypeCheck {
- allow_file: false,
- allow_dir: type_check.allow_dir,
- allow_symlink: type_check.allow_symlink,
- }),
- ..self
- },
- None => Self {
- type_check: Some(PathTypeCheck {
- allow_file: false,
- allow_dir: true,
- allow_symlink: true,
- }),
- ..self
- },
- }
- }
-
- /// Denies the path from being a directory
- #[must_use]
- pub const fn deny_dir(self) -> Self {
- match self.type_check {
- Some(type_check) => Self {
- type_check: Some(PathTypeCheck {
- allow_file: type_check.allow_file,
- allow_dir: false,
- allow_symlink: type_check.allow_symlink,
- }),
- ..self
- },
- None => Self {
- type_check: Some(PathTypeCheck {
- allow_file: true,
- allow_dir: false,
- allow_symlink: true,
- }),
- ..self
- },
- }
- }
-
- /// Denies the path from being a symlink
- #[must_use]
- pub const fn deny_symlink(self) -> Self {
- match self.type_check {
- Some(type_check) => Self {
- type_check: Some(PathTypeCheck {
- allow_file: type_check.allow_file,
- allow_dir: type_check.allow_dir,
- allow_symlink: false,
- }),
- ..self
- },
- None => Self {
- type_check: Some(PathTypeCheck {
- allow_file: true,
- allow_dir: true,
- allow_symlink: false,
- }),
- ..self
- },
- }
- }
-
- /// Requires the path to be a file (overrides type checks)
- #[must_use]
- pub const fn must_file(self) -> Self {
- Self {
- type_check: Some(PathTypeCheck {
- allow_file: true,
- allow_dir: false,
- allow_symlink: false,
- }),
- ..self
- }
- }
-
- /// Requires the path to be a directory (overrides type checks)
- #[must_use]
- pub const fn must_dir(self) -> Self {
- Self {
- type_check: Some(PathTypeCheck {
- allow_file: false,
- allow_dir: true,
- allow_symlink: false,
- }),
- ..self
- }
- }
-
- /// Requires the path to be a symlink (overrides type checks)
- #[must_use]
- pub const fn must_symlink(self) -> Self {
- Self {
- type_check: Some(PathTypeCheck {
- allow_file: false,
- allow_dir: false,
- allow_symlink: true,
- }),
- ..self
- }
- }
-
- /// Requires the path to exist
- #[must_use]
- pub const fn must_exist(self) -> Self {
- Self {
- exist_check: Some(PathExistCheck::Exists),
- ..self
- }
- }
-
- /// Requires the path to not exist
- #[must_use]
- pub const fn must_not_exist(self) -> Self {
- Self {
- exist_check: Some(PathExistCheck::NotExists),
- ..self
- }
- }
-}
diff --git a/mingling/src/parser/test.rs b/mingling/src/parser/test.rs
deleted file mode 100644
index 172d1da..0000000
--- a/mingling/src/parser/test.rs
+++ /dev/null
@@ -1,731 +0,0 @@
-// Doc Not Optimize
-use crate::parser::picker::bools::{True, Yes};
-use crate::parser::{Argument, Pick1, Picker};
-
-#[test]
-fn test_argument_from_static_str() {
- let arg: Argument = "hello".into();
- assert_eq!(arg.len(), 1);
- assert_eq!(arg[0], "hello");
-}
-
-#[test]
-fn test_argument_from_slice() {
- let arg: Argument = (&["--name", "value"][..]).into();
- assert_eq!(arg.len(), 2);
- assert_eq!(arg[0], "--name");
- assert_eq!(arg[1], "value");
-}
-
-#[test]
-fn test_argument_from_array() {
- let arg: Argument = ["--file", "test.txt"].into();
- assert_eq!(arg.len(), 2);
-}
-
-#[test]
-fn test_argument_from_vec() {
- let arg: Argument = vec!["a".to_string(), "b".to_string()].into();
- assert_eq!(arg.len(), 2);
-}
-
-#[test]
-fn test_argument_default_is_empty() {
- let arg = Argument::default();
- assert!(arg.is_empty());
-}
-
-#[test]
-fn test_pick_argument_with_flag() {
- let mut arg: Argument = vec!["--name", "Alice", "--verbose"].into();
- let value = arg.pick_argument("--name");
- assert_eq!(value, Some("Alice".to_string()));
- // After picking, the flag and its value are removed
- assert_eq!(arg.as_ref(), &["--verbose"]);
-}
-
-#[test]
-fn test_pick_argument_flag_not_found() {
- let mut arg: Argument = vec!["--name", "Alice"].into();
- let value = arg.pick_argument("--missing");
- assert_eq!(value, None);
- // Original args unchanged
- assert_eq!(arg.as_ref(), &["--name", "Alice"]);
-}
-
-#[test]
-fn test_pick_argument_empty() {
- let mut arg: Argument = Argument::default();
- let value = arg.pick_argument("--flag");
- assert_eq!(value, None);
-}
-
-#[test]
-fn test_pick_argument_flag_at_end_no_value() {
- let mut arg: Argument = vec!["--name"].into();
- let value = arg.pick_argument("--name");
- assert_eq!(value, None);
- assert!(arg.is_empty());
-}
-
-#[test]
-fn test_pick_argument_no_flag_positional() {
- let mut arg: Argument = vec!["first", "second", "--flag", "val"].into();
- let value = arg.pick_argument(());
- assert_eq!(value, Some("first".to_string()));
- assert_eq!(arg.as_ref(), &["second", "--flag", "val"]);
-}
-
-#[test]
-fn test_pick_argument_positional_all() {
- let mut arg: Argument = vec!["one", "two", "three"].into();
- let v1 = arg.pick_argument(());
- let v2 = arg.pick_argument(());
- let v3 = arg.pick_argument(());
- let v4 = arg.pick_argument(());
- assert_eq!(v1, Some("one".to_string()));
- assert_eq!(v2, Some("two".to_string()));
- assert_eq!(v3, Some("three".to_string()));
- assert_eq!(v4, None);
-}
-
-#[test]
-fn test_pick_argument_empty_args_no_flag() {
- let mut arg: Argument = Argument::default();
- let value = arg.pick_argument(());
- assert_eq!(value, None);
-}
-
-#[test]
-fn test_pick_argument_with_flag_from_iter() {
- let mut arg: Argument = vec!["-f", "data.txt", "--other"].into();
- let value = arg.pick_argument(&["-f", "--file"][..]);
- assert_eq!(value, Some("data.txt".to_string()));
- assert_eq!(arg.as_ref(), &["--other"]);
-}
-
-#[test]
-fn test_pick_arguments_multiple_values() {
- let mut arg: Argument = vec!["--files", "a.txt", "b.txt", "c.txt", "--other"].into();
- let values = arg.pick_arguments("--files");
- assert_eq!(values, vec!["a.txt", "b.txt", "c.txt"]);
- assert_eq!(arg.as_ref(), &["--other"]);
-}
-
-#[test]
-fn test_pick_arguments_single_value() {
- let mut arg: Argument = vec!["--name", "Alice", "--verbose"].into();
- let values = arg.pick_arguments("--name");
- assert_eq!(values, vec!["Alice"]);
- assert_eq!(arg.as_ref(), &["--verbose"]);
-}
-
-#[test]
-fn test_pick_arguments_no_values() {
- let mut arg: Argument = vec!["--flag", "--other", "val"].into();
- let values = arg.pick_arguments("--flag");
- assert!(values.is_empty());
- assert_eq!(arg.as_ref(), &["--other", "val"]);
-}
-
-#[test]
-fn test_pick_arguments_flag_not_found() {
- let mut arg: Argument = vec!["--name", "Alice"].into();
- let values = arg.pick_arguments("--missing");
- assert!(values.is_empty());
- assert_eq!(arg.as_ref(), &["--name", "Alice"]);
-}
-
-#[test]
-fn test_pick_arguments_stops_at_next_flag() {
- let mut arg: Argument = vec!["--list", "a", "b", "-c", "d", "e"].into();
- let values = arg.pick_arguments("--list");
- assert_eq!(values, vec!["a", "b"]);
- assert_eq!(arg.as_ref(), &["-c", "d", "e"]);
-}
-
-#[test]
-fn test_pick_arguments_empty_flag_positional() {
- let mut arg: Argument = vec!["pos1", "pos2", "--flag", "val"].into();
- let values = arg.pick_arguments(());
- assert_eq!(values, vec!["pos1", "pos2"]);
- assert_eq!(arg.as_ref(), &["--flag", "val"]);
-}
-
-#[test]
-fn test_pick_arguments_empty_args() {
- let mut arg: Argument = Argument::default();
- let values = arg.pick_arguments("--flag");
- assert!(values.is_empty());
-}
-
-#[test]
-fn test_pick_flag_found() {
- let mut arg: Argument = vec!["--verbose", "--name", "Alice"].into();
- let result = arg.pick_flag("--verbose");
- assert!(result);
- assert_eq!(arg.as_ref(), &["--name", "Alice"]);
-}
-
-#[test]
-fn test_pick_flag_not_found() {
- let mut arg: Argument = vec!["--name", "Alice"].into();
- let result = arg.pick_flag("--verbose");
- assert!(!result);
- assert_eq!(arg.as_ref(), &["--name", "Alice"]);
-}
-
-#[test]
-fn test_pick_flag_empty_args() {
- let mut arg: Argument = Argument::default();
- let result = arg.pick_flag("--flag");
- assert!(!result);
-}
-
-#[test]
-fn test_pick_flag_with_flag_iter() {
- let mut arg: Argument = vec!["-h", "--name", "Alice"].into();
- let result = arg.pick_flag(&["-h", "--help"][..]);
- assert!(result);
-}
-
-#[test]
-fn test_pick_flag_second_not_first() {
- let mut arg: Argument = vec!["--name", "Alice"].into();
- let result = arg.pick_flag(&["-h", "--help"][..]);
- assert!(!result);
-}
-
-#[test]
-fn test_pick_flag_positional_yes() {
- let mut arg: Argument = vec!["yes"].into();
- let result = arg.pick_flag(());
- assert!(result);
- assert!(arg.is_empty());
-}
-
-#[test]
-fn test_pick_flag_positional_no() {
- let mut arg: Argument = vec!["no"].into();
- let result = arg.pick_flag(());
- assert!(!result);
-}
-
-#[test]
-fn test_pick_flag_positional_true() {
- let mut arg: Argument = vec!["true"].into();
- let result = arg.pick_flag(());
- assert!(result);
-}
-
-#[test]
-fn test_pick_flag_positional_false() {
- let mut arg: Argument = vec!["false"].into();
- let result = arg.pick_flag(());
- assert!(!result);
-}
-
-#[test]
-fn test_pick_flag_positional_1() {
- let mut arg: Argument = vec!["1"].into();
- let result = arg.pick_flag(());
- assert!(result);
-}
-
-#[test]
-fn test_pick_flag_positional_0() {
- let mut arg: Argument = vec!["0"].into();
- let result = arg.pick_flag(());
- assert!(!result);
-}
-
-#[test]
-fn test_pick_flag_positional_unknown() {
- let mut arg: Argument = vec!["unknown_value"].into();
- let result = arg.pick_flag(());
- assert!(!result);
-}
-
-#[test]
-fn test_pick_flag_positional_case_insensitive_yes() {
- let mut arg: Argument = vec!["YeS"].into();
- let result = arg.pick_flag(());
- assert!(result);
-}
-
-#[test]
-fn test_dump_remains() {
- let mut arg: Argument = vec!["a", "b", "c"].into();
- let remains = arg.dump_remains();
- assert_eq!(remains, vec!["a", "b", "c"]);
- assert!(arg.is_empty());
-}
-
-#[test]
-fn test_dump_remains_empty() {
- let mut arg: Argument = Argument::default();
- let remains = arg.dump_remains();
- assert!(remains.is_empty());
-}
-
-#[test]
-fn test_dump_remains_after_pick() {
- let mut arg: Argument = vec!["--flag", "value", "extra"].into();
- let _ = arg.pick_argument("--flag");
- let remains = arg.dump_remains();
- assert_eq!(remains, vec!["extra"]);
-}
-
-#[test]
-fn test_strip_all_flags() {
- let arg: Argument = vec!["--verbose", "file.txt", "--format", "json"].into();
- let result = arg.strip_all_flags();
- assert_eq!(result.as_ref(), &["file.txt", "json"]);
-}
-
-#[test]
-fn test_strip_all_flags_no_flags() {
- let arg: Argument = vec!["just", "positional", "args"].into();
- let result = arg.strip_all_flags();
- assert_eq!(result.as_ref(), &["just", "positional", "args"]);
-}
-
-#[test]
-fn test_strip_all_flags_all_flags() {
- let arg: Argument = vec!["--a", "-b", "--c"].into();
- let result = arg.strip_all_flags();
- assert!(result.is_empty());
-}
-
-#[test]
-fn test_strip_all_flags_empty() {
- let arg: Argument = Argument::default();
- let result = arg.strip_all_flags();
- assert!(result.is_empty());
-}
-
-#[test]
-fn test_picker_new() {
- let picker = Picker::new(vec!["--name", "Alice"]);
- assert_eq!(picker.args.len(), 2);
-}
-
-#[test]
-fn test_picker_from_trait() {
- let picker: Picker = vec!["--name", "Alice"].into();
- assert_eq!(picker.args.len(), 2);
-}
-
-#[test]
-fn test_picker_pick_string() {
- let result: String = Picker::new(vec!["--name", "Alice"]).pick("--name").unpack();
- assert_eq!(result, "Alice");
-}
-
-#[test]
-fn test_picker_pick_string_default_when_missing() {
- let result: String = Picker::new(vec!["--other", "val"])
- .pick::<String>("--name")
- .unpack();
- assert_eq!(result, "");
-}
-
-#[test]
-fn test_picker_pick_string_default_when_missing_with_or() {
- let result: String = Picker::new(vec!["--other", "val"])
- .pick_or("--name", "default_name")
- .unpack();
- assert_eq!(result, "default_name");
-}
-
-#[test]
-fn test_picker_pick_bool_flag_present() {
- let result: bool = Picker::new(vec!["--verbose", "--name", "Alice"])
- .pick::<bool>("--verbose")
- .unpack();
- assert!(result);
-}
-
-#[test]
-fn test_picker_pick_bool_flag_absent() {
- let result: bool = Picker::new(vec!["--name", "Alice"])
- .pick::<bool>("--verbose")
- .unpack();
- assert!(!result);
-}
-
-#[test]
-fn test_picker_pick_i32() {
- let result: i32 = Picker::new(vec!["--count", "42"]).pick("--count").unpack();
- assert_eq!(result, 42);
-}
-
-#[test]
-fn test_picker_pick_i32_default_zero() {
- let result: i32 = Picker::new(vec!["--other"]).pick::<i32>("--count").unpack();
- assert_eq!(result, 0);
-}
-
-#[test]
-fn test_picker_pick_f64() {
- let result: f64 = Picker::new(vec!["--ratio", "5.16"])
- .pick("--ratio")
- .unpack();
- let expected: f64 = 5.16;
- assert!((result - expected).abs() < 1e-10);
-}
-
-#[test]
-fn test_picker_pick_u64() {
- let result: u64 = Picker::new(vec!["--size", "100"]).pick("--size").unpack();
- assert_eq!(result, 100);
-}
-
-#[test]
-fn test_picker_pick_i32_parse_failure_returns_default() {
- let result: i32 = Picker::new(vec!["--count", "not-a-number"])
- .pick::<i32>("--count")
- .unpack();
- assert_eq!(result, 0);
-}
-
-#[test]
-fn test_picker_pick_usize_bytes() {
- let result: usize = Picker::new(vec!["--limit", "1024"])
- .pick("--limit")
- .unpack();
- assert_eq!(result, 1024);
-}
-
-#[test]
-fn test_picker_pick_usize_kib() {
- let result: usize = Picker::new(vec!["--limit", "1KiB"])
- .pick("--limit")
- .unpack();
- assert_eq!(result, 1024);
-}
-
-#[test]
-fn test_picker_pick_usize_mib() {
- let result: usize = Picker::new(vec!["--limit", "2MiB"])
- .pick("--limit")
- .unpack();
- assert_eq!(result, 2 * 1024 * 1024);
-}
-
-#[test]
-fn test_picker_pick_usize_parse_failure_returns_default() {
- let result: usize = Picker::new(vec!["--limit", "invalid"])
- .pick::<usize>("--limit")
- .unpack();
- assert_eq!(result, 0);
-}
-
-#[test]
-fn test_picker_pick_vec_string() {
- let result: Vec<String> = Picker::new(vec!["--files", "a.txt", "b.txt", "c.txt"])
- .pick("--files")
- .unpack();
- assert_eq!(result, vec!["a.txt", "b.txt", "c.txt"]);
-}
-
-#[test]
-fn test_picker_pick_vec_string_missing() {
- let result: Vec<String> = Picker::new(vec!["--other", "val"])
- .pick::<Vec<String>>("--files")
- .unpack();
- assert!(result.is_empty());
-}
-
-#[test]
-fn test_picker_pick_vec_usize() {
- let result: Vec<usize> = Picker::new(vec!["--sizes", "100", "1KiB", "2MiB"])
- .pick("--sizes")
- .unpack();
- assert_eq!(result, vec![100, 1024, 2 * 1024 * 1024]);
-}
-
-#[test]
-fn test_picker_pick_vec_i32() {
- let result: Vec<i32> = Picker::new(vec!["--nums", "10", "20", "30"])
- .pick("--nums")
- .unpack();
- assert_eq!(result, vec![10, 20, 30]);
-}
-
-#[test]
-fn test_picker_pick_yes_yes() {
- let result: Yes = Picker::new(vec!["--flag", "y"]).pick("--flag").unpack();
- assert!(result.is_yes());
- assert!(*result);
-}
-
-#[test]
-fn test_picker_pick_yes_no() {
- let result: Yes = Picker::new(vec!["--flag", "no"]).pick("--flag").unpack();
- assert!(result.is_no());
- assert!(!*result);
-}
-
-#[test]
-fn test_picker_pick_yes_default_no() {
- let result: Yes = Picker::new(vec!["--other"]).pick::<Yes>("--flag").unpack();
- assert!(result.is_no());
-}
-
-#[test]
-fn test_picker_pick_true_true() {
- let result: True = Picker::new(vec!["--flag", "true"]).pick("--flag").unpack();
- assert!(result.is_true());
- assert!(*result);
-}
-
-#[test]
-fn test_picker_pick_true_false() {
- let result: True = Picker::new(vec!["--flag", "anything"])
- .pick("--flag")
- .unpack();
- assert!(result.is_false());
- assert!(!*result);
-}
-
-#[test]
-fn test_picker_pick_true_default_false() {
- let result: True = Picker::new(vec!["--other"]).pick::<True>("--flag").unpack();
- assert!(result.is_false());
-}
-
-#[test]
-fn test_picker_pick_or_fallback() {
- let result: String = Picker::new(vec!["--other", "val"])
- .pick_or("--name", "fallback")
- .unpack();
- assert_eq!(result, "fallback");
-}
-
-#[test]
-fn test_picker_pick_or_existing() {
- let result: String = Picker::new(vec!["--name", "Alice"])
- .pick_or("--name", "fallback")
- .unpack();
- assert_eq!(result, "Alice");
-}
-
-#[test]
-fn test_picker_pick_or_numeric_fallback() {
- let result: i32 = Picker::new(vec!["--other"]).pick_or("--count", 99).unpack();
- assert_eq!(result, 99);
-}
-
-#[test]
-fn test_picker_pick_or_route_present() {
- let result = Picker::new(vec!["--name", "Alice"])
- .pick_or_route::<String, _>("--name", "missing_name")
- .unpack();
- assert_eq!(result, Ok("Alice".to_string()));
-}
-
-#[test]
-fn test_picker_pick_or_route_missing() {
- let result = Picker::new(vec!["--other"])
- .pick_or_route::<String, _>("--name", "missing_name")
- .unpack();
- assert_eq!(result, Err("missing_name"));
-}
-
-#[test]
-fn test_picker_require_present() {
- let result: Option<String> = Picker::new(vec!["--name", "Alice"])
- .require::<String>("--name")
- .map(super::picker::Pick1::unpack);
- assert_eq!(result, Some("Alice".to_string()));
-}
-
-#[test]
-fn test_picker_require_missing() {
- let result: Option<Pick1<String>> = Picker::new(vec!["--other"]).require::<String>("--name");
- assert!(result.is_none());
-}
-
-#[test]
-fn test_picker_chaining_two_values() {
- let (name, count): (String, i32) = Picker::new(vec!["--name", "Alice", "--count", "42"])
- .pick::<String>("--name")
- .pick::<i32>("--count")
- .unpack();
- assert_eq!(name, "Alice");
- assert_eq!(count, 42);
-}
-
-#[test]
-fn test_picker_chaining_three_values() {
- let (_name, _verbose, count): (String, bool, i32) =
- Picker::new(vec!["--name", "Alice", "--count", "42", "--verbose"])
- .pick::<String>("--name")
- .pick::<bool>("--verbose")
- .pick::<i32>("--count")
- .unpack();
- assert_eq!(count, 42);
-}
-
-#[test]
-fn test_picker_chaining_with_pick_or() {
- let (name, count): (String, i32) = Picker::new(vec!["--name", "Alice"])
- .pick::<String>("--name")
- .pick_or("--count", 10)
- .unpack();
- assert_eq!(name, "Alice");
- assert_eq!(count, 10);
-}
-
-#[test]
-fn test_picker_chaining_with_mixed_flag_styles() {
- let (name, verbose): (String, bool) = Picker::new(vec!["-n", "Bob", "--verbose"])
- .pick::<String>("-n")
- .pick::<bool>("--verbose")
- .unpack();
- assert_eq!(name, "Bob");
- assert!(verbose);
-}
-
-#[test]
-fn test_pick_after_modification() {
- let result: String = Picker::new(vec!["--name", " Alice "])
- .pick::<String>("--name")
- .after(|s| s.trim().to_string())
- .unpack();
- assert_eq!(result, "Alice");
-}
-
-#[test]
-fn test_pick_after_chained() {
- let (name, count): (String, i32) = Picker::new(vec!["--name", "alice", "--count", "7"])
- .pick::<String>("--name")
- .after(|s| s.to_uppercase())
- .pick::<i32>("--count")
- .after(|n| n * 2)
- .unpack();
- assert_eq!(name, "ALICE");
- assert_eq!(count, 14);
-}
-
-#[test]
-fn test_pick_after_or_route_ok() {
- let result = Picker::new(vec!["--name", "Alice"])
- .pick::<String>("--name")
- .after_or_route(|s| {
- if s.len() > 3 {
- Ok(s.clone())
- } else {
- Err("too_short")
- }
- })
- .unpack();
- assert_eq!(result, Ok("Alice".to_string()));
-}
-
-#[test]
-fn test_pick_after_or_route_err() {
- let result = Picker::new(vec!["--name", "Ab"])
- .pick::<String>("--name")
- .after_or_route(|s| {
- if s.len() > 3 {
- Ok(s.clone())
- } else {
- Err("too_short")
- }
- })
- .unpack();
- assert_eq!(result, Err("too_short"));
-}
-
-#[test]
-fn test_pick_with_route_unpack_ok() {
- let result = Picker::new(vec!["--name", "Alice"])
- .pick_or_route::<String, _>("--name", "error")
- .unpack();
- assert_eq!(result, Ok("Alice".to_string()));
-}
-
-#[test]
-fn test_pick_with_route_unpack_err() {
- let result: Result<String, &str> = Picker::new(vec!["--other"])
- .pick_or_route::<String, _>("--name", "missing")
- .unpack();
- assert_eq!(result, Err("missing"));
-}
-
-#[test]
-fn test_pick_with_route_unpack_directly() {
- let result: String = Picker::new(vec!["--other"])
- .pick_or_route::<String, _>("--name", "fallback_in_route")
- .unpack_directly();
- // When route is set, unpack_directly returns the default value (empty string for String)
- assert_eq!(result, "");
-}
-
-#[test]
-fn test_pick_with_route_chaining_present() {
- let result = Picker::new(vec!["--name", "Alice", "--count", "42"])
- .pick_or_route::<String, _>("--name", "err_name")
- .pick::<i32>("--count")
- .unpack();
- assert_eq!(result, Ok(("Alice".to_string(), 42)));
-}
-
-#[test]
-fn test_pick_with_route_chaining_missing_first_route_propagates() {
- let result = Picker::new(vec!["--count", "42"])
- .pick_or_route::<String, _>("--name", "err_name")
- .pick::<i32>("--count")
- .unpack();
- assert_eq!(result, Err("err_name"));
-}
-
-#[test]
-fn test_pick_with_route_chaining_pick_or_route_second_missing() {
- let result = Picker::new(vec!["--name", "Alice"])
- .pick_or_route::<String, _>("--name", "err_name")
- .pick_or_route::<i32>("--count", "err_count")
- .unpack();
- assert_eq!(result, Err("err_count"));
-}
-
-#[test]
-fn test_pick_with_route_after_or_route_preserves_existing_route() {
- let result = Picker::new(vec!["--other"])
- .pick_or_route::<String, _>("--name", "missing_name")
- .after_or_route(|_s: &String| {
- // This won't be called because route is already set, but let's see behavior
- Ok("should_not_matter".to_string())
- })
- .unpack();
- assert_eq!(result, Err("missing_name"));
-}
-
-#[test]
-fn test_picker_operate_args_filter() {
- let result: String = Picker::new(vec!["--name", "Alice", "--verbose"])
- .operate_args(Argument::strip_all_flags)
- .pick_or("--name", "fallback_name")
- .unpack();
- // After stripping flags, "--name" and "--verbose" are gone, "Alice" is a positional arg.
- // But --name with a value won't be present as a flag, so it falls back to positional.
- // Actually, strip_all_flags removes anything starting with '-'.
- // So "--name" is removed, and "Alice" remains as a positional argument.
- // When we try to pick "--name", it won't find it, so we get the fallback.
- assert_eq!(result, "fallback_name");
-}
-
-#[test]
-fn test_picker_operate_args_transform() {
- let result: Vec<String> = Picker::new(vec!["--files", "a.txt", "b.txt", "c.txt"])
- .operate_args(|mut args| {
- // Add an extra file
- args.push("d.txt".to_string());
- args
- })
- .pick::<Vec<String>>("--files")
- .unpack();
- assert_eq!(result, vec!["a.txt", "b.txt", "c.txt", "d.txt"]);
-}
diff --git a/mingling_core/tests/test-all/Cargo.lock b/mingling_core/tests/test-all/Cargo.lock
index 07bc08e..d239541 100644
--- a/mingling_core/tests/test-all/Cargo.lock
+++ b/mingling_core/tests/test-all/Cargo.lock
@@ -3,6 +3,23 @@
version = 4
[[package]]
+name = "arg-picker"
+version = "0.2.0"
+dependencies = [
+ "arg-picker-macros",
+ "just_fmt 0.2.0",
+]
+
+[[package]]
+name = "arg-picker-macros"
+version = "0.2.0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
name = "bitflags"
version = "2.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -131,10 +148,10 @@ dependencies = [
name = "mingling"
version = "0.5.0"
dependencies = [
+ "arg-picker",
"mingling_core",
"mingling_macros",
"serde",
- "size",
]
[[package]]
@@ -345,12 +362,6 @@ dependencies = [
]
[[package]]
-name = "size"
-version = "0.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1b6709c7b6754dca1311b3c73e79fcce40dd414c782c66d88e8823030093b02b"
-
-[[package]]
name = "smallvec"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/mingling_core/tests/test-all/Cargo.toml b/mingling_core/tests/test-all/Cargo.toml
index 272f1d2..df2efdb 100644
--- a/mingling_core/tests/test-all/Cargo.toml
+++ b/mingling_core/tests/test-all/Cargo.toml
@@ -13,7 +13,7 @@ mingling = { path = "../../../mingling", features = [
"builds",
"repl",
"dispatch_tree",
- "parser",
+ "picker",
"extras",
] }
tokio = { version = "1", features = ["full"] }
diff --git a/mingling_core/tests/test-structural-renderer/Cargo.lock b/mingling_core/tests/test-structural-renderer/Cargo.lock
index fcf244d..6126fb0 100644
--- a/mingling_core/tests/test-structural-renderer/Cargo.lock
+++ b/mingling_core/tests/test-structural-renderer/Cargo.lock
@@ -3,6 +3,23 @@
version = 4
[[package]]
+name = "arg-picker"
+version = "0.2.0"
+dependencies = [
+ "arg-picker-macros",
+ "just_fmt",
+]
+
+[[package]]
+name = "arg-picker-macros"
+version = "0.2.0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
name = "bitflags"
version = "2.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -67,10 +84,10 @@ dependencies = [
name = "mingling"
version = "0.5.0"
dependencies = [
+ "arg-picker",
"mingling_core",
"mingling_macros",
"serde",
- "size",
]
[[package]]
@@ -215,12 +232,6 @@ dependencies = [
]
[[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.118"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/mingling_core/tests/test-structural-renderer/Cargo.toml b/mingling_core/tests/test-structural-renderer/Cargo.toml
index 6ae8fce..a12d426 100644
--- a/mingling_core/tests/test-structural-renderer/Cargo.toml
+++ b/mingling_core/tests/test-structural-renderer/Cargo.toml
@@ -7,5 +7,5 @@ publish = false
[workspace]
[dependencies]
-mingling = { path = "../../../mingling", features = ["structural_renderer_full", "parser"] }
+mingling = { path = "../../../mingling", features = ["structural_renderer_full", "picker"] }
serde = { version = "1", features = ["derive"] }