aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md39
-rw-r--r--Cargo.lock4
-rw-r--r--Cargo.toml8
-rw-r--r--arg_picker/Cargo.toml16
l---------arg_picker/LICENSE-APACHE1
l---------arg_picker/LICENSE-MIT1
-rw-r--r--arg_picker/README.md47
-rw-r--r--arg_picker/mingling-ci.toml2
-rw-r--r--arg_picker/src/arg.rs378
-rw-r--r--arg_picker/src/builtin.rs10
-rw-r--r--arg_picker/src/builtin/pick_bool.rs23
-rw-r--r--arg_picker/src/builtin/pick_flag.rs22
-rw-r--r--arg_picker/src/builtin/pick_ip_attr.rs37
-rw-r--r--arg_picker/src/builtin/pick_numbers.rs78
-rw-r--r--arg_picker/src/builtin/pick_pathbuf.rs15
-rw-r--r--arg_picker/src/builtin/pick_paths.rs168
-rw-r--r--arg_picker/src/builtin/pick_picker_args.rs22
-rw-r--r--arg_picker/src/builtin/pick_socket_attr.rs37
-rw-r--r--arg_picker/src/builtin/pick_string.rs9
-rw-r--r--arg_picker/src/constants.rs17
-rw-r--r--arg_picker/src/infos.rs481
-rw-r--r--arg_picker/src/lib.rs66
-rw-r--r--arg_picker/src/parselib.rs168
-rw-r--r--arg_picker/src/parselib/arg_matcher.rs143
-rw-r--r--arg_picker/src/parselib/flag_matcher.rs83
-rw-r--r--arg_picker/src/parselib/multi_arg_matcher.rs142
-rw-r--r--arg_picker/src/parselib/pos_matcher.rs72
-rw-r--r--arg_picker/src/parselib/single_matcher.rs59
-rw-r--r--arg_picker/src/parselib/style.rs244
-rw-r--r--arg_picker/src/parselib/utils.rs273
-rw-r--r--arg_picker/src/pickable.rs92
-rw-r--r--arg_picker/src/pickable/multi_pickable.rs93
-rw-r--r--arg_picker/src/pickable/single_pickable.rs70
-rw-r--r--arg_picker/src/picker.rs543
-rw-r--r--arg_picker/src/picker/parse.rs229
-rw-r--r--arg_picker/src/picker/patterns.rs201
-rw-r--r--arg_picker/src/picker/result.rs108
-rw-r--r--arg_picker/src/value.rs9
-rw-r--r--arg_picker/src/value/flag.rs146
-rw-r--r--arg_picker/src/value/paths.rs306
-rw-r--r--arg_picker/src/value/vec_until.rs137
-rw-r--r--arg_picker/test/Cargo.lock68
-rw-r--r--arg_picker/test/Cargo.toml9
-rw-r--r--arg_picker/test/src/lib.rs23
-rw-r--r--arg_picker/test/src/test.rs10
-rw-r--r--arg_picker/test/src/test/arg_matcher_test.rs289
-rw-r--r--arg_picker/test/src/test/basic_test.rs223
-rw-r--r--arg_picker/test/src/test/multi_arg_test.rs186
-rw-r--r--arg_picker/test/src/test/multi_value_test.rs66
-rw-r--r--arg_picker/test/src/test/pos_matcher_test.rs141
-rw-r--r--arg_picker/test/src/test/priority_test.rs85
-rw-r--r--arg_picker/test/src/test/route_test.rs200
-rw-r--r--arg_picker/test/src/test/style_test.rs300
-rw-r--r--arg_picker/test/src/test/value_flag_test.rs174
-rw-r--r--arg_picker/test/src/test/value_string_test.rs171
-rw-r--r--arg_picker_macros/Cargo.toml20
l---------arg_picker_macros/LICENSE-APACHE1
l---------arg_picker_macros/LICENSE-MIT1
-rw-r--r--arg_picker_macros/README.md40
-rw-r--r--arg_picker_macros/mingling-ci.toml2
-rw-r--r--arg_picker_macros/src/arg.rs215
-rw-r--r--arg_picker_macros/src/internal_repeat.rs316
-rw-r--r--arg_picker_macros/src/lib.rs34
-rw-r--r--dev/configs/rust-analyzer.json2
-rw-r--r--dev/run/src/bin/build.ps18
-rwxr-xr-xdev/run/src/bin/build.sh6
-rw-r--r--docs/dev/_sidebar.md10
-rw-r--r--docs/dev/pages/issues/_modify-dispatcher-syntax.md2
-rw-r--r--docs/dev/pages/issues/_remove-pack-macros.md2
-rw-r--r--docs/dev/pages/issues/_remove-parser-feature.md2
-rw-r--r--docs/dev/pages/issues/_remove-with-dispatcher.md2
-rw-r--r--docs/dev/pages/issues/_t2_automated-dispatch-tree-optimization.md76
-rw-r--r--docs/dev/pages/issues/t2_automated-dispatch-tree-optimization.md57
-rw-r--r--examples/example-argument-picker/Cargo.lock4
-rw-r--r--examples/example-async-support/Cargo.lock4
-rw-r--r--examples/example-command-macro/Cargo.lock4
-rw-r--r--examples/example-completion/Cargo.lock4
-rw-r--r--examples/example-enum-tag/Cargo.lock4
-rw-r--r--examples/example-panic-unwind/Cargo.lock4
-rw-r--r--examples/example-repl-basic/Cargo.lock4
-rw-r--r--examples/example-resources/Cargo.lock4
-rw-r--r--examples/example-structural-renderer/Cargo.lock4
-rw-r--r--examples/full-todolist/Cargo.lock4
-rw-r--r--mingling/Cargo.toml4
-rw-r--r--mingling_cli/Cargo.lock4
-rw-r--r--mingling_core/tests/test-all/Cargo.lock4
-rw-r--r--mingling_core/tests/test-structural-renderer/Cargo.lock4
-rw-r--r--mingling_macros/Cargo.toml2
88 files changed, 198 insertions, 7200 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ed8d388..ddde767 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -56,13 +56,45 @@ None
### 0.5.0 (Unreleased)
+This release introduces comprehensive Breaking Changes, paying off a large amount of accumulated technical debt and significantly reducing the cost for users getting started with Mingling for the first time.
+
+Additionally, the `arg-picker` crate has been extracted from the mingling repository and now lives independently in its own repository: <https://github.com/catilgrass/arg-picker>. When the `picker` feature is enabled, Cargo will automatically pull it from crates.io.
+
#### Fixes:
1. **[`macros:gen_program`]** Fixed the empty `do_chain` fallback generated by `program_final_gen` to respect the `async` feature. When a program has no chains registered, the synthesized `do_chain` previously always emitted the synchronous signature `fn do_chain(...) -> ChainProcess<Self::Enum>`, which fails to compile under the `async` feature with E0053 (method signature does not match the `ProgramCollect` trait, which requires a `Pin<Box<dyn Future<Output = ChainProcess> + Send>>` return in async mode). The generator now checks the compile-time `ASYNC_ENABLED` flag for the empty-chain case, mirroring the non-empty branch: when async is enabled it emits the boxed-future signature with a `Box::pin(async { panic!(...) })` body, and otherwise emits the synchronous signature. This fixes programs that declare zero chains (relying solely on entry/fallback behavior) when built with the `async` feature.
#### Optimizations:
-None
+1. **[`macros:dispatch`]** Introduced an "auto" dispatch-strategy selection mode plus a third explicit strategy. Previously, `dispatch_tree` was the only strategy-related feature (enabling a char-level trie; with it disabled, a linear longest-prefix list was used). Now there are three mutually-exclusive dispatch features — `dispatch_linear`, `dispatch_tree`, `dispatch_phf` — and when none is enabled, `gen_program!` selects the best strategy from the command table automatically.
+
+ **New feature flags** (`mingling/Cargo.toml`, `mingling_macros/Cargo.toml`):
+
+ - `dispatch_linear` — forces the linear longest-prefix list generator
+ - `dispatch_tree` — forces the char-level trie generator (unchanged behavior)
+ - `dispatch_phf` — new: forces the CHD minimal perfect-hash generator
+ - `bench_support` — workspace-internal feature (in `mingling_macros`) that compiles all three generators and exposes the `bench_cell!` proc macro used by the `dev/bench/dispatch` harness
+
+ The three dispatch features are mutually exclusive: enabling more than one triggers a `compile_error!` (in `mingling_macros/src/lib.rs`). Enabling none selects _auto_ mode.
+
+ **Auto mode** (`mingling_macros/src/systems/dispatch_auto.rs`): when no dispatch feature is enabled, `program_final_gen` calls `dispatch_auto::select_strategy(&entries)` at macro-expansion time, which picks `Linear`, `Trie`, or `Phf` from the normalized command table based on a cost model calibrated against the `dev/bench/dispatch` matrix:
+
+ - deep nested chains at modest sizes (`max_words ≥ 8`, `n ≤ 128`) → linear list (a few short memcmps beat the trie's per-level `nth(0)` walk plus the fallback call on non-leaf hits);
+ - single-word tables with long names → perfect hash (one hash beats the trie's char walk once names grow past ~16 chars);
+ - small tables (`n ≤ 64`) → linear vs trie by a cost model (linear wins on short names, loses once `count × length` grows);
+ - everything else → char trie (O(depth) hit cost independent of table size, best miss path).
+
+ **New perfect-hash generator** (`mingling_macros/src/systems/dispatch_phf_gen.rs`): implements a CHD (Belazzougui, Botelho, Dietzfelbinger) minimal perfect hash computed at macro-expansion time. Semantics match the other two generators exactly: longest registered word-aligned prefix wins; every hash hit is verified with an exact byte equality against the stored key; duplicate normalized names are dropped (first wins). Runtime cost is one byte scan over the first `max_words` words plus at most `max_words` double-hash + verify attempts; code size is O(1) in the command count.
+
+ **Trie generator refactor** (`mingling_macros/src/systems/dispatch_tree_gen.rs`): the trie's longest-prefix fallback is no longer inlined into every arm. Each trie node gets an id and every arm _calls_ a single generic `__trie_fallback<G>` method that runs that node's exact-endpoint checks and tail-recurses to the parent, returning `None` when nothing in the chain matches. This keeps the generated code linear in the table size — previously, inlining the whole fallback chain per arm grew quadratically with nesting depth (a 1024×16 nested table emitted ~13 MB of tokens). The generator now returns two token streams: the `dispatch_args` method (for the `ProgramCollect` trait impl) and the `__trie_fallback` method (for an inherent impl of the program type). The inherent impl is emitted inside the generated program's `impl` block (via the new `dispatch_extra` handling in `program_final_gen.rs`).
+
+ **New feature constants** in `mingling/src/features.rs`: `MINGLING_DISPATCH_LINEAR` and `MINGLING_DISPATCH_PHF` (both `false`/`true` gated on their features, alongside the existing `MINGLING_DISPATCH_TREE`).
+
+ **New benchmark harness** (`dev/bench/dispatch/`): a workspace-internal `mingling_bench` crate that measures the three explicit strategies plus auto mode across a matrix of command-table shapes (length 4/8/16/32 × count 128/256 × single-word/multi-word/nested-depth-4/nested-depth-10). A `build.rs` generates the full cell matrix via the new `bench_cell!` proc macro, and `src/main.rs` renders a `prettytable` report with per-cell hit/miss ns/op, geomeans, per-cell strategy wins, and auto-selection quality (exact-match and within-5% counts). A `cargo dispatch-bench` alias was added to `.cargo/config.toml`.
+
+ **Doc updates**: `ProgramCollect::dispatch_args` docs and `register_dispatcher!` docs updated to describe the three strategies and auto mode.
+
+ _No behavioral change for existing code that uses `dispatch_tree` (still the trie) or no dispatch feature (now auto-selected instead of always linear — the auto rules preserve the old linear behavior for the previously-common small/nested command tables). The mutual-exclusion `compile_error!` only fires when multiple dispatch features are enabled, which was previously impossible and remains an error.
#### Features:
@@ -496,6 +528,11 @@ None
_No behavioral changes — the setup still registers the same `ResExitCode` resource (initialised to `0`) and installs the same program-finish hook that overrides the program's exit code when the resource holds a non-zero value. The type simplification is purely ergonomic._
+11. **[`features`]** Updated the preset feature groups in `mingling/Cargo.toml`:
+
+- **`advanced`** — Changed from `extras`, `picker`, `repl`, `comp`, `dispatch_tree`, `structural_renderer` to `extras`, `picker`, `repl`, `comp`, `structural_renderer`, `pathf`. The `dispatch_tree` feature was removed and `pathf` was added.
+- **`full`** — Changed from `extras`, `picker`, `repl`, `clap`, `comp`, `dispatch_tree`, `structural_renderer_full`, `pathf` to `extras`, `picker`, `repl`, `clap`, `comp`, `dispatch_tree`, `structural_renderer_full`. The `pathf` feature was removed, while `dispatch_tree` remains.
+
---
## Contents
diff --git a/Cargo.lock b/Cargo.lock
index 47aa118..0f83f2d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -64,6 +64,8 @@ dependencies = [
[[package]]
name = "arg-picker"
version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e326de90a1c279562cc1583470daccfd4defe882661c61607edff75b797e483e"
dependencies = [
"arg-picker-macros",
"just_fmt 0.2.0",
@@ -72,6 +74,8 @@ dependencies = [
[[package]]
name = "arg-picker-macros"
version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f5f9bbe04b69744a30def8bee5526268b986616314bd1696804d7f819f03d1f3"
dependencies = [
"proc-macro2",
"quote",
diff --git a/Cargo.toml b/Cargo.toml
index 34e36ab..930b1e5 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -8,10 +8,6 @@ members = [
# Pathfinder
"mingling_pathf",
-
- # Picker2
- "arg_picker",
- "arg_picker_macros",
]
exclude = [
@@ -34,8 +30,8 @@ mingling_core = { path = "mingling_core", default-features = false }
mingling_macros = { path = "mingling_macros", default-features = false }
mingling_pathf = { path = "mingling_pathf", default-features = false }
-arg-picker = { path = "arg_picker", default-features = false }
-arg-picker-macros = { path = "arg_picker_macros", default-features = false }
+arg-picker = { version = "0.2.0", default-features = false }
+arg-picker-macros = { version = "0.2.0", default-features = false }
just_fmt = "0.2.0"
just_template = "0.2.0"
diff --git a/arg_picker/Cargo.toml b/arg_picker/Cargo.toml
deleted file mode 100644
index 533a187..0000000
--- a/arg_picker/Cargo.toml
+++ /dev/null
@@ -1,16 +0,0 @@
-[package]
-name = "arg-picker"
-version = "0.2.0"
-edition = "2024"
-license = "MIT OR Apache-2.0"
-repository = "https://github.com/mingling-rs/mingling/tree/main/arg_picker"
-authors = ["Weicao-CatilGrass"]
-readme = "README.md"
-description = "A lightweight, type-safe CLI argument parser"
-
-[features]
-mingling_support = ["arg-picker-macros/mingling_support"]
-
-[dependencies]
-arg-picker-macros.workspace = true
-just_fmt.workspace = true
diff --git a/arg_picker/LICENSE-APACHE b/arg_picker/LICENSE-APACHE
deleted file mode 120000
index 965b606..0000000
--- a/arg_picker/LICENSE-APACHE
+++ /dev/null
@@ -1 +0,0 @@
-../LICENSE-APACHE \ No newline at end of file
diff --git a/arg_picker/LICENSE-MIT b/arg_picker/LICENSE-MIT
deleted file mode 120000
index 76219eb..0000000
--- a/arg_picker/LICENSE-MIT
+++ /dev/null
@@ -1 +0,0 @@
-../LICENSE-MIT \ No newline at end of file
diff --git a/arg_picker/README.md b/arg_picker/README.md
deleted file mode 100644
index 80aa106..0000000
--- a/arg_picker/README.md
+++ /dev/null
@@ -1,47 +0,0 @@
-# Argument Picker
-
-A command-line argument parser for [Mingling](https://github.com/mingling-rs/mingling), enabled by the `mingling/picker` feature.
-
-```toml
-[dependencies.mingling]
-version = "0.3.0"
-features = [
- "picker"
-]
-```
-
-Of course, you can also use it as a standalone crate by replacing `mingling::picker` with `arg_picker`:
-
-```toml
-[dependencies]
-arg-picker = "0.2.0"
-```
-
-## Chained Argument Parser
-
-Provides a clean chained-call API for declaring arguments to parse:
-
-```rust
-use arg_picker::prelude::*;
-
-let args: Vec<&str> = vec!["--name", "Bob", "--age", "24"];
-
-let (name, age) = args
- .pick(&arg![name: String])
- .or(|| "Alice".to_string())
- .pick(&arg![age: i32])
- .or(|| 24)
- .post(|num| num.clamp(0, 120))
- .unwrap();
-
-assert_eq!(name, "Bob".to_string());
-assert_eq!(age, 24);
-```
-
-## Parsing Function Library
-
-Provides a pure function library `parselib` for analyzing the structure of command-line arguments.
-
-```rust
-use arg_picker::parselib::*;
-```
diff --git a/arg_picker/mingling-ci.toml b/arg_picker/mingling-ci.toml
deleted file mode 100644
index 73a9935..0000000
--- a/arg_picker/mingling-ci.toml
+++ /dev/null
@@ -1,2 +0,0 @@
-[test]
-command = ["cargo", "test", "-p", "arg-picker"]
diff --git a/arg_picker/src/arg.rs b/arg_picker/src/arg.rs
deleted file mode 100644
index 6fad6c7..0000000
--- a/arg_picker/src/arg.rs
+++ /dev/null
@@ -1,378 +0,0 @@
-// Doc Not Optimize
-use crate::{Pickable, PickerArgInfo, parselib::ParserStyle};
-use std::marker::PhantomData;
-
-/// Represents a constraint definition for a parameter selection.
-///
-/// This structure describes the constraints that a command-line parameter (Picker parameter item)
-/// should satisfy, including its full name list (with aliases), short name form, and whether it is
-/// positional.
-///
-/// # Field Descriptions
-///
-/// - `full`: Full name or alias list. For example, `["config", "cfg"]` means the parameter can be
-/// matched with either `--config` or `--cfg`. Must contain at least one non-empty string.
-///
-/// - `short`: Short name (single character). For example, `Some('c')` means it can be passed using
-/// the `-c` form. If set to `None`, the short name form is not supported.
-///
-/// - `positional`: Whether the parameter is positional (i.e., an argument without a flag).
-/// - `true`: The parameter is positional; it is matched by its position in the command line rather
-/// than by a `--name` or `-n` flag.
-/// - `false`: The parameter is a named (flag-based) parameter.
-///
-/// - `_type`: [`PhantomData`] to hold the type parameter.
-#[derive(Default, Clone, Copy)]
-pub struct PickerArg<'a, Type>
-where
- Type: Pickable<'a>,
-{
- /// Full name, may include variant names (aliases), e.g., `["config", "cfg"]`.
- pub full: &'a [&'a str],
-
- /// Short name, e.g., `'c'`.
- pub short: Option<char>,
-
- /// Whether the parameter is positional (no flag, matched by position).
- pub positional: bool,
-
- /// [`PhantomData`] to hold the type parameter.
- pub internal_type: PhantomData<Type>,
-}
-
-impl<'a, Type> From<&'a Self> for PickerArg<'a, Type>
-where
- Type: Pickable<'a>,
-{
- fn from(value: &'a Self) -> Self {
- Self {
- full: value.full,
- short: value.short,
- positional: value.positional,
- internal_type: PhantomData,
- }
- }
-}
-
-impl<'a, Type> PickerArg<'a, Type>
-where
- Type: Pickable<'a>,
-{
- /// Creates a new `PickerArg` with the provided parameters.
- #[must_use]
- pub const fn new(full: &'a [&'a str], short: Option<char>, positional: bool) -> Self {
- Self {
- full,
- short,
- positional,
- internal_type: PhantomData,
- }
- }
-
- /// Returns the full name list (including aliases).
- #[must_use]
- pub const fn full(&self) -> &'a [&'a str] {
- self.full
- }
-
- /// Returns the short name, if any.
- #[must_use]
- pub const fn short(&self) -> Option<char> {
- self.short
- }
-
- /// Returns whether the parameter is positional.
- ///
- /// If `full` is empty or `short` is `None`, the parameter is considered positional
- /// regardless of the stored value.
- #[must_use]
- pub const fn is_positional(&self) -> bool {
- if self.full.is_empty() && self.short.is_none() {
- true
- } else {
- self.positional
- }
- }
-
- /// Sets the full name list.
- pub const fn set_full(&mut self, full: &'a [&'a str]) {
- self.full = full;
- }
-
- /// Sets the short name.
- pub const fn set_short(&mut self, short: Option<char>) {
- self.short = short;
- }
-
- /// Sets whether the parameter is positional.
- pub const fn set_positional(&mut self, positional: bool) {
- self.positional = positional;
- }
-
- /// Sets the full name list and returns self.
- #[must_use]
- pub const fn with_full(mut self, full: &'a [&'a str]) -> Self {
- self.full = full;
- self
- }
-
- /// Clears the full name list (sets it to an empty slice) and returns self.
- #[must_use]
- pub const fn without_full(mut self) -> Self {
- self.full = &[];
- self
- }
-
- /// Sets the short name to the given character and returns self.
- #[must_use]
- pub const fn with_short(mut self, short: char) -> Self {
- self.short = Some(short);
- self
- }
-
- /// Clears the short name (sets it to None) and returns self.
- #[must_use]
- pub const fn without_short(mut self) -> Self {
- self.short = None;
- self
- }
-
- /// Sets whether the parameter is positional and returns self.
- #[must_use]
- pub const fn with_positional(mut self, positional: bool) -> Self {
- self.positional = positional;
- self
- }
-
- /// Converts this `PickerArg` into a `PickerArgInfo` value.
- ///
- /// This is a convenience method equivalent to calling `PickerArgInfo::from(self)`.
- #[must_use]
- pub fn into_info(self) -> PickerArgInfo<'a> {
- let value = self;
- let (long, alias) = if value.full.is_empty() {
- (None, None)
- } else {
- let long = Some(value.full[0]);
- let alias = if value.full.len() > 1 {
- Some(value.full[1..].to_vec())
- } else {
- None
- };
- (long, alias)
- };
-
- PickerArgInfo {
- short: value.short,
- long,
- alias,
- positional: value.positional,
- optional: false,
- multi: false,
- is_flag: false,
- }
- }
-}
-
-impl<'a, Type> From<PickerArg<'a, Type>> for Vec<String>
-where
- Type: Pickable<'a>,
-{
- fn from(value: PickerArg<'a, Type>) -> Self {
- let mut result = Self::new();
- let info = PickerArgInfo::from(value);
- let possible_flags =
- crate::parselib::build_possible_flags(ParserStyle::global_style(), &info);
- for flag in possible_flags {
- result.push(flag);
- }
- result
- }
-}
-
-/// Describes the attribute (behavior) of a command-line parameter.
-///
-/// The ordering reflects parse priority (higher = parsed first):
-/// `Postprocess < Final < PositionalMulti < Positional < Flag < Single < Multi < Begin < Preprocess`
-///
-/// # Variants
-///
-/// - `Postprocess` — Reserved lowest priority, used only in special cases.
-/// - `Final` — Reserved post-processing priority, used only in special cases.
-/// - `PositionalMulti` — Positional argument that accepts multiple values (e.g., multiple input files).
-/// - `Positional` — Positional argument matched by its position (e.g., an input file).
-/// - `Flag` — Boolean flag with no associated value (e.g., `--verbose`).
-/// - `Single` — Accepts a single value (e.g., `--name Alice`).
-/// - `Multi` — Accepts multiple values (e.g., `--file a.txt --file b.txt`).
-/// - `Begin` — Reserved pre-processing priority, used only in special cases.
-/// - `Preprocess` — Reserved highest priority, used only in special cases.
-#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
-pub enum PickerArgAttr {
- /// Reserved lowest priority, used only in special cases.
- Postprocess,
-
- /// Reserved post-processing priority, used only in special cases.
- Final,
-
- /// Positional argument that accepts multiple values (e.g., multiple input files).
- PositionalMulti,
-
- /// Positional argument matched by its position (e.g., an input file).
- #[default]
- Positional,
-
- /// Boolean flag with no associated value (e.g., `--verbose`).
- Flag,
-
- /// Accepts a single value (e.g., `--name Alice`).
- Single,
-
- /// Accepts multiple values (e.g., `--file a.txt --file b.txt`).
- Multi,
-
- /// Reserved pre-processing priority, used only in special cases.
- Begin,
-
- /// Reserved highest priority, used only in special cases.
- Preprocess,
-}
-
-impl PickerArgAttr {
- /// Determines if the given `PickerArg` represents a positional parameter.
- ///
- /// If the flag is positional (determined by `flag.is_positional()`), returns
- /// `Self::Positional`. Otherwise, invokes the `other` closure to
- /// produce and return a `Self`.
- ///
- /// # Parameters
- ///
- /// - `flag`: A reference to the [`PickerArg`] to evaluate.
- /// - `other`: A closure that returns a [`PickerArgAttr`] when the flag is
- /// **not** positional.
- #[inline]
- pub fn positional_or_else<'a, T>(flag: &PickerArg<'a, T>, other: fn() -> Self) -> Self
- where
- T: Pickable<'a>,
- {
- if flag.is_positional() {
- Self::Positional
- } else {
- other()
- }
- }
-
- /// Determines if the given `PickerArg` represents a positional parameter and returns
- /// `Self::Positional` if so. Otherwise, returns the provided `default` attribute.
- ///
- /// # Parameters
- ///
- /// - `flag`: A reference to the [`PickerArg`] to evaluate.
- /// - `default`: The [`PickerArgAttr`] to return if the flag is not positional.
- #[must_use]
- #[inline]
- pub const fn positional_or<'a, T>(flag: &PickerArg<'a, T>, default: Self) -> Self
- where
- T: Pickable<'a>,
- {
- if flag.is_positional() {
- Self::Positional
- } else {
- default
- }
- }
-
- /// Determines if the given `PickerArg` represents a positional parameter and returns
- /// `Self::Positional` if so. Otherwise, returns `Self::Single`.
- ///
- /// # Parameters
- ///
- /// - `flag`: A reference to the [`PickerArg`] to evaluate.
- #[must_use]
- #[inline]
- pub const fn positional_or_single<'a, T>(flag: &PickerArg<'a, T>) -> Self
- where
- T: Pickable<'a>,
- {
- if flag.is_positional() {
- Self::Positional
- } else {
- Self::Single
- }
- }
-
- /// Determines if the given `PickerArg` represents a positional parameter and returns
- /// `Self::PositionalMulti` if so. Otherwise, returns `Self::Multi`.
- ///
- /// # Parameters
- ///
- /// - `flag`: A reference to the [`PickerArg`] to evaluate.
- #[must_use]
- #[inline]
- pub const fn positional_or_multi<'a, T>(flag: &PickerArg<'a, T>) -> Self
- where
- T: Pickable<'a>,
- {
- if flag.is_positional() {
- Self::PositionalMulti
- } else {
- Self::Multi
- }
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- fn test_picker_flag_attr_ordering() {
- // Multi > Single > Flag > Positional > PositionalMulti
- assert!(PickerArgAttr::Multi > PickerArgAttr::Single);
- assert!(PickerArgAttr::Multi > PickerArgAttr::Flag);
- assert!(PickerArgAttr::Multi > PickerArgAttr::Positional);
- assert!(PickerArgAttr::Multi > PickerArgAttr::PositionalMulti);
-
- assert!(PickerArgAttr::Single > PickerArgAttr::Flag);
- assert!(PickerArgAttr::Single > PickerArgAttr::Positional);
- assert!(PickerArgAttr::Single > PickerArgAttr::PositionalMulti);
-
- assert!(PickerArgAttr::Flag > PickerArgAttr::Positional);
- assert!(PickerArgAttr::Flag > PickerArgAttr::PositionalMulti);
-
- assert!(PickerArgAttr::Positional > PickerArgAttr::PositionalMulti);
-
- // PartialOrd
- assert!(PickerArgAttr::Multi >= PickerArgAttr::Single);
- assert!(PickerArgAttr::Single >= PickerArgAttr::Flag);
- assert!(PickerArgAttr::Flag >= PickerArgAttr::Positional);
- assert!(PickerArgAttr::Positional >= PickerArgAttr::PositionalMulti);
-
- assert!(PickerArgAttr::PositionalMulti < PickerArgAttr::Positional);
- assert!(PickerArgAttr::Positional < PickerArgAttr::Flag);
- assert!(PickerArgAttr::Flag < PickerArgAttr::Single);
- assert!(PickerArgAttr::Single < PickerArgAttr::Multi);
- }
-
- #[test]
- fn test_picker_flag_attr_sorting() {
- // Sort
- let mut values = vec![
- PickerArgAttr::Flag,
- PickerArgAttr::Single,
- PickerArgAttr::Positional,
- PickerArgAttr::Multi,
- PickerArgAttr::PositionalMulti,
- ];
- values.sort();
- assert_eq!(
- values,
- vec![
- PickerArgAttr::PositionalMulti,
- PickerArgAttr::Positional,
- PickerArgAttr::Flag,
- PickerArgAttr::Single,
- PickerArgAttr::Multi,
- ]
- );
- }
-}
diff --git a/arg_picker/src/builtin.rs b/arg_picker/src/builtin.rs
deleted file mode 100644
index 0053631..0000000
--- a/arg_picker/src/builtin.rs
+++ /dev/null
@@ -1,10 +0,0 @@
-// Doc Not Optimize
-mod pick_bool;
-mod pick_flag;
-mod pick_ip_attr;
-mod pick_numbers;
-mod pick_pathbuf;
-mod pick_paths;
-mod pick_picker_args;
-mod pick_socket_attr;
-mod pick_string;
diff --git a/arg_picker/src/builtin/pick_bool.rs b/arg_picker/src/builtin/pick_bool.rs
deleted file mode 100644
index e508c7e..0000000
--- a/arg_picker/src/builtin/pick_bool.rs
+++ /dev/null
@@ -1,23 +0,0 @@
-// Doc Not Optimize
-use crate::parselib::{FlagMatcher, Matcher};
-use crate::pickable_needed::*;
-
-impl<'a> Pickable<'a> for bool {
- fn get_attr(_: &'a PickerArg<'a, Self>) -> PickerArgAttr {
- PickerArgAttr::Flag
- }
-
- fn tag(ctx: TagPhaseContext) -> Vec<usize> {
- FlagMatcher::match_all(ctx.into())
- }
-
- fn pick(raw_strs: &[&str]) -> PickerArgResult<Self> {
- if raw_strs.is_empty() {
- // No matching flag found — signal NotFound so the fallback chain
- // (default → route) gets a chance to run.
- PickerArgResult::NotFound
- } else {
- PickerArgResult::Parsed(true)
- }
- }
-}
diff --git a/arg_picker/src/builtin/pick_flag.rs b/arg_picker/src/builtin/pick_flag.rs
deleted file mode 100644
index 15b13a3..0000000
--- a/arg_picker/src/builtin/pick_flag.rs
+++ /dev/null
@@ -1,22 +0,0 @@
-// Doc Not Optimize
-use crate::parselib::{FlagMatcher, Matcher};
-use crate::pickable_needed::*;
-use crate::value::Flag;
-
-impl<'a> Pickable<'a> for Flag {
- fn get_attr(_: &'a PickerArg<'a, Self>) -> PickerArgAttr {
- PickerArgAttr::Flag
- }
-
- fn tag(ctx: TagPhaseContext) -> Vec<usize> {
- FlagMatcher::match_all(ctx.into())
- }
-
- fn pick(raw_strs: &[&str]) -> PickerArgResult<Self> {
- if raw_strs.is_empty() {
- PickerArgResult::Parsed(Self::Inactive)
- } else {
- PickerArgResult::Parsed(Self::Active)
- }
- }
-}
diff --git a/arg_picker/src/builtin/pick_ip_attr.rs b/arg_picker/src/builtin/pick_ip_attr.rs
deleted file mode 100644
index cf32426..0000000
--- a/arg_picker/src/builtin/pick_ip_attr.rs
+++ /dev/null
@@ -1,37 +0,0 @@
-// Doc Not Optimize
-use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
-
-use crate::SinglePickable;
-
-impl SinglePickable for IpAddr {
- fn pick_single(str: Option<&str>) -> crate::PickerArgResult<Self> {
- str.map_or(crate::PickerArgResult::NotFound, |s| {
- s.parse::<Self>()
- .map_or(crate::PickerArgResult::NotFound, |addr| {
- crate::PickerArgResult::Parsed(addr)
- })
- })
- }
-}
-
-impl SinglePickable for Ipv4Addr {
- fn pick_single(str: Option<&str>) -> crate::PickerArgResult<Self> {
- str.map_or(crate::PickerArgResult::NotFound, |s| {
- s.parse::<Self>()
- .map_or(crate::PickerArgResult::NotFound, |addr| {
- crate::PickerArgResult::Parsed(addr)
- })
- })
- }
-}
-
-impl SinglePickable for Ipv6Addr {
- fn pick_single(str: Option<&str>) -> crate::PickerArgResult<Self> {
- str.map_or(crate::PickerArgResult::NotFound, |s| {
- s.parse::<Self>()
- .map_or(crate::PickerArgResult::NotFound, |addr| {
- crate::PickerArgResult::Parsed(addr)
- })
- })
- }
-}
diff --git a/arg_picker/src/builtin/pick_numbers.rs b/arg_picker/src/builtin/pick_numbers.rs
deleted file mode 100644
index 3a8f380..0000000
--- a/arg_picker/src/builtin/pick_numbers.rs
+++ /dev/null
@@ -1,78 +0,0 @@
-// Doc Not Optimize
-use crate::{BoundaryCheck, SinglePickable, pickable_needed::*};
-
-macro_rules! impl_single_pickable_num {
- ($($t:ty),+ $(,)?) => {
- $(impl SinglePickable for $t {
- fn pick_single(str: Option<&str>) -> PickerArgResult<Self> {
- match str {
- Some(s) => s.parse::<$t>().map(PickerArgResult::Parsed).unwrap_or(PickerArgResult::NotFound),
- None => PickerArgResult::NotFound,
- }
- }
- })+
- };
-}
-
-/// Returns `true` if `raw` looks like an integer (digits, optional `+`/`-` prefix,
-/// no decimal point or exponent).
-fn is_int_like(raw: &str) -> bool {
- let s = raw.trim();
- let bytes = s.as_bytes();
- if bytes.is_empty() {
- return false;
- }
- let i = usize::from(bytes[0] == b'-' || bytes[0] == b'+');
- if i >= bytes.len() {
- return false;
- }
- for &b in &bytes[i..] {
- if !b.is_ascii_digit() {
- return false;
- }
- }
- true
-}
-
-/// Returns `true` if `raw` looks like a float (contains `.`, `e`, or `E`).
-fn is_float_like(raw: &str) -> bool {
- let s = raw.trim();
- s.contains('.') || s.contains('e') || s.contains('E')
-}
-
-impl_single_pickable_num! {
- i8, i16, i32, i64, i128, isize,
- u8, u16, u32, u64, u128, usize,
- f32, f64,
-}
-
-// Integer boundary: only accept strings that look like integers.
-// Float-like strings trigger a boundary.
-macro_rules! impl_boundary_check_int {
- ($($t:ty),+ $(,)?) => {
- $(impl BoundaryCheck for $t {
- fn check_boundary(raw: &str) -> bool {
- !is_int_like(raw)
- }
- })+
- };
-}
-
-impl_boundary_check_int! {
- i8, i16, i32, i64, i128, isize,
- u8, u16, u32, u64, u128, usize,
-}
-
-// Float boundary: only accept strings that look like floats.
-// Integer-like strings trigger a boundary.
-impl BoundaryCheck for f32 {
- fn check_boundary(raw: &str) -> bool {
- !is_float_like(raw) || raw.parse::<Self>().is_err()
- }
-}
-
-impl BoundaryCheck for f64 {
- fn check_boundary(raw: &str) -> bool {
- !is_float_like(raw) || raw.parse::<Self>().is_err()
- }
-}
diff --git a/arg_picker/src/builtin/pick_pathbuf.rs b/arg_picker/src/builtin/pick_pathbuf.rs
deleted file mode 100644
index ec2cae1..0000000
--- a/arg_picker/src/builtin/pick_pathbuf.rs
+++ /dev/null
@@ -1,15 +0,0 @@
-// Doc Not Optimize
-use std::path::PathBuf;
-
-use crate::{
- PickerArgResult::{NotFound, Parsed},
- SinglePickable,
-};
-
-impl SinglePickable for PathBuf {
- fn pick_single(str: Option<&str>) -> crate::PickerArgResult<Self> {
- str.map_or(NotFound, |str| {
- just_fmt::fmt_path_str(str).map_or(NotFound, |formated| Parsed(Self::from(formated)))
- })
- }
-}
diff --git a/arg_picker/src/builtin/pick_paths.rs b/arg_picker/src/builtin/pick_paths.rs
deleted file mode 100644
index d5edc65..0000000
--- a/arg_picker/src/builtin/pick_paths.rs
+++ /dev/null
@@ -1,168 +0,0 @@
-// Doc Not Optimize
-use std::{
- fs,
- path::{Path, PathBuf},
-};
-
-use crate::{
- PickerArgResult::{self, NotFound, Parsed, Unparsed},
- SinglePickable,
- value::{
- DirPath, FilePath, NoDirPath, NoFilePath, NoPath, NoSymlinkPath, RecursiveFiles,
- SymlinkPath,
- },
-};
-
-impl SinglePickable for FilePath {
- fn pick_single(str: Option<&str>) -> PickerArgResult<Self> {
- match <PathBuf as SinglePickable>::pick_single(str) {
- Parsed(path) => {
- if path.exists() && path.is_file() {
- Parsed(Self::from(path))
- } else {
- NotFound
- }
- }
- Unparsed => Unparsed,
- NotFound => NotFound,
- }
- }
-}
-
-impl SinglePickable for NoFilePath {
- fn pick_single(str: Option<&str>) -> PickerArgResult<Self> {
- match <PathBuf as SinglePickable>::pick_single(str) {
- Parsed(path) => {
- if !path.exists() || !path.is_file() {
- Parsed(Self::from(path))
- } else {
- NotFound
- }
- }
- Unparsed => Unparsed,
- NotFound => NotFound,
- }
- }
-}
-
-impl SinglePickable for DirPath {
- fn pick_single(str: Option<&str>) -> PickerArgResult<Self> {
- match <PathBuf as SinglePickable>::pick_single(str) {
- Parsed(path) => {
- if path.exists() && path.is_dir() {
- Parsed(Self::from(path))
- } else {
- NotFound
- }
- }
- Unparsed => Unparsed,
- NotFound => NotFound,
- }
- }
-}
-
-impl SinglePickable for NoDirPath {
- fn pick_single(str: Option<&str>) -> PickerArgResult<Self> {
- match <PathBuf as SinglePickable>::pick_single(str) {
- Parsed(path) => {
- if !path.exists() || !path.is_dir() {
- Parsed(Self::from(path))
- } else {
- NotFound
- }
- }
- Unparsed => Unparsed,
- NotFound => NotFound,
- }
- }
-}
-
-impl SinglePickable for SymlinkPath {
- fn pick_single(str: Option<&str>) -> PickerArgResult<Self> {
- match <PathBuf as SinglePickable>::pick_single(str) {
- Parsed(path) => {
- if path.exists() && path.is_symlink() {
- Parsed(Self::from(path))
- } else {
- NotFound
- }
- }
- Unparsed => Unparsed,
- NotFound => NotFound,
- }
- }
-}
-
-impl SinglePickable for NoSymlinkPath {
- fn pick_single(str: Option<&str>) -> PickerArgResult<Self> {
- match <PathBuf as SinglePickable>::pick_single(str) {
- Parsed(path) => {
- if !path.exists() || !path.is_symlink() {
- Parsed(Self::from(path))
- } else {
- NotFound
- }
- }
- Unparsed => Unparsed,
- NotFound => NotFound,
- }
- }
-}
-
-impl SinglePickable for NoPath {
- fn pick_single(str: Option<&str>) -> PickerArgResult<Self> {
- match <PathBuf as SinglePickable>::pick_single(str) {
- Parsed(path) => {
- if path.exists() {
- NotFound
- } else {
- Parsed(Self::from(path))
- }
- }
- Unparsed => Unparsed,
- NotFound => NotFound,
- }
- }
-}
-
-impl SinglePickable for RecursiveFiles {
- fn pick_single(str: Option<&str>) -> PickerArgResult<Self> {
- match <PathBuf as SinglePickable>::pick_single(str) {
- Parsed(path) => {
- if !path.exists() {
- return NotFound;
- }
- if path.is_file() || path.is_symlink() {
- return Parsed(Self::from(vec![path]));
- }
- let mut entries = Vec::new();
- if let Ok(dir_entries) = fs::read_dir(&path) {
- for entry in dir_entries.flatten() {
- let entry_path = entry.path();
- if entry_path.is_file() || entry_path.is_symlink() {
- entries.push(entry_path);
- } else if entry_path.is_dir() {
- collect_files(&entry_path, &mut entries);
- }
- }
- }
- Parsed(Self::from(entries))
- }
- Unparsed => Unparsed,
- NotFound => NotFound,
- }
- }
-}
-
-fn collect_files(dir: &Path, entries: &mut Vec<PathBuf>) {
- if let Ok(dir_entries) = fs::read_dir(dir) {
- for entry in dir_entries.flatten() {
- let entry_path = entry.path();
- if entry_path.is_file() || entry_path.is_symlink() {
- entries.push(entry_path);
- } else if entry_path.is_dir() {
- collect_files(&entry_path, entries);
- }
- }
- }
-}
diff --git a/arg_picker/src/builtin/pick_picker_args.rs b/arg_picker/src/builtin/pick_picker_args.rs
deleted file mode 100644
index ea72ef1..0000000
--- a/arg_picker/src/builtin/pick_picker_args.rs
+++ /dev/null
@@ -1,22 +0,0 @@
-// Doc Not Optimize
-use crate::{PickerArgResult::Parsed, PickerArgs, parselib::build_masked_args, pickable_needed::*};
-
-impl<'a> Pickable<'a> for PickerArgs<'a> {
- fn get_attr(_flag: &'a PickerArg<'a, Self>) -> PickerArgAttr {
- // Use the lowest priority attribute
- PickerArgAttr::Postprocess
- }
-
- fn tag(ctx: TagPhaseContext) -> Vec<usize> {
- // Collect all remaining raw index values
- build_masked_args(ctx.args, ctx.mask)
- .iter()
- .map(|m| m.raw_idx)
- .collect()
- }
-
- fn pick(raw_strs: &[&str]) -> PickerArgResult<Self> {
- let remains: Vec<String> = raw_strs.iter().map(ToString::to_string).collect();
- Parsed(PickerArgs::Owned(remains))
- }
-}
diff --git a/arg_picker/src/builtin/pick_socket_attr.rs b/arg_picker/src/builtin/pick_socket_attr.rs
deleted file mode 100644
index c227b2b..0000000
--- a/arg_picker/src/builtin/pick_socket_attr.rs
+++ /dev/null
@@ -1,37 +0,0 @@
-// Doc Not Optimize
-use std::net::{SocketAddr, SocketAddrV4, SocketAddrV6};
-
-use crate::SinglePickable;
-
-impl SinglePickable for SocketAddr {
- fn pick_single(str: Option<&str>) -> crate::PickerArgResult<Self> {
- str.map_or(crate::PickerArgResult::NotFound, |s| {
- s.parse::<Self>()
- .map_or(crate::PickerArgResult::NotFound, |addr| {
- crate::PickerArgResult::Parsed(addr)
- })
- })
- }
-}
-
-impl SinglePickable for SocketAddrV4 {
- fn pick_single(str: Option<&str>) -> crate::PickerArgResult<Self> {
- str.map_or(crate::PickerArgResult::NotFound, |s| {
- s.parse::<Self>()
- .map_or(crate::PickerArgResult::NotFound, |addr| {
- crate::PickerArgResult::Parsed(addr)
- })
- })
- }
-}
-
-impl SinglePickable for SocketAddrV6 {
- fn pick_single(str: Option<&str>) -> crate::PickerArgResult<Self> {
- str.map_or(crate::PickerArgResult::NotFound, |s| {
- s.parse::<Self>()
- .map_or(crate::PickerArgResult::NotFound, |addr| {
- crate::PickerArgResult::Parsed(addr)
- })
- })
- }
-}
diff --git a/arg_picker/src/builtin/pick_string.rs b/arg_picker/src/builtin/pick_string.rs
deleted file mode 100644
index 0a08693..0000000
--- a/arg_picker/src/builtin/pick_string.rs
+++ /dev/null
@@ -1,9 +0,0 @@
-// Doc Not Optimize
-use crate::PickerArgResult::NotFound;
-use crate::{SinglePickable, pickable_needed::*};
-
-impl SinglePickable for String {
- fn pick_single(str: Option<&str>) -> PickerArgResult<Self> {
- str.map_or(NotFound, |str| PickerArgResult::Parsed(str.to_string()))
- }
-}
diff --git a/arg_picker/src/constants.rs b/arg_picker/src/constants.rs
deleted file mode 100644
index 0dcdc45..0000000
--- a/arg_picker/src/constants.rs
+++ /dev/null
@@ -1,17 +0,0 @@
-// Doc Not Optimize
-use std::marker::PhantomData;
-
-use crate::{PickerArg, PickerArgs};
-
-/// Remaining positional arguments (anything not consumed as an option).
-/// - `full`: `[]` (empty — not triggered by any `--` prefix).
-/// - `short`: (none)
-/// - `positional`: `false` (this is a meta‑argument that collects everything left).
-/// This constant is used internally to access any leftover arguments after
-/// all defined flags/options have been processed.
-pub const REMAINS: PickerArg<PickerArgs> = PickerArg::<PickerArgs> {
- full: &[],
- short: None,
- positional: false,
- internal_type: PhantomData,
-};
diff --git a/arg_picker/src/infos.rs b/arg_picker/src/infos.rs
deleted file mode 100644
index a564687..0000000
--- a/arg_picker/src/infos.rs
+++ /dev/null
@@ -1,481 +0,0 @@
-// Doc Not Optimize
-use crate::{Pickable, PickerArg, parselib::ParserStyle};
-
-/// Represents the result of parsing or looking up a value.
-///
-/// This enum is generic over the type being parsed. It models three possible outcomes:
-/// - [`Unparsed`](Self::Unparsed): The value has not yet been parsed (default).
-/// - [`Parsed`](Self::Parsed): The value was successfully parsed into `Type`.
-/// - [`NotFound`](Self::NotFound): The requested value could not be found.
-#[derive(Default)]
-pub enum PickerArgResult<Type> {
- /// The value has not yet been parsed (default).
- #[default]
- Unparsed,
-
- /// The value was successfully parsed into `Type`.
- Parsed(Type),
-
- /// The requested value could not be found.
- NotFound,
-}
-
-impl<Type, E> From<Result<Type, E>> for PickerArgResult<Type> {
- /// Converts a `Result<Type, E>` into a `PickerArgResult<Type>`.
- ///
- /// - `Ok(value)` maps to [`Parsed(value)`](Self::Parsed).
- /// - `Err(_)` maps to [`NotFound`](Self::NotFound).
- fn from(result: Result<Type, E>) -> Self {
- result.map_or_else(|_| Self::NotFound, |value| Self::Parsed(value))
- }
-}
-
-impl<Type> From<Option<Type>> for PickerArgResult<Type> {
- /// Converts an `Option<Type>` into a `PickerArgResult<Type>`.
- ///
- /// - `Some(value)` maps to [`Parsed(value)`](Self::Parsed).
- /// - `None` maps to [`NotFound`](Self::NotFound).
- fn from(option: Option<Type>) -> Self {
- option.map_or_else(|| Self::NotFound, |value| Self::Parsed(value))
- }
-}
-
-impl<Type> PickerArgResult<Type> {
- /// Returns `true` if the result is [`Parsed`](Self::Parsed).
- ///
- /// # Examples
- ///
- /// ```
- /// use arg_picker::PickerArgResult;
- ///
- /// let result: PickerArgResult<i32> = PickerArgResult::Parsed(42);
- /// assert!(result.is_parsed());
- ///
- /// let result: PickerArgResult<i32> = PickerArgResult::NotFound;
- /// assert!(!result.is_parsed());
- /// ```
- pub const fn is_parsed(&self) -> bool {
- matches!(self, Self::Parsed(_))
- }
-
- /// Returns `true` if the result is [`Parsed`](Self::Parsed) or [`NotFound`](Self::NotFound).
- /// i.e., the value exists (was either found or not yet parsed).
- /// Typically indicates the value was "found" in some sense.
- ///
- /// # Examples
- ///
- /// ```
- /// use arg_picker::PickerArgResult;
- ///
- /// let result: PickerArgResult<i32> = PickerArgResult::Parsed(42);
- /// assert!(result.is_found());
- ///
- /// let result: PickerArgResult<i32> = PickerArgResult::NotFound;
- /// assert!(result.is_found());
- /// ```
- pub const fn is_found(&self) -> bool {
- matches!(self, Self::Parsed(_) | Self::NotFound)
- }
-
- /// Returns `true` if the result is [`Unparsed`](Self::Unparsed) or [`NotFound`](Self::NotFound).
- ///
- /// # Examples
- ///
- /// ```
- /// use arg_picker::PickerArgResult;
- ///
- /// let result: PickerArgResult<i32> = PickerArgResult::Unparsed;
- /// assert!(result.is_err());
- ///
- /// let result: PickerArgResult<i32> = PickerArgResult::Parsed(10);
- /// assert!(!result.is_err());
- /// ```
- pub const fn is_err(&self) -> bool {
- !matches!(self, Self::Parsed(_))
- }
-
- /// Returns `Some(&Type)` if [`Parsed`](Self::Parsed), otherwise `None`.
- ///
- /// # Examples
- ///
- /// ```
- /// use arg_picker::PickerArgResult;
- ///
- /// let result: PickerArgResult<i32> = PickerArgResult::Parsed(42);
- /// assert_eq!(result.parsed(), Some(&42));
- ///
- /// let result: PickerArgResult<i32> = PickerArgResult::NotFound;
- /// assert_eq!(result.parsed(), None);
- /// ```
- pub const fn parsed(&self) -> Option<&Type> {
- if let Self::Parsed(value) = self {
- Some(value)
- } else {
- None
- }
- }
-
- /// Returns the contained [`Parsed`](Self::Parsed) value or panics with a given message.
- ///
- /// # Panics
- /// Panics if the value is not [`Parsed`](Self::Parsed), with a message including the provided `msg`.
- ///
- /// # Examples
- ///
- /// ```should_panic
- /// use arg_picker::PickerArgResult;
- ///
- /// let result: PickerArgResult<i32> = PickerArgResult::NotFound;
- /// result.expect("expected a parsed value");
- /// ```
- pub fn expect(self, msg: &str) -> Type {
- match self {
- Self::Parsed(value) => value,
- _ => panic!("{}", msg),
- }
- }
-
- /// Returns the contained [`Parsed`](Self::Parsed) value or panics.
- ///
- /// # Panics
- /// Panics if the value is not [`Parsed`](Self::Parsed).
- ///
- /// # Examples
- ///
- /// ```
- /// use arg_picker::PickerArgResult;
- ///
- /// let result: PickerArgResult<i32> = PickerArgResult::Parsed(42);
- /// assert_eq!(result.unwrap(), 42);
- /// ```
- ///
- /// ```should_panic
- /// use arg_picker::PickerArgResult;
- ///
- /// let result: PickerArgResult<i32> = PickerArgResult::NotFound;
- /// result.unwrap();
- /// ```
- pub fn unwrap(self) -> Type {
- match self {
- Self::Parsed(value) => value,
- Self::Unparsed => {
- panic!("called `PickerArgResult::unwrap()` on an `Unparsed` value")
- }
- Self::NotFound => {
- panic!("called `PickerArgResult::unwrap()` on a `NotFound` value")
- }
- }
- }
-
- /// Returns the contained [`Parsed`](Self::Parsed) value or a provided `default`.
- ///
- /// # Examples
- ///
- /// ```
- /// use arg_picker::PickerArgResult;
- ///
- /// let result: PickerArgResult<i32> = PickerArgResult::Parsed(42);
- /// assert_eq!(result.unwrap_or(0), 42);
- ///
- /// let result: PickerArgResult<i32> = PickerArgResult::NotFound;
- /// assert_eq!(result.unwrap_or(0), 0);
- /// ```
- pub fn unwrap_or(self, default: Type) -> Type {
- match self {
- Self::Parsed(value) => value,
- _ => default,
- }
- }
-
- /// Returns the contained [`Parsed`](Self::Parsed) value or computes it from a closure.
- ///
- /// # Examples
- ///
- /// ```
- /// use arg_picker::PickerArgResult;
- ///
- /// let result: PickerArgResult<i32> = PickerArgResult::Parsed(42);
- /// assert_eq!(result.unwrap_or_else(|| 0), 42);
- ///
- /// let result: PickerArgResult<i32> = PickerArgResult::NotFound;
- /// assert_eq!(result.unwrap_or_else(|| 0), 0);
- /// ```
- pub fn unwrap_or_else<F: FnOnce() -> Type>(self, f: F) -> Type {
- match self {
- Self::Parsed(value) => value,
- _ => f(),
- }
- }
-
- /// Returns the contained [`Parsed`](Self::Parsed) value or the default value of `Type`.
- ///
- /// # Examples
- ///
- /// ```
- /// use arg_picker::PickerArgResult;
- ///
- /// let result: PickerArgResult<i32> = PickerArgResult::Parsed(42);
- /// assert_eq!(result.unwrap_or_default(), 42);
- ///
- /// let result: PickerArgResult<i32> = PickerArgResult::NotFound;
- /// assert_eq!(result.unwrap_or_default(), 0);
- /// ```
- pub fn unwrap_or_default(self) -> Type
- where
- Type: Default,
- {
- match self {
- Self::Parsed(value) => value,
- _ => Type::default(),
- }
- }
-
- /// Converts `PickerArgResult<Type>` into `Option<Type>`.
- ///
- /// Returns `Some(Type)` if [`Parsed`](Self::Parsed), otherwise `None`.
- ///
- /// # Examples
- ///
- /// ```
- /// use arg_picker::PickerArgResult;
- ///
- /// let result: PickerArgResult<i32> = PickerArgResult::Parsed(42);
- /// assert_eq!(result.to_option(), Some(42));
- ///
- /// let result: PickerArgResult<i32> = PickerArgResult::NotFound;
- /// assert_eq!(result.to_option(), None);
- ///
- /// let result: PickerArgResult<i32> = PickerArgResult::Unparsed;
- /// assert_eq!(result.to_option(), None);
- /// ```
- pub fn to_option(self) -> Option<Type> {
- match self {
- Self::Parsed(value) => Some(value),
- _ => None,
- }
- }
-}
-
-// In PickerArgInfo, positional, optional, multi, and is_flag may coexist.
-#[allow(clippy::struct_excessive_bools)]
-/// Represents metadata about a command-line argument or flag.
-///
-/// This struct stores all relevant information about a tag/argument that can be used
-/// for parsing command-line inputs. It includes the short form (e.g., `-n`), long form
-/// (e.g., `--name`), aliases, and various flags that control parsing behavior.
-pub struct PickerArgInfo<'a> {
- /// The short form of the tag, e.g. `'n'` for `-n`.
- pub short: Option<char>,
- /// The long form of the tag, e.g. `"name"` for `--name`.
- pub long: Option<&'a str>,
- /// Alternative names for the tag, e.g. `["-N", "--nickname"]`.
- pub alias: Option<Vec<&'a str>>,
- /// Whether this tag is a positional argument (no `-` or `--` prefix).
- pub positional: bool,
- /// Whether this tag is optional or required.
- pub optional: bool,
- /// Whether this tag can accept multiple values.
- pub multi: bool,
- /// Whether this tag participates in parsing after a `--` separator.
- pub is_flag: bool,
-}
-
-impl<'a, T> From<PickerArg<'a, T>> for PickerArgInfo<'a>
-where
- T: Pickable<'a>,
-{
- fn from(value: PickerArg<'a, T>) -> Self {
- value.into_info()
- }
-}
-
-impl<'a, T: Pickable<'a>> From<&'a PickerArg<'a, T>> for PickerArgInfo<'a> {
- fn from(value: &'a PickerArg<'a, T>) -> Self {
- let (long, alias) = if value.full.is_empty() {
- (None, None)
- } else {
- let long = Some(value.full[0]);
- let alias = if value.full.len() > 1 {
- Some(value.full[1..].to_vec())
- } else {
- None
- };
- (long, alias)
- };
-
- Self {
- short: value.short,
- long,
- alias,
- positional: value.positional,
- optional: false,
- multi: false,
- is_flag: false,
- }
- }
-}
-
-impl<'a> PickerArgInfo<'a> {
- /// Create a new `PickerTag` with default values.
- #[must_use]
- pub const fn new() -> Self {
- Self {
- short: None,
- long: None,
- alias: None,
- positional: false,
- optional: false,
- multi: false,
- is_flag: false,
- }
- }
-
- /// Set the short flag (e.g., `'n'` for `-n`).
- #[must_use]
- pub const fn with_short(mut self, short: char) -> Self {
- self.short = Some(short);
- self
- }
-
- /// Set the long flag (e.g., `"name"` for `--name`).
- #[must_use]
- pub const fn with_long(mut self, long: &'a str) -> Self {
- self.long = Some(long);
- self
- }
-
- /// Set aliases for the tag.
- #[must_use]
- pub fn with_alias(mut self, alias: Vec<&'a str>) -> Self {
- self.alias = Some(alias);
- self
- }
-
- /// Mark the tag as positional.
- #[must_use]
- pub const fn with_positional(mut self, positional: bool) -> Self {
- self.positional = positional;
- self
- }
-
- /// Mark the tag as optional.
- #[must_use]
- pub const fn with_optional(mut self, optional: bool) -> Self {
- self.optional = optional;
- self
- }
-
- /// Mark the tag as multi-value.
- #[must_use]
- pub const fn with_multi(mut self, multi: bool) -> Self {
- self.multi = multi;
- self
- }
-
- /// Mark the tag as a flag that participates in parsing after `--`.
- #[must_use]
- pub const fn with_is_flag(mut self, is_flag: bool) -> Self {
- self.is_flag = is_flag;
- self
- }
-
- /// Set the short flag (e.g., `'n'` for `-n`).
- pub const fn set_short(&mut self, short: char) -> &mut Self {
- self.short = Some(short);
- self
- }
-
- /// Set the long flag (e.g., `"name"` for `--name`).
- pub const fn set_long(&mut self, long: &'a str) -> &mut Self {
- self.long = Some(long);
- self
- }
-
- /// Set aliases for the tag.
- pub fn set_alias(&mut self, alias: Vec<&'a str>) -> &mut Self {
- self.alias = Some(alias);
- self
- }
-
- /// Set whether this tag is positional.
- pub const fn set_positional(&mut self, positional: bool) -> &mut Self {
- self.positional = positional;
- self
- }
-
- /// Set whether this tag is optional.
- pub const fn set_optional(&mut self, optional: bool) -> &mut Self {
- self.optional = optional;
- self
- }
-
- /// Set whether this tag accepts multiple values.
- pub const fn set_multi(&mut self, multi: bool) -> &mut Self {
- self.multi = multi;
- self
- }
-
- /// Set whether this tag participates in parsing after a `--` separator.
- pub const fn set_is_flag(&mut self, is_flag: bool) -> &mut Self {
- self.is_flag = is_flag;
- self
- }
-
- /// Returns the short flag string, e.g. `-n` for short `n`.
- ///
- /// Uses [`ParserStyle::global_style()`] to format the flag.
- ///
- /// # Returns
- ///
- /// - `Some(String)` if `self.short` is set.
- /// - `None` if `self.short` is `None`.
- ///
- /// # Examples
- ///
- /// ```
- /// use arg_picker::PickerArgInfo;
- ///
- /// let info = PickerArgInfo::new().with_short('n');
- /// assert_eq!(info.short_flag(), Some("-n".to_string()));
- ///
- /// let info = PickerArgInfo::new();
- /// assert_eq!(info.short_flag(), None);
- /// ```
- #[must_use]
- pub fn short_flag(&self) -> Option<String> {
- let short = self.short?;
- Some(ParserStyle::global_style().flag_string(short))
- }
-
- /// Returns the long flag string, e.g. `--name` for long `"name"`.
- ///
- /// Uses [`ParserStyle::global_style()`] to format the flag.
- ///
- /// # Returns
- ///
- /// - `Some(String)` if `self.long` is set.
- /// - `None` if `self.long` is `None`.
- ///
- /// # Examples
- ///
- /// ```
- /// use arg_picker::PickerArgInfo;
- ///
- /// let info = PickerArgInfo::new().with_long("name");
- /// assert_eq!(info.long_flag(), Some("--name".to_string()));
- ///
- /// let info = PickerArgInfo::new();
- /// assert_eq!(info.long_flag(), None);
- /// ```
- #[must_use]
- pub fn long_flag(&self) -> Option<String> {
- let long = self.long?;
- Some(ParserStyle::global_style().flag_string(long))
- }
-}
-
-impl Default for PickerArgInfo<'_> {
- fn default() -> Self {
- Self::new()
- }
-}
diff --git a/arg_picker/src/lib.rs b/arg_picker/src/lib.rs
deleted file mode 100644
index 1de67a3..0000000
--- a/arg_picker/src/lib.rs
+++ /dev/null
@@ -1,66 +0,0 @@
-// Doc Not Optimize
-#![doc = include_str!("../README.md")]
-#![deny(missing_docs)]
-#![deny(clippy::pedantic)]
-#![deny(clippy::nursery)]
-// Some code requires wildcard imports to reduce boilerplate code
-#![allow(clippy::wildcard_imports)]
-
-mod builtin;
-
-mod picker;
-pub use picker::*;
-
-mod pickable;
-pub use pickable::*;
-
-mod arg;
-pub use arg::*;
-
-mod infos;
-pub use infos::*;
-
-/// Provides the specific parsing logic for command-line arguments and common utilities,
-/// as well as customization of command-line argument styles.
-pub mod parselib;
-
-/// Parser-provided parseable command-line types
-pub mod value;
-
-/// The prelude module, which re-exports the most commonly used traits and types.
-///
-/// This module is intended to be imported with a wildcard import:
-///
-/// ```
-/// use arg_picker::prelude::*;
-/// ```
-pub mod prelude {
- pub use crate::IntoPicker;
- pub use crate::macros::arg;
-}
-
-/// Re-export of the `arg_picker_macros` crate
-pub mod macros {
- pub use arg_picker_macros::arg;
-}
-
-/// Provides the types necessary for implementing the `Pickable` trait
-pub mod pickable_needed {
- pub use crate::{Pickable, PickerArg, PickerArgAttr, PickerArgResult, TagPhaseContext};
-}
-
-/// Provides the types necessary for implementing the `Matcher` trait
-pub mod matcher_needed {
- pub use crate::PickerArgInfo;
- pub use crate::parselib::{MaskedArg, Matcher, ParserStyle};
-}
-
-mod constants;
-
-/// Re-export of constants used by `arg-picker`.
-///
-/// This module provides access to various constants defined internally, such as
-/// default values, configuration limits, and other static parameters.
-pub mod consts {
- pub use crate::constants::*;
-}
diff --git a/arg_picker/src/parselib.rs b/arg_picker/src/parselib.rs
deleted file mode 100644
index 8e5faae..0000000
--- a/arg_picker/src/parselib.rs
+++ /dev/null
@@ -1,168 +0,0 @@
-// Doc Not Optimize
-mod flag_matcher;
-pub use flag_matcher::*;
-
-mod arg_matcher;
-pub use arg_matcher::*;
-
-mod multi_arg_matcher;
-pub use multi_arg_matcher::*;
-
-mod pos_matcher;
-pub use pos_matcher::*;
-
-mod single_matcher;
-pub use single_matcher::*;
-
-mod style;
-pub use style::*;
-
-mod utils;
-pub use utils::*;
-
-use crate::{PickerArgInfo, PickerArgs};
-
-/// Represents a single argument with its original raw string and index.
-///
-/// This is used during pattern matching to provide context about
-/// which argument is being processed.
-#[derive(Clone, Copy, PartialEq, Eq)]
-pub struct MaskedArg<'a> {
- /// The raw string value of the argument.
- pub raw: &'a str,
- /// The original index of the argument in the full argument list.
- pub raw_idx: usize,
-}
-
-/// Trait for defining matching logic against masked arguments.
-///
-/// Implementors can define custom strategies for matching one or all
-/// arguments that pass through a mask filter.
-pub trait Matcher {
- /// Called when only one match is needed.
- ///
- /// Returns the index of the first matched argument, or `None` if no match.
- fn on_match_one(
- args: &[MaskedArg],
- style: &ParserStyle,
- arg_info: &PickerArgInfo,
- ) -> Option<usize>;
-
- /// Called when all matches are needed.
- ///
- /// Returns a vector of indices of all matched arguments.
- fn on_match_all(
- args: &[MaskedArg],
- style: &ParserStyle,
- arg_info: &PickerArgInfo,
- ) -> Vec<usize>;
-
- /// Convenience method that builds masked arguments from `PickerArgs` and a mask,
- /// then calls `on_match_one`.
- #[must_use]
- fn match_one(ctx: MatcherContext<'_>) -> Option<usize> {
- let masked_args = build_masked_args(ctx.args, ctx.mask);
- Self::on_match_one(masked_args.as_slice(), ctx.style, ctx.arg_info)
- }
-
- /// Convenience method that builds masked arguments from `PickerArgs` and a mask,
- /// then calls `on_match_all`.
- #[must_use]
- fn match_all(ctx: MatcherContext<'_>) -> Vec<usize> {
- let masked_args = build_masked_args(ctx.args, ctx.mask);
- Self::on_match_all(masked_args.as_slice(), ctx.style, ctx.arg_info)
- }
-}
-
-/// Context for matcher operations
-///
-/// This struct bundles together the key pieces of data needed during matching:
-/// - `args`: The full set of parsed arguments.
-/// - `mask`: A byte mask indicating which arguments are currently active (non-zero = active).
-/// - `style`: The parsing style configuration.
-pub struct MatcherContext<'a> {
- /// The full set of parsed arguments.
- pub args: &'a PickerArgs<'a>,
-
- /// A byte mask where non-zero values indicate the argument at that position is active/should be matched.
- pub mask: &'a [u8],
-
- /// The parsing style configuration.
- pub style: &'a ParserStyle<'a>,
-
- /// Metadata about the command-line argument/flag being processed.
- ///
- /// Contains information such as short form (`-n`), long form (`--name`),
- /// aliases, and parsing flags (positional, optional, multi, `is_flag`).
- /// Used by matchers to make decisions based on argument characteristics.
- pub arg_info: &'a PickerArgInfo<'a>,
-}
-
-impl<'a> From<&'a crate::TagPhaseContext<'a>> for MatcherContext<'a> {
- fn from(ctx: &'a crate::TagPhaseContext<'a>) -> Self {
- MatcherContext {
- args: ctx.args,
- mask: ctx.mask,
- style: ParserStyle::global_style(),
- arg_info: ctx.arg_info,
- }
- }
-}
-
-impl<'a> From<crate::TagPhaseContext<'a>> for MatcherContext<'a> {
- fn from(ctx: crate::TagPhaseContext<'a>) -> Self {
- MatcherContext {
- args: ctx.args,
- mask: ctx.mask,
- style: ParserStyle::global_style(),
- arg_info: ctx.arg_info,
- }
- }
-}
-
-/// Checks whether the argument at index `idx` is already claimed (masked).
-///
-/// Returns `true` if `idx` is within the mask bounds and the mask value is non-zero,
-/// indicating the argument has been claimed by a previous matcher.
-///
-/// # Arguments
-///
-/// * `mask` - A byte slice where non-zero values indicate claimed arguments.
-/// * `idx` - The index to check in the mask.
-#[inline]
-#[must_use]
-pub fn is_masked(mask: &[u8], idx: usize) -> bool {
- idx < mask.len() && mask[idx] != 0
-}
-
-/// Builds a vector of [`MaskedArg`] from the given `PickerArgs` and mask.
-///
-/// Only arguments whose mask entry is `0` (i.e., available/not yet claimed) are included.
-/// Each resulting [`MaskedArg`] retains its original raw string and its index in the full
-/// argument list for later reference.
-///
-/// # Arguments
-///
-/// * `args` - The full set of parsed arguments.
-/// * `mask` - A byte slice where `0` means available and non-zero means already claimed.
-#[inline]
-#[must_use]
-pub fn build_masked_args<'a>(args: &'a PickerArgs, mask: &'a [u8]) -> Vec<MaskedArg<'a>> {
- let mut cidx = 0;
- args.iter()
- .filter_map(|r| {
- let idx = cidx;
- cidx += 1;
- // Include args where mask is 0 (available/not yet claimed).
- // mask[i] = 0 means available; mask[i] != 0 means already claimed.
- if is_masked(mask, idx) {
- None
- } else {
- Some(MaskedArg {
- raw: r,
- raw_idx: idx,
- })
- }
- })
- .collect()
-}
diff --git a/arg_picker/src/parselib/arg_matcher.rs b/arg_picker/src/parselib/arg_matcher.rs
deleted file mode 100644
index 24785d9..0000000
--- a/arg_picker/src/parselib/arg_matcher.rs
+++ /dev/null
@@ -1,143 +0,0 @@
-// Doc Not Optimize
-use crate::{
- matcher_needed::*,
- parselib::{build_possible_flags, seek_end_of_options},
-};
-
-/// `ArgMatcher` is used for parameters that carry a single value.
-///
-/// It handles two scenarios:
-///
-/// **Named** — `--name Alice` or `--name=Alice`.
-/// Each flag occurrence consumes **one** following argument as its value,
-/// regardless of what it is (even if it looks like a flag).
-/// This ensures the mask correctly claims the value slot; validation is
-/// the `Pickable`'s responsibility.
-///
-/// **Positional** — no flag prefix, matched by position.
-///
-/// # Examples
-///
-/// | Input | `on_match_one` | `on_match_all` |
-/// |-------|----------------|----------------|
-/// | `--name Alice` | `[0, 1]` (via Pickable tag) | `[0, 1]` |
-/// | `--name=Alice` | `[0]` | `[0]` |
-/// | `--val a --val b` | `[0, 1]` | `[0, 1, 2, 3]` |
-///
-/// Args after `--` are ignored.
-pub struct ArgMatcher;
-
-impl ArgMatcher {
- /// Check whether `raw` matches `flag_str`, optionally with an inline value
- /// separated by the style's value separator (`=` for Unix, `:` for PowerShell).
- #[inline]
- fn matches(raw: &str, flag_str: &str, case_sensitive: bool, sep: char) -> bool {
- let eq_match =
- |r: &str, f: &str| r.len() > f.len() && r.as_bytes().get(f.len()) == Some(&(sep as u8));
-
- if case_sensitive {
- raw == flag_str || (raw.starts_with(flag_str) && eq_match(raw, flag_str))
- } else {
- raw.eq_ignore_ascii_case(flag_str)
- || (raw.len() > flag_str.len()
- && raw[..flag_str.len()].eq_ignore_ascii_case(flag_str)
- && raw.as_bytes()[flag_str.len()] == sep as u8)
- }
- }
-
- /// Check whether the argument contains its value inline via the style's
- /// value separator (eq mode), so no extra mask slot is needed.
- #[inline]
- fn is_inline_value(raw: &str, flag_str: &str, sep: char) -> bool {
- raw.len() > flag_str.len() && raw.as_bytes().get(flag_str.len()) == Some(&(sep as u8))
- }
-}
-
-impl Matcher for ArgMatcher {
- fn on_match_one(
- args: &[MaskedArg],
- style: &ParserStyle,
- arg_info: &PickerArgInfo,
- ) -> Option<usize> {
- if arg_info.positional {
- return args.first().map(|a| a.raw_idx);
- }
-
- let possible_flags = build_possible_flags(style, arg_info);
- let end = seek_end_of_options(args, style);
- let sep = style.value_separator;
-
- for arg in args {
- if end.is_some_and(|e| arg.raw_idx >= e) {
- break;
- }
-
- let matched = possible_flags
- .iter()
- .any(|f| Self::matches(arg.raw, f, style.case_sensitive, sep));
- if matched {
- return Some(arg.raw_idx);
- }
- }
-
- None
- }
-
- fn on_match_all(
- args: &[MaskedArg],
- style: &ParserStyle,
- arg_info: &PickerArgInfo,
- ) -> Vec<usize> {
- if arg_info.positional {
- let end = seek_end_of_options(args, style);
- return args
- .iter()
- .take_while(|a| end.is_none_or(|e| a.raw_idx < e))
- .map(|a| a.raw_idx)
- .collect();
- }
-
- let possible_flags = build_possible_flags(style, arg_info);
- let end = seek_end_of_options(args, style);
- let sep = style.value_separator;
-
- let mut result = Vec::new();
- let mut i = 0;
- while i < args.len() {
- if end.is_some_and(|e| args[i].raw_idx >= e) {
- break;
- }
-
- let matched = possible_flags
- .iter()
- .any(|f| Self::matches(args[i].raw, f, style.case_sensitive, sep));
-
- if matched {
- let flag_str = possible_flags
- .iter()
- .find(|f| Self::matches(args[i].raw, f, style.case_sensitive, sep))
- .expect("already matched");
-
- result.push(args[i].raw_idx);
-
- if !Self::is_inline_value(args[i].raw, flag_str, sep) {
- if i + 1 < args.len()
- // Don't consume `--` (end-of-options marker) as a value.
- && end.is_none_or(|e| args[i + 1].raw_idx < e)
- {
- result.push(args[i + 1].raw_idx);
- i += 2;
- continue;
- }
- i += 1;
- continue;
- }
- i += 1;
- continue;
- }
- i += 1;
- }
-
- result
- }
-}
diff --git a/arg_picker/src/parselib/flag_matcher.rs b/arg_picker/src/parselib/flag_matcher.rs
deleted file mode 100644
index 339b1d8..0000000
--- a/arg_picker/src/parselib/flag_matcher.rs
+++ /dev/null
@@ -1,83 +0,0 @@
-// Doc Not Optimize
-use crate::{
- matcher_needed::*,
- parselib::{build_possible_flags, get_seeked_first, multi_seek_eq, seek_end_of_options},
-};
-
-/// `FlagMatcher` is used to match flags in command-line arguments.
-///
-/// Flags typically start with `-` or `--` (e.g., `-h`, `--help`),
-/// and do not carry additional values. This matcher is responsible for finding
-/// these flags in the argument list, taking into account that flags after `--`
-/// (end-of-options marker) should not be matched.
-pub struct FlagMatcher;
-
-impl Matcher for FlagMatcher {
- fn on_match_one(
- args: &[MaskedArg],
- style: &ParserStyle,
- arg_info: &PickerArgInfo,
- ) -> Option<usize> {
- let possible_flags = build_possible_flags(style, arg_info);
- let flag_refs: Vec<&str> = possible_flags.iter().map(String::as_str).collect();
- let end_of_options = seek_end_of_options(args, style);
-
- let result = get_seeked_first(multi_seek_eq(args, &flag_refs, style.case_sensitive));
-
- match (end_of_options, result) {
- (Some(end), Some(current)) if current > end => None,
- _ => result,
- }
- }
-
- fn on_match_all(
- args: &[MaskedArg],
- style: &ParserStyle,
- arg_info: &PickerArgInfo,
- ) -> Vec<usize> {
- let possible_flags = build_possible_flags(style, arg_info);
- single_pass_match_all(args, style, &possible_flags)
- }
-}
-
-/// Single-pass match: finds the `--` marker and matching flags in one iteration.
-fn single_pass_match_all(
- args: &[MaskedArg],
- style: &ParserStyle,
- possible_flags: &[String],
-) -> Vec<usize> {
- let flag_refs: Vec<&str> = possible_flags.iter().map(String::as_str).collect();
- let eoo = style.end_of_options;
- let case_sensitive = style.case_sensitive;
-
- let mut end_pos: Option<usize> = None;
- let mut matches: Vec<usize> = Vec::new();
-
- for arg in args {
- if end_pos.is_none() {
- let is_eoo = if case_sensitive {
- arg.raw == eoo
- } else {
- arg.raw.eq_ignore_ascii_case(eoo)
- };
- if is_eoo {
- end_pos = Some(arg.raw_idx);
- continue;
- }
- }
-
- // Only match flags before the end-of-options marker.
- if end_pos.is_none() {
- let is_matched = if case_sensitive {
- flag_refs.contains(&arg.raw)
- } else {
- flag_refs.iter().any(|s| arg.raw.eq_ignore_ascii_case(s))
- };
- if is_matched {
- matches.push(arg.raw_idx);
- }
- }
- }
-
- matches
-}
diff --git a/arg_picker/src/parselib/multi_arg_matcher.rs b/arg_picker/src/parselib/multi_arg_matcher.rs
deleted file mode 100644
index a251470..0000000
--- a/arg_picker/src/parselib/multi_arg_matcher.rs
+++ /dev/null
@@ -1,142 +0,0 @@
-// Doc Not Optimize
-use crate::{
- matcher_needed::*,
- parselib::{build_possible_flags, seek_end_of_options},
-};
-
-/// `MultiArgMatcher` matches a named flag and **all** consecutive arguments
-/// that follow it, stopping at the next flag, the `--` marker, or the end
-/// of the argument list.
-///
-/// This is the tag implementation for `Multi` and `GreedyMulti` types
-/// such as `Vec<String>` (`--files a.txt b.txt`).
-///
-/// # Behavior
-///
-/// | Input | `on_match_all` |
-/// |-------|----------------|
-/// | `--val a b --val d e` | `[0, 1, 2, 5, 6]` (two groups) |
-/// | `--val=1 2` | `[0, 1]` (eq mode + one extra value) |
-///
-/// Args after `--` are ignored.
-pub struct MultiArgMatcher;
-
-impl Matcher for MultiArgMatcher {
- fn on_match_one(
- args: &[MaskedArg],
- style: &ParserStyle,
- arg_info: &PickerArgInfo,
- ) -> Option<usize> {
- if arg_info.positional {
- return args.first().map(|a| a.raw_idx);
- }
-
- let possible_flags = build_possible_flags(style, arg_info);
- let end = seek_end_of_options(args, style);
- let sep = style.value_separator;
-
- for arg in args {
- if end.is_some_and(|e| arg.raw_idx >= e) {
- break;
- }
- let matched = possible_flags
- .iter()
- .any(|f| Self::flag_match(arg.raw, f, style.case_sensitive, sep));
- if matched {
- return Some(arg.raw_idx);
- }
- }
- None
- }
-
- fn on_match_all(
- args: &[MaskedArg],
- style: &ParserStyle,
- arg_info: &PickerArgInfo,
- ) -> Vec<usize> {
- if arg_info.positional {
- let end = seek_end_of_options(args, style);
- return args
- .iter()
- .take_while(|a| end.is_none_or(|e| a.raw_idx < e))
- .map(|a| a.raw_idx)
- .collect();
- }
-
- let possible_flags = build_possible_flags(style, arg_info);
- let end = seek_end_of_options(args, style);
- let sep = style.value_separator;
- let is_flag =
- |raw: &str| raw.starts_with(style.long_prefix) || raw.starts_with(style.short_prefix);
- let is_our_flag = |raw: &str| {
- possible_flags
- .iter()
- .any(|f| Self::flag_match(raw, f, style.case_sensitive, sep))
- };
-
- let mut result = Vec::new();
- let mut i = 0;
- while i < args.len() {
- if end.is_some_and(|e| args[i].raw_idx >= e) {
- break;
- }
-
- let matched = is_our_flag(args[i].raw);
-
- if matched {
- result.push(args[i].raw_idx);
-
- if Self::is_eq_match(args[i].raw, &possible_flags, style.case_sensitive, sep) {
- i += 1;
- while i < args.len()
- && end.is_none_or(|e| args[i].raw_idx < e)
- && !is_flag(args[i].raw)
- {
- result.push(args[i].raw_idx);
- i += 1;
- }
- continue;
- }
-
- i += 1;
- while i < args.len()
- && end.is_none_or(|e| args[i].raw_idx < e)
- && !is_flag(args[i].raw)
- {
- result.push(args[i].raw_idx);
- i += 1;
- }
- continue;
- }
- i += 1;
- }
-
- result
- }
-}
-
-impl MultiArgMatcher {
- #[inline]
- fn flag_match(raw: &str, flag_str: &str, case_sensitive: bool, sep: char) -> bool {
- let eq =
- |r: &str, f: &str| r.len() > f.len() && r.as_bytes().get(f.len()) == Some(&(sep as u8));
-
- if case_sensitive {
- raw == flag_str || (raw.starts_with(flag_str) && eq(raw, flag_str))
- } else {
- raw.eq_ignore_ascii_case(flag_str)
- || (raw.len() > flag_str.len()
- && raw[..flag_str.len()].eq_ignore_ascii_case(flag_str)
- && raw.as_bytes()[flag_str.len()] == sep as u8)
- }
- }
-
- #[inline]
- fn is_eq_match(raw: &str, flags: &[String], case_sensitive: bool, sep: char) -> bool {
- flags.iter().any(|f| {
- Self::flag_match(raw, f, case_sensitive, sep)
- && raw.len() > f.len()
- && raw.as_bytes().get(f.len()) == Some(&(sep as u8))
- })
- }
-}
diff --git a/arg_picker/src/parselib/pos_matcher.rs b/arg_picker/src/parselib/pos_matcher.rs
deleted file mode 100644
index a37c0df..0000000
--- a/arg_picker/src/parselib/pos_matcher.rs
+++ /dev/null
@@ -1,72 +0,0 @@
-// Doc Not Optimize
-use crate::{matcher_needed::*, parselib::seek_end_of_options};
-
-/// `PositionalMatcher` matches positional arguments — values not associated
-/// with any named flag.
-///
-/// # Rules
-///
-/// * Before `--`: skips any argument that starts with the style's long or short
-/// prefix (those belong to named matchers).
-/// * After `--`: takes **everything** — the `--` marker signals that all
-/// remaining values are positional, even if they look like flags.
-/// * Runs at the lowest priority (see [`PickerArgAttr::Positional`](crate::PickerArgAttr::Positional)).
-pub struct PositionalMatcher;
-
-impl PositionalMatcher {
- /// Check whether `raw` looks like a named flag (starts with a prefix).
- #[inline]
- fn is_flag_like(raw: &str, style: &ParserStyle) -> bool {
- raw.starts_with(style.long_prefix) || raw.starts_with(style.short_prefix)
- }
-}
-
-impl Matcher for PositionalMatcher {
- fn on_match_one(
- args: &[MaskedArg],
- style: &ParserStyle,
- _arg_info: &PickerArgInfo,
- ) -> Option<usize> {
- let end = seek_end_of_options(args, style);
-
- for arg in args {
- if end.is_some_and(|e| arg.raw_idx == e) {
- // Hit `--`: everything from here on is positional,
- // including the first arg after `--`.
- continue;
- }
- if end.is_some_and(|e| arg.raw_idx > e) {
- // After `--`: accept everything.
- return Some(arg.raw_idx);
- }
- // Before `--`: skip flag-like args.
- if !Self::is_flag_like(arg.raw, style) {
- return Some(arg.raw_idx);
- }
- }
-
- None
- }
-
- fn on_match_all(
- args: &[MaskedArg],
- style: &ParserStyle,
- _arg_info: &PickerArgInfo,
- ) -> Vec<usize> {
- let end = seek_end_of_options(args, style);
- let mut after_end = false;
- let mut result = Vec::new();
-
- for arg in args {
- if end.is_some_and(|e| arg.raw_idx == e) {
- after_end = true;
- continue;
- }
- if after_end || !Self::is_flag_like(arg.raw, style) {
- result.push(arg.raw_idx);
- }
- }
-
- result
- }
-}
diff --git a/arg_picker/src/parselib/single_matcher.rs b/arg_picker/src/parselib/single_matcher.rs
deleted file mode 100644
index b29513a..0000000
--- a/arg_picker/src/parselib/single_matcher.rs
+++ /dev/null
@@ -1,59 +0,0 @@
-// Doc Not Optimize
-use crate::TagPhaseContext;
-use crate::parselib::{ArgMatcher, Matcher, ParserStyle, PositionalMatcher};
-
-/// `SingleMatcher` is a composite matcher for single-value parameters.
-///
-/// It delegates to [`PositionalMatcher`] for positional args and
-/// [`ArgMatcher`] for named args, adding a guard: if a named flag
-/// captures only itself with no inline value (eq mode), the result
-/// is cleared so that [`Pickable::pick`](crate::Pickable::pick) receives `[]` → `NotFound`.
-///
-/// This is the standard tag implementation for all `Single`-type
-/// `Pickable` implementations (e.g., `String`, `i32`, `u64`).
-pub struct SingleMatcher;
-
-impl SingleMatcher {
- /// Match a single positional value or a named flag+value pair.
- ///
- /// For named args, only complete pairs (flag + value) are kept.
- /// Flag occurrences without a following value or inline separator
- /// are dropped so they remain available for other matchers.
- #[inline]
- #[must_use]
- pub fn tag(ctx: TagPhaseContext) -> Vec<usize> {
- if ctx.arg_info.positional {
- PositionalMatcher::match_one(ctx.into()).map_or_else(Vec::new, |i| vec![i])
- } else {
- let args = ctx.args;
- let positions = ArgMatcher::match_all(ctx.into());
- let sep = ParserStyle::global_style().value_separator;
-
- // Walk pairs: [flag, value, flag, value, ...]
- // Drop any flag that has no following value and no inline separator.
- let mut i = 0;
- let mut result = Vec::with_capacity(positions.len());
- while i < positions.len() {
- let flag_idx = positions[i];
- if let Some(raw) = args.get(flag_idx)
- && raw.contains(sep)
- {
- // Eq mode: value is inline, keep just the flag.
- result.push(flag_idx);
- i += 1;
- continue;
- }
- if i + 1 < positions.len() {
- // Pair: flag + value.
- result.push(flag_idx);
- result.push(positions[i + 1]);
- i += 2;
- } else {
- // Flag without value — drop it.
- i += 1;
- }
- }
- result
- }
- }
-}
diff --git a/arg_picker/src/parselib/style.rs b/arg_picker/src/parselib/style.rs
deleted file mode 100644
index 9b55158..0000000
--- a/arg_picker/src/parselib/style.rs
+++ /dev/null
@@ -1,244 +0,0 @@
-// Doc Not Optimize
-use std::sync::OnceLock;
-use std::sync::atomic::{AtomicBool, Ordering};
-
-use crate::parselib::ParserStyleNamingCase::{Kebab, Pascal};
-
-/// Defines the style of command-line argument parsing (prefixes, separators, etc.).
-#[derive(Clone, Copy, PartialEq, Eq)]
-pub struct ParserStyle<'a> {
- /// End-of-options marker (e.g., `--`)
- pub end_of_options: &'a str,
-
- /// Prefix for long options (e.g., `--` or `/`)
- pub long_prefix: &'a str,
-
- /// Prefix for short options (e.g., `-` or `/`)
- pub short_prefix: &'a str,
-
- /// Prefix for combined short flags (e.g., `-abc`)
- pub combine_prefix: &'a str,
-
- /// Separator between name and value (e.g., `=` or `:`)
- pub value_separator: char,
-
- /// Whether option names are case-sensitive
- pub case_sensitive: bool,
-
- /// Whether combining short flags is allowed (e.g., `-abc` for `-a -b -c`)
- pub allow_combine: bool,
-
- /// Naming case
- pub naming_case: ParserStyleNamingCase,
-}
-
-impl<'a> ParserStyle<'a> {
- /// Formats a flag (short or long) into a full command-line option string.
- ///
- /// This method takes any type that can be converted into a `FlagStr` and produces
- /// a complete option string by prepending the appropriate prefix.
- ///
- /// # Examples
- ///
- /// ```
- /// # use arg_picker::parselib::{ParserStyle, FlagStr, UNIX_STYLE};
- /// let style = &UNIX_STYLE;
- ///
- /// assert_eq!(style.flag_string('v'), "-v");
- /// assert_eq!(style.flag_string("verbose"), "--verbose");
- /// ```
- ///
- /// # Parameters
- ///
- /// * `flag` - A value that can be converted to `FlagStr`, either a `char` for short flags
- /// or a `&str` for long flags.
- ///
- /// # Returns
- ///
- /// A `String` with the prefix and the flag name combined.
- #[must_use]
- #[inline]
- pub fn flag_string<F>(&self, flag: F) -> String
- where
- F: Into<FlagStr<'a>>,
- {
- match flag.into() {
- FlagStr::Short(short) => format!("{}{}", self.short_prefix, short),
- FlagStr::Long(long) => format!("{}{}", self.long_prefix, long),
- }
- }
-}
-
-/// Represents a flag name for command-line argument parsing.
-///
-/// This enum can hold either a short flag (a single character, e.g., `'v'` for `-v`)
-/// or a long flag (a string, e.g., `"verbose"` for `--verbose`).
-///
-/// # Examples
-///
-/// ```
-/// use arg_picker::parselib::FlagStr;
-///
-/// let short: FlagStr = 'v'.into();
-/// let long: FlagStr = "verbose".into();
-/// ```
-pub enum FlagStr<'a> {
- /// A short flag represented by a single character.
- Short(char),
- /// A long flag represented by a string slice.
- Long(&'a str),
-}
-
-impl From<char> for FlagStr<'_> {
- /// Converts a single character into a `FlagStr::Short`.
- fn from(c: char) -> Self {
- FlagStr::Short(c)
- }
-}
-
-impl<'a> From<&'a str> for FlagStr<'a> {
- /// Converts a string slice into a `FlagStr::Long`.
- fn from(s: &'a str) -> Self {
- FlagStr::Long(s)
- }
-}
-
-impl<'a> From<&'a String> for FlagStr<'a> {
- /// Converts a reference to a `String` into a `FlagStr::Long`.
- fn from(s: &'a String) -> Self {
- FlagStr::Long(s.as_str())
- }
-}
-
-/// Defines the naming convention for command-line option names.
-///
-/// Each variant represents a different case format that can be applied
-/// to option names (e.g., long option names) during parsing or generation.
-///
-/// # Examples
-///
-/// ```
-/// # use arg_picker::IntoPicker;
-/// use arg_picker::parselib::ParserStyleNamingCase;
-///
-/// let case = ParserStyleNamingCase::Kebab;
-/// assert_eq!(
-/// case.convert("brew_coffee".to_string()),
-/// "brew-coffee".to_string()
-/// );
-/// ```
-#[repr(u8)]
-#[derive(Default, Clone, Copy, PartialEq, Eq)]
-pub enum ParserStyleNamingCase {
- /// `snake_case` format: words are separated by underscores, all lowercase.
- ///
- /// Example: `brew_coffee`
- #[default]
- Snake,
- /// `camelCase` format: first word is lowercase, subsequent words are capitalized.
- ///
- /// Example: `brewCoffee`
- Camel,
- /// `PascalCase` format: every word starts with an uppercase letter.
- ///
- /// Example: `BrewCoffee`
- Pascal,
- /// `kebab-case` format: words are separated by hyphens, all lowercase.
- ///
- /// Example: `brew-coffee`
- Kebab,
- /// `dot.case` format: words are separated by dots, all lowercase.
- ///
- /// Example: `brew.coffee`
- Dot,
-}
-
-impl ParserStyleNamingCase {
- /// Converts the input string `s` to the naming case represented by this variant.
- ///
- /// This method takes any type `S` that can be converted into a `String` and
- /// produced from a `String`, applies the corresponding case transformation,
- /// and returns the result.
- ///
- /// # Examples
- ///
- /// ```
- /// use arg_picker::parselib::ParserStyleNamingCase;
- ///
- /// let camel = ParserStyleNamingCase::Camel;
- /// assert_eq!(camel.convert("brew_coffee".to_string()), "brewCoffee");
- ///
- /// let kebab = ParserStyleNamingCase::Kebab;
- /// assert_eq!(kebab.convert("BrewCoffee".to_string()), "brew-coffee");
- /// ```
- pub fn convert<S>(&self, s: S) -> S
- where
- S: Into<String> + From<String>,
- {
- match self {
- Self::Camel => just_fmt::camel_case!(s.into()).into(),
- Self::Pascal => just_fmt::pascal_case!(s.into()).into(),
- Self::Kebab => just_fmt::kebab_case!(s.into()).into(),
- Self::Snake => just_fmt::snake_case!(s.into()).into(),
- Self::Dot => just_fmt::dot_case!(s.into()).into(),
- }
- }
-}
-
-/// Unix-like style (e.g., `--verbose`, `-v`, `--name=value`)
-pub const UNIX_STYLE: ParserStyle = ParserStyle {
- end_of_options: "--",
- long_prefix: "--",
- short_prefix: "-",
- combine_prefix: "-",
- value_separator: '=',
- case_sensitive: true,
- allow_combine: true,
- naming_case: Kebab,
-};
-
-/// PowerShell style (e.g., `-Verbose`, `-Name:value`)
-pub const POWERSHELL_STYLE: ParserStyle = ParserStyle {
- end_of_options: "--",
- long_prefix: "-",
- short_prefix: "-",
- combine_prefix: "-",
- value_separator: ':',
- case_sensitive: false,
- allow_combine: false,
- naming_case: Pascal,
-};
-
-/// Windows-style command-line (e.g., `/Verbose`, `/Name:value`)
-pub const WINDOWS_STYLE: ParserStyle = ParserStyle {
- end_of_options: "--",
- long_prefix: "/",
- short_prefix: "/",
- combine_prefix: "/",
- value_separator: ':',
- case_sensitive: false,
- allow_combine: false,
- naming_case: Pascal,
-};
-
-static GLOBAL_STYLE: OnceLock<ParserStyle<'static>> = OnceLock::new();
-static GLOBAL_STYLE_SET: AtomicBool = AtomicBool::new(false);
-
-impl ParserStyle<'_> {
- /// Sets the global parser style.
- ///
- /// This function can only be called once. Subsequent calls will have no effect.
- /// The style is stored as a static reference; the provided style must be a static
- /// constant (e.g., `&'static ParserStyle`). Use the built-in constants like
- /// `UNIX_STYLE`, `POWERSHELL_STYLE`, or `WINDOWS_STYLE`.
- pub fn set_global_style(style: &'static ParserStyle<'static>) {
- if !GLOBAL_STYLE_SET.load(Ordering::Acquire) && GLOBAL_STYLE.set(*style).is_ok() {
- GLOBAL_STYLE_SET.store(true, Ordering::Release);
- }
- }
-
- /// Returns the global parser style, falling back to `UNIX_STYLE` if not set.
- pub fn global_style() -> &'static ParserStyle<'static> {
- GLOBAL_STYLE.get().unwrap_or(&UNIX_STYLE)
- }
-}
diff --git a/arg_picker/src/parselib/utils.rs b/arg_picker/src/parselib/utils.rs
deleted file mode 100644
index 97f64df..0000000
--- a/arg_picker/src/parselib/utils.rs
+++ /dev/null
@@ -1,273 +0,0 @@
-// Doc Not Optimize
-use crate::{
- PickerArgInfo,
- parselib::{MaskedArg, ParserStyle},
-};
-
-/// Builds a list of possible flag strings for the given argument info
-///
-/// This function generates formatted flag strings (e.g., `-h`, `--help`) from the short flag,
-/// long flag, and any aliases defined in the argument info. The long flag and alias names
-/// are converted according to the style's naming case convention before being formatted.
-#[must_use]
-pub fn build_possible_flags(style: &ParserStyle, arg_info: &PickerArgInfo) -> Vec<String> {
- let mut possible_flags = vec![];
-
- if let Some(short) = arg_info.short {
- possible_flags.push(style.flag_string(short));
- }
-
- if let Some(long) = arg_info.long {
- let converted = style.naming_case.convert(long.to_string());
- possible_flags.push(style.flag_string(&converted));
- }
-
- if let Some(aliases) = &arg_info.alias {
- for alias in aliases {
- let converted = style.naming_case.convert(alias.to_string());
- possible_flags.push(style.flag_string(&converted));
- }
- }
-
- possible_flags
-}
-
-/// Extract a single value from the raw strings tagged by [`SingleMatcher`](crate::parselib::SingleMatcher).
-///
-/// Returns `None` if no value is available (empty slice),
-/// the inline value after the style separator if present (eq mode),
-/// or the value directly (positional or flag-following).
-///
-/// This is the standard `pick` helper for all `Single`-type
-/// [`Pickable`](crate::Pickable) implementations.
-#[must_use]
-pub fn seek_single<'a>(raw_strs: &'a [&'a str]) -> Option<&'a str> {
- match raw_strs.len() {
- 0 => None,
- 1 => {
- let s = raw_strs[0];
- let sep = ParserStyle::global_style().value_separator;
- s.rfind(sep).map_or(Some(s), |pos| Some(&s[pos + 1..]))
- }
- _ => Some(raw_strs[1]),
- }
-}
-
-/// Seeks the index of the end-of-options marker (`--`) in the argument list.
-///
-/// This function searches for the standard end-of-options separator (`--`)
-/// in the given argument list, respecting the parser's style settings
-/// (e.g., case sensitivity). The end-of-options marker indicates that all
-/// subsequent arguments should be treated as positional arguments, not flags.
-#[must_use]
-pub fn seek_end_of_options(args: &[MaskedArg], style: &ParserStyle) -> Option<usize> {
- args.iter()
- .find(|arg| {
- if style.case_sensitive {
- arg.raw == style.end_of_options
- } else {
- arg.raw.eq_ignore_ascii_case(style.end_of_options)
- }
- })
- .map(|arg| arg.raw_idx)
-}
-
-/// Seeks arguments in `args` that are exactly equal to the given `string`.
-///
-/// Returns the indices of matching arguments.
-#[must_use]
-#[inline]
-pub fn seek_eq(args: &[MaskedArg], string: &str, case_sensitive: bool) -> Vec<usize> {
- args.iter()
- .filter(|arg| {
- if case_sensitive {
- arg.raw == string
- } else {
- arg.raw.eq_ignore_ascii_case(string)
- }
- })
- .map(|arg| arg.raw_idx)
- .collect()
-}
-
-/// Seeks arguments in `args` that contain the given `string` as a substring.
-///
-/// Returns the indices of matching arguments.
-#[must_use]
-#[inline]
-pub fn seek_contains(args: &[MaskedArg], string: &str, case_sensitive: bool) -> Vec<usize> {
- args.iter()
- .filter(|arg| {
- if case_sensitive {
- arg.raw.contains(string)
- } else {
- arg.raw.to_lowercase().contains(&string.to_lowercase())
- }
- })
- .map(|arg| arg.raw_idx)
- .collect()
-}
-
-/// Seeks arguments in `args` that start with the given `string`.
-///
-/// Returns the indices of matching arguments.
-#[must_use]
-#[inline]
-pub fn seek_start_with(args: &[MaskedArg], string: &str, case_sensitive: bool) -> Vec<usize> {
- args.iter()
- .filter(|arg| {
- if case_sensitive {
- arg.raw.starts_with(string)
- } else {
- arg.raw.to_lowercase().starts_with(&string.to_lowercase())
- }
- })
- .map(|arg| arg.raw_idx)
- .collect()
-}
-
-/// Seeks arguments in `args` that end with the given `string`.
-///
-/// Returns the indices of matching arguments.
-#[must_use]
-#[inline]
-pub fn seek_end_with(args: &[MaskedArg], string: &str, case_sensitive: bool) -> Vec<usize> {
- args.iter()
- .filter(|arg| {
- if case_sensitive {
- arg.raw.ends_with(string)
- } else {
- arg.raw.to_lowercase().ends_with(&string.to_lowercase())
- }
- })
- .map(|arg| arg.raw_idx)
- .collect()
-}
-
-/// Seeks arguments in `args` that are exactly equal to any of the given `strings`.
-///
-/// Returns the indices of matching arguments.
-#[must_use]
-#[inline]
-pub fn multi_seek_eq(args: &[MaskedArg], strings: &[&str], case_sensitive: bool) -> Vec<usize> {
- args.iter()
- .filter(|arg| {
- if case_sensitive {
- strings.contains(&arg.raw)
- } else {
- strings.iter().any(|s| arg.raw.eq_ignore_ascii_case(s))
- }
- })
- .map(|arg| arg.raw_idx)
- .collect()
-}
-
-/// Seeks arguments in `args` that contain any of the given `strings` as a substring.
-///
-/// Returns the indices of matching arguments.
-#[must_use]
-#[inline]
-pub fn multi_seek_contains(
- args: &[MaskedArg],
- strings: &[&str],
- case_sensitive: bool,
-) -> Vec<usize> {
- args.iter()
- .filter(|arg| {
- if case_sensitive {
- strings.iter().any(|s| arg.raw.contains(s))
- } else {
- let lower_raw = arg.raw.to_lowercase();
- strings
- .iter()
- .any(|s| lower_raw.contains(&s.to_lowercase()))
- }
- })
- .map(|arg| arg.raw_idx)
- .collect()
-}
-
-/// Seeks arguments in `args` that start with any of the given `strings`.
-///
-/// Returns the indices of matching arguments.
-#[must_use]
-#[inline]
-pub fn multi_seek_start_with(
- args: &[MaskedArg],
- strings: &[&str],
- case_sensitive: bool,
-) -> Vec<usize> {
- args.iter()
- .filter(|arg| {
- if case_sensitive {
- strings.iter().any(|s| arg.raw.starts_with(s))
- } else {
- let lower_raw = arg.raw.to_lowercase();
- strings
- .iter()
- .any(|s| lower_raw.starts_with(&s.to_lowercase()))
- }
- })
- .map(|arg| arg.raw_idx)
- .collect()
-}
-
-/// Seeks arguments in `args` that end with any of the given `strings`.
-///
-/// Returns the indices of matching arguments.
-#[must_use]
-#[inline]
-pub fn multi_seek_end_with(
- args: &[MaskedArg],
- strings: &[&str],
- case_sensitive: bool,
-) -> Vec<usize> {
- args.iter()
- .filter(|arg| {
- if case_sensitive {
- strings.iter().any(|s| arg.raw.ends_with(s))
- } else {
- let lower_raw = arg.raw.to_lowercase();
- strings
- .iter()
- .any(|s| lower_raw.ends_with(&s.to_lowercase()))
- }
- })
- .map(|arg| arg.raw_idx)
- .collect()
-}
-
-/// Converts a `&Vec<String>` into a `Vec<&str>` by borrowing each string's slice.
-///
-/// This is useful for converting owned `String` vectors into borrowed `&str` slices
-/// for functions that take `&[&str]` or similar parameters.
-#[must_use]
-#[inline]
-#[doc(hidden)]
-pub fn vec_string_to_vec_str(input: &[String]) -> Vec<&str> {
- input.iter().map(String::as_str).collect()
-}
-
-/// Converts a `&Vec<String>` into a `Vec<&str>` by borrowing each string's slice.
-///
-/// This is useful for converting owned `String` vectors into borrowed `&str` slices
-/// for functions that take `&[&str]` or similar parameters.
-#[macro_export]
-#[doc(hidden)]
-macro_rules! vec_string_slice {
- ($v:expr) => {
- $v.iter()
- .map(|s| s.as_str())
- .collect::<Vec<&str>>()
- .as_slice()
- };
-}
-
-/// Gets the first element from a vector of seek results, if any.
-///
-/// Returns `Some(index)` if the vector is non-empty, otherwise `None`.
-#[must_use]
-#[inline]
-pub fn get_seeked_first(seeked: Vec<usize>) -> Option<usize> {
- seeked.into_iter().next()
-}
diff --git a/arg_picker/src/pickable.rs b/arg_picker/src/pickable.rs
deleted file mode 100644
index 014bbc2..0000000
--- a/arg_picker/src/pickable.rs
+++ /dev/null
@@ -1,92 +0,0 @@
-// Doc Not Optimize
-use crate::{PickerArg, PickerArgAttr, PickerArgInfo, PickerArgResult, PickerArgs};
-
-mod single_pickable;
-pub use single_pickable::*;
-
-mod multi_pickable;
-pub use multi_pickable::*;
-
-/// `Pickable` trait defines how to parse a type instance from command-line arguments.
-///
-/// This trait is the core abstraction of the `Picker` argument parsing system, dividing the
-/// parsing process into two phases:
-///
-/// 1. **Tag phase ([`Pickable::tag`])**: Determines which argument positions the `Pickable` needs to handle.
-/// 2. **Pick phase ([`Pickable::pick`])**: Converts the raw strings at the tagged positions into the actual type.
-///
-/// Types implementing this trait must also implement [`Default`], so that a default value
-/// can be used as a fallback when parsing fails.
-///
-/// # Type Parameters
-///
-/// * `'a` - Lifetime parameter, used to associate references in [`PickerArg`].
-pub trait Pickable<'a>
-where
- Self: Sized,
-{
- /// Returns the parse-order attribute of this flag.
- ///
- /// This attribute is used to inform the parser about the parse order
- /// between different `Pickable` types.
- /// See [`PickerArgAttr`] for specific ordering definitions.
- ///
- /// # Parameters
- ///
- /// * `flag` - The current flag instance, which contains a reference to `Self`.
- ///
- /// # Returns
- ///
- /// Returns a [`PickerArgAttr`] describing the parse-order attribute of this flag.
- fn get_attr(flag: &'a PickerArg<'a, Self>) -> PickerArgAttr;
-
- /// Tag phase: Determines which argument positions the `Pickable` needs to handle.
- ///
- /// This function receives a [`TagPhaseContext`] containing argument context information.
- /// During this phase, the parser invokes each `Pickable` and collects the position indices
- /// they return, in order to determine which arguments to parse later.
- ///
- /// # Parameters
- ///
- /// * `ctx` - The tag phase context, containing argument information, all parameters of the
- /// current Picker, and an availability mask.
- ///
- /// # Returns
- ///
- /// Returns a `Vec<usize>` representing the indices of the arguments in the argument list
- /// that this `Pickable` needs to handle.
- fn tag(ctx: TagPhaseContext) -> Vec<usize>;
-
- /// Pick phase: Converts the raw string arguments tagged during the `tag` phase into
- /// the actual expected type.
- ///
- /// This function receives a slice of the raw strings that were tagged in the `tag` step
- /// and converts them into an instance of `Self`.
- ///
- /// # Parameters
- ///
- /// * `raw_strs` - A slice of strings containing the raw argument values to parse.
- ///
- /// # Returns
- ///
- /// Returns [`PickerArgResult<Self>`], i.e., the `Self` instance on success, or an appropriate
- /// error message on failure.
- fn pick(raw_strs: &[&str]) -> PickerArgResult<Self>;
-}
-
-/// Tag phase context, providing the necessary argument and state information for
-/// [`Pickable::tag`].
-pub struct TagPhaseContext<'a> {
- /// Argument information describing the structure and metadata of the argument
- /// to be parsed.
- pub arg_info: &'a PickerArgInfo<'a>,
-
- /// A read-only list of all arguments in the current [`Picker`](crate::Picker).
- pub args: &'a PickerArgs<'a>,
-
- /// Mask indicating which argument positions have already been claimed.
- ///
- /// For example, if the mask is `[0, 0, 1, 0]`, then the argument at index `2`
- /// has already been tagged by another `Pickable`.
- pub mask: &'a [u8],
-}
diff --git a/arg_picker/src/pickable/multi_pickable.rs b/arg_picker/src/pickable/multi_pickable.rs
deleted file mode 100644
index 81d1571..0000000
--- a/arg_picker/src/pickable/multi_pickable.rs
+++ /dev/null
@@ -1,93 +0,0 @@
-// Doc Not Optimize
-use crate::{
- Pickable, PickerArg, PickerArgAttr, PickerArgResult, SinglePickable, TagPhaseContext,
- matcher_needed::Matcher,
- parselib::{MultiArgMatcher, ParserStyle},
-};
-
-/// Boundary check for multi-value positional parameters.
-///
-/// Determines whether a raw string marks the end of a multi-value
-/// parameter's input range.
-pub trait BoundaryCheck {
- /// Returns `true` if `raw` indicates a boundary (i.e., the start of
- /// a new parameter), stopping greedy collection.
- fn check_boundary(raw: &str) -> bool;
-}
-
-/// Trait for multi-value parameters.
-///
-/// Implementors define how a sequence of raw strings is converted into
-/// a single value, with an associated [`BoundaryCheck`] to control where
-/// collection stops.
-pub trait MultiPickableWithBoundary: Sized {
- /// The boundary checker type that determines when to stop consuming
- /// positional arguments.
- type Checker: BoundaryCheck;
-
- /// Parse and collect multiple raw string values into `Self`.
- ///
- /// The caller should stop passing additional items once the
- /// associated [`Checker`](Self::Checker) signals a boundary.
- fn pick_multi(raw: Vec<String>) -> PickerArgResult<Self>;
-}
-
-/// Marker: unit type that always accepts — no boundary.
-pub struct NoBoundary;
-
-impl BoundaryCheck for NoBoundary {
- #[inline]
- fn check_boundary(_raw: &str) -> bool {
- false
- }
-}
-
-/// `Vec<T>` is greedy — it takes everything with `NoBoundary`.
-impl<T: SinglePickable> MultiPickableWithBoundary for Vec<T> {
- type Checker = NoBoundary;
-
- fn pick_multi(raw: Vec<String>) -> PickerArgResult<Self> {
- let mut result = Self::with_capacity(raw.len());
- for s in &raw {
- match T::pick_single(Some(s)) {
- PickerArgResult::Parsed(v) => result.push(v),
- PickerArgResult::NotFound => return PickerArgResult::NotFound,
- PickerArgResult::Unparsed => {}
- }
- }
- PickerArgResult::Parsed(result)
- }
-}
-
-/// If the first raw string looks like a named flag (starts with the
-/// style's long or short prefix), strip it — it's the flag, not a value.
-fn strip_flag<'a>(raw_strs: &'a [&'a str]) -> &'a [&'a str] {
- if let Some(first) = raw_strs.first() {
- let style = ParserStyle::global_style();
- if first.starts_with(style.long_prefix) || first.starts_with(style.short_prefix) {
- return &raw_strs[1..];
- }
- }
- raw_strs
-}
-
-// Pickable impl for Vec<T>
-
-impl<'a, T> Pickable<'a> for Vec<T>
-where
- T: SinglePickable,
-{
- fn get_attr(flag: &'a PickerArg<'a, Self>) -> PickerArgAttr {
- PickerArgAttr::positional_or_multi(flag)
- }
-
- fn tag(ctx: TagPhaseContext) -> Vec<usize> {
- MultiArgMatcher::match_all(ctx.into())
- }
-
- fn pick(raw_strs: &[&str]) -> PickerArgResult<Self> {
- let strs = strip_flag(raw_strs);
- let owned: Vec<String> = strs.iter().map(|&s| s.to_string()).collect();
- <Self as MultiPickableWithBoundary>::pick_multi(owned)
- }
-}
diff --git a/arg_picker/src/pickable/single_pickable.rs b/arg_picker/src/pickable/single_pickable.rs
deleted file mode 100644
index d916bcc..0000000
--- a/arg_picker/src/pickable/single_pickable.rs
+++ /dev/null
@@ -1,70 +0,0 @@
-// Doc Not Optimize
-use crate::{Pickable, PickerArg, PickerArgAttr, PickerArgResult, TagPhaseContext};
-
-/// `SinglePickable` trait defines how to parse a type from a single command-line argument.
-///
-/// This trait provides a simplified interface for types that consume exactly one argument value.
-/// It is automatically implemented by the blanket `impl` of [`Pickable`], so types implementing
-/// `SinglePickable` will work with the full `Pickable` argument parsing system.
-///
-/// Additionally, `Option<S>` where `S: SinglePickable` also implements [`Pickable`], allowing
-/// optional arguments to be parsed naturally.
-///
-/// # Type Parameters
-///
-/// * `Self` - The type to be parsed from a single argument string.
-pub trait SinglePickable
-where
- Self: Sized,
-{
- /// Parse a single optional string value into an instance of `Self`.
- ///
- /// # Parameters
- ///
- /// * `str` - An `Option<&str>` representing the raw argument value. If `None`,
- /// it indicates that no argument value was provided (e.g., for flag-like arguments).
- ///
- /// # Returns
- ///
- /// Returns [`PickerArgResult<Self>`], i.e., the parsed `Self` instance on success,
- /// or an appropriate error message on failure.
- fn pick_single(str: Option<&str>) -> PickerArgResult<Self>;
-}
-
-impl<'a, S> Pickable<'a> for S
-where
- S: SinglePickable,
-{
- fn get_attr(flag: &'a PickerArg<'a, Self>) -> PickerArgAttr {
- PickerArgAttr::positional_or_single(flag)
- }
-
- fn tag(ctx: TagPhaseContext) -> Vec<usize> {
- crate::parselib::SingleMatcher::tag(ctx)
- }
-
- fn pick(raw_strs: &[&str]) -> PickerArgResult<Self> {
- Self::pick_single(crate::parselib::seek_single(raw_strs))
- }
-}
-
-impl<'a, S> Pickable<'a> for Option<S>
-where
- S: SinglePickable,
-{
- fn get_attr(flag: &'a PickerArg<'a, Self>) -> PickerArgAttr {
- PickerArgAttr::positional_or_single(flag)
- }
-
- fn tag(ctx: TagPhaseContext) -> Vec<usize> {
- crate::parselib::SingleMatcher::tag(ctx)
- }
-
- fn pick(raw_strs: &[&str]) -> PickerArgResult<Self> {
- match S::pick(raw_strs) {
- PickerArgResult::Unparsed => PickerArgResult::Unparsed,
- PickerArgResult::Parsed(r) => PickerArgResult::Parsed(Some(r)),
- PickerArgResult::NotFound => PickerArgResult::Parsed(None),
- }
- }
-}
diff --git a/arg_picker/src/picker.rs b/arg_picker/src/picker.rs
deleted file mode 100644
index e8256ce..0000000
--- a/arg_picker/src/picker.rs
+++ /dev/null
@@ -1,543 +0,0 @@
-// Doc Not Optimize
-use std::{marker::PhantomData, ops::Index};
-
-mod parse;
-
-mod patterns;
-pub use patterns::*;
-
-mod result;
-pub use result::*;
-
-use crate::{Pickable, PickerArg, PickerArgResult};
-
-#[doc = include_str!("../README.md")]
-pub struct Picker<'a, Route = ()> {
- pub(crate) route_phantom: PhantomData<Route>,
-
- /// Internal arguments of Picker
- pub(crate) args: PickerArgs<'a>,
-}
-
-impl<'a> Picker<'a> {
- /// Creates a new `Picker` from the command-line arguments (excluding the program name).
- ///
- /// This is equivalent to calling `std::env::args().skip(1)`, which
- /// collects all arguments passed to the program except the first one
- /// (the executable path).
- #[must_use]
- pub fn from_args() -> Self {
- Self::from_args_skip(1)
- }
-
- /// Creates a new `Picker` from the command-line arguments, skipping the
- /// first `skip` entries.
- ///
- /// This method is useful when you want more control over which arguments
- /// are included. For example, pass `skip = 2` to skip both the program
- /// name and the first argument.
- #[must_use]
- pub fn from_args_skip(skip: usize) -> Self {
- let args = std::env::args().skip(skip).collect::<Vec<String>>();
- Picker {
- route_phantom: PhantomData,
- args: PickerArgs::Owned(args),
- }
- }
-
- /// Changes the route (phantom type parameter) of the `Picker`.
- ///
- /// This method allows converting a `Picker` from one route type to another,
- /// while preserving the same underlying arguments. The route type is typically
- /// used to distinguish different parsing contexts or to carry compile-time
- /// state information through the picking chain.
- #[must_use]
- pub fn with_route<NewRoute>(self) -> Picker<'a, NewRoute>
- where
- Self: Sized,
- {
- Picker {
- route_phantom: PhantomData,
- args: self.args,
- }
- }
-}
-
-/// Internal arguments of Picker
-///
-/// - `Slice` - borrowed slice of string slices
-/// - `Vec` - owned vector of borrowed string slices
-/// - `Owned` - owned vector of owned strings
-pub enum PickerArgs<'a> {
- /// Borrowed slice of string slices
- Slice(&'a [&'a str]),
- /// Owned vector of borrowed string slices
- Vec(Vec<&'a str>),
- /// Owned vector of owned strings
- Owned(Vec<String>),
-}
-
-impl<'a> From<PickerArgs<'a>> for Vec<String> {
- fn from(value: PickerArgs<'a>) -> Self {
- match value {
- PickerArgs::Slice(items) => items.iter().map(ToString::to_string).collect(),
- PickerArgs::Vec(items) => items.into_iter().map(ToString::to_string).collect(),
- PickerArgs::Owned(items) => items,
- }
- }
-}
-
-impl<'a> From<&'a PickerArgs<'a>> for Vec<&'a str> {
- fn from(value: &'a PickerArgs<'a>) -> Self {
- match value {
- PickerArgs::Slice(items) => items.to_vec(),
- PickerArgs::Vec(items) => items.clone(),
- PickerArgs::Owned(items) => items.iter().map(String::as_str).collect(),
- }
- }
-}
-
-impl Default for PickerArgs<'_> {
- fn default() -> Self {
- Self::Vec(vec![])
- }
-}
-
-impl<'a> PickerArgs<'a> {
- /// Returns the number of arguments.
- #[must_use]
- pub const fn len(&self) -> usize {
- match self {
- PickerArgs::Slice(items) => items.len(),
- PickerArgs::Vec(items) => items.len(),
- PickerArgs::Owned(items) => items.len(),
- }
- }
-
- /// Returns `true` if there are no arguments.
- #[must_use]
- pub const fn is_empty(&self) -> bool {
- self.len() == 0
- }
-
- /// Returns an iterator over the arguments, yielding `&str` values.
- #[must_use]
- pub fn iter(&'a self) -> PickerIter<'a> {
- match self {
- PickerArgs::Slice(items) => PickerIter::Slice(items.iter()),
- PickerArgs::Vec(items) => PickerIter::Vec(items.iter()),
- PickerArgs::Owned(items) => PickerIter::Owned(items.iter()),
- }
- }
-
- /// Returns a reference to the argument at `index`, if it exists.
- #[must_use]
- pub fn get(&self, index: usize) -> Option<&str> {
- match self {
- PickerArgs::Slice(items) => items.get(index).copied(),
- PickerArgs::Vec(items) => items.get(index).copied(),
- PickerArgs::Owned(items) => items.get(index).map(String::as_str),
- }
- }
-}
-
-impl Index<usize> for PickerArgs<'_> {
- type Output = str;
-
- fn index(&self, index: usize) -> &Self::Output {
- match self {
- PickerArgs::Slice(items) => items[index],
- PickerArgs::Vec(items) => items[index],
- PickerArgs::Owned(items) => &items[index],
- }
- }
-}
-
-impl<'a> IntoIterator for &'a PickerArgs<'a> {
- type Item = &'a str;
- type IntoIter = PickerIter<'a>;
-
- fn into_iter(self) -> Self::IntoIter {
- match self {
- PickerArgs::Slice(items) => PickerIter::Slice(items.iter()),
- PickerArgs::Vec(items) => PickerIter::Vec(items.iter()),
- PickerArgs::Owned(items) => PickerIter::Owned(items.iter()),
- }
- }
-}
-
-impl<'a, Route> From<PickerArgs<'a>> for Picker<'a, Route> {
- fn from(args: PickerArgs<'a>) -> Self {
- Picker {
- route_phantom: PhantomData,
- args,
- }
- }
-}
-
-impl<'a, Route> From<&'a [&'a str]> for Picker<'a, Route> {
- fn from(value: &'a [&'a str]) -> Self {
- Picker {
- route_phantom: PhantomData,
- args: PickerArgs::Slice(value),
- }
- }
-}
-
-impl<'a, Route> From<Vec<&'a str>> for Picker<'a, Route> {
- fn from(value: Vec<&'a str>) -> Self {
- Picker {
- route_phantom: PhantomData,
- args: PickerArgs::Vec(value),
- }
- }
-}
-
-impl<Route> From<Vec<String>> for Picker<'_, Route> {
- fn from(value: Vec<String>) -> Self {
- Picker {
- route_phantom: PhantomData,
- args: PickerArgs::Owned(value),
- }
- }
-}
-
-impl<'a, Route> Picker<'a, Route> {
- /// Returns a reference to the internal `PickerArgs`.
- #[must_use]
- pub const fn args(&self) -> &PickerArgs<'a> {
- &self.args
- }
-
- /// Returns a mutable reference to the internal `PickerArgs`.
- pub const fn args_mut(&mut self) -> &mut PickerArgs<'a> {
- &mut self.args
- }
-
- /// Consumes `self` and returns the internal `PickerArgs`.
- #[must_use]
- pub fn into_args(self) -> PickerArgs<'a> {
- self.args
- }
-
- /// Returns the number of arguments.
- #[must_use]
- pub const fn len(&self) -> usize {
- self.args.len()
- }
-
- /// Returns `true` if there are no arguments.
- #[must_use]
- pub const fn is_empty(&self) -> bool {
- self.args.is_empty()
- }
-
- /// Returns an iterator over the arguments, yielding `&str` values.
- #[must_use]
- pub fn iter(&'a self) -> PickerIter<'a> {
- self.args.iter()
- }
-}
-
-impl Index<usize> for Picker<'_> {
- type Output = str;
-
- fn index(&self, index: usize) -> &Self::Output {
- &self.args[index]
- }
-}
-
-impl<'a, Route> Index<usize> for &'a Picker<'a, Route> {
- type Output = str;
-
- fn index(&self, index: usize) -> &Self::Output {
- &self.args[index]
- }
-}
-
-impl<'a, Route> IntoIterator for &'a Picker<'a, Route> {
- type Item = &'a str;
- type IntoIter = PickerIter<'a>;
-
- fn into_iter(self) -> Self::IntoIter {
- self.args.iter()
- }
-}
-
-/// Iterator for `Picker` (and `PickerArgs`), yielding `&'a str` values.
-pub enum PickerIter<'a> {
- /// Iterates over a borrowed slice (`&[&str]`)
- Slice(std::slice::Iter<'a, &'a str>),
- /// Iterates over an owned vector of borrowed string slices (`Vec<&str>`)
- Vec(std::slice::Iter<'a, &'a str>),
- /// Iterates over an owned vector of owned strings (`Vec<String>`)
- Owned(std::slice::Iter<'a, String>),
-}
-
-impl<'a> Iterator for PickerIter<'a> {
- type Item = &'a str;
-
- fn next(&mut self) -> Option<Self::Item> {
- match self {
- PickerIter::Slice(iter) | PickerIter::Vec(iter) => iter.next().copied(),
- PickerIter::Owned(iter) => iter.next().map(String::as_str),
- }
- }
-
- fn size_hint(&self) -> (usize, Option<usize>) {
- match self {
- PickerIter::Slice(iter) | PickerIter::Vec(iter) => iter.size_hint(),
- PickerIter::Owned(iter) => iter.size_hint(),
- }
- }
-}
-
-impl ExactSizeIterator for PickerIter<'_> {}
-
-impl<'a, Route> Picker<'a, Route> {
- /// Creates a `PickerPattern1` from the given arg to start a picking chain.
- ///
- /// This method initiates a parameter picking chain with one arg.
- /// The result is initially `Unparsed`.
- pub fn pick<N>(self, arg: impl Into<&'a PickerArg<'a, N>>) -> PickerPattern1<'a, N, Route>
- where
- N: Pickable<'a>,
- {
- Self::build_pattern1(self.args, arg.into(), None::<Route>)
- }
-
- /// Creates a `PickerPattern1` from the given arg.
- /// If parsing fails, attempts the fallback arg.
- pub fn pick_or<N, F>(
- self,
- arg: impl Into<&'a PickerArg<'a, N>>,
- or_arg: F,
- ) -> PickerPattern1<'a, N, Route>
- where
- N: Pickable<'a>,
- F: FnMut() -> N + 'static,
- {
- self.pick(arg).or(or_arg)
- }
-
- /// Creates a `PickerPattern1` from the given arg.
- /// If parsing fails, uses the provided default value.
- pub fn pick_or_default<N>(
- self,
- arg: impl Into<&'a PickerArg<'a, N>>,
- ) -> PickerPattern1<'a, N, Route>
- where
- N: Pickable<'a> + Default,
- {
- self.pick(arg).or_default()
- }
-
- /// Creates a `PickerPattern1` from the given arg.
- /// If parsing fails, switches to the given error route.
- pub fn pick_or_route<N, F>(
- self,
- arg: impl Into<&'a PickerArg<'a, N>>,
- error_route: F,
- ) -> PickerPattern1<'a, N, Route>
- where
- N: Pickable<'a>,
- F: FnMut() -> Route + 'static,
- {
- self.pick(arg).or_route(error_route)
- }
-}
-
-/// Trait for converting types into a `Picker`
-///
-/// Implemented for:
-/// - `&[&str]` (borrowed slice)
-/// - `&[String]` (borrowed slice of owned strings)
-/// - `Vec<&str>` (owned vector of borrowed strings)
-/// - `Vec<String>` (owned vector of owned strings)
-pub trait IntoPicker<'a> {
- /// Converts the value into a `Picker`
- ///
- /// # Examples
- ///
- /// ```
- /// use arg_picker::{IntoPicker, Picker};
- ///
- /// let args: Picker = (&["hello", "world"][..]).to_picker();
- /// assert_eq!(args.len(), 2);
- ///
- /// let args: Picker = vec!["foo", "bar"].to_picker();
- /// assert_eq!(args.len(), 2);
- ///
- /// let args: Picker = vec!["a".to_string(), "b".to_string()].to_picker();
- /// assert_eq!(args.len(), 2);
- /// ```
- fn to_picker(self) -> Picker<'a, ()>;
-
- /// Creates a `PickerPattern1` from the given arg for the `pick` method.
- ///
- /// This method converts the value into a `Picker` and starts a parameter
- /// picking chain with one arg. The result is initially `Unparsed`.
- fn pick<N>(self, arg: impl Into<&'a PickerArg<'a, N>>) -> PickerPattern1<'a, N, ()>
- where
- Self: Sized,
- N: Pickable<'a> + Sized,
- {
- Picker::build_pattern1(self.to_picker().args, arg.into(), None::<()>)
- }
-
- /// Starts building a picker pattern with the first argument, using a default value provider.
- ///
- /// This is a shorthand for calling `.pick(arg).or(func)`.
- ///
- /// # Type Parameters
- ///
- /// * `Next` — The nominal type of the first argument; must implement [`Pickable`].
- ///
- /// # Parameters
- ///
- /// * `arg` — The argument definition, typically obtained from [`crate::macros::arg`].
- /// * `func` — A closure that provides a default value if the arg is not provided by the user.
- fn pick_or<Next, F>(
- self,
- arg: impl Into<&'a PickerArg<'a, Next>>,
- func: F,
- ) -> PickerPattern1<'a, Next, ()>
- where
- Self: Sized,
- Next: Pickable<'a> + Sized,
- F: FnMut() -> Next + 'static,
- {
- self.pick(arg).or(func)
- }
-
- /// Starts building a picker pattern with the first argument, using a default value.
- ///
- /// This is a shorthand for calling `.pick(arg).or_default()`.
- ///
- /// # Type Parameters
- ///
- /// * `Next` — The nominal type of the first argument; must implement [`Pickable`] and [`Default`].
- ///
- /// # Parameters
- ///
- /// * `arg` — The argument definition, typically obtained from [`crate::macros::arg`].
- fn pick_or_default<Next>(
- self,
- arg: impl Into<&'a PickerArg<'a, Next>>,
- ) -> PickerPattern1<'a, Next, ()>
- where
- Self: Sized,
- Next: Pickable<'a> + Default + Sized,
- {
- self.pick(arg).or_default()
- }
-
- /// Starts building a picker pattern with the first argument, using a route if the arg is not provided.
- ///
- /// This is a shorthand for calling `.pick(arg).or_route(func)`.
- ///
- /// # Type Parameters
- ///
- /// * `Next` — The nominal type of the first argument; must implement [`Pickable`].
- ///
- /// # Parameters
- ///
- /// * `arg` — The argument definition, typically obtained from [`crate::macros::arg`].
- /// * `func` — A closure that produces a route value if the arg is not provided by the user.
- fn pick_or_route<Next, F, Route>(
- self,
- arg: impl Into<&'a PickerArg<'a, Next>>,
- func: F,
- ) -> PickerPattern1<'a, Next, Route>
- where
- Self: Sized,
- Next: Pickable<'a> + Sized,
- F: FnMut() -> Route + 'static,
- {
- self.pick(arg).with_route::<Route>().or_route(func)
- }
-
- /// Converts the value into a `Picker` with a specified route type.
- ///
- /// This method allows changing the route (phantom type parameter) of the picker.
- /// The route type is typically used to distinguish different parsing contexts or
- /// to carry compile-time state information through the picking chain.
- fn with_route<NewRoute>(self) -> Picker<'a, NewRoute>
- where
- Self: Sized,
- {
- Picker {
- route_phantom: PhantomData,
- args: self.to_picker().args,
- }
- }
-}
-
-impl<'a> IntoPicker<'a> for &'a [&'a str] {
- fn to_picker(self) -> Picker<'a, ()> {
- Picker {
- route_phantom: PhantomData,
- args: PickerArgs::Slice(self),
- }
- }
-}
-
-impl<'a> IntoPicker<'a> for &'a [String] {
- fn to_picker(self) -> Picker<'a, ()> {
- let vec: Vec<&str> = self.iter().map(String::as_str).collect();
- Picker {
- route_phantom: PhantomData,
- args: PickerArgs::Vec(vec),
- }
- }
-}
-
-impl<'a> IntoPicker<'a> for Vec<&'a str> {
- fn to_picker(self) -> Picker<'a, ()> {
- Picker {
- route_phantom: PhantomData,
- args: PickerArgs::Vec(self),
- }
- }
-}
-
-impl<'a> IntoPicker<'a> for &'a Vec<String> {
- fn to_picker(self) -> Picker<'a, ()> {
- let slice: Vec<&str> = self.iter().map(String::as_str).collect();
- Picker {
- route_phantom: PhantomData,
- args: PickerArgs::Vec(slice),
- }
- }
-}
-
-impl<'a> IntoPicker<'a> for Vec<String> {
- fn to_picker(self) -> Picker<'a, ()> {
- Picker {
- route_phantom: PhantomData,
- args: PickerArgs::Owned(self),
- }
- }
-}
-
-impl<'a, Route> Picker<'a, Route> {
- /// Build the `PickerPattern` via Arguments
- pub fn build_pattern1<N>(
- args: PickerArgs<'a>,
- arg: &'a PickerArg<'a, N>,
- error_route: Option<Route>,
- ) -> PickerPattern1<'a, N, Route>
- where
- N: Pickable<'a>,
- {
- PickerPattern1 {
- args,
- error_route,
- arg_1: arg,
- result_1: PickerArgResult::Unparsed,
- route_1: None,
- default_1: None,
- post_1: None,
- }
- }
-}
diff --git a/arg_picker/src/picker/parse.rs b/arg_picker/src/picker/parse.rs
deleted file mode 100644
index 9d973f2..0000000
--- a/arg_picker/src/picker/parse.rs
+++ /dev/null
@@ -1,229 +0,0 @@
-// Doc Not Optimize
-// --------------------------------------------------------------------------------------------
-// I have to say, the code generated by this `internal_repeat!` macro is really UGLY.
-//
-// But I have to admit, this is a **trade-off**. To achieve the syntax of `pick().pick().pick()`
-// while ensuring type safety, this is the best approach I could think of.
-//
-// P.S. If there's a better way, please let me know. Thanks!
-// --------------------------------------------------------------------------------------------
-//
-// Then, I must disable `clippy::type_complexity` — this guy is way too noisy.
-#![allow(clippy::type_complexity)]
-
-use crate::{Pickable, PickerArgAttr, PickerArgInfo, PickerArgResult, PickerArgs, TagPhaseContext};
-use arg_picker_macros::internal_repeat;
-
-internal_repeat!(1..=32 => {
- use crate::PickerPattern$;
- use crate::PickerResult$;
-});
-
-internal_repeat!(1..=32 => {
- impl<'a, (T$,+), Route> PickerPattern$<'a, (T$,+), Route>
- where (T$: Pickable<'a>,+) {
- /// Unwraps the result, panicking if a route was selected or a required
- /// value is missing.
- ///
- /// # Panics
- ///
- /// Panics if a route was selected, or if a required argument was not
- /// provided by the user.
- pub fn unwrap(self) -> ((T$,+)) {
- let p = self.parse();
- ((p.v$.expect(concat!("missing required argument at position ", $)),+))
- }
-
- /// Returns the parsed values, using the default values for any missing
- /// required arguments, or panicking if a route was selected.
- ///
- /// # Panics
- ///
- /// Panics if a route was selected.
- ///
- /// # Type Constraints
- ///
- /// All types in the tuple must implement [`Default`].
- pub fn unwrap_or_default(self) -> ((T$,+))
- where
- (
- T$: Default,
- +) {
- let p = self.parse();
- ((p.v$.unwrap_or_default(),+))
- }
-
- /// Returns the parsed values, using the provided closure to generate
- /// default values for any missing required arguments, or panicking if
- /// a route was selected.
- ///
- /// # Panics
- ///
- /// Panics if a route was selected.
- pub fn unwrap_or_else<F>(self, op: F) -> ((T$,+))
- where
- F: FnOnce(Route) -> ((T$,+)),
- {
- let r = self.to_result();
- r.unwrap_or_else(op)
- }
-
- /// Returns the parsed values, or panics with the given message if
- /// a route was selected.
- ///
- /// # Panics
- ///
- /// Panics if a route was selected, with the provided message.
- ///
- /// # Type Constraints
- ///
- /// `Route` must implement [`std::fmt::Debug`] so that the error
- /// message can include the route value.
- pub fn expect(self, msg: &str) -> ((T$,+)) where Route: std::fmt::Debug {
- self.to_result().expect(msg)
- }
-
- /// Returns the individual option values without checking the route.
- pub fn unpack(self) -> ((Option<T$>,+)) {
- let p = self.parse();
- ((p.v$,+))
- }
-
- /// Converts to a `Result`, returning `Err(route)` if a route was selected,
- /// or `Ok(values)` otherwise.
- pub fn to_result(self) -> Result<((T$,+)), Route> {
- let p = self.parse();
- if let Some(r) = p.route {
- return Err(r);
- }
- Ok(p.unwrap())
- }
-
- /// Converts to an `Option`, returning `None` if a route was selected,
- /// or `Some(values)` otherwise.
- pub fn to_option(self) -> Option<((T$,+))> {
- let p = self.parse();
- if p.route.is_some() {
- return None;
- }
- Some(p.unwrap())
- }
- }
-});
-
-internal_repeat!(1..=32 => {
- impl<'a, (T$,+), Route> PickerPattern$<'a, (T$,+), Route>
- where (T$: Pickable<'a>,+)
- {
- pub fn parse(mut self) -> PickerResult$<(T$,+), Route> {
- // ArgInfos
- let arg_infos: [PickerArgInfo; $] = [
- (
- PickerArgInfo::from(self.arg_$),
- +)
- ];
-
- let mut bundle: [
- (
- // Arg Attr
- PickerArgAttr,
-
- // Tag Func
- Box<dyn FnOnce(&PickerArgs<'a>, &[u8]) -> Vec<usize>>,
-
- // Pick Func
- Box<dyn FnOnce(&[&str], &mut Option<Route>)>,
-
- // Index
- usize
- )
- ; $] = [
- (
- (
- // Arg Attr
- T$::get_attr(self.arg_$),
-
- // Tag Func
- Box::new(|args, mask| {
- let ctx = TagPhaseContext {
- arg_info: &arg_infos[$-],
- args,
- mask
- };
- T$::tag(ctx)
- }),
-
- // Pick Func
- Box::new(|args, error_route| {
- self.result_$ = match T$::pick(args) {
- PickerArgResult::Parsed(mut value) => {
- // Postprocess
- if let Some(post) = self.post_$ {
- value = post(value);
- }
- PickerArgResult::Parsed(value)
- },
- other => {
- if let Some(get_default) = self.default_$ {
- let mut value = get_default();
-
- // Postprocess
- if let Some(post) = self.post_$ {
- value = post(value);
- }
-
- PickerArgResult::Parsed(value)
- } else {
- if error_route.is_none() {
- if let Some(get_route) = self.route_$ {
- *error_route = Some(get_route().into());
- }
- }
- other
- }
- },
-
- }
- }),
-
- // Index
- $
- ),
- +)
- ];
-
- // Sort by Bundle Ord (descending)
- bundle.sort_by(|a, b| b.0.cmp(&a.0));
-
- // Mask — size = number of args (not args), so use args length
- let mut mask: Vec<u8> = vec![0u8; self.args.len()];
-
- // Parsing
- for (_, tag_func, pick_func, _idx) in bundle {
-
- // Tag phase
- let tagged = tag_func(&self.args, mask.as_slice());
- let mut args_to_pick: Vec<&str> = vec![];
-
- for i in tagged {
- mask[i] = 1;
-
- // Update args to pick
- args_to_pick.push(self.args.get(i).unwrap_or_default());
- }
-
- // Pick phase
- pick_func(args_to_pick.as_slice(), &mut self.error_route);
- }
-
- // Combine Result
- let result: PickerResult$<(T$,+), Route> = PickerResult$ {
- route: self.error_route,
- (
- v$: self.result_$.to_option(),
- +)
- };
- result
- }
- }
-});
diff --git a/arg_picker/src/picker/patterns.rs b/arg_picker/src/picker/patterns.rs
deleted file mode 100644
index 75b780d..0000000
--- a/arg_picker/src/picker/patterns.rs
+++ /dev/null
@@ -1,201 +0,0 @@
-// Doc Not Optimize
-use arg_picker_macros::internal_repeat;
-
-use crate::{Pickable, PickerArg, PickerArgResult, PickerArgs};
-
-internal_repeat!(1..=32 => {
- #[doc(hidden)]
- pub struct PickerPattern$<'a, (T$,+), Route>
- where (T$: Pickable<'a>,+)
- {
- pub args: PickerArgs<'a>,
- pub error_route: Option<Route>,
- (
- pub(crate) arg_$: &'a PickerArg<'a, T$>,
- pub(crate) result_$: PickerArgResult<T$>,
- pub(crate) default_$: Option<Box<dyn FnOnce() -> T$>>,
- pub(crate) route_$: Option<Box<dyn FnOnce() -> Route>>,
- pub(crate) post_$: Option<Box<dyn FnOnce(T$) -> T$>>,
- +)
- }
-});
-
-internal_repeat!(1..=32 => {
- impl<'a, (T$,+), Route> PickerPattern$<'a, (T$,+), Route>
- where (T$: Pickable<'a>,+)
- {
- /// Sets a default value provider for this arg.
- ///
- /// If the arg is not provided by the user at runtime, the given closure will be
- /// called to produce a default value. The closure is expected to return `T$`.
- ///
- /// # Example
- ///
- #[allow(clippy::type_complexity)]
- pub fn or<F>(mut self, func: F) -> Self
- where
- F: 'static + FnMut() -> T$,
- {
- self.default_$ = Some(Box::new(func));
- self
- }
-
- /// Uses the default value for this arg's type if the arg is not provided.
- ///
- /// If the arg is not provided by the user at runtime, the default value for `T$`
- /// (as defined by the `Default` trait) will be used.
- ///
- /// # Example
- ///
- #[allow(clippy::type_complexity)]
- pub fn or_default(mut self) -> Self
- where
- T$: Default,
- {
- self.default_$ = Some(Box::new(|| T$::default()));
- self
- }
-
- /// Sets a route for when the arg is not provided.
- ///
- /// If the arg is not provided by the user at runtime, the given closure will be
- /// called to produce a route value that will be returned early.
- ///
- /// # Example
- ///
- pub fn or_route<F>(mut self, func: F) -> Self
- where
- F: 'static + FnMut() -> Route,
- {
- self.route_$ = Some(Box::new(func));
- self
- }
-
-
- /// Resets the route for this picker pattern, allowing a different route type.
- ///
- /// This method converts the current `PickerPattern` into a new one with a different
- /// route type `NewRoute`. All existing arg configurations, defaults, and post-
- /// processing functions are preserved, but the `error_route` and individual
- /// `route_$` fields are cleared (set to `None`).
- ///
- /// This is useful when you want to change the error/redirect route type mid-chain,
- /// for example when composing patterns from different contexts that use different
- /// route enums.
- #[allow(clippy::type_complexity)]
- pub fn with_route<NewRoute>(self) -> PickerPattern$<'a, (T$,+), NewRoute>
- {
- PickerPattern$ {
- args: self.args,
- error_route: None,
- (
- arg_$: self.arg_$,
- result_$: self.result_$,
- default_$: self.default_$,
- route_$: None,
- post_$: self.post_$,
- +)
- }
- }
-
- /// Attaches a post-processing function to this arg.
- ///
- /// After the arg's value is parsed (or defaulted), the given closure will be
- /// invoked with the parsed value and its return value will be used as the final
- /// result. This allows transforming or validating the parsed value.
- ///
- /// # Example
- ///
- #[allow(clippy::type_complexity)]
- pub fn post<F>(mut self, func: F) -> Self
- where
- F: 'static + FnMut(T$) -> T$,
- {
- self.post_$ = Some(Box::new(func));
- self
- }
- }
-});
-
-internal_repeat!(1..32 => {
- impl<'a, (T$,+), Route> PickerPattern$<'a, (T$,+), Route>
- where (T$: Pickable<'a>,+)
- {
- #[allow(clippy::type_complexity)]
- /// Adds a new arg to the picking chain, returning a new `PickerPattern` with one more type parameter.
- ///
- /// This method extends the current picking pattern by appending an additional arg.
- /// The previous args and their results are preserved as part of the new pattern.
- /// The new arg's result is initially `Unparsed`.
- pub fn pick<N>(self, arg: impl Into<&'a PickerArg<'a, N>>) -> PickerPattern$+<'a, (T$,+), N, Route>
- where
- N: Pickable<'a>,
- {
- PickerPattern$+ {
- // Args
- args: self.args,
- error_route: self.error_route,
-
- // Current
- arg_$+: arg.into(),
- result_$+: PickerArgResult::Unparsed,
- default_$+: None,
- route_$+: None,
- post_$+: None,
-
- // Prev
- (
- arg_$: self.arg_$,
- result_$: self.result_$,
- default_$: self.default_$,
- route_$: self.route_$,
- post_$: self.post_$,
- +)
- }
- }
-
- /// Picks a new arg with a default value provider in a single call.
- ///
- /// This is a shorthand for calling `.pick(arg).or(func)`.
- ///
- /// # Example
- ///
- #[allow(clippy::type_complexity)]
- pub fn pick_or<N, F>(self, arg: impl Into<&'a PickerArg<'a, N>>, func: F) -> PickerPattern$+<'a, (T$,+), N, Route>
- where
- N: Pickable<'a>,
- F: FnMut() -> N + 'static,
- {
- self.pick(arg).or(func)
- }
-
- /// Picks a new arg with a default value in a single call.
- ///
- /// This is a shorthand for calling `.pick(arg).or_default()`.
- ///
- /// # Example
- ///
- #[allow(clippy::type_complexity)]
- pub fn pick_or_default<N>(self, arg: impl Into<&'a PickerArg<'a, N>>) -> PickerPattern$+<'a, (T$,+), N, Route>
- where
- N: Pickable<'a> + Default,
- {
- self.pick(arg).or_default()
- }
-
- /// Picks a new arg with a route in a single call.
- ///
- /// This is a shorthand for calling `.pick(arg).or_route(func)`.
- ///
- /// # Example
- ///
- #[allow(clippy::type_complexity)]
- pub fn pick_or_route<N, F>(self, arg: impl Into<&'a PickerArg<'a, N>>, func: F) -> PickerPattern$+<'a, (T$,+), N, Route>
- where
- N: Pickable<'a>,
- F: FnMut() -> Route + 'static,
- {
- self.pick(arg).or_route(func)
- }
- }
-});
diff --git a/arg_picker/src/picker/result.rs b/arg_picker/src/picker/result.rs
deleted file mode 100644
index 32c81cd..0000000
--- a/arg_picker/src/picker/result.rs
+++ /dev/null
@@ -1,108 +0,0 @@
-// Doc Not Optimize
-#![allow(clippy::type_complexity)] // Aha, Type Gymnastics!
-
-use arg_picker_macros::internal_repeat;
-
-internal_repeat!(1..=32 => {
- #[doc(hidden)]
- pub struct PickerResult$<(T$,+), Route> {
- /// The route selected by the picker, if any.
- /// If this is `Some`, the picker chose to follow a route instead of selecting values,
- /// and all value fields (`v1`, `v2`, ...) will be `None`.
- ///
- /// Note: "route" here refers to an alternative path/choice, not a network route.
- pub route: Option<Route>,
-
- (
- #[doc = concat!("The optional value for the ", $, "th type parameter.")]
- pub v$: Option<T$>,
- +)
- }
-});
-
-internal_repeat!(1..=32 => {
- impl<(T$,+), Route> PickerResult$<(T$,+), Route> {
- /// Unwraps the result, panicking if a route was selected or a required
- /// value is missing.
- ///
- /// # Panics
- ///
- /// Panics if `self.route` is `Some(...)`, or if a required argument was
- /// not provided by the user.
- pub fn unwrap(self) -> ((T$,+)) {
- ((self.v$.expect(concat!("missing required argument at position ", $)),+))
- }
-
- /// Returns the parsed values, using the default values for any missing
- /// required arguments, or panicking if a route was selected.
- ///
- /// # Panics
- ///
- /// Panics if a route was selected.
- ///
- /// # Type Constraints
- ///
- /// All types in the tuple must implement [`Default`].
- pub fn unwrap_or_default(self) -> ((T$,+))
- where
- (
- T$: Default,
- +) {
- let p = self;
- ((p.v$.unwrap_or_default(),+))
- }
-
- /// Returns the parsed values, using the provided closure to generate
- /// default values for any missing required arguments, or panicking if
- /// a route was selected.
- ///
- /// # Panics
- ///
- /// Panics if a route was selected.
- pub fn unwrap_or_else<F>(self, op: F) -> ((T$,+))
- where
- F: FnOnce(Route) -> ((T$,+)),
- {
- let r = self.to_result();
- r.unwrap_or_else(op)
- }
-
- /// Returns the parsed values, or panics with the given message if
- /// a route was selected.
- ///
- /// # Panics
- ///
- /// Panics if a route was selected, with the provided message.
- ///
- /// # Type Constraints
- ///
- /// `Route` must implement [`std::fmt::Debug`] so that the error
- /// message can include the route value.
- pub fn expect(self, msg: &str) -> ((T$,+)) where Route: std::fmt::Debug {
- self.to_result().expect(msg)
- }
-
- /// Returns the individual option values without checking the route.
- pub fn unpack(self) -> ((Option<T$>,+)) {
- ((self.v$,+))
- }
-
- /// Converts to a `Result`, returning `Err(route)` if a route was selected,
- /// or `Ok(values)` otherwise.
- pub fn to_result(self) -> Result<((T$,+)), Route> {
- if let Some(r) = self.route {
- return Err(r);
- }
- Ok(self.unwrap())
- }
-
- /// Converts to an `Option`, returning `None` if a route was selected,
- /// or `Some(values)` otherwise.
- pub fn to_option(self) -> Option<((T$,+))> {
- if let Some(_) = self.route {
- return None;
- }
- Some(self.unwrap())
- }
- }
-});
diff --git a/arg_picker/src/value.rs b/arg_picker/src/value.rs
deleted file mode 100644
index fc23f3d..0000000
--- a/arg_picker/src/value.rs
+++ /dev/null
@@ -1,9 +0,0 @@
-// Doc Not Optimize
-mod flag;
-pub use flag::*;
-
-mod paths;
-pub use paths::*;
-
-mod vec_until;
-pub use vec_until::*;
diff --git a/arg_picker/src/value/flag.rs b/arg_picker/src/value/flag.rs
deleted file mode 100644
index 9a9e058..0000000
--- a/arg_picker/src/value/flag.rs
+++ /dev/null
@@ -1,146 +0,0 @@
-// Doc Not Optimize
-use std::{
- fmt::{Debug, Display},
- ops::{Deref, Not},
-};
-
-/// Parsed result of a boolean-style command-line flag.
-///
-/// `Flag` is a **value type** that can be declared in [`PickerArg`].
-/// When the user passes `--verbose` on the command line, the parsed result is `Flag::Active`;
-/// when the flag is absent, the result is `Flag::Inactive`.
-///
-/// # Why not just `bool`?
-///
-/// Unlike a raw `bool`, `Flag` carries **explicit semantics** about whether
-/// the flag was actually provided by the user (`Active`) or simply omitted
-/// (`Inactive`). This distinction matters when you want to distinguish
-/// "the user intentionally omitted the flag" from "the flag was processed but
-/// resolved to false" — the `Pickable` implementation for `Flag` always
-/// returns `Parsed(Flag::Inactive)` when no matching argument is found,
-/// rather than `NotFound`, making it always succeed with a meaningful default.
-///
-/// # Conversions
-///
-/// `Flag` interoperates seamlessly with `bool`: `Flag::Active` is `true`,
-/// `Flag::Inactive` is `false`. The [`Deref`] impl allows using a `Flag`
-/// directly in boolean contexts:
-///
-/// ```
-/// # use arg_picker::value::Flag;
-/// let flag = Flag::Active;
-/// if *flag { /* runs */ }
-/// ```
-///
-/// [`PickerArg`]: crate::PickerArg
-#[derive(Default, Clone, Copy, PartialEq, Eq)]
-pub enum Flag {
- /// The flag was **not** present on the command line.
- ///
- /// This is the default state, equivalent to `false`.
- #[default]
- Inactive,
-
- /// The flag **was** present on the command line.
- ///
- /// Equivalent to `true`.
- Active,
-}
-
-impl Debug for Flag {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- match self {
- Self::Inactive => write!(f, "inactive"),
- Self::Active => write!(f, "active"),
- }
- }
-}
-
-impl Display for Flag {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- match self {
- Self::Inactive => write!(f, "inactive"),
- Self::Active => write!(f, "active"),
- }
- }
-}
-
-impl Flag {
- /// Converts this `Flag` into a `bool`.
- ///
- /// Returns `true` if the flag is [`Active`], `false` if [`Inactive`].
- ///
- /// # Examples
- ///
- /// ```
- /// # use arg_picker::value::Flag;
- /// assert!(Flag::Active.bool());
- /// assert!(!Flag::Inactive.bool());
- /// ```
- ///
- /// [`Active`]: Flag::Active
- /// [`Inactive`]: Flag::Inactive
- #[must_use]
- #[inline]
- pub fn bool(&self) -> bool {
- *self == Self::Active
- }
-}
-
-impl PartialEq<bool> for Flag {
- fn eq(&self, other: &bool) -> bool {
- self.bool() == *other
- }
-}
-
-/// Compares `bool` with `Flag` using `==`.
-impl PartialEq<Flag> for bool {
- fn eq(&self, other: &Flag) -> bool {
- *self == other.bool()
- }
-}
-
-impl From<bool> for Flag {
- fn from(value: bool) -> Self {
- if value { Self::Active } else { Self::Inactive }
- }
-}
-
-impl From<Flag> for bool {
- fn from(val: Flag) -> Self {
- val == Flag::Active
- }
-}
-
-/// Allows `Flag` to be used in boolean contexts via `*flag`.
-///
-/// # Examples
-///
-/// ```
-/// # use arg_picker::value::Flag;
-/// let flag = Flag::Active;
-/// if *flag {
-/// println!("flag is set");
-/// }
-/// ```
-impl Deref for Flag {
- type Target = bool;
-
- fn deref(&self) -> &bool {
- match self {
- Self::Active => &true,
- Self::Inactive => &false,
- }
- }
-}
-
-impl Not for Flag {
- type Output = Self;
-
- fn not(self) -> Self {
- match self {
- Self::Active => Self::Inactive,
- Self::Inactive => Self::Active,
- }
- }
-}
diff --git a/arg_picker/src/value/paths.rs b/arg_picker/src/value/paths.rs
deleted file mode 100644
index 83f262d..0000000
--- a/arg_picker/src/value/paths.rs
+++ /dev/null
@@ -1,306 +0,0 @@
-// Doc Not Optimize
-use std::{
- ops::{Deref, DerefMut},
- path::{Path, PathBuf},
-};
-
-/// A file path.
-///
-/// `FilePath` is a wrapper type around `PathBuf` representing an arbitrary file path.
-///
-/// It implements `Pickable` and can be parsed by a `Picker`.
-///
-/// # Parsing Behavior
-///
-/// At the moment of parsing, checks whether the path exists and is a file.
-/// If not a file, returns `NotFound`.
-#[non_exhaustive]
-pub struct FilePath {
- path: PathBuf,
-}
-
-/// A file path that should not exist.
-///
-/// `NoFilePath` is a wrapper type around `PathBuf` representing an arbitrary path
-/// that must not currently point to an existing file.
-///
-/// It implements `Pickable` and can be parsed by a `Picker`.
-///
-/// # Parsing Behavior
-///
-/// At the moment of parsing, checks whether no file exists at the given path.
-/// If a file already exists (regardless of whether it is a regular file, directory,
-/// symlink, or other type), returns `NotFound`.
-#[non_exhaustive]
-pub struct NoFilePath {
- path: PathBuf,
-}
-
-/// A directory path.
-///
-/// `DirPath` is a wrapper type around `PathBuf` representing an arbitrary existing
-/// directory path.
-///
-/// It implements `Pickable` and can be parsed by a `Picker`.
-///
-/// # Parsing Behavior
-///
-/// At the moment of parsing, checks whether a directory exists at the given path.
-/// If no directory exists, returns `NotFound`.
-#[non_exhaustive]
-pub struct DirPath {
- path: PathBuf,
-}
-
-/// A directory path that should not exist.
-///
-/// `NoDirPath` is a wrapper type around `PathBuf` representing an arbitrary path
-/// that must not currently point to an existing directory.
-///
-/// It implements `Pickable` and can be parsed by a `Picker`.
-///
-/// # Parsing Behavior
-///
-/// At the moment of parsing, checks whether no directory exists at the given path.
-/// If a directory already exists (regardless of whether it is a regular file, file,
-/// symlink, or other type), returns `NotFound`.
-#[non_exhaustive]
-pub struct NoDirPath {
- path: PathBuf,
-}
-
-/// A symbolic link path.
-///
-/// `SymlinkPath` is a wrapper type around `PathBuf` representing an existing symbolic link.
-///
-/// It implements `Pickable` and can be parsed by a `Picker`.
-///
-/// # Parsing Behavior
-///
-/// At the moment of parsing, checks whether the path exists and is a symlink.
-/// If not a symlink, returns `NotFound`.
-#[non_exhaustive]
-pub struct SymlinkPath {
- path: PathBuf,
-}
-
-/// A symbolic link path that should not exist.
-///
-/// `NoSymlinkPath` is a wrapper type around `PathBuf` representing an arbitrary path
-/// that must not currently point to an existing symbolic link.
-///
-/// It implements `Pickable` and can be parsed by a `Picker`.
-///
-/// # Parsing Behavior
-///
-/// At the moment of parsing, checks whether no symlink exists at the given path.
-/// If a symlink already exists (regardless of whether it points to a file, directory,
-/// or other type), returns `NotFound`.
-#[non_exhaustive]
-pub struct NoSymlinkPath {
- path: PathBuf,
-}
-
-/// A path that should not exist at all.
-///
-/// `NoPath` is a wrapper type around `PathBuf` representing an arbitrary path
-/// that must not currently exist on the filesystem (as a file, directory, symlink,
-/// or any other type).
-///
-/// It implements `Pickable` and can be parsed by a `Picker`.
-///
-/// # Parsing Behavior
-///
-/// At the moment of parsing, checks whether any filesystem entry exists at the given path.
-/// If anything exists at that path, returns `NotFound`.
-#[non_exhaustive]
-pub struct NoPath {
- path: PathBuf,
-}
-
-/// Implements common trait impls (`From`, `AsRef`, `Deref`, `DerefMut`) for a path wrapper type.
-macro_rules! impl_path_traits {
- ($type:ident) => {
- impl From<PathBuf> for $type {
- fn from(value: PathBuf) -> Self {
- $type { path: value }
- }
- }
-
- impl From<&PathBuf> for $type {
- fn from(value: &PathBuf) -> Self {
- $type {
- path: value.clone(),
- }
- }
- }
-
- impl AsRef<Path> for $type {
- fn as_ref(&self) -> &Path {
- &self.path
- }
- }
-
- impl DerefMut for $type {
- fn deref_mut(&mut self) -> &mut Self::Target {
- &mut self.path
- }
- }
-
- impl Deref for $type {
- type Target = PathBuf;
-
- fn deref(&self) -> &Self::Target {
- &self.path
- }
- }
-
- impl From<$type> for PathBuf {
- fn from(value: $type) -> Self {
- value.path
- }
- }
-
- impl From<&$type> for PathBuf {
- fn from(value: &$type) -> Self {
- value.path.clone()
- }
- }
- };
-}
-
-impl_path_traits!(FilePath);
-impl_path_traits!(NoFilePath);
-impl_path_traits!(DirPath);
-impl_path_traits!(NoDirPath);
-impl_path_traits!(SymlinkPath);
-impl_path_traits!(NoSymlinkPath);
-impl_path_traits!(NoPath);
-
-/// Recursive file paths.
-///
-/// `RecursiveFiles` is a wrapper type around `Vec<PathBuf>` representing a list of
-/// existing files.
-///
-/// It implements `Pickable` and can be parsed by a `Picker`.
-///
-/// # Parsing Behavior
-///
-/// - If a file path is given, returns a list of length 1 containing that file.
-/// - If a directory path is given, recursively collects all files under that directory
-/// and returns them as a list.
-#[non_exhaustive]
-pub struct RecursiveFiles {
- paths: Vec<PathBuf>,
-}
-
-impl From<Vec<PathBuf>> for RecursiveFiles {
- fn from(paths: Vec<PathBuf>) -> Self {
- Self { paths }
- }
-}
-
-impl From<RecursiveFiles> for Vec<PathBuf> {
- fn from(value: RecursiveFiles) -> Self {
- value.paths
- }
-}
-
-impl AsRef<[PathBuf]> for RecursiveFiles {
- fn as_ref(&self) -> &[PathBuf] {
- &self.paths
- }
-}
-
-impl Deref for RecursiveFiles {
- type Target = Vec<PathBuf>;
-
- fn deref(&self) -> &Self::Target {
- &self.paths
- }
-}
-
-impl DerefMut for RecursiveFiles {
- fn deref_mut(&mut self) -> &mut Self::Target {
- &mut self.paths
- }
-}
-
-impl RecursiveFiles {
- /// Returns the number of file paths.
- #[must_use]
- pub const fn len(&self) -> usize {
- self.paths.len()
- }
-
- /// Returns `true` if there are no file paths.
- #[must_use]
- pub const fn is_empty(&self) -> bool {
- self.paths.is_empty()
- }
-
- /// Returns an iterator over the file paths.
- pub fn iter(&self) -> std::slice::Iter<'_, PathBuf> {
- self.paths.iter()
- }
-}
-
-impl<'a> IntoIterator for &'a RecursiveFiles {
- type Item = &'a PathBuf;
- type IntoIter = std::slice::Iter<'a, PathBuf>;
-
- fn into_iter(self) -> Self::IntoIter {
- self.iter()
- }
-}
-
-impl From<Vec<Self>> for RecursiveFiles {
- fn from(value: Vec<Self>) -> Self {
- Self {
- paths: value.into_iter().flat_map(|r| r.paths).collect(),
- }
- }
-}
-
-/// Trait for types that can be combined into a single `RecursiveFiles`.
-pub trait IntoRecursiveFiles {
- /// Combines multiple sources of file paths into a single `RecursiveFiles`.
- fn combine(self) -> RecursiveFiles;
-}
-
-impl<T> IntoRecursiveFiles for Vec<T>
-where
- T: Into<RecursiveFiles>,
-{
- fn combine(self) -> RecursiveFiles {
- self.into_iter()
- .map(Into::into)
- .collect::<Vec<RecursiveFiles>>()
- .into()
- }
-}
-
-impl<T> IntoRecursiveFiles for &[T]
-where
- T: Into<RecursiveFiles> + Clone,
-{
- fn combine(self) -> RecursiveFiles {
- self.iter()
- .cloned()
- .map(Into::into)
- .collect::<Vec<RecursiveFiles>>()
- .into()
- }
-}
-
-impl<T, const N: usize> IntoRecursiveFiles for [T; N]
-where
- T: Into<RecursiveFiles>,
-{
- fn combine(self) -> RecursiveFiles {
- self.into_iter()
- .map(Into::into)
- .collect::<Vec<RecursiveFiles>>()
- .into()
- }
-}
diff --git a/arg_picker/src/value/vec_until.rs b/arg_picker/src/value/vec_until.rs
deleted file mode 100644
index 0f7fede..0000000
--- a/arg_picker/src/value/vec_until.rs
+++ /dev/null
@@ -1,137 +0,0 @@
-// Doc Not Optimize
-use std::marker::PhantomData;
-use std::ops::{Deref, DerefMut};
-
-use crate::{
- BoundaryCheck, MultiPickableWithBoundary, Pickable, PickerArg, PickerArgAttr, PickerArgResult,
- SinglePickable, TagPhaseContext,
- matcher_needed::Matcher,
- parselib::{MultiArgMatcher, ParserStyle},
-};
-
-/// A `Vec`-like container that stops collecting when [`BoundaryCheck`]
-/// returns `true`.
-///
-/// This type exists to signal "I know what I'm doing with boundaries"
-/// at the type level (as opposed to `Vec<T>` which greedily takes
-/// everything).
-#[derive(Debug, Clone, PartialEq, Eq, Default)]
-pub struct VecUntil<T> {
- pub(crate) inner: Vec<T>,
- _marker: PhantomData<T>,
-}
-
-impl<T> VecUntil<T> {
- /// Consumes `self` and returns the underlying [`Vec<T>`].
- #[must_use]
- pub fn into_inner(self) -> Vec<T> {
- self.inner
- }
-}
-
-impl<T> From<Vec<T>> for VecUntil<T> {
- fn from(v: Vec<T>) -> Self {
- Self {
- inner: v,
- _marker: PhantomData,
- }
- }
-}
-
-impl<T> From<VecUntil<T>> for Vec<T> {
- fn from(v: VecUntil<T>) -> Self {
- v.inner
- }
-}
-
-impl<T> Deref for VecUntil<T> {
- type Target = Vec<T>;
- fn deref(&self) -> &Vec<T> {
- &self.inner
- }
-}
-
-impl<T> DerefMut for VecUntil<T> {
- fn deref_mut(&mut self) -> &mut Vec<T> {
- &mut self.inner
- }
-}
-
-// MultiPickableWithBoundary impl
-
-impl<T> MultiPickableWithBoundary for VecUntil<T>
-where
- T: SinglePickable + BoundaryCheck,
-{
- type Checker = T;
-
- fn pick_multi(raw: Vec<String>) -> PickerArgResult<Self> {
- let mut inner = Vec::with_capacity(raw.len());
- for s in &raw {
- match T::pick_single(Some(s)) {
- PickerArgResult::Parsed(v) => inner.push(v),
- PickerArgResult::NotFound => return PickerArgResult::NotFound,
- PickerArgResult::Unparsed => {}
- }
- }
- PickerArgResult::Parsed(Self {
- inner,
- _marker: PhantomData,
- })
- }
-}
-
-// Pickable impl
-
-impl<'a, T> Pickable<'a> for VecUntil<T>
-where
- T: SinglePickable + BoundaryCheck,
-{
- fn get_attr(flag: &'a PickerArg<'a, Self>) -> PickerArgAttr {
- PickerArgAttr::positional_or_multi(flag)
- }
-
- fn tag(ctx: TagPhaseContext) -> Vec<usize> {
- let args = ctx.args;
- let is_positional = ctx.arg_info.positional;
- let positions = MultiArgMatcher::match_all(ctx.into());
- if positions.is_empty() {
- return positions;
- }
-
- let start = usize::from(!is_positional);
- if start >= positions.len() {
- return positions;
- }
-
- let mut cut = start;
- for &idx in &positions[start..] {
- if let Some(raw) = args.get(idx)
- && T::check_boundary(raw)
- {
- break;
- }
- cut += 1;
- }
-
- positions[..cut].to_vec()
- }
-
- fn pick(raw_strs: &[&str]) -> PickerArgResult<Self> {
- let strs = strip_flag(raw_strs);
- let owned: Vec<String> = strs.iter().map(|&s| s.to_string()).collect();
- <Self as MultiPickableWithBoundary>::pick_multi(owned)
- }
-}
-
-/// If the first raw string looks like a named flag (starts with the
-/// style's long or short prefix), strip it — it's the flag, not a value.
-fn strip_flag<'a>(raw_strs: &'a [&'a str]) -> &'a [&'a str] {
- if let Some(first) = raw_strs.first() {
- let style = ParserStyle::global_style();
- if first.starts_with(style.long_prefix) || first.starts_with(style.short_prefix) {
- return &raw_strs[1..];
- }
- }
- raw_strs
-}
diff --git a/arg_picker/test/Cargo.lock b/arg_picker/test/Cargo.lock
deleted file mode 100644
index 81ec660..0000000
--- a/arg_picker/test/Cargo.lock
+++ /dev/null
@@ -1,68 +0,0 @@
-# This file is automatically @generated by Cargo.
-# It is not intended for manual editing.
-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 = "arg-picker-test"
-version = "0.1.0"
-dependencies = [
- "arg-picker",
-]
-
-[[package]]
-name = "just_fmt"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6170dccbc3ea15dfb7f2da964097f814aba1dd8f746d4ffc56f33245c38e6d96"
-
-[[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 = "syn"
-version = "2.0.119"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
-dependencies = [
- "proc-macro2",
- "quote",
- "unicode-ident",
-]
-
-[[package]]
-name = "unicode-ident"
-version = "1.0.24"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
diff --git a/arg_picker/test/Cargo.toml b/arg_picker/test/Cargo.toml
deleted file mode 100644
index 2c366c7..0000000
--- a/arg_picker/test/Cargo.toml
+++ /dev/null
@@ -1,9 +0,0 @@
-[package]
-name = "arg-picker-test"
-version = "0.1.0"
-edition = "2024"
-
-[workspace]
-
-[dependencies]
-arg-picker = { path = "../" }
diff --git a/arg_picker/test/src/lib.rs b/arg_picker/test/src/lib.rs
deleted file mode 100644
index 0cfa575..0000000
--- a/arg_picker/test/src/lib.rs
+++ /dev/null
@@ -1,23 +0,0 @@
-// Using `assert_eq!(x, true)` is clearer than `assert!(x)` for expressing expected values
-//
-// BECAUSE `assert!` only checks if the boolean value is true,
-// while `assert_eq!` explicitly shows the expected value
-#![allow(clippy::bool_assert_comparison)]
-
-#[cfg(test)]
-mod test;
-
-use arg_picker::parselib::MaskedArg;
-
-/// Create a single `MaskedArg` from a raw string and its original index.
-pub fn make_masked(raw: &str, idx: usize) -> MaskedArg<'_> {
- MaskedArg { raw, raw_idx: idx }
-}
-
-/// Create a `Vec<MaskedArg>` from an array of `(raw, raw_idx)` pairs.
-pub fn make_args<'a>(pairs: &'a [(&'a str, usize)]) -> Vec<MaskedArg<'a>> {
- pairs
- .iter()
- .map(|&(raw, idx)| MaskedArg { raw, raw_idx: idx })
- .collect()
-}
diff --git a/arg_picker/test/src/test.rs b/arg_picker/test/src/test.rs
deleted file mode 100644
index 9c53514..0000000
--- a/arg_picker/test/src/test.rs
+++ /dev/null
@@ -1,10 +0,0 @@
-mod arg_matcher_test;
-mod basic_test;
-mod multi_arg_test;
-mod multi_value_test;
-mod pos_matcher_test;
-mod priority_test;
-mod route_test;
-mod style_test;
-mod value_flag_test;
-mod value_string_test;
diff --git a/arg_picker/test/src/test/arg_matcher_test.rs b/arg_picker/test/src/test/arg_matcher_test.rs
deleted file mode 100644
index d1d363e..0000000
--- a/arg_picker/test/src/test/arg_matcher_test.rs
+++ /dev/null
@@ -1,289 +0,0 @@
-use arg_picker::parselib::{ArgMatcher, Matcher, POWERSHELL_STYLE, UNIX_STYLE};
-use arg_picker::PickerArgInfo;
-
-use crate::make_args;
-
-// on_match_one — Named
-
-#[test]
-fn test_match_one_named_basic() {
- let mut info = PickerArgInfo::new();
- info.set_long("name");
- let args = make_args(&[("--name", 0), ("Alice", 1)]);
- let result = ArgMatcher::on_match_one(&args, &UNIX_STYLE, &info);
- assert_eq!(result, Some(0));
-}
-
-#[test]
-fn test_match_one_named_eq_mode() {
- let mut info = PickerArgInfo::new();
- info.set_long("name");
- let args = make_args(&[("--name=Alice", 0)]);
- let result = ArgMatcher::on_match_one(&args, &UNIX_STYLE, &info);
- assert_eq!(result, Some(0));
-}
-
-#[test]
-fn test_match_one_named_no_match() {
- let mut info = PickerArgInfo::new();
- info.set_long("name");
- let args = make_args(&[("--other", 0), ("Alice", 1)]);
- let result = ArgMatcher::on_match_one(&args, &UNIX_STYLE, &info);
- assert_eq!(result, None);
-}
-
-#[test]
-fn test_match_one_named_short_flag() {
- let mut info = PickerArgInfo::new();
- info.set_long("name");
- info.set_short('n');
- let args = make_args(&[("-n", 0), ("Alice", 1)]);
- let result = ArgMatcher::on_match_one(&args, &UNIX_STYLE, &info);
- assert_eq!(result, Some(0));
-}
-
-#[test]
-fn test_match_one_named_after_end_of_options() {
- // Flags after `--` should not be matched.
- let mut info = PickerArgInfo::new();
- info.set_long("name");
- let args = make_args(&[("--", 0), ("--name", 1), ("Alice", 2)]);
- let result = ArgMatcher::on_match_one(&args, &UNIX_STYLE, &info);
- assert_eq!(result, None);
-}
-
-// on_match_one — Positional
-
-#[test]
-fn test_match_one_positional_basic() {
- let mut info = PickerArgInfo::new();
- info.set_positional(true);
- let args = make_args(&[("file.txt", 0)]);
- let result = ArgMatcher::on_match_one(&args, &UNIX_STYLE, &info);
- assert_eq!(result, Some(0));
-}
-
-#[test]
-fn test_match_one_positional_takes_first() {
- let mut info = PickerArgInfo::new();
- info.set_positional(true);
- let args = make_args(&[("a.txt", 0), ("b.txt", 1)]);
- let result = ArgMatcher::on_match_one(&args, &UNIX_STYLE, &info);
- assert_eq!(result, Some(0));
-}
-
-// on_match_all — Named, single occurrence
-
-#[test]
-fn test_match_all_named_flag_plus_value() {
- let mut info = PickerArgInfo::new();
- info.set_long("name");
- let args = make_args(&[("--name", 0), ("Alice", 1)]);
- let result = ArgMatcher::on_match_all(&args, &UNIX_STYLE, &info);
- assert_eq!(result, vec![0, 1]);
-}
-
-#[test]
-fn test_match_all_named_eq_mode() {
- // --name=Alice: value is inline, only tag the flag position.
- let mut info = PickerArgInfo::new();
- info.set_long("name");
- let args = make_args(&[("--name=Alice", 0)]);
- let result = ArgMatcher::on_match_all(&args, &UNIX_STYLE, &info);
- assert_eq!(result, vec![0]);
-}
-
-#[test]
-fn test_match_all_named_no_value() {
- // Flag at end with no following arg: only tag the flag.
- let mut info = PickerArgInfo::new();
- info.set_long("name");
- let args = make_args(&[("--name", 0)]);
- let result = ArgMatcher::on_match_all(&args, &UNIX_STYLE, &info);
- assert_eq!(result, vec![0]);
-}
-
-#[test]
-fn test_match_all_named_value_looks_like_flag() {
- // The next arg looks like a flag — still tag it.
- // Validation is the Pickable's responsibility.
- let mut info = PickerArgInfo::new();
- info.set_long("name");
- let args = make_args(&[("--name", 0), ("--other", 1)]);
- let result = ArgMatcher::on_match_all(&args, &UNIX_STYLE, &info);
- assert_eq!(result, vec![0, 1]);
-}
-
-// on_match_all — Named, multiple occurrences (Single per flag)
-
-#[test]
-fn test_match_all_named_two_occurrences() {
- // --name Alice --name Bob → each occurrence gets one value.
- let mut info = PickerArgInfo::new();
- info.set_long("name");
- let args = make_args(&[("--name", 0), ("Alice", 1), ("--name", 2), ("Bob", 3)]);
- let result = ArgMatcher::on_match_all(&args, &UNIX_STYLE, &info);
- assert_eq!(result, vec![0, 1, 2, 3]);
-}
-
-#[test]
-fn test_match_all_named_skips_non_matching_args() {
- // --val a b --val d → only pairs (0,1) and (3,4); idx 2 ("b") left free.
- let mut info = PickerArgInfo::new();
- info.set_long("val");
- let args = make_args(&[("--val", 0), ("a", 1), ("b", 2), ("--val", 3), ("d", 4)]);
- let result = ArgMatcher::on_match_all(&args, &UNIX_STYLE, &info);
- assert_eq!(result, vec![0, 1, 3, 4]);
-}
-
-// on_match_all — Named, short flag
-
-#[test]
-fn test_match_all_named_short_flag() {
- let mut info = PickerArgInfo::new();
- info.set_long("name");
- info.set_short('n');
- let args = make_args(&[("-n", 0), ("Alice", 1)]);
- let result = ArgMatcher::on_match_all(&args, &UNIX_STYLE, &info);
- assert_eq!(result, vec![0, 1]);
-}
-
-// on_match_all — Named, eq + non-eq mixed
-
-#[test]
-fn test_match_all_named_mixed_eq_and_regular() {
- let mut info = PickerArgInfo::new();
- info.set_long("name");
- let args = make_args(&[("--name=Alice", 0), ("--name", 1), ("Bob", 2)]);
- let result = ArgMatcher::on_match_all(&args, &UNIX_STYLE, &info);
- assert_eq!(result, vec![0, 1, 2]);
-}
-
-// on_match_all — Named, case insensitive (PowerShell)
-
-#[test]
-fn test_match_all_named_powershell_case_insensitive() {
- let mut info = PickerArgInfo::new();
- info.set_long("Name");
- let args = make_args(&[("-name", 0), ("Alice", 1)]);
- let result = ArgMatcher::on_match_all(&args, &POWERSHELL_STYLE, &info);
- assert_eq!(result, vec![0, 1]);
-}
-
-// on_match_all — Positional
-
-#[test]
-fn test_match_all_positional_single() {
- let mut info = PickerArgInfo::new();
- info.set_positional(true);
- let args = make_args(&[("file.txt", 0)]);
- let result = ArgMatcher::on_match_all(&args, &UNIX_STYLE, &info);
- assert_eq!(result, vec![0]);
-}
-
-#[test]
-fn test_match_all_positional_multiple() {
- let mut info = PickerArgInfo::new();
- info.set_positional(true);
- let args = make_args(&[("a.txt", 0), ("b.txt", 1)]);
- let result = ArgMatcher::on_match_all(&args, &UNIX_STYLE, &info);
- assert_eq!(result, vec![0, 1]);
-}
-
-// End-of-options marker (`--`)
-
-#[test]
-fn test_match_all_named_stops_at_end_of_options() {
- // --name before `--` should match, --name after should not.
- let mut info = PickerArgInfo::new();
- info.set_long("name");
- let args = make_args(&[
- ("--name", 0),
- ("Alice", 1),
- ("--", 2),
- ("--name", 3),
- ("Bob", 4),
- ]);
- let result = ArgMatcher::on_match_all(&args, &UNIX_STYLE, &info);
- assert_eq!(result, vec![0, 1]);
-}
-
-#[test]
-fn test_match_all_positional_stops_at_end_of_options() {
- let mut info = PickerArgInfo::new();
- info.set_positional(true);
- let args = make_args(&[("before", 0), ("--", 1), ("after", 2)]);
- let result = ArgMatcher::on_match_all(&args, &UNIX_STYLE, &info);
- assert_eq!(result, vec![0]);
-}
-
-// Empty args
-
-#[test]
-fn test_match_one_empty() {
- let mut info = PickerArgInfo::new();
- info.set_long("name");
- let args = vec![];
- let result = ArgMatcher::on_match_one(&args, &UNIX_STYLE, &info);
- assert_eq!(result, None);
-}
-
-#[test]
-fn test_match_all_empty() {
- let mut info = PickerArgInfo::new();
- info.set_long("name");
- let args = vec![];
- let result = ArgMatcher::on_match_all(&args, &UNIX_STYLE, &info);
- assert!(result.is_empty());
-}
-
-// Verify that -- itself is never matched as a flag
-
-#[test]
-fn test_match_all_end_of_options_not_matched() {
- // `--` should neither match as a flag nor take a value.
- let mut info = PickerArgInfo::new();
- info.set_long("name");
- let args = make_args(&[("--", 0)]);
- let result = ArgMatcher::on_match_all(&args, &UNIX_STYLE, &info);
- assert!(result.is_empty());
-}
-
-// `--` should never be consumed as a value by a named flag.
-
-#[test]
-fn test_match_all_named_does_not_consume_end_marker() {
- // `--flag` before `--`, but the next position IS `--`.
- // Only tag the flag, don't consume the end-of-options marker.
- let mut info = PickerArgInfo::new();
- info.set_long("flag");
- let args = make_args(&[("--flag", 0), ("--", 1), ("value", 2)]);
- let result = ArgMatcher::on_match_all(&args, &UNIX_STYLE, &info);
- assert_eq!(result, vec![0], "should NOT consume -- as a value");
-}
-
-#[test]
-fn test_match_all_named_does_not_consume_end_marker_flag_before_end() {
- // Simulates: --flag -- you where -- is end-of-options.
- // The flag should be tagged but -- should NOT be consumed as its value.
- let mut info = PickerArgInfo::new();
- info.set_long("flag");
-
- let args = make_args(&[("--flag", 0), ("--", 1), ("you", 2)]);
- let result = ArgMatcher::on_match_all(&args, &UNIX_STYLE, &info);
- assert_eq!(
- result,
- vec![0],
- "flag before --: only tag flag, leave -- for positional"
- );
-}
-
-#[test]
-fn test_match_all_flag_after_end_has_value() {
- // Flag after `--` should not match at all.
- let mut info = PickerArgInfo::new();
- info.set_long("flag");
- let args = make_args(&[("--", 0), ("--flag", 1), ("value", 2)]);
- let result = ArgMatcher::on_match_all(&args, &UNIX_STYLE, &info);
- assert!(result.is_empty());
-}
diff --git a/arg_picker/test/src/test/basic_test.rs b/arg_picker/test/src/test/basic_test.rs
deleted file mode 100644
index b01adb6..0000000
--- a/arg_picker/test/src/test/basic_test.rs
+++ /dev/null
@@ -1,223 +0,0 @@
-use arg_picker::{macros::arg, IntoPicker};
-
-// Basic bool flag — present / absent
-
-#[test]
-fn test_bool_flag_present() {
- let args = vec!["--verbose"];
- let parsed = args
- .to_picker()
- .pick(&arg![verbose: bool])
- .or_default()
- .unwrap();
- assert_eq!(parsed, true);
-}
-
-#[test]
-fn test_bool_flag_absent() {
- let args: Vec<&str> = vec![];
- let parsed = args
- .to_picker()
- .pick(&arg![verbose: bool])
- .or_default()
- .unwrap();
- assert_eq!(parsed, false);
-}
-
-// Short flag — '-v'
-
-#[test]
-fn test_bool_short_flag_present() {
- let args = vec!["-v"];
- let parsed = args
- .to_picker()
- .pick(&arg![verbose: bool, 'v'])
- .or_default()
- .unwrap();
- assert_eq!(parsed, true);
-}
-
-// Multiple bool flags at once
-
-#[test]
-fn test_two_bool_flags_both_present() {
- // UNIX_STYLE now uses Kebab naming: `flag_a` → "flag-a" → --flag-a
- let args = vec!["--flag-a", "--flag-b"];
- let (a, b) = args
- .to_picker()
- .pick(&arg![flag_a: bool])
- .or_default()
- .pick(&arg![flag_b: bool])
- .or_default()
- .unwrap();
- assert_eq!(a, true);
- assert_eq!(b, true);
-}
-
-#[test]
-fn test_two_bool_flags_one_present() {
- let args = vec!["--flag-a"];
- let (a, b) = args
- .to_picker()
- .pick(&arg![flag_a: bool])
- .or_default()
- .pick(&arg![flag_b: bool])
- .or_default()
- .unwrap();
- assert_eq!(a, true);
- assert_eq!(b, false);
-}
-
-#[test]
-fn test_two_bool_flags_neither_present() {
- let args: Vec<&str> = vec![];
- let (a, b) = args
- .to_picker()
- .pick(&arg![flag_a: bool])
- .or_default()
- .pick(&arg![flag_b: bool])
- .or_default()
- .unwrap();
- assert_eq!(a, false);
- assert_eq!(b, false);
-}
-
-// Mixed short and long flags
-
-#[test]
-fn test_short_and_long_flags() {
- let args = vec!["-a", "--long-b"];
- let (a, b) = args
- .to_picker()
- .pick(&arg![flag_a: bool, 'a'])
- .or_default()
- .pick(&arg![long_b: bool])
- .or_default()
- .unwrap();
- assert_eq!(a, true);
- assert_eq!(b, true);
-}
-
-// Flags after `--` (end-of-options marker) should not be parsed.
-
-#[test]
-fn test_flag_after_end_of_options() {
- let args = vec!["--", "--verbose"];
- let parsed = args
- .to_picker()
- .pick(&arg![verbose: bool])
- .or_default()
- .unwrap();
- assert_eq!(parsed, false);
-}
-
-// Alias matching for bool flags
-
-#[test]
-fn test_bool_flag_with_alias() {
- let args = vec!["--cfg"];
- let parsed = args
- .to_picker()
- .pick(&arg![config: bool, "cfg"])
- .or_default()
- .unwrap();
- assert_eq!(parsed, true);
-}
-
-#[test]
-fn test_bool_flag_primary_name() {
- let args = vec!["--config"];
- let parsed = args
- .to_picker()
- .pick(&arg![config: bool, "cfg"])
- .or_default()
- .unwrap();
- assert_eq!(parsed, true);
-}
-
-// Short flag + alias for bool flag
-
-#[test]
-fn test_bool_flag_short_and_alias() {
- let args = vec!["-v"];
- let parsed = args
- .to_picker()
- .pick(&arg![verbose: bool, 'v', "cfg"])
- .or_default()
- .unwrap();
- assert_eq!(parsed, true);
-}
-
-// Default values: .or() / .or_default()
-
-#[test]
-fn test_or_default_without_args() {
- let args: Vec<&str> = vec![];
- let parsed = args
- .to_picker()
- .pick(&arg![verbose: bool])
- .or_default()
- .unwrap();
- assert_eq!(parsed, false);
-}
-
-#[test]
-fn test_or_custom_default() {
- let args: Vec<&str> = vec![];
- let parsed = args
- .to_picker()
- .pick(&arg![verbose: bool])
- .or(|| true)
- .unwrap();
- assert_eq!(parsed, true);
-}
-
-// to_result / to_option interface
-
-#[test]
-fn test_to_result_ok() {
- let args = vec!["--verbose"];
- let result = args
- .to_picker()
- .pick(&arg![verbose: bool])
- .or_default()
- .to_result();
- assert_eq!(result, Ok(true));
-}
-
-#[test]
-fn test_to_option_some() {
- let args = vec!["--verbose"];
- let opt = args
- .to_picker()
- .pick(&arg![verbose: bool])
- .or_default()
- .to_option();
- assert_eq!(opt, Some(true));
-}
-
-// Chain with_route passthrough
-
-#[test]
-fn test_with_route_chain() {
- let args = vec!["--flag"];
- let parsed = args
- .with_route::<String>()
- .pick(&arg![flag: bool])
- .or_default()
- .unwrap();
- assert_eq!(parsed, true);
-}
-
-// Unrelated flag should not match
-
-#[test]
-fn test_unrelated_flag_does_not_match() {
- let args = vec!["--other"];
- let parsed = args
- .to_picker()
- .pick(&arg![verbose: bool])
- .or_default()
- .unwrap();
- assert_eq!(parsed, false);
-}
diff --git a/arg_picker/test/src/test/multi_arg_test.rs b/arg_picker/test/src/test/multi_arg_test.rs
deleted file mode 100644
index 49517f1..0000000
--- a/arg_picker/test/src/test/multi_arg_test.rs
+++ /dev/null
@@ -1,186 +0,0 @@
-use arg_picker::parselib::{Matcher, MultiArgMatcher, UNIX_STYLE};
-use arg_picker::PickerArgInfo;
-
-use crate::make_args;
-
-// on_match_one — basic
-
-#[test]
-fn test_multi_one_finds_first_flag() {
- let mut info = PickerArgInfo::new();
- info.set_long("val");
- let args = make_args(&[("--other", 0), ("--val", 1), ("a", 2), ("b", 3)]);
- let result = MultiArgMatcher::on_match_one(&args, &UNIX_STYLE, &info);
- assert_eq!(result, Some(1));
-}
-
-#[test]
-fn test_multi_one_no_match() {
- let mut info = PickerArgInfo::new();
- info.set_long("val");
- let args = make_args(&[("--other", 0)]);
- let result = MultiArgMatcher::on_match_one(&args, &UNIX_STYLE, &info);
- assert_eq!(result, None);
-}
-
-// on_match_all — named, basic multi-value
-
-#[test]
-fn test_multi_all_flag_takes_all_values_until_next_flag() {
- // --val a b c → all three values belong to --val
- let mut info = PickerArgInfo::new();
- info.set_long("val");
- let args = make_args(&[("--val", 0), ("a", 1), ("b", 2), ("c", 3)]);
- let result = MultiArgMatcher::on_match_all(&args, &UNIX_STYLE, &info);
- assert_eq!(result, vec![0, 1, 2, 3]);
-}
-
-#[test]
-fn test_multi_all_stops_at_next_flag() {
- // --val a b --other c d → only a,b belong to --val
- let mut info = PickerArgInfo::new();
- info.set_long("val");
- let args = make_args(&[("--val", 0), ("a", 1), ("b", 2), ("--other", 3), ("c", 4)]);
- let result = MultiArgMatcher::on_match_all(&args, &UNIX_STYLE, &info);
- assert_eq!(result, vec![0, 1, 2]);
-}
-
-#[test]
-fn test_multi_all_flag_no_values() {
- // --val at end with no values → just the flag
- let mut info = PickerArgInfo::new();
- info.set_long("val");
- let args = make_args(&[("--val", 0)]);
- let result = MultiArgMatcher::on_match_all(&args, &UNIX_STYLE, &info);
- assert_eq!(result, vec![0]);
-}
-
-#[test]
-fn test_multi_all_empty() {
- let mut info = PickerArgInfo::new();
- info.set_long("val");
- let args = vec![];
- let result = MultiArgMatcher::on_match_all(&args, &UNIX_STYLE, &info);
- assert!(result.is_empty());
-}
-
-// on_match_all — named, multiple occurrences of same flag
-
-#[test]
-fn test_multi_all_two_occurrences() {
- // --val a b --val c d → two groups
- let mut info = PickerArgInfo::new();
- info.set_long("val");
- let args = make_args(&[
- ("--val", 0),
- ("a", 1),
- ("b", 2),
- ("--val", 3),
- ("c", 4),
- ("d", 5),
- ]);
- let result = MultiArgMatcher::on_match_all(&args, &UNIX_STYLE, &info);
- assert_eq!(result, vec![0, 1, 2, 3, 4, 5]);
-}
-
-#[test]
-fn test_multi_all_skips_non_matching_args() {
- // --val a --other b --val c → only --val groups
- let mut info = PickerArgInfo::new();
- info.set_long("val");
- let args = make_args(&[
- ("--val", 0),
- ("a", 1),
- ("--other", 2),
- ("b", 3),
- ("--val", 4),
- ("c", 5),
- ]);
- let result = MultiArgMatcher::on_match_all(&args, &UNIX_STYLE, &info);
- assert_eq!(result, vec![0, 1, 4, 5]);
-}
-
-// on_match_all — named, eq mode
-
-#[test]
-fn test_multi_all_eq_mode() {
- // --val=a b → eq mode + one extra value
- let mut info = PickerArgInfo::new();
- info.set_long("val");
- let args = make_args(&[("--val=a", 0), ("b", 1)]);
- let result = MultiArgMatcher::on_match_all(&args, &UNIX_STYLE, &info);
- assert_eq!(result, vec![0, 1]);
-}
-
-#[test]
-fn test_multi_all_eq_mode_no_extra() {
- // --val=a alone → just the eq flag
- let mut info = PickerArgInfo::new();
- info.set_long("val");
- let args = make_args(&[("--val=a", 0)]);
- let result = MultiArgMatcher::on_match_all(&args, &UNIX_STYLE, &info);
- assert_eq!(result, vec![0]);
-}
-
-#[test]
-fn test_multi_all_mixed_eq_and_regular() {
- // --val=a b --val c d
- let mut info = PickerArgInfo::new();
- info.set_long("val");
- let args = make_args(&[("--val=a", 0), ("b", 1), ("--val", 2), ("c", 3), ("d", 4)]);
- let result = MultiArgMatcher::on_match_all(&args, &UNIX_STYLE, &info);
- assert_eq!(result, vec![0, 1, 2, 3, 4]);
-}
-
-// on_match_all — short flag
-
-#[test]
-fn test_multi_all_short_flag() {
- let mut info = PickerArgInfo::new();
- info.set_short('v');
- info.set_long("val");
- let args = make_args(&[("-v", 0), ("a", 1), ("b", 2)]);
- let result = MultiArgMatcher::on_match_all(&args, &UNIX_STYLE, &info);
- assert_eq!(result, vec![0, 1, 2]);
-}
-
-// on_match_all — end-of-options marker
-
-#[test]
-fn test_multi_all_stops_at_end_of_options() {
- // --val a -- b → stops before `--`
- let mut info = PickerArgInfo::new();
- info.set_long("val");
- let args = make_args(&[("--val", 0), ("a", 1), ("--", 2), ("b", 3)]);
- let result = MultiArgMatcher::on_match_all(&args, &UNIX_STYLE, &info);
- assert_eq!(result, vec![0, 1]);
-}
-
-#[test]
-fn test_multi_all_flag_after_end_ignored() {
- let mut info = PickerArgInfo::new();
- info.set_long("val");
- let args = make_args(&[("--", 0), ("--val", 1), ("a", 2)]);
- let result = MultiArgMatcher::on_match_all(&args, &UNIX_STYLE, &info);
- assert!(result.is_empty());
-}
-
-// on_match_all — positional
-
-#[test]
-fn test_multi_all_positional() {
- let mut info = PickerArgInfo::new();
- info.set_positional(true);
- let args = make_args(&[("a.txt", 0), ("b.txt", 1)]);
- let result = MultiArgMatcher::on_match_all(&args, &UNIX_STYLE, &info);
- assert_eq!(result, vec![0, 1]);
-}
-
-#[test]
-fn test_multi_all_positional_stops_at_end_of_options() {
- let mut info = PickerArgInfo::new();
- info.set_positional(true);
- let args = make_args(&[("a.txt", 0), ("--", 1), ("b.txt", 2)]);
- let result = MultiArgMatcher::on_match_all(&args, &UNIX_STYLE, &info);
- assert_eq!(result, vec![0]);
-}
diff --git a/arg_picker/test/src/test/multi_value_test.rs b/arg_picker/test/src/test/multi_value_test.rs
deleted file mode 100644
index cbae645..0000000
--- a/arg_picker/test/src/test/multi_value_test.rs
+++ /dev/null
@@ -1,66 +0,0 @@
-use arg_picker::value::{Flag, VecUntil};
-use arg_picker::{macros::arg, IntoPicker};
-
-#[test]
-fn test_vec_until_i16_named() {
- let (nums, rest): (VecUntil<i16>, Flag) = vec!["--nums", "1", "2", "3", "abc"]
- .to_picker()
- .pick(&arg![nums: VecUntil<i16>])
- .pick(&arg![rest: Flag])
- .unwrap();
- assert_eq!(*nums, vec![1i16, 2, 3]);
- assert_eq!(rest, Flag::Inactive);
-}
-
-#[test]
-fn test_vec_until_i16_stops_at_non_number() {
- let nums: VecUntil<i16> = vec!["--nums", "42", "abc", "100"]
- .to_picker()
- .pick(&arg![nums: VecUntil<i16>])
- .unwrap();
- assert_eq!(*nums, vec![42i16]);
-}
-
-#[test]
-fn test_vec_until_i16_empty() {
- let nums: VecUntil<i16> = vec!["--nums"]
- .to_picker()
- .pick(&arg![nums: VecUntil<i16>])
- .unwrap();
- assert!(nums.is_empty());
-}
-
-#[test]
-fn test_vec_until_i16_stops_at_next_flag() {
- let nums: VecUntil<i16> = vec!["--nums", "1", "2", "--other", "3"]
- .to_picker()
- .pick(&arg![nums: VecUntil<i16>])
- .unwrap();
- assert_eq!(*nums, vec![1i16, 2]);
-}
-
-#[test]
-fn test_vec_until_i16_stops_at_end_of_options() {
- let nums: VecUntil<i16> = vec!["--nums", "1", "2", "--", "3"]
- .to_picker()
- .pick(&arg![nums: VecUntil<i16>])
- .unwrap();
- assert_eq!(*nums, vec![1i16, 2]);
-}
-
-// Two VecUntil<T> with different boundary behaviours
-
-#[test]
-fn test_vec_until_f64_and_i16_positional() {
- // Both are positional VecUntil. f64 takes all valid floats,
- // i16 takes what's left. But note: "1", "2", "3" are also valid
- // f64 values, so f64's check_boundary never fires — it consumes
- // everything, and i16 gets nothing.
- let (floats, ints): (VecUntil<f64>, VecUntil<i16>) = vec!["1.5", "2.5", "3.5", "1", "2", "3"]
- .to_picker()
- .pick(&arg![VecUntil<f64>])
- .pick(&arg![VecUntil<i16>])
- .unwrap();
- assert_eq!(*floats, vec![1.5, 2.5, 3.5]);
- assert_eq!(*ints, vec![1i16, 2, 3]);
-}
diff --git a/arg_picker/test/src/test/pos_matcher_test.rs b/arg_picker/test/src/test/pos_matcher_test.rs
deleted file mode 100644
index 0d4f87e..0000000
--- a/arg_picker/test/src/test/pos_matcher_test.rs
+++ /dev/null
@@ -1,141 +0,0 @@
-use arg_picker::parselib::{MaskedArg, Matcher, PositionalMatcher, UNIX_STYLE, WINDOWS_STYLE};
-use arg_picker::PickerArgInfo;
-
-fn make_args<'a>(pairs: &'a [(&'a str, usize)]) -> Vec<MaskedArg<'a>> {
- pairs
- .iter()
- .map(|&(raw, idx)| MaskedArg { raw, raw_idx: idx })
- .collect()
-}
-
-// on_match_one — basic
-
-#[test]
-fn test_pos_one_takes_first_non_flag() {
- let info = PickerArgInfo::new();
- let args = make_args(&[("--verbose", 0), ("file.txt", 1)]);
- let result = PositionalMatcher::on_match_one(&args, &UNIX_STYLE, &info);
- assert_eq!(result, Some(1));
-}
-
-#[test]
-fn test_pos_one_takes_first_if_no_flag() {
- let info = PickerArgInfo::new();
- let args = make_args(&[("file.txt", 0)]);
- let result = PositionalMatcher::on_match_one(&args, &UNIX_STYLE, &info);
- assert_eq!(result, Some(0));
-}
-
-#[test]
-fn test_pos_one_all_flags_returns_none() {
- let info = PickerArgInfo::new();
- let args = make_args(&[("--verbose", 0), ("--name", 1)]);
- let result = PositionalMatcher::on_match_one(&args, &UNIX_STYLE, &info);
- assert_eq!(result, None);
-}
-
-#[test]
-fn test_pos_one_empty_returns_none() {
- let info = PickerArgInfo::new();
- let args = vec![];
- let result = PositionalMatcher::on_match_one(&args, &UNIX_STYLE, &info);
- assert_eq!(result, None);
-}
-
-// on_match_one — after `--`
-
-#[test]
-fn test_pos_one_after_end_takes_even_flag_like() {
- // After `--`, accept everything including `--verbose`.
- let info = PickerArgInfo::new();
- let args = make_args(&[("--", 0), ("--verbose", 1), ("file.txt", 2)]);
- let result = PositionalMatcher::on_match_one(&args, &UNIX_STYLE, &info);
- assert_eq!(result, Some(1));
-}
-
-#[test]
-fn test_pos_one_only_end_returns_none() {
- let info = PickerArgInfo::new();
- let args = make_args(&[("--", 0)]);
- let result = PositionalMatcher::on_match_one(&args, &UNIX_STYLE, &info);
- assert_eq!(result, None);
-}
-
-// on_match_one — Windows style prefix
-
-#[test]
-fn test_pos_one_windows_skips_slash_prefix() {
- let info = PickerArgInfo::new();
- let args = make_args(&[("/Verbose", 0), ("file.txt", 1)]);
- let result = PositionalMatcher::on_match_one(&args, &WINDOWS_STYLE, &info);
- assert_eq!(result, Some(1));
-}
-
-// on_match_all — basic
-
-#[test]
-fn test_pos_all_collects_non_flags() {
- let info = PickerArgInfo::new();
- let args = make_args(&[("--verbose", 0), ("a.txt", 1), ("--name", 2), ("b.txt", 3)]);
- let result = PositionalMatcher::on_match_all(&args, &UNIX_STYLE, &info);
- assert_eq!(result, vec![1, 3]);
-}
-
-#[test]
-fn test_pos_all_only_flags_returns_empty() {
- let info = PickerArgInfo::new();
- let args = make_args(&[("--a", 0), ("--b", 1)]);
- let result = PositionalMatcher::on_match_all(&args, &UNIX_STYLE, &info);
- assert!(result.is_empty());
-}
-
-#[test]
-fn test_pos_all_empty() {
- let info = PickerArgInfo::new();
- let args = vec![];
- let result = PositionalMatcher::on_match_all(&args, &UNIX_STYLE, &info);
- assert!(result.is_empty());
-}
-
-// on_match_all — after `--`
-
-#[test]
-fn test_pos_all_after_end_accepts_everything() {
- // After `--`, even `--verbose` is accepted as positional.
- let info = PickerArgInfo::new();
- let args = make_args(&[
- ("--verbose", 0),
- ("a.txt", 1),
- ("--", 2),
- ("--flag", 3),
- ("b.txt", 4),
- ]);
- let result = PositionalMatcher::on_match_all(&args, &UNIX_STYLE, &info);
- assert_eq!(result, vec![1, 3, 4]);
-}
-
-#[test]
-fn test_pos_all_only_after_end() {
- let info = PickerArgInfo::new();
- let args = make_args(&[("--", 0), ("arg1", 1), ("--arg2", 2)]);
- let result = PositionalMatcher::on_match_all(&args, &UNIX_STYLE, &info);
- assert_eq!(result, vec![1, 2]);
-}
-
-// on_match_all — mixed before/after `--`
-
-#[test]
-fn test_pos_all_mixed() {
- let info = PickerArgInfo::new();
- let args = make_args(&[
- ("infile", 0),
- ("--verbose", 1),
- ("--", 2),
- ("outfile", 3),
- ("--extra", 4),
- ]);
- let result = PositionalMatcher::on_match_all(&args, &UNIX_STYLE, &info);
- // Before `--`: "infile" (skip --verbose).
- // After `--`: everything — "outfile", "--extra".
- assert_eq!(result, vec![0, 3, 4]);
-}
diff --git a/arg_picker/test/src/test/priority_test.rs b/arg_picker/test/src/test/priority_test.rs
deleted file mode 100644
index 8f0a452..0000000
--- a/arg_picker/test/src/test/priority_test.rs
+++ /dev/null
@@ -1,85 +0,0 @@
-use arg_picker::value::Flag;
-use arg_picker::{macros::arg, IntoPicker};
-
-// Same flag name, different Pickable types
-//
-// PickerArgAttr priority: Flag < Single
-// So Single and Multi should parse BEFORE Flag when sharing
-// the same flag name.
-
-#[test]
-fn test_single_takes_flag_when_sharing_name() {
- // --name Alice: String consumes it, Flag sees nothing.
- let (name, verbose): (String, Flag) = vec!["--name", "Alice"]
- .to_picker()
- .pick(&arg![name: String]) // Single, parsed first
- .pick(&arg![name: Flag]) // Flag, parsed second, nothing left
- .unwrap();
- assert_eq!(name, "Alice");
- assert_eq!(verbose, Flag::Inactive);
-}
-
-#[test]
-fn test_flag_only_triggers_when_single_missing() {
- // --verbose: only Flag matches, String gets default.
- let (name, verbose): (String, Flag) = vec!["--verbose"]
- .to_picker()
- .pick(&arg![name: String]) // Single, no match → default ""
- .or_default()
- .pick(&arg![name: Flag]) // Flag, no --name in args → Inactive
- .unwrap();
- assert_eq!(name, "");
- assert_eq!(verbose, Flag::Inactive);
-}
-
-#[test]
-fn test_flag_gets_leftovers_after_single_consumes_value() {
- // --name Alice --name: String takes "--name Alice", Flag takes "--name".
- let (name, verbose): (String, Flag) = vec!["--name", "Alice", "--name"]
- .to_picker()
- .pick(&arg![name: String]) // Single: tag [0, 1], consumes --name Alice
- .pick(&arg![name: Flag]) // Flag: tags position 2 (--name)
- .unwrap();
- assert_eq!(name, "Alice");
- assert_eq!(
- verbose,
- Flag::Active,
- "Flag should see --name at position 2 after String consumed positions 0-1"
- );
-}
-
-#[test]
-fn test_short_flag_sharing_same_letter() {
- // -n Alice: String takes it, Flag misses.
- let (name, verbose): (String, Flag) = vec!["-n", "Alice"]
- .to_picker()
- .pick(&arg![name: String, 'n']) // Single, parsed first
- .pick(&arg![name: Flag, 'n']) // Flag, parsed second
- .unwrap();
- assert_eq!(name, "Alice");
- assert_eq!(verbose, Flag::Inactive);
-}
-
-#[test]
-fn test_flag_captures_remaining_after_single_partial_consume() {
- // --name Alice --verbose: String takes --name Alice, Flag takes --verbose.
- let (name, verbose): (String, Flag) = vec!["--name", "Alice", "--verbose"]
- .to_picker()
- .pick(&arg![name: String]) // Single: tag [0, 1]
- .pick(&arg![verbose: Flag]) // Flag: tag [2]
- .unwrap();
- assert_eq!(name, "Alice");
- assert_eq!(verbose, Flag::Active);
-}
-
-#[test]
-fn test_single_skips_already_claimed_positions() {
- // --verbose --name Alice: Flag takes --verbose, String takes --name Alice.
- let (verbose, name): (Flag, String) = vec!["--verbose", "--name", "Alice"]
- .to_picker()
- .pick(&arg![verbose: Flag]) // Flag: tag [0]
- .pick(&arg![name: String]) // Single: tag [1, 2]
- .unwrap();
- assert_eq!(verbose, Flag::Active);
- assert_eq!(name, "Alice");
-}
diff --git a/arg_picker/test/src/test/route_test.rs b/arg_picker/test/src/test/route_test.rs
deleted file mode 100644
index c9cd5ab..0000000
--- a/arg_picker/test/src/test/route_test.rs
+++ /dev/null
@@ -1,200 +0,0 @@
-use arg_picker::{IntoPicker, macros::arg};
-
-// Route mechanism — or_route
-
-#[test]
-fn test_or_route_triggered_to_result() {
- // flag not present and no default value → route triggered → Err
- let args: Vec<&str> = vec![];
- let result: Result<bool, &'static str> = args
- .with_route::<&'static str>()
- .pick(&arg![verbose: bool])
- .or_route(|| "missing_verbose")
- .to_result();
- assert_eq!(result, Err("missing_verbose"));
-}
-
-#[test]
-fn test_or_route_not_triggered_when_flag_present() {
- // flag present → route not triggered → Ok
- let args = vec!["--verbose"];
- let result: Result<bool, &'static str> = args
- .with_route::<&'static str>()
- .pick(&arg![verbose: bool])
- .or_route(|| "missing_verbose")
- .to_result();
- assert_eq!(result, Ok(true));
-}
-
-#[test]
-fn test_or_default_priority_over_or_route() {
- // or_default takes priority over or_route: even if the flag is absent,
- // having a default prevents the route from being triggered
- let args: Vec<&str> = vec![];
- let result: Result<bool, &'static str> = args
- .with_route::<&'static str>()
- .pick(&arg![verbose: bool])
- .or_default()
- .or_route(|| "should_not_reach")
- .to_result();
- assert_eq!(result, Ok(false));
-}
-
-#[test]
-fn test_or_route_to_option_returns_none() {
- // When route is triggered, to_option returns None
- let args: Vec<&str> = vec![];
- let opt: Option<bool> = args
- .with_route::<&'static str>()
- .pick(&arg![verbose: bool])
- .or_route(|| "missing")
- .to_option();
- assert_eq!(opt, None);
-}
-
-#[test]
-#[should_panic(expected = "missing required argument")]
-fn test_or_route_unwrap_panics() {
- let args: Vec<&str> = vec![];
- args.with_route::<&'static str>()
- .pick(&arg![verbose: bool])
- .or_route(|| "missing")
- .unwrap();
-}
-
-#[test]
-fn test_or_route_with_string_route() {
- // Route type is String
- let args: Vec<&str> = vec![];
- let result: Result<bool, String> = args
- .with_route::<String>()
- .pick(&arg![verbose: bool])
- .or_route(|| "route_hit".to_string())
- .to_result();
- assert_eq!(result, Err("route_hit".to_string()));
-}
-
-#[test]
-fn test_or_route_with_custom_enum() {
- // Route type is a custom enum
- #[derive(Debug, Clone, Copy, PartialEq, Eq)]
- #[allow(dead_code)]
- enum Redirect {
- Help,
- Version,
- }
-
- let args: Vec<&str> = vec![];
- let result: Result<bool, Redirect> = args
- .with_route::<Redirect>()
- .pick(&arg![verbose: bool])
- .or_route(|| Redirect::Help)
- .to_result();
- assert_eq!(result, Err(Redirect::Help));
-}
-
-// Route mechanism — multiple flags
-
-#[test]
-fn test_two_flags_first_missing_triggers_route() {
- // Both flags missing; first has or_route → Err("first");
- // first route wins (first-checked, first-triggered)
- let args: Vec<&str> = vec![];
- let result: Result<(bool, bool), &'static str> = args
- .with_route::<&'static str>()
- .pick(&arg![flag_a: bool])
- .or_route(|| "first_missing")
- .pick(&arg![flag_b: bool])
- .or_route(|| "second_missing")
- .to_result();
- assert_eq!(result, Err("first_missing"));
-}
-
-#[test]
-fn test_two_flags_second_missing_triggers_route() {
- // First has or_default (no route triggered), second missing with or_route → Err("second")
- let args: Vec<&str> = vec![];
- let result: Result<(bool, bool), &'static str> = args
- .with_route::<&'static str>()
- .pick(&arg![flag_a: bool])
- .or_default()
- .pick(&arg![flag_b: bool])
- .or_route(|| "second_missing")
- .to_result();
- assert_eq!(result, Err("second_missing"));
-}
-
-#[test]
-fn test_two_flags_both_present_route_not_triggered() {
- // Both flags present → route not triggered → Ok((true, true))
- let args = vec!["--flag-a", "--flag-b"];
- let result: Result<(bool, bool), &'static str> = args
- .with_route::<&'static str>()
- .pick(&arg![flag_a: bool])
- .or_route(|| "first_missing")
- .pick(&arg![flag_b: bool])
- .or_route(|| "second_missing")
- .to_result();
- assert_eq!(result, Ok((true, true)));
-}
-
-#[test]
-fn test_two_flags_first_missing_no_route_second_has_route() {
- // First missing but has no or_route, second missing with or_route → Err("second")
- // Note: the first has neither route nor default, so pick failure does not modify error_route
- let args: Vec<&str> = vec![];
- let result: Result<(bool, bool), &'static str> = args
- .with_route::<&'static str>()
- .pick(&arg![flag_a: bool])
- // No or_default, no or_route either
- .pick(&arg![flag_b: bool])
- .or_route(|| "second_missing")
- .to_result();
- assert_eq!(result, Err("second_missing"));
-}
-
-#[test]
-fn test_two_flags_only_second_has_default() {
- // First is missing with no default/route (v1=NotFound), second is missing but has or_default
- // Use unpack to check both results
- let args: Vec<&str> = vec![];
- let (a, b) = args
- .to_picker()
- .pick(&arg![flag_a: bool])
- .pick(&arg![flag_b: bool])
- .or_default()
- .unpack();
- assert_eq!(a, None);
- assert_eq!(b, Some(false));
-}
-
-// Route with with_route
-
-#[test]
-fn test_with_route_and_or_route() {
- // with_route::&lt;String&gt;() sets the Route type + or_route triggers
- let args: Vec<&str> = vec![];
- let result: Result<bool, String> = args
- .with_route::<String>()
- .pick(&arg![verbose: bool])
- .or_route(|| "redirected".to_string())
- .to_result();
- assert_eq!(result, Err("redirected".to_string()));
-}
-
-#[test]
-fn test_with_route_type_switch() {
- // with_route switching Route type clears old route_$ and error_route
- let args: Vec<&str> = vec![];
- let result: Result<(bool, bool), i32> = args
- .with_route::<String>()
- .pick(&arg![verbose: bool])
- .or_route(|| "string_route".to_string())
- .with_route::<i32>()
- .pick(&arg![other: bool])
- .or_route(|| -1)
- .to_result();
- // The first flag is missing, but its or_route is cleared when with_route switches (route_$ = None)
- // The second flag is missing and has or_route → Err(-1)
- assert_eq!(result, Err(-1));
-}
diff --git a/arg_picker/test/src/test/style_test.rs b/arg_picker/test/src/test/style_test.rs
deleted file mode 100644
index b470d4f..0000000
--- a/arg_picker/test/src/test/style_test.rs
+++ /dev/null
@@ -1,300 +0,0 @@
-use arg_picker::parselib::{
- build_possible_flags, FlagMatcher, Matcher, ParserStyle, ParserStyleNamingCase,
- POWERSHELL_STYLE, UNIX_STYLE, WINDOWS_STYLE,
-};
-use arg_picker::PickerArgInfo;
-
-use crate::make_masked;
-
-// Style: formatting utilities
-
-#[test]
-fn test_unix_style_flag_string() {
- assert_eq!(UNIX_STYLE.flag_string('v'), "-v");
- assert_eq!(UNIX_STYLE.flag_string("verbose"), "--verbose");
-}
-
-#[test]
-fn test_windows_style_flag_string() {
- assert_eq!(WINDOWS_STYLE.flag_string('v'), "/v");
- assert_eq!(WINDOWS_STYLE.flag_string("verbose"), "/verbose");
-}
-
-#[test]
-fn test_powershell_style_flag_string() {
- assert_eq!(POWERSHELL_STYLE.flag_string('v'), "-v");
- assert_eq!(POWERSHELL_STYLE.flag_string("Verbose"), "-Verbose");
-}
-
-#[test]
-fn test_build_possible_flags_windows() {
- // Build PickerArgInfo from a flag definition: `verbose: bool`
- let mut info = PickerArgInfo::new();
- info.set_long("verbose");
- let flags = build_possible_flags(&WINDOWS_STYLE, &info);
- assert_eq!(flags, vec!["/Verbose"]);
-}
-
-#[test]
-fn test_build_possible_flags_with_short_and_alias() {
- let mut info = PickerArgInfo::new();
- info.set_short('n');
- info.set_long("name");
- info.set_alias(vec!["nickname"]);
- let flags = build_possible_flags(&UNIX_STYLE, &info);
- assert_eq!(flags, vec!["-n", "--name", "--nickname"]);
-}
-
-// Style: matching with different styles via Matcher trait
-
-#[test]
-fn test_windows_style_match() {
- // Windows style: /verbose (case insensitive)
- let mut info = PickerArgInfo::new();
- info.set_long("verbose");
-
- let args = vec![make_masked("/verbose", 0)];
- let result = FlagMatcher::on_match_one(&args, &WINDOWS_STYLE, &info);
- assert_eq!(result, Some(0));
-}
-
-#[test]
-fn test_windows_style_match_case_insensitive() {
- // Windows style is case-insensitive: /VERBOSE should match "verbose"
- let mut info = PickerArgInfo::new();
- info.set_long("verbose");
-
- let args = vec![make_masked("/VERBOSE", 0)];
- let result = FlagMatcher::on_match_one(&args, &WINDOWS_STYLE, &info);
- assert_eq!(result, Some(0));
-}
-
-#[test]
-fn test_windows_style_no_match_on_unrelated_flag() {
- // Different flag should not match
- let mut info = PickerArgInfo::new();
- info.set_long("verbose");
-
- let args = vec![make_masked("/output", 0)];
- let result = FlagMatcher::on_match_one(&args, &WINDOWS_STYLE, &info);
- assert_eq!(result, None);
-}
-
-#[test]
-fn test_powershell_style_match() {
- // PowerShell style: -Verbose (case insensitive)
- let mut info = PickerArgInfo::new();
- info.set_long("Verbose");
-
- let args = vec![make_masked("-Verbose", 0)];
- let result = FlagMatcher::on_match_one(&args, &POWERSHELL_STYLE, &info);
- assert_eq!(result, Some(0));
-}
-
-#[test]
-fn test_powershell_style_match_case_insensitive() {
- let mut info = PickerArgInfo::new();
- info.set_long("Verbose");
-
- let args = vec![make_masked("-VERBOSE", 0)];
- let result = FlagMatcher::on_match_one(&args, &POWERSHELL_STYLE, &info);
- assert_eq!(result, Some(0));
-}
-
-#[test]
-fn test_unix_style_case_sensitive_no_match() {
- // UNIX style is case-sensitive: --VERBOSE should NOT match --verbose
- let mut info = PickerArgInfo::new();
- info.set_long("verbose");
-
- let args = vec![make_masked("--VERBOSE", 0)];
- let result = FlagMatcher::on_match_one(&args, &UNIX_STYLE, &info);
- assert_eq!(result, None);
-}
-
-#[test]
-fn test_windows_style_match_all() {
- // on_match_all should find all matching flags
- let mut info = PickerArgInfo::new();
- info.set_long("verbose");
- info.set_short('v');
-
- let args = vec![
- make_masked("/v", 0),
- make_masked("/output", 1),
- make_masked("/VERBOSE", 2),
- ];
- let result = FlagMatcher::on_match_all(&args, &WINDOWS_STYLE, &info);
- assert_eq!(result, vec![0, 2]);
-}
-
-#[test]
-fn test_windows_style_match_after_end_of_options() {
- // end_of_options is always "--" regardless of style
- // Flags after -- should not match
- let mut info = PickerArgInfo::new();
- info.set_long("verbose");
-
- let args = vec![
- make_masked("/verbose", 0),
- make_masked("--", 1),
- make_masked("/verbose", 2),
- ];
- let result = FlagMatcher::on_match_all(&args, &WINDOWS_STYLE, &info);
- // end_of_options is always "--" regardless of style
- assert_eq!(result, vec![0]);
-}
-
-// Naming case conversion
-
-/// A Unix-like style with kebab-case naming.
-const KEBAB_STYLE: ParserStyle<'static> = ParserStyle {
- naming_case: ParserStyleNamingCase::Kebab,
- ..UNIX_STYLE
-};
-
-#[test]
-fn test_kebab_case_naming_for_multiword_flag() {
- // flag name `flag_a` → Kebab → `flag-a` → `--flag-a`
- let mut info = PickerArgInfo::new();
- info.set_long("flag_a");
-
- let args = vec![make_masked("--flag-a", 0)];
- let result = FlagMatcher::on_match_one(&args, &KEBAB_STYLE, &info);
- assert_eq!(
- result,
- Some(0),
- "--flag-a should match flag_a via kebab-case conversion"
- );
-}
-
-#[test]
-fn test_snake_case_should_not_match_as_long_flag() {
- // With kebab naming, `--flag_a` (snake) should NOT match `flag_a`
- let mut info = PickerArgInfo::new();
- info.set_long("flag_a");
-
- let args = vec![make_masked("--flag_a", 0)];
- let result = FlagMatcher::on_match_one(&args, &KEBAB_STYLE, &info);
- assert_eq!(
- result, None,
- "--flag_a should NOT match in kebab-style context"
- );
-}
-
-#[test]
-fn test_kebab_case_naming_for_unix_style() {
- // UNIX_STYLE now uses Kebab case: `flag_a` → `flag-a` → `--flag-a`
- let mut info = PickerArgInfo::new();
- info.set_long("flag_a");
-
- let args = vec![make_masked("--flag-a", 0)];
- let result = FlagMatcher::on_match_one(&args, &UNIX_STYLE, &info);
- assert_eq!(
- result,
- Some(0),
- "--flag-a should match with kebab-style UNIX_STYLE"
- );
-}
-
-#[test]
-fn test_snake_case_rejected_by_unix_style() {
- // UNIX_STYLE uses Kebab: `--flag_a` (snake) should NOT match
- let mut info = PickerArgInfo::new();
- info.set_long("flag_a");
-
- let args = vec![make_masked("--flag_a", 0)];
- let result = FlagMatcher::on_match_one(&args, &UNIX_STYLE, &info);
- assert_eq!(
- result, None,
- "--flag_a should NOT match under kebab-style UNIX_STYLE"
- );
-}
-
-#[test]
-fn test_powershell_pascal_case_naming() {
- // POWERSHELL_STYLE uses Pascal case: `verbose` → `Verbose` → `-Verbose`
- let mut info = PickerArgInfo::new();
- info.set_long("verbose");
-
- let args = vec![make_masked("-Verbose", 0)];
- let result = FlagMatcher::on_match_one(&args, &POWERSHELL_STYLE, &info);
- assert_eq!(
- result,
- Some(0),
- "-Verbose should match verbose via Pascal case"
- );
-}
-
-#[test]
-fn test_flag_naming_kebab_matches_my_name() {
- // UNIX_STYLE now uses Kebab: `my_name` → `my-name` → `--my-name`
- let mut info = PickerArgInfo::new();
- info.set_long("my_name");
-
- let args = vec![make_masked("--my-name", 0)];
- let result = FlagMatcher::on_match_one(&args, &UNIX_STYLE, &info);
- assert_eq!(
- result,
- Some(0),
- "--my-name should match via kebab conversion"
- );
-}
-
-#[test]
-fn test_flag_naming_kebab_rejects_my_name_underscore() {
- // Kebab style: `--my_name` (snake) should NOT match
- let mut info = PickerArgInfo::new();
- info.set_long("my_name");
-
- let args = vec![make_masked("--my_name", 0)];
- let result = FlagMatcher::on_match_one(&args, &UNIX_STYLE, &info);
- assert_eq!(
- result, None,
- "--my_name should NOT match under kebab-style UNIX_STYLE"
- );
-}
-
-#[test]
-fn test_flag_naming_pascal_matches_my_name() {
- // `my_name` under Pascal → `MyName` → `-MyName`
- let mut info = PickerArgInfo::new();
- info.set_long("my_name");
-
- let args = vec![make_masked("-MyName", 0)];
- let result = FlagMatcher::on_match_one(&args, &POWERSHELL_STYLE, &info);
- assert_eq!(
- result,
- Some(0),
- "-MyName should match via Pascal conversion"
- );
-}
-
-#[test]
-fn test_flag_naming_pascal_matches_lowercase() {
- // PowerShell is case-insensitive: `-myname` should also match
- let mut info = PickerArgInfo::new();
- info.set_long("my_name");
-
- let args = vec![make_masked("-myname", 0)];
- let result = FlagMatcher::on_match_one(&args, &POWERSHELL_STYLE, &info);
- assert_eq!(
- result,
- Some(0),
- "-myname (lowercase) should match via case-insensitive Pascal"
- );
-}
-
-#[test]
-fn test_flag_naming_pascal_rejects_my_name_underscore() {
- // Pascal style: `-my_name` (underscore) should NOT match
- let mut info = PickerArgInfo::new();
- info.set_long("my_name");
-
- let args = vec![make_masked("-my_name", 0)];
- let result = FlagMatcher::on_match_one(&args, &POWERSHELL_STYLE, &info);
- assert_eq!(
- result, None,
- "-my_name should NOT match under Pascal-style naming"
- );
-}
diff --git a/arg_picker/test/src/test/value_flag_test.rs b/arg_picker/test/src/test/value_flag_test.rs
deleted file mode 100644
index c60ab1e..0000000
--- a/arg_picker/test/src/test/value_flag_test.rs
+++ /dev/null
@@ -1,174 +0,0 @@
-use arg_picker::value::Flag;
-use arg_picker::{IntoPicker, macros::arg};
-
-// Basic Flag — present / absent
-
-#[test]
-fn test_flag_present() {
- let flag: Flag = vec!["--verbose"]
- .to_picker()
- .pick(&arg![verbose: Flag])
- .unwrap();
- assert_eq!(flag, Flag::Active);
-}
-
-#[test]
-fn test_flag_absent_returns_inactive() {
- // Unlike bool, Flag::pick returns Parsed(Inactive) when no match is found,
- // so or_default() is NOT required — unwrap() works directly.
- let flag: Flag = Vec::<&str>::new()
- .to_picker()
- .pick(&arg![verbose: Flag])
- .unwrap();
- assert_eq!(flag, Flag::Inactive);
-}
-
-// Short Flag
-
-#[test]
-fn test_flag_short_present() {
- let flag: Flag = vec!["-v"]
- .to_picker()
- .pick(&arg![verbose: Flag, 'v'])
- .unwrap();
- assert_eq!(flag, Flag::Active);
-}
-
-// Multiple Flags
-
-#[test]
-fn test_two_flags_both_present() {
- let (a, b): (Flag, Flag) = vec!["--flag-a", "--flag-b"]
- .to_picker()
- .pick(&arg![flag_a: Flag])
- .pick(&arg![flag_b: Flag])
- .unwrap();
- assert_eq!(a, Flag::Active);
- assert_eq!(b, Flag::Active);
-}
-
-#[test]
-fn test_two_flags_one_present() {
- let (a, b): (Flag, Flag) = vec!["--flag-a"]
- .to_picker()
- .pick(&arg![flag_a: Flag])
- .pick(&arg![flag_b: Flag])
- .unwrap();
- assert_eq!(a, Flag::Active);
- assert_eq!(b, Flag::Inactive);
-}
-
-#[test]
-fn test_two_flags_neither_present() {
- let (a, b): (Flag, Flag) = Vec::<&str>::new()
- .to_picker()
- .pick(&arg![flag_a: Flag])
- .pick(&arg![flag_b: Flag])
- .unwrap();
- assert_eq!(a, Flag::Inactive);
- assert_eq!(b, Flag::Inactive);
-}
-
-// After `--` (end-of-options)
-
-#[test]
-fn test_flag_after_end_of_options() {
- let flag: Flag = vec!["--", "--verbose"]
- .to_picker()
- .pick(&arg![verbose: Flag])
- .unwrap();
- assert_eq!(flag, Flag::Inactive);
-}
-
-// Alias
-
-#[test]
-fn test_flag_with_alias() {
- let flag: Flag = vec!["--cfg"]
- .to_picker()
- .pick(&arg![config: Flag, "cfg"])
- .unwrap();
- assert_eq!(flag, Flag::Active);
-}
-
-#[test]
-fn test_flag_primary_name() {
- let flag: Flag = vec!["--config"]
- .to_picker()
- .pick(&arg![config: Flag, "cfg"])
- .unwrap();
- assert_eq!(flag, Flag::Active);
-}
-
-// Unrelated flag should not match
-
-#[test]
-fn test_unrelated_flag_does_not_match() {
- let flag: Flag = vec!["--other"]
- .to_picker()
- .pick(&arg![verbose: Flag])
- .unwrap();
- assert_eq!(flag, Flag::Inactive);
-}
-
-// to_result / to_option
-
-#[test]
-fn test_flag_to_result() {
- let result: Result<Flag, ()> = vec!["--verbose"]
- .to_picker()
- .pick(&arg![verbose: Flag])
- .to_result();
- assert_eq!(result, Ok(Flag::Active));
-}
-
-#[test]
-fn test_flag_to_option() {
- let opt: Option<Flag> = vec!["--verbose"]
- .to_picker()
- .pick(&arg![verbose: Flag])
- .to_option();
- assert_eq!(opt, Some(Flag::Active));
-}
-
-// Bool conversions
-
-#[test]
-fn test_flag_converts_to_bool() {
- let flag = Flag::Active;
- assert_eq!(bool::from(flag), true);
-
- let flag = Flag::Inactive;
- assert_eq!(bool::from(flag), false);
-}
-
-#[test]
-fn test_flag_from_bool() {
- assert_eq!(Flag::from(true), Flag::Active);
- assert_eq!(Flag::from(false), Flag::Inactive);
-}
-
-#[test]
-fn test_flag_deref_to_bool() {
- let active = Flag::Active;
- assert_eq!(*active, true);
-
- let inactive = Flag::Inactive;
- assert_eq!(*inactive, false);
-}
-
-// Flag never triggers route (unlike bool)
-//
-// Flag::pick always returns Parsed, so the fallback chain
-// (default → route) is never entered.
-
-#[test]
-fn test_flag_absent_does_not_trigger_route() {
- // Even without or_default / or_route, absent flag returns Inactive, not a route
- let result: Result<Flag, &str> = Vec::<&str>::new()
- .with_route::<&str>()
- .pick(&arg![verbose: Flag])
- .or_route(|| "should_not_fire")
- .to_result();
- assert_eq!(result, Ok(Flag::Inactive));
-}
diff --git a/arg_picker/test/src/test/value_string_test.rs b/arg_picker/test/src/test/value_string_test.rs
deleted file mode 100644
index 2c3b50b..0000000
--- a/arg_picker/test/src/test/value_string_test.rs
+++ /dev/null
@@ -1,171 +0,0 @@
-use arg_picker::{macros::arg, IntoPicker};
-
-// Basic named String — present / absent
-
-#[test]
-fn test_string_named_present() {
- let val: String = vec!["--name", "Alice"]
- .to_picker()
- .pick(&arg![name: String])
- .or_default()
- .unwrap();
- assert_eq!(val, "Alice");
-}
-
-#[test]
-fn test_string_named_absent_uses_default() {
- let val: String = Vec::<&str>::new()
- .to_picker()
- .pick(&arg![name: String])
- .or_default()
- .unwrap();
- assert_eq!(val, "");
-}
-
-// Named String — eq mode
-
-#[test]
-fn test_string_named_eq_mode() {
- let val: String = vec!["--name=Alice"]
- .to_picker()
- .pick(&arg![name: String])
- .or_default()
- .unwrap();
- assert_eq!(val, "Alice");
-}
-
-// Named String — short flag
-
-#[test]
-fn test_string_named_short_flag() {
- let val: String = vec!["-n", "Alice"]
- .to_picker()
- .pick(&arg![name: String, 'n'])
- .or_default()
- .unwrap();
- assert_eq!(val, "Alice");
-}
-
-// Named String — no value after flag
-
-#[test]
-fn test_string_named_missing_value_triggers_default() {
- // --name at end with no following arg → pick returns NotFound → or_default gives ""
- let val: String = vec!["--name"]
- .to_picker()
- .pick(&arg![name: String])
- .or_default()
- .unwrap();
- assert_eq!(val, "");
-}
-
-// Positional String
-
-#[test]
-fn test_string_positional() {
- let val: String = vec!["file.txt"]
- .to_picker()
- .pick(&arg![String])
- .or_default()
- .unwrap();
- assert_eq!(val, "file.txt");
-}
-
-#[test]
-fn test_string_positional_takes_first() {
- let val: String = vec!["first", "second"]
- .to_picker()
- .pick(&arg![String])
- .or_default()
- .unwrap();
- assert_eq!(val, "first");
-}
-
-// Multiple occurrences (Single only tags one occurrence per Parseable)
-
-#[test]
-fn test_string_two_named_flags() {
- let (a, b): (String, String) = vec!["--name", "Alice", "--greeting", "Hello"]
- .to_picker()
- .pick(&arg![name: String])
- .or_default()
- .pick(&arg![greeting: String])
- .or_default()
- .unwrap();
- assert_eq!(a, "Alice");
- assert_eq!(b, "Hello");
-}
-
-// Mixed named + positional
-
-#[test]
-fn test_string_named_and_positional() {
- let (name, file): (String, String) = vec!["--name", "Alice", "file.txt"]
- .to_picker()
- .pick(&arg![name: String])
- .or_default()
- .pick(&arg![String])
- .or_default()
- .unwrap();
- assert_eq!(name, "Alice");
- assert_eq!(file, "file.txt");
-}
-
-// After `--` (end-of-options)
-
-#[test]
-fn test_string_named_after_end_of_options() {
- // Named arg after `--` should not be matched → default
- let val: String = vec!["--", "--name", "Alice"]
- .to_picker()
- .pick(&arg![name: String])
- .or_default()
- .unwrap();
- assert_eq!(val, "");
-}
-
-// Unrelated flag should not match → default
-
-#[test]
-fn test_string_unrelated_flag() {
- let val: String = vec!["--other", "value"]
- .to_picker()
- .pick(&arg![name: String])
- .or_default()
- .unwrap();
- assert_eq!(val, "");
-}
-
-// to_result / to_option
-
-#[test]
-fn test_string_to_result() {
- let result: Result<String, ()> = vec!["--name", "Alice"]
- .to_picker()
- .pick(&arg![name: String])
- .or_default()
- .to_result();
- assert_eq!(result, Ok("Alice".to_string()));
-}
-
-#[test]
-fn test_string_to_option() {
- let opt: Option<String> = vec!["--name", "Alice"]
- .to_picker()
- .pick(&arg![name: String])
- .or_default()
- .to_option();
- assert_eq!(opt, Some("Alice".to_string()));
-}
-
-// Custom default via .or()
-
-#[test]
-fn test_string_custom_default() {
- let val: String = Vec::<&str>::new()
- .to_picker()
- .pick(&arg![name: String])
- .or(|| "default_name".to_string())
- .unwrap();
- assert_eq!(val, "default_name");
-}
diff --git a/arg_picker_macros/Cargo.toml b/arg_picker_macros/Cargo.toml
deleted file mode 100644
index a824b0d..0000000
--- a/arg_picker_macros/Cargo.toml
+++ /dev/null
@@ -1,20 +0,0 @@
-[package]
-name = "arg-picker-macros"
-version = "0.2.0"
-edition = "2024"
-license = "MIT OR Apache-2.0"
-repository = "https://github.com/mingling-rs/mingling/tree/main/arg_picker"
-authors = ["Weicao-CatilGrass"]
-readme = "README.md"
-description = "Macros for arg-picker"
-
-[lib]
-proc-macro = true
-
-[features]
-mingling_support = []
-
-[dependencies]
-syn.workspace = true
-quote.workspace = true
-proc-macro2.workspace = true
diff --git a/arg_picker_macros/LICENSE-APACHE b/arg_picker_macros/LICENSE-APACHE
deleted file mode 120000
index 965b606..0000000
--- a/arg_picker_macros/LICENSE-APACHE
+++ /dev/null
@@ -1 +0,0 @@
-../LICENSE-APACHE \ No newline at end of file
diff --git a/arg_picker_macros/LICENSE-MIT b/arg_picker_macros/LICENSE-MIT
deleted file mode 120000
index 76219eb..0000000
--- a/arg_picker_macros/LICENSE-MIT
+++ /dev/null
@@ -1 +0,0 @@
-../LICENSE-MIT \ No newline at end of file
diff --git a/arg_picker_macros/README.md b/arg_picker_macros/README.md
deleted file mode 100644
index 5a3f220..0000000
--- a/arg_picker_macros/README.md
+++ /dev/null
@@ -1,40 +0,0 @@
-# Mingling Picker Macros
-
-Procedural macros for [Mingling Picker](https://github.com/mingling-rs/mingling/tree/main/mingling_picker), enabled by the `mingling/picker` feature.
-
-```toml
-[dependencies.mingling]
-version = "0.3.0"
-features = [
- "picker"
-]
-```
-
-## Provided Macros
-
-### Macro `arg!`
-
-Declares a parameter definition for use with `Picker`'s `.pick()` method:
-
-```rust,ignore
-use mingling_picker_macros::arg;
-
-// Named flag with a value
-let flag = arg![name: String];
-
-// Named flag with short form
-let flag = arg![name: String, 'n'];
-
-// Named flag with alias
-let flag = arg![name: String, 'n', "nickname"];
-
-// Positional parameter
-let flag = arg![String];
-
-// Flag-only parameter (boolean)
-let flag = arg![verbose: Flag];
-```
-
-### Macro `internal_repeat!` (Internal)
-
-Internal macro used by Picker to generate `PickerPattern1..=32` and their parsing logic. Not intended for direct use.
diff --git a/arg_picker_macros/mingling-ci.toml b/arg_picker_macros/mingling-ci.toml
deleted file mode 100644
index 62fc2b1..0000000
--- a/arg_picker_macros/mingling-ci.toml
+++ /dev/null
@@ -1,2 +0,0 @@
-[test]
-command = ["cargo", "test", "-p", "arg-picker-macros"]
diff --git a/arg_picker_macros/src/arg.rs b/arg_picker_macros/src/arg.rs
deleted file mode 100644
index 1ad895e..0000000
--- a/arg_picker_macros/src/arg.rs
+++ /dev/null
@@ -1,215 +0,0 @@
-// Doc Not Optimize
-use proc_macro::{TokenStream, TokenTree};
-use proc_macro2::TokenStream as TS2;
-use quote::quote;
-
-pub(crate) fn arg(input: TokenStream) -> TokenStream {
- let tokens: Vec<TokenTree> = input.into_iter().collect();
- let args = split_at_commas(&tokens);
- if args.is_empty() {
- return quote! { compile_error!("arg! flaguires at least one argument") }.into();
- }
-
- let first = &args[0];
- let rest = &args[1..];
-
- // Validate: at most one char literal
- let char_count = rest.iter().filter(|a| is_char_literal(a)).count();
- if char_count > 1 {
- return quote! { compile_error!("arg! only supports at most one short name") }.into();
- }
-
- // Extract short char and string aliases
- let short_char: Option<char> = rest
- .iter()
- .find(|a| is_char_literal(a))
- .and_then(|a| extract_char(a));
- let aliases: Vec<String> = rest
- .iter()
- .filter(|a| is_string_literal(a))
- .filter_map(|a| extract_string(a))
- .collect();
-
- // Parse first argument
- // arg![name: Type] → name, with explicit type
- // arg![Type] → positional type, no name
- let colon_pos = first
- .iter()
- .position(|t| matches!(t, TokenTree::Punct(p) if p.as_char() == ':'));
- let has_named_colon = colon_pos.is_some_and(|pos| pos > 0 && !is_double_colon(first, pos));
-
- let first_slice = first.as_slice();
- let (name, ty, is_named) = if has_named_colon {
- let pos = colon_pos.unwrap();
- // `name : Type`
- (
- Some(&first_slice[..pos]),
- Some(&first_slice[pos + 1..]),
- true,
- )
- } else {
- // No `name:` prefix → the entire first argument is the type
- (None, Some(first_slice), false)
- };
-
- // Build full names
- let full_names: Vec<String> = match name {
- Some(n) => {
- let mut v = vec![join_idents(n)];
- v.extend(aliases);
- v
- }
- None => aliases,
- };
-
- let path: TS2 = {
- let ty_ts: TS2 = ty
- .map(|t| {
- let ts: TokenStream = t.iter().cloned().collect();
- TS2::from(ts)
- })
- .unwrap_or(TS2::new());
-
- #[cfg(feature = "mingling_support")]
- let import = quote! { ::mingling::picker::PickerArg };
-
- #[cfg(not(feature = "mingling_support"))]
- let import = quote! { ::arg_picker::PickerArg };
-
- if ty.is_some() {
- quote! { #import::<#ty_ts> }
- } else {
- quote! { #import::<_> }
- }
- };
-
- // full: &["name", "alias", ...] or &[]
- let full_value: TS2 = if !full_names.is_empty() {
- let strs: Vec<proc_macro2::Literal> = full_names
- .iter()
- .map(|s| proc_macro2::Literal::string(s))
- .collect();
- quote! { &[#(#strs),*] }
- } else {
- quote! { &[] }
- };
-
- // short: Some('c') or None
- let short_value: TS2 = match short_char {
- Some(c) => {
- let lit = proc_macro2::Literal::character(c);
- quote! { ::std::option::Option::Some(#lit) }
- }
- None => quote! { ::std::option::Option::None },
- };
-
- let positional_value: bool = !is_named;
-
- let result = quote! {
- #path {
- full: #full_value,
- short: #short_value,
- positional: #positional_value,
- internal_type: ::std::marker::PhantomData,
- }
- };
-
- result.into()
-}
-
-fn split_at_commas(tokens: &[TokenTree]) -> Vec<Vec<TokenTree>> {
- let mut result = vec![Vec::new()];
- let mut depth = 0u32;
- let mut angle_depth = 0u32;
- for t in tokens {
- match t {
- TokenTree::Group(_g) => {
- depth += 1;
- result.last_mut().unwrap().push(t.clone());
- depth -= 1;
- }
- TokenTree::Punct(p) if p.as_char() == '<' => {
- angle_depth += 1;
- result.last_mut().unwrap().push(t.clone());
- }
- TokenTree::Punct(p) if p.as_char() == '>' && angle_depth > 0 => {
- angle_depth -= 1;
- result.last_mut().unwrap().push(t.clone());
- }
- TokenTree::Punct(p) if p.as_char() == ',' && depth == 0 && angle_depth == 0 => {
- result.push(Vec::new());
- }
- _ => result.last_mut().unwrap().push(t.clone()),
- }
- }
- result
-}
-
-fn is_double_colon(tokens: &[TokenTree], pos: usize) -> bool {
- pos > 0 && matches!(&tokens[pos - 1], TokenTree::Punct(p) if p.as_char() == ':')
-}
-
-fn is_char_literal(tokens: &[TokenTree]) -> bool {
- tokens.len() == 1
- && matches!(&tokens[0], TokenTree::Literal(l) if {
- let s = l.to_string();
- s.starts_with('\'') && s.len() >= 3
- })
-}
-
-fn is_string_literal(tokens: &[TokenTree]) -> bool {
- tokens.len() == 1
- && matches!(&tokens[0], TokenTree::Literal(l) if {
- l.to_string().starts_with('"')
- })
-}
-
-fn extract_char(tokens: &[TokenTree]) -> Option<char> {
- match &tokens[0] {
- TokenTree::Literal(l) => {
- let s = l.to_string();
- let cs: Vec<char> = s.chars().collect();
- if cs.len() >= 3 && cs[0] == '\'' && cs[cs.len() - 1] == '\'' {
- let inner: String = cs[1..cs.len() - 1].iter().collect();
- // inner is the character between the quotes of a char literal.
- // For a literal `'n'`, inner is `"n"` (the character n).
- // For an escape `'\n'`, inner is the actual newline character.
- // The catch-all handles both literal single chars and escape
- // sequences (the escaped char IS the actual control character).
- match inner.as_str() {
- "\\\\" => Some('\\'),
- "\\'" => Some('\''),
- _ => inner.chars().next(),
- }
- } else {
- None
- }
- }
- _ => None,
- }
-}
-
-fn extract_string(tokens: &[TokenTree]) -> Option<String> {
- match &tokens[0] {
- TokenTree::Literal(l) => {
- let s = l.to_string();
- let cs: Vec<char> = s.chars().collect();
- if cs.len() >= 2 && cs[0] == '"' && cs[cs.len() - 1] == '"' {
- Some(cs[1..cs.len() - 1].iter().collect())
- } else {
- None
- }
- }
- _ => None,
- }
-}
-
-fn join_idents(tokens: &[TokenTree]) -> String {
- tokens
- .iter()
- .map(|t| match t {
- TokenTree::Ident(id) => id.to_string(),
- _ => String::new(),
- })
- .collect()
-}
diff --git a/arg_picker_macros/src/internal_repeat.rs b/arg_picker_macros/src/internal_repeat.rs
deleted file mode 100644
index 359ae6a..0000000
--- a/arg_picker_macros/src/internal_repeat.rs
+++ /dev/null
@@ -1,316 +0,0 @@
-// Doc Not Optimize
-use proc_macro::{Delimiter, Group, Ident, Literal, TokenStream, TokenTree};
-
-pub(crate) fn internal_repeat(input: TokenStream) -> TokenStream {
- let tokens: Vec<TokenTree> = input.into_iter().collect();
- let (range_start, range_end, body_start) = parse_range(&tokens);
-
- let mut body: Vec<TokenTree> = tokens[body_start..].to_vec();
- if body.len() == 1
- && let TokenTree::Group(g) = &body[0]
- && g.delimiter() == Delimiter::Brace
- {
- body = g.stream().into_iter().collect();
- }
-
- let mut result = Vec::new();
- for i in range_start..=range_end {
- result.extend(expand_body(&body, i, range_start, range_end));
- }
- result.into_iter().collect()
-}
-
-/// Parse `start .. end =>` or `start ..= end =>` or `count =>` (backward compat).
-/// Returns `(start, end_inclusive, body_start_index)`.
-fn parse_range(tokens: &[TokenTree]) -> (usize, usize, usize) {
- // Find => separator
- let arrow_pos = tokens.windows(2).position(|w| {
- matches!(&w[0], TokenTree::Punct(p) if p.as_char() == '=')
- && matches!(&w[1], TokenTree::Punct(p) if p.as_char() == '>')
- });
-
- let (arrow_pos, body_start) = match arrow_pos {
- Some(p) => (p, p + 2),
- None => return (1, 12, 0), // fallback
- };
-
- let before: Vec<&TokenTree> = tokens[..arrow_pos].iter().collect();
-
- // Try to find `..` or `..=` pattern
- // `..` is two Punct('.') tokens
- let dotdot = before.windows(2).position(|w| {
- matches!(w[0], TokenTree::Punct(p) if p.as_char() == '.')
- && matches!(w[1], TokenTree::Punct(p) if p.as_char() == '.')
- });
-
- if let Some(dd) = dotdot {
- // Start value: tokens before `..`
- let start = parse_usize_tokens(&before[..dd]);
- let after_dd = &before[dd + 2..];
-
- // Check for `..=` (inclusive range)
- let (inclusive, end_tokens) = if after_dd
- .first()
- .is_some_and(|t| matches!(t, TokenTree::Punct(p) if p.as_char() == '='))
- {
- (true, &after_dd[1..])
- } else {
- (false, after_dd)
- };
-
- let end = parse_usize_tokens(end_tokens);
-
- if inclusive {
- (start, end, body_start)
- } else {
- // Exclusive end: if end >= start, iterate start..end, so end_inclusive = end - 1
- if end > start {
- (start, end - 1, body_start)
- } else {
- (1, 12, body_start) // fallback
- }
- }
- } else {
- // No `..` found — fallback to simple count
- let count = parse_usize_tokens(&before);
- (1, count, body_start)
- }
-}
-
-/// Parse a sequence of tokens as a single usize value.
-fn parse_usize_tokens(tokens: &[&TokenTree]) -> usize {
- let s: String = tokens
- .iter()
- .map(|t| match t {
- TokenTree::Literal(l) => l.to_string(),
- TokenTree::Ident(id) => id.to_string(),
- _ => String::new(),
- })
- .collect::<Vec<_>>()
- .join("")
- .replace(' ', "");
-
- s.parse().unwrap_or(12)
-}
-
-/// Walk tokens, replacing:
-/// `$` → current
-/// `^$` → max
-/// `$^` → min
-/// `$+` → current + 1 (clamped)
-/// `$-` → current - 1 (clamped)
-/// `ident$` → ident{current}
-/// and expanding `( … )+` / `( … ,)+` / `( … ;)+` groups.
-fn expand_body(tokens: &[TokenTree], current: usize, min: usize, max: usize) -> Vec<TokenTree> {
- let mut out = Vec::new();
- let mut i = 0;
- while i < tokens.len() {
- // Check for a parenthesized repetition group: ( ... ) sep? +
- if let Some(exp) = try_expand_paren_group(tokens, i, current, min, max) {
- let (items, consumed) = exp;
- out.extend(items);
- i += consumed;
- continue;
- }
-
- // `^$` — max value
- if let TokenTree::Punct(p) = &tokens[i]
- && p.as_char() == '^'
- && i + 1 < tokens.len()
- && let TokenTree::Punct(p2) = &tokens[i + 1]
- && p2.as_char() == '$'
- {
- out.push(TokenTree::Literal(Literal::usize_suffixed(max)));
- i += 2;
- continue;
- }
-
- // `ident$` / `ident$+` / `ident$-` / `ident$^`
- // → {ident}{current} / {ident}{current+1} / {ident}{current-1} / {ident}{min}
- if let TokenTree::Ident(id) = &tokens[i] {
- if i + 1 < tokens.len()
- && let TokenTree::Punct(p) = &tokens[i + 1]
- && p.as_char() == '$'
- {
- // Check for ident$+ / ident$- / ident$^
- if i + 2 < tokens.len() {
- match &tokens[i + 2] {
- TokenTree::Punct(p2) if p2.as_char() == '+' => {
- // ident$+ → {ident}{current+1}
- let name = format!("{}{}", id, current + 1);
- out.push(TokenTree::Ident(Ident::new(&name, id.span())));
- i += 3;
- continue;
- }
- TokenTree::Punct(p2) if p2.as_char() == '-' => {
- // ident$- → {ident}{current-1}
- let val = current.saturating_sub(1);
- let name = format!("{}{}", id, val);
- out.push(TokenTree::Ident(Ident::new(&name, id.span())));
- i += 3;
- continue;
- }
- TokenTree::Punct(p2) if p2.as_char() == '^' => {
- let name = format!("{}{}", id, min);
- out.push(TokenTree::Ident(Ident::new(&name, id.span())));
- i += 3;
- continue;
- }
- _ => {}
- }
- }
- // ident$ alone → {ident}{current}
- let name = format!("{}{}", id, current);
- out.push(TokenTree::Ident(Ident::new(&name, id.span())));
- i += 2;
- continue;
- }
- out.push(tokens[i].clone());
- i += 1;
- continue;
- }
-
- match &tokens[i] {
- TokenTree::Punct(p) if p.as_char() == '$' => {
- // lookahead for $^, $+, $-
- if i + 1 < tokens.len() {
- match &tokens[i + 1] {
- TokenTree::Punct(p2) if p2.as_char() == '^' => {
- // $^ → min
- out.push(TokenTree::Literal(Literal::usize_suffixed(min)));
- i += 2;
- continue;
- }
- TokenTree::Punct(p2) if p2.as_char() == '+' => {
- // $+ → current + 1
- out.push(TokenTree::Literal(Literal::usize_suffixed(current + 1)));
- i += 2;
- continue;
- }
- TokenTree::Punct(p2) if p2.as_char() == '-' => {
- // $- → current - 1
- let val = current.saturating_sub(1);
- out.push(TokenTree::Literal(Literal::usize_suffixed(val)));
- i += 2;
- continue;
- }
- _ => {}
- }
- }
- // `$` alone → current
- out.push(TokenTree::Literal(Literal::usize_suffixed(current)));
- i += 1;
- continue;
- }
- TokenTree::Group(g) => {
- let inner = expand_body_vec(&g.stream(), current, min, max);
- out.push(TokenTree::Group(Group::new(
- g.delimiter(),
- inner.into_iter().collect(),
- )));
- }
- other => out.push(other.clone()),
- }
- i += 1;
- }
- out
-}
-
-fn expand_body_vec(stream: &TokenStream, current: usize, min: usize, max: usize) -> Vec<TokenTree> {
- let v: Vec<TokenTree> = stream.clone().into_iter().collect();
- expand_body(&v, current, min, max)
-}
-
-/// Try to expand a repetition group.
-///
-/// New syntax (repetition marker `+` is the LAST token INSIDE the parens):
-/// - `(group,+)` — repeat `*` times (current), `,` is the separator
-/// - `(group,+)` — repeat `*` times, `,` is the separator
-/// - `(group,+)` — repeat `*` times, `;` is the separator
-/// - `(group +)` — repeat `*` times, no separator
-/// - `(group,+)+` — repeat `*+1` times (with separator)
-/// - `(group,+)--` — repeat `*-1` times (with separator) [not yet used]
-///
-/// The `*` is the current counter value. An optional `+` or `-` immediately
-/// after the closing paren shifts the repeat count up or down by one.
-fn try_expand_paren_group(
- tokens: &[TokenTree],
- i: usize,
- current: usize,
- _min: usize,
- _max: usize,
-) -> Option<(Vec<TokenTree>, usize)> {
- let group = match tokens.get(i)? {
- TokenTree::Group(g) if g.delimiter() == Delimiter::Parenthesis => g,
- _ => return None,
- };
-
- let stream: Vec<TokenTree> = group.stream().into_iter().collect();
-
- // Repetition syntax: the LAST token inside the parens MUST be `+`.
- // `(content,+)` — repeat * times, `,` separator
- // `(content;+)` — repeat * times, `;` separator
- // `(content,+)` — repeat * times, no separator
- // `(content,+)+` — repeat *+1 times
- // `(content,+)-` — repeat *-1 times
- // An optional `+` / `-` right after `)` shifts the count by ±1.
- //
- // Any `+` tokens inside `content` are treated as regular Rust syntax
- // (trait bounds, etc.) — the repetition marker is ONLY the final `+`.
-
- let last_is_plus = stream
- .last()
- .is_some_and(|t| matches!(t, TokenTree::Punct(p) if p.as_char() == '+'));
- if !last_is_plus {
- return None;
- }
-
- // Determine separator and inner content.
- let (inner, sep_str): (Vec<TokenTree>, &str) = {
- if stream.len() >= 2 {
- let sep_idx = stream.len() - 2;
- match &stream[sep_idx] {
- TokenTree::Punct(p) if p.as_char() == ',' => {
- // (...,content,+) — inner is everything before the last `,`
- let content: Vec<TokenTree> = stream[..sep_idx].into();
- (content, ",")
- }
- TokenTree::Punct(p) if p.as_char() == ';' => {
- let content: Vec<TokenTree> = stream[..sep_idx].into();
- (content, ";")
- }
- _ => {
- // (content+) — no separator, the `+` is the only special token
- let content: Vec<TokenTree> = stream[..stream.len() - 1].into();
- (content, "")
- }
- }
- } else {
- // (+) — bare repetition marker, empty content
- (vec![], "")
- }
- };
-
- // Handle modifier after `)`
- let rest = &tokens[i + 1..];
- let modifier: isize = match rest.first() {
- Some(TokenTree::Punct(p)) if p.as_char() == '+' => 1,
- Some(TokenTree::Punct(p)) if p.as_char() == '-' => -1,
- _ => 0,
- };
- let consumed = if modifier != 0 { 2 } else { 1 };
- let repeat_count = (current as isize + modifier) as usize;
-
- let mut out = Vec::new();
- for n in 1..=repeat_count {
- if n > 1 && !sep_str.is_empty() {
- out.push(TokenTree::Punct(proc_macro::Punct::new(
- sep_str.chars().next().unwrap(),
- proc_macro::Spacing::Alone,
- )));
- }
- out.extend(expand_body(&inner, n, 1, repeat_count));
- }
-
- Some((out, consumed))
-}
diff --git a/arg_picker_macros/src/lib.rs b/arg_picker_macros/src/lib.rs
deleted file mode 100644
index 5bf4fd1..0000000
--- a/arg_picker_macros/src/lib.rs
+++ /dev/null
@@ -1,34 +0,0 @@
-// Doc Not Optimize
-#![doc = include_str!("../README.md")]
-#![deny(missing_docs)]
-
-use proc_macro::TokenStream;
-
-mod arg;
-mod internal_repeat;
-
-/// Core proc-macro: repeats a template body `count` times.
-///
-/// Internal call signature: `internal_repeat!(count => { template })`
-#[proc_macro]
-pub fn internal_repeat(input: TokenStream) -> TokenStream {
- internal_repeat::internal_repeat(input)
-}
-
-/// Quick builder for `PickerArg`.
-///
-/// # Syntax
-///
-/// ```ignore
-/// use arg_picker_macros::flag;
-///
-/// let basic = arg![name: String];
-/// let with_short_name = arg![name: String, 'n'];
-/// let with_short_alias = arg![name: String, 'n', "alias"];
-/// let positional = arg![String];
-/// let positional_with_name = arg![String, 'n', "alias"];
-/// ```
-#[proc_macro]
-pub fn arg(input: TokenStream) -> TokenStream {
- arg::arg(input)
-}
diff --git a/dev/configs/rust-analyzer.json b/dev/configs/rust-analyzer.json
index ab97bf7..ebc1ffe 100644
--- a/dev/configs/rust-analyzer.json
+++ b/dev/configs/rust-analyzer.json
@@ -6,8 +6,6 @@
".run/Cargo.toml",
"dev/ci/Cargo.toml",
"mingling_pathf/test/Cargo.toml",
- "arg_picker/Cargo.toml",
- "arg_picker/test/Cargo.toml",
"mingling_cli/Cargo.toml"
],
"rust-analyzer.cargo.features": [],
diff --git a/dev/run/src/bin/build.ps1 b/dev/run/src/bin/build.ps1
new file mode 100644
index 0000000..4f35ed8
--- /dev/null
+++ b/dev/run/src/bin/build.ps1
@@ -0,0 +1,8 @@
+$starting_dir = Get-Location
+Get-ChildItem -Recurse -Filter "Cargo.toml" | ForEach-Object {
+ $project_dir = $_.DirectoryName
+ Push-Location $project_dir
+ cargo build
+ Pop-Location
+}
+Set-Location $starting_dir
diff --git a/dev/run/src/bin/build.sh b/dev/run/src/bin/build.sh
new file mode 100755
index 0000000..2036b41
--- /dev/null
+++ b/dev/run/src/bin/build.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+
+find . -name "Cargo.toml" -type f | while read -r cargo_file; do
+ project_dir=$(dirname "$cargo_file")
+ (cd "$project_dir" && cargo build)
+done
diff --git a/docs/dev/_sidebar.md b/docs/dev/_sidebar.md
index e0a77d3..9ed13d1 100644
--- a/docs/dev/_sidebar.md
+++ b/docs/dev/_sidebar.md
@@ -1,18 +1,18 @@
- [Welcome!](README)
* ❓ Issues
* [[Solved] The Picker2 Arguments Parser](pages/issues/_add-picker2)
- * [[T1] Modify the dispatcher! Syntax](pages/issues/_modify-dispatcher-syntax)
- * [[T0] Remove the pack! Family of Macros](pages/issues/_remove-pack-macros)
- * [[T0] Remove the parser Feature](pages/issues/_remove-parser-feature)
+ * [[Solved] [T1] Modify the dispatcher! Syntax](pages/issues/_modify-dispatcher-syntax)
+ * [[Solved] [T0] Remove the pack! Family of Macros](pages/issues/_remove-pack-macros)
+ * [[Solved] [T0] Remove the parser Feature](pages/issues/_remove-parser-feature)
* [[Solved] Remove r_print! and r_println! Macros](pages/issues/_remove-r-print-macro)
- * [[T0] Remove with_dispatcher and with_dispatchers](pages/issues/_remove-with-dispatcher)
+ * [[Solved] [T0] Remove with_dispatcher and with_dispatchers](pages/issues/_remove-with-dispatcher)
+ * [[Solved] [T2] Automated dispatcher_tree Optimization Decisions](pages/issues/_t2_automated-dispatch-tree-optimization)
* [[Solved] The Command Macro](pages/issues/_the-command-macro)
* [[Solved] The Mod Pathfinder](pages/issues/_the-mod-pathfinder)
* [[T0] Generalize the REPL System](pages/issues/t0_generalize-repl-system)
* [[T1] Higher-Level Abstractions for the Completion System](pages/issues/t1_completion-higher-level-abstractions)
* [[T1] Move structural_renderer from mingling_core to mingling](pages/issues/t1_move-structural-renderer)
* [[T1] The pathf_export Attribute Macro](pages/issues/t1_pathf-export-macro)
- * [[T2] Automated dispatcher_tree Optimization Decisions](pages/issues/t2_automated-dispatch-tree-optimization)
* [The Next-Gen Mingling Pipeline](pages/issues/the-next-pipeline)
* [Some Situations Where You'd Be Like "Shit!"](pages/issues/the-shit-time)
* 💡 Abouts
diff --git a/docs/dev/pages/issues/_modify-dispatcher-syntax.md b/docs/dev/pages/issues/_modify-dispatcher-syntax.md
index ed409ef..cfee64b 100644
--- a/docs/dev/pages/issues/_modify-dispatcher-syntax.md
+++ b/docs/dev/pages/issues/_modify-dispatcher-syntax.md
@@ -1,4 +1,4 @@
-<h1 align="center">[T1] Modify the dispatcher! Syntax</h1>
+<h1 align="center">[Solved] [T1] Modify the dispatcher! Syntax</h1>
<p align="center">
Breaking: drop the <code>CMD*</code> struct from the explicit form of <code>dispatcher!</code>
</p>
diff --git a/docs/dev/pages/issues/_remove-pack-macros.md b/docs/dev/pages/issues/_remove-pack-macros.md
index 74c21aa..153004e 100644
--- a/docs/dev/pages/issues/_remove-pack-macros.md
+++ b/docs/dev/pages/issues/_remove-pack-macros.md
@@ -1,4 +1,4 @@
-`<h1 align="center">[T0] Remove the pack! Family of Macros</h1>
+`<h1 align="center">[Solved] [T0] Remove the pack! Family of Macros</h1>
<p align="center">
Breaking: retire the entire <code>pack!</code> family in favor of the <code>Grouped</code> derive
</p>
diff --git a/docs/dev/pages/issues/_remove-parser-feature.md b/docs/dev/pages/issues/_remove-parser-feature.md
index c2a11c0..7653c47 100644
--- a/docs/dev/pages/issues/_remove-parser-feature.md
+++ b/docs/dev/pages/issues/_remove-parser-feature.md
@@ -1,4 +1,4 @@
-<h1 align="center">[T0] Remove the parser Feature</h1>
+<h1 align="center">[Solved] [T0] Remove the parser Feature</h1>
<p align="center">
Breaking: retire the legacy argument parsing in favor of <code>picker</code>
</p>
diff --git a/docs/dev/pages/issues/_remove-with-dispatcher.md b/docs/dev/pages/issues/_remove-with-dispatcher.md
index b0d972f..42b12e5 100644
--- a/docs/dev/pages/issues/_remove-with-dispatcher.md
+++ b/docs/dev/pages/issues/_remove-with-dispatcher.md
@@ -1,4 +1,4 @@
-<h1 align="center">[T0] Remove with_dispatcher and with_dispatchers</h1>
+<h1 align="center">[Solved] [T0] Remove with_dispatcher and with_dispatchers</h1>
<p align="center">
Breaking: make <code>Dispatcher</code> registration compile-time collected in all modes
</p>
diff --git a/docs/dev/pages/issues/_t2_automated-dispatch-tree-optimization.md b/docs/dev/pages/issues/_t2_automated-dispatch-tree-optimization.md
new file mode 100644
index 0000000..d2e2473
--- /dev/null
+++ b/docs/dev/pages/issues/_t2_automated-dispatch-tree-optimization.md
@@ -0,0 +1,76 @@
+<h1 align="center">[Solved] [T2] Automated dispatcher_tree Optimization Decisions</h1>
+<p align="center">
+ Feature: let Mingling decide when <code>dispatch_tree</code> pays off (implemented)
+</p>
+
+> [!NOTE]
+>
+> This item is **implemented**. It depends on [Remove with_dispatcher and with_dispatchers](t0_remove-with-dispatcher).
+
+## Background
+
+`dispatch_tree` provides a faster dispatch path, but it is not always a win. Currently users must manually enable the `dispatch_tree` feature and make the trade-off themselves.
+
+After dispatcher registration becomes compile-time collected (see [Remove with_dispatcher and with_dispatchers](t0_remove-with-dispatcher)), Mingling can know the full set and depth of registered commands at compile time — making automated decisions implementable.
+
+## Plan
+
+Mingling can automatically decide whether to use `dispatcher_tree` to optimize dispatch efficiency based on the current number and depth of registered commands, so users no longer need to manually enable the `dispatch_tree` feature.
+
+### Conditions
+
+`dispatch_tree` has an advantage in cases where command depth is too high and the number of commands is too large. However, if the number of commands is too small, the increased CPU prediction failure rate will inevitably make it less efficient than linear lookup; specifics need to be tuned during implementation.
+
+### Resolve the `pathf` + `dispatch_tree` build-dependency issue
+
+Additionally, the issue where `pathf` + `dispatch_tree` must be explicitly specified in `[build-dependencies]` will be resolved:
+
+```toml
+# Before
+[build-dependencies.mingling]
+version = "0.4.0"
+features = [ "build", "pathf", "dispatch_tree" ] # `dispatch_tree` must be explicitly specified for `pathf` to recognize it
+
+# After
+[build-dependencies.mingling]
+version = "0.4.0"
+features = [ "build", "pathf" ] # No `dispatch_tree` feature; `pathf` no longer needs to consider its branches
+```
+
+## Final Implementation
+
+The automated dispatch-strategy selection is now in place. A new `dispatch_auto` module (the default when no dispatch feature is enabled) picks at macro-expansion time from three strategies — **linear list**, **char trie**, and **perfect hash** — based on a cost model calibrated against the `dev/bench/dispatch` benchmark matrix.
+
+### Two new dispatch features
+
+In addition to the existing `dispatch_tree`, two new mutually-exclusive features now exist:
+
+- **`dispatch_linear`** — force linear longest-prefix list (the former default).
+- **`dispatch_phf`** — force a CHD minimal perfect hash (constant-time lookup, O(1) code size).
+- **`dispatch_tree`** — force the char-level trie.
+- **(none)** — **auto mode**: pick the best strategy from the command table.
+
+Enabling more than one triggers a `compile_error!`.
+
+### Auto-selection heuristic
+
+`dispatch_auto::select_strategy` inspects the normalized command table (names, depth, nesting) and picks:
+
+- **deep nested chains at modest sizes** (`max_words ≥ 8`, `n ≤ 128`) → linear list (short memcmps beat the trie's per-level char walk plus fallback calls);
+- **single-word tables with long names** (avg_len ≥ 16–24) → perfect hash (one hash beats the char walk);
+- **small tables** (`n ≤ 64`) → linear vs trie by an internal cost model;
+- **everything else** → char trie (O(depth) hits, linear code size after the fallback-chain refactor).
+
+The heuristic is empirical and may drift as the benchmark matrix grows.
+
+### Benchmark harness
+
+A workspace-internal harness `dev/bench/dispatch` (`cargo dispatch-bench`) measures all four strategies across a `len×count×type` matrix (4/8/16/32 × 128/256 × single/multi/nested4/nested10), reporting per-cell ns/op for hits and misses, geometric means, and how often auto matches the per-cell best / stays within 5%.
+
+### Trie code-size fix
+
+The trie generator was rewritten so the longest-prefix fallback is a single shared `__trie_fallback` method (called, not inlined, per arm) rather than inlined into every arm. This keeps generated code linear in the table size — a 1024×16 nested table previously emitted ~13 MB of tokens.
+
+<p align="center" style="font-size: 0.85em; color: gray;">
+ Written by @Weicao-CatilGrass
+</p>
diff --git a/docs/dev/pages/issues/t2_automated-dispatch-tree-optimization.md b/docs/dev/pages/issues/t2_automated-dispatch-tree-optimization.md
deleted file mode 100644
index 691fd1a..0000000
--- a/docs/dev/pages/issues/t2_automated-dispatch-tree-optimization.md
+++ /dev/null
@@ -1,57 +0,0 @@
-<h1 align="center">[T2] Automated dispatcher_tree Optimization Decisions</h1>
-<p align="center">
- Feature: let Mingling decide when <code>dispatch_tree</code> pays off (under consideration)
-</p>
-
-> [!NOTE]
->
-> This item is **under consideration**. It depends on [Remove with_dispatcher and with_dispatchers](t0_remove-with-dispatcher).
-
-## Background
-
-`dispatch_tree` provides a faster dispatch path, but it is not always a win. Currently users must manually enable the `dispatch_tree` feature and make the trade-off themselves.
-
-After dispatcher registration becomes compile-time collected (see [Remove with_dispatcher and with_dispatchers](t0_remove-with-dispatcher)), Mingling can know the full set and depth of registered commands at compile time — making automated decisions implementable.
-
-## Plan
-
-Mingling can automatically decide whether to use `dispatcher_tree` to optimize dispatch efficiency based on the current number and depth of registered commands, so users no longer need to manually enable the `dispatch_tree` feature.
-
-### Conditions
-
-`dispatch_tree` has an advantage in cases where command depth is too high and the number of commands is too large. However, if the number of commands is too small, the increased CPU prediction failure rate will inevitably make it less efficient than linear lookup; specifics need to be tuned during implementation.
-
-### Resolve the `pathf` + `dispatch_tree` build-dependency issue
-
-Additionally, the issue where `pathf` + `dispatch_tree` must be explicitly specified in `[build-dependencies]` will be resolved:
-
-```toml
-# Before
-[build-dependencies.mingling]
-version = "0.4.0"
-features = [ "build", "pathf", "dispatch_tree" ] # `dispatch_tree` must be explicitly specified for `pathf` to recognize it
-
-# After
-[build-dependencies.mingling]
-version = "0.4.0"
-features = [ "build", "pathf" ] # No `dispatch_tree` feature; `pathf` no longer needs to consider its branches
-```
-
-## Tasks
-
-- [ ] Collect statistics about registered commands (count, depth) at compile time
-- [ ] Benchmark / tune the threshold between linear lookup and `dispatch_tree`
-- [ ] Implement the automatic decision and wire it into dispatch code generation
-- [ ] Remove the manual `dispatch_tree` feature toggle (or keep it as an override?)
-- [ ] Refactor `pathf` so it no longer branches on `dispatch_tree`
-- [ ] Update examples, tests, and docs
-
-## 🕘 Progress
-
-- [ ] Under Consideration
-- [ ] In Progress
-- [ ] Complete
-
-<p align="center" style="font-size: 0.85em; color: gray;">
- Written by @Weicao-CatilGrass
-</p>
diff --git a/examples/example-argument-picker/Cargo.lock b/examples/example-argument-picker/Cargo.lock
index f7969b6..f035691 100644
--- a/examples/example-argument-picker/Cargo.lock
+++ b/examples/example-argument-picker/Cargo.lock
@@ -5,6 +5,8 @@ version = 4
[[package]]
name = "arg-picker"
version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e326de90a1c279562cc1583470daccfd4defe882661c61607edff75b797e483e"
dependencies = [
"arg-picker-macros",
"just_fmt",
@@ -13,6 +15,8 @@ dependencies = [
[[package]]
name = "arg-picker-macros"
version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f5f9bbe04b69744a30def8bee5526268b986616314bd1696804d7f819f03d1f3"
dependencies = [
"proc-macro2",
"quote",
diff --git a/examples/example-async-support/Cargo.lock b/examples/example-async-support/Cargo.lock
index f122f6d..447da38 100644
--- a/examples/example-async-support/Cargo.lock
+++ b/examples/example-async-support/Cargo.lock
@@ -5,6 +5,8 @@ version = 4
[[package]]
name = "arg-picker"
version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e326de90a1c279562cc1583470daccfd4defe882661c61607edff75b797e483e"
dependencies = [
"arg-picker-macros",
"just_fmt",
@@ -13,6 +15,8 @@ dependencies = [
[[package]]
name = "arg-picker-macros"
version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f5f9bbe04b69744a30def8bee5526268b986616314bd1696804d7f819f03d1f3"
dependencies = [
"proc-macro2",
"quote",
diff --git a/examples/example-command-macro/Cargo.lock b/examples/example-command-macro/Cargo.lock
index 53d6f18..5ab4dec 100644
--- a/examples/example-command-macro/Cargo.lock
+++ b/examples/example-command-macro/Cargo.lock
@@ -5,6 +5,8 @@ version = 4
[[package]]
name = "arg-picker"
version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e326de90a1c279562cc1583470daccfd4defe882661c61607edff75b797e483e"
dependencies = [
"arg-picker-macros",
"just_fmt",
@@ -13,6 +15,8 @@ dependencies = [
[[package]]
name = "arg-picker-macros"
version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f5f9bbe04b69744a30def8bee5526268b986616314bd1696804d7f819f03d1f3"
dependencies = [
"proc-macro2",
"quote",
diff --git a/examples/example-completion/Cargo.lock b/examples/example-completion/Cargo.lock
index d98661c..af7ed94 100644
--- a/examples/example-completion/Cargo.lock
+++ b/examples/example-completion/Cargo.lock
@@ -5,6 +5,8 @@ version = 4
[[package]]
name = "arg-picker"
version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e326de90a1c279562cc1583470daccfd4defe882661c61607edff75b797e483e"
dependencies = [
"arg-picker-macros",
"just_fmt 0.2.0",
@@ -13,6 +15,8 @@ dependencies = [
[[package]]
name = "arg-picker-macros"
version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f5f9bbe04b69744a30def8bee5526268b986616314bd1696804d7f819f03d1f3"
dependencies = [
"proc-macro2",
"quote",
diff --git a/examples/example-enum-tag/Cargo.lock b/examples/example-enum-tag/Cargo.lock
index 9b97008..48c7570 100644
--- a/examples/example-enum-tag/Cargo.lock
+++ b/examples/example-enum-tag/Cargo.lock
@@ -5,6 +5,8 @@ version = 4
[[package]]
name = "arg-picker"
version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e326de90a1c279562cc1583470daccfd4defe882661c61607edff75b797e483e"
dependencies = [
"arg-picker-macros",
"just_fmt 0.2.0",
@@ -13,6 +15,8 @@ dependencies = [
[[package]]
name = "arg-picker-macros"
version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f5f9bbe04b69744a30def8bee5526268b986616314bd1696804d7f819f03d1f3"
dependencies = [
"proc-macro2",
"quote",
diff --git a/examples/example-panic-unwind/Cargo.lock b/examples/example-panic-unwind/Cargo.lock
index 20671f5..ed55971 100644
--- a/examples/example-panic-unwind/Cargo.lock
+++ b/examples/example-panic-unwind/Cargo.lock
@@ -5,6 +5,8 @@ version = 4
[[package]]
name = "arg-picker"
version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e326de90a1c279562cc1583470daccfd4defe882661c61607edff75b797e483e"
dependencies = [
"arg-picker-macros",
"just_fmt",
@@ -13,6 +15,8 @@ dependencies = [
[[package]]
name = "arg-picker-macros"
version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f5f9bbe04b69744a30def8bee5526268b986616314bd1696804d7f819f03d1f3"
dependencies = [
"proc-macro2",
"quote",
diff --git a/examples/example-repl-basic/Cargo.lock b/examples/example-repl-basic/Cargo.lock
index 9764a85..86a5895 100644
--- a/examples/example-repl-basic/Cargo.lock
+++ b/examples/example-repl-basic/Cargo.lock
@@ -5,6 +5,8 @@ version = 4
[[package]]
name = "arg-picker"
version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e326de90a1c279562cc1583470daccfd4defe882661c61607edff75b797e483e"
dependencies = [
"arg-picker-macros",
"just_fmt 0.2.0",
@@ -13,6 +15,8 @@ dependencies = [
[[package]]
name = "arg-picker-macros"
version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f5f9bbe04b69744a30def8bee5526268b986616314bd1696804d7f819f03d1f3"
dependencies = [
"proc-macro2",
"quote",
diff --git a/examples/example-resources/Cargo.lock b/examples/example-resources/Cargo.lock
index bfc04f0..b02de98 100644
--- a/examples/example-resources/Cargo.lock
+++ b/examples/example-resources/Cargo.lock
@@ -5,6 +5,8 @@ version = 4
[[package]]
name = "arg-picker"
version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e326de90a1c279562cc1583470daccfd4defe882661c61607edff75b797e483e"
dependencies = [
"arg-picker-macros",
"just_fmt",
@@ -13,6 +15,8 @@ dependencies = [
[[package]]
name = "arg-picker-macros"
version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f5f9bbe04b69744a30def8bee5526268b986616314bd1696804d7f819f03d1f3"
dependencies = [
"proc-macro2",
"quote",
diff --git a/examples/example-structural-renderer/Cargo.lock b/examples/example-structural-renderer/Cargo.lock
index cf2cfe3..101e8d9 100644
--- a/examples/example-structural-renderer/Cargo.lock
+++ b/examples/example-structural-renderer/Cargo.lock
@@ -5,6 +5,8 @@ version = 4
[[package]]
name = "arg-picker"
version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e326de90a1c279562cc1583470daccfd4defe882661c61607edff75b797e483e"
dependencies = [
"arg-picker-macros",
"just_fmt",
@@ -13,6 +15,8 @@ dependencies = [
[[package]]
name = "arg-picker-macros"
version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f5f9bbe04b69744a30def8bee5526268b986616314bd1696804d7f819f03d1f3"
dependencies = [
"proc-macro2",
"quote",
diff --git a/examples/full-todolist/Cargo.lock b/examples/full-todolist/Cargo.lock
index ad9f475..7abe3ae 100644
--- a/examples/full-todolist/Cargo.lock
+++ b/examples/full-todolist/Cargo.lock
@@ -5,6 +5,8 @@ version = 4
[[package]]
name = "arg-picker"
version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e326de90a1c279562cc1583470daccfd4defe882661c61607edff75b797e483e"
dependencies = [
"arg-picker-macros",
"just_fmt",
@@ -13,6 +15,8 @@ dependencies = [
[[package]]
name = "arg-picker-macros"
version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f5f9bbe04b69744a30def8bee5526268b986616314bd1696804d7f819f03d1f3"
dependencies = [
"proc-macro2",
"quote",
diff --git a/mingling/Cargo.toml b/mingling/Cargo.toml
index 92cca39..f0515d8 100644
--- a/mingling/Cargo.toml
+++ b/mingling/Cargo.toml
@@ -37,8 +37,8 @@ features = [
# Presets
mini = ["extras", "picker"]
-advanced = ["extras", "picker", "repl", "comp", "dispatch_tree", "structural_renderer"]
-full = ["extras", "picker", "repl", "clap", "comp", "dispatch_tree", "structural_renderer_full", "pathf"]
+advanced = ["extras", "picker", "repl", "comp", "structural_renderer", "pathf"]
+full = ["extras", "picker", "repl", "clap", "comp", "dispatch_tree", "structural_renderer_full"]
# Core
core = ["dep:mingling_core", "mingling_core/default"]
diff --git a/mingling_cli/Cargo.lock b/mingling_cli/Cargo.lock
index aab6b82..11e52d5 100644
--- a/mingling_cli/Cargo.lock
+++ b/mingling_cli/Cargo.lock
@@ -28,6 +28,8 @@ checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
[[package]]
name = "arg-picker"
version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e326de90a1c279562cc1583470daccfd4defe882661c61607edff75b797e483e"
dependencies = [
"arg-picker-macros",
"just_fmt 0.2.0",
@@ -36,6 +38,8 @@ dependencies = [
[[package]]
name = "arg-picker-macros"
version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f5f9bbe04b69744a30def8bee5526268b986616314bd1696804d7f819f03d1f3"
dependencies = [
"proc-macro2",
"quote",
diff --git a/mingling_core/tests/test-all/Cargo.lock b/mingling_core/tests/test-all/Cargo.lock
index 79202f9..1bfab41 100644
--- a/mingling_core/tests/test-all/Cargo.lock
+++ b/mingling_core/tests/test-all/Cargo.lock
@@ -5,6 +5,8 @@ version = 4
[[package]]
name = "arg-picker"
version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e326de90a1c279562cc1583470daccfd4defe882661c61607edff75b797e483e"
dependencies = [
"arg-picker-macros",
"just_fmt 0.2.0",
@@ -13,6 +15,8 @@ dependencies = [
[[package]]
name = "arg-picker-macros"
version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f5f9bbe04b69744a30def8bee5526268b986616314bd1696804d7f819f03d1f3"
dependencies = [
"proc-macro2",
"quote",
diff --git a/mingling_core/tests/test-structural-renderer/Cargo.lock b/mingling_core/tests/test-structural-renderer/Cargo.lock
index 6126fb0..b6055bf 100644
--- a/mingling_core/tests/test-structural-renderer/Cargo.lock
+++ b/mingling_core/tests/test-structural-renderer/Cargo.lock
@@ -5,6 +5,8 @@ version = 4
[[package]]
name = "arg-picker"
version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e326de90a1c279562cc1583470daccfd4defe882661c61607edff75b797e483e"
dependencies = [
"arg-picker-macros",
"just_fmt",
@@ -13,6 +15,8 @@ dependencies = [
[[package]]
name = "arg-picker-macros"
version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f5f9bbe04b69744a30def8bee5526268b986616314bd1696804d7f819f03d1f3"
dependencies = [
"proc-macro2",
"quote",
diff --git a/mingling_macros/Cargo.toml b/mingling_macros/Cargo.toml
index 2183d67..9d0cad7 100644
--- a/mingling_macros/Cargo.toml
+++ b/mingling_macros/Cargo.toml
@@ -25,7 +25,7 @@ structural_renderer = []
repl = []
pathf = ["dep:mingling_pathf"]
-# Dispatch strategy (mutually exclusive; none = auto, currently linear)
+# Dispatch strategy (mutually exclusive; none = auto)
dispatch_linear = []
dispatch_tree = []
dispatch_phf = []