diff options
| author | 魏曹先生 <1992414357@qq.com> | 2026-08-17 04:05:41 +0800 |
|---|---|---|
| committer | 魏曹先生 <1992414357@qq.com> | 2026-08-17 04:05:41 +0800 |
| commit | aa251efb87b561f62266628f06ad341253fbbdc5 (patch) | |
| tree | 7d2cc2f34f3e4a7282d8d6f0ec0f5ae4bfc87002 | |
| parent | c23c590330af83afb6e146bcd9b0a274b3689d22 (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.
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 @@ -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"] } |
