diff options
| author | 魏曹先生 <1992414357@qq.com> | 2026-07-17 11:08:07 +0800 |
|---|---|---|
| committer | 魏曹先生 <1992414357@qq.com> | 2026-07-17 11:11:28 +0800 |
| commit | 79ec6878877f0fd9246d67d3cd4f8cc2d1200150 (patch) | |
| tree | e3cd8cfc7ef5cd2a6a7ceb93d9a4b1764fa21b61 /mingling_picker | |
| parent | e6136f22cff446b16dbebf3b26a6fdea6dbc0e83 (diff) | |
refactor: rename `mingling_picker` to `arg_picker`
Diffstat (limited to 'mingling_picker')
44 files changed, 0 insertions, 5645 deletions
diff --git a/mingling_picker/Cargo.toml b/mingling_picker/Cargo.toml deleted file mode 100644 index ddab33c..0000000 --- a/mingling_picker/Cargo.toml +++ /dev/null @@ -1,17 +0,0 @@ -[package] -name = "mingling_picker" -version.workspace = true -edition.workspace = true -license.workspace = true -repository.workspace = true -authors = ["Weicao-CatilGrass"] -readme = "README.md" -description = "Mingling's lightweight argument parser" - -[features] -mingling_support = ["dep:mingling_core", "mingling_picker_macros/mingling_support"] - -[dependencies] -mingling_core = { workspace = true, optional = true } -mingling_picker_macros.workspace = true -just_fmt.workspace = true diff --git a/mingling_picker/README.md b/mingling_picker/README.md deleted file mode 100644 index db67825..0000000 --- a/mingling_picker/README.md +++ /dev/null @@ -1,47 +0,0 @@ -# Mingling 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 `mingling_picker`: - -```toml -[dependencies] -mingling_picker = "0.3.0" -``` - -## Chained Argument Parser - -Provides a clean chained-call API for declaring arguments to parse: - -```rust -use mingling_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 mingling_picker::parselib::*; -``` diff --git a/mingling_picker/src/arg.rs b/mingling_picker/src/arg.rs deleted file mode 100644 index 78ad539..0000000 --- a/mingling_picker/src/arg.rs +++ /dev/null @@ -1,299 +0,0 @@ -use crate::Pickable; -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 PickerArg<'a, Type>> for PickerArg<'a, Type> -where - Type: Pickable<'a>, -{ - fn from(value: &'a PickerArg<'a, Type>) -> Self { - PickerArg { - 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. - pub 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). - pub fn full(&self) -> &'a [&'a str] { - self.full - } - - /// Returns the short name, if any. - pub 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. - pub fn is_positional(&self) -> bool { - if self.full.is_empty() && self.short.is_none() { - true - } else { - self.positional - } - } - - /// Sets the full name list. - pub fn set_full(&mut self, full: &'a [&'a str]) { - self.full = full; - } - - /// Sets the short name. - pub fn set_short(&mut self, short: Option<char>) { - self.short = short; - } - - /// Sets whether the parameter is positional. - pub fn set_positional(&mut self, positional: bool) { - self.positional = positional; - } - - /// Sets the full name list and returns self. - pub 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. - pub fn without_full(mut self) -> Self { - self.full = &[]; - self - } - - /// Sets the short name to the given character and returns self. - pub fn with_short(mut self, short: char) -> Self { - self.short = Some(short); - self - } - - /// Clears the short name (sets it to None) and returns self. - pub fn without_short(mut self) -> Self { - self.short = None; - self - } - - /// Sets whether the parameter is positional and returns self. - pub fn with_positional(mut self, positional: bool) -> Self { - self.positional = positional; - self - } -} - -/// Describes the attribute (behavior) of a command-line parameter. -/// -/// The ordering reflects parse priority (higher = parsed first): -/// `PositionalMulti < Positional < Flag < Single < Multi` -#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub enum PickerArgAttr { - /// 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, -} - -impl PickerArgAttr { - /// Determines if the given `PickerArg` represents a positional parameter. - /// - /// If the flag is positional (determined by `flag.is_positional()`), returns - /// `PickerArgAttr::Positional`. Otherwise, invokes the `other` closure to - /// produce and return a `PickerArgAttr`. - /// - /// # Parameters - /// - /// - `flag`: A reference to the [`PickerArg`] to evaluate. - /// - `other`: A closure that returns a [`PickerArgAttr`] when the flag is - /// **not** positional. - #[inline(always)] - pub fn positional_or_else<'a, T>( - flag: &PickerArg<'a, T>, - other: fn() -> PickerArgAttr, - ) -> PickerArgAttr - where - T: Pickable<'a>, - { - if flag.is_positional() { - PickerArgAttr::Positional - } else { - other() - } - } - - /// Determines if the given `PickerArg` represents a positional parameter and returns - /// `PickerArgAttr::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. - #[inline(always)] - pub fn positional_or<'a, T>(flag: &PickerArg<'a, T>, default: PickerArgAttr) -> PickerArgAttr - where - T: Pickable<'a>, - { - if flag.is_positional() { - PickerArgAttr::Positional - } else { - default - } - } - - /// Determines if the given `PickerArg` represents a positional parameter and returns - /// `PickerArgAttr::Positional` if so. Otherwise, returns `PickerArgAttr::Single`. - /// - /// # Parameters - /// - /// - `flag`: A reference to the [`PickerArg`] to evaluate. - #[inline(always)] - pub fn positional_or_single<'a, T>(flag: &PickerArg<'a, T>) -> PickerArgAttr - where - T: Pickable<'a>, - { - if flag.is_positional() { - PickerArgAttr::Positional - } else { - PickerArgAttr::Single - } - } - - /// Determines if the given `PickerArg` represents a positional parameter and returns - /// `PickerArgAttr::PositionalMulti` if so. Otherwise, returns `PickerArgAttr::Multi`. - /// - /// # Parameters - /// - /// - `flag`: A reference to the [`PickerArg`] to evaluate. - #[inline(always)] - pub fn positional_or_multi<'a, T>(flag: &PickerArg<'a, T>) -> PickerArgAttr - where - T: Pickable<'a>, - { - if flag.is_positional() { - PickerArgAttr::PositionalMulti - } else { - PickerArgAttr::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/mingling_picker/src/builtin.rs b/mingling_picker/src/builtin.rs deleted file mode 100644 index e855b08..0000000 --- a/mingling_picker/src/builtin.rs +++ /dev/null @@ -1,4 +0,0 @@ -mod pick_bool; -mod pick_flag; -mod pick_numbers; -mod pick_string; diff --git a/mingling_picker/src/builtin/pick_bool.rs b/mingling_picker/src/builtin/pick_bool.rs deleted file mode 100644 index ccc4424..0000000 --- a/mingling_picker/src/builtin/pick_bool.rs +++ /dev/null @@ -1,22 +0,0 @@ -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/mingling_picker/src/builtin/pick_flag.rs b/mingling_picker/src/builtin/pick_flag.rs deleted file mode 100644 index b642a9a..0000000 --- a/mingling_picker/src/builtin/pick_flag.rs +++ /dev/null @@ -1,21 +0,0 @@ -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(Flag::Inactive) - } else { - PickerArgResult::Parsed(Flag::Active) - } - } -} diff --git a/mingling_picker/src/builtin/pick_numbers.rs b/mingling_picker/src/builtin/pick_numbers.rs deleted file mode 100644 index a5ab0a9..0000000 --- a/mingling_picker/src/builtin/pick_numbers.rs +++ /dev/null @@ -1,80 +0,0 @@ -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 mut i = 0; - if bytes[0] == b'-' || bytes[0] == b'+' { - i = 1; - } - 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::<f32>().is_err() - } -} - -impl BoundaryCheck for f64 { - fn check_boundary(raw: &str) -> bool { - !is_float_like(raw) || raw.parse::<f64>().is_err() - } -} diff --git a/mingling_picker/src/builtin/pick_string.rs b/mingling_picker/src/builtin/pick_string.rs deleted file mode 100644 index c96f667..0000000 --- a/mingling_picker/src/builtin/pick_string.rs +++ /dev/null @@ -1,11 +0,0 @@ -use crate::PickerArgResult::NotFound; -use crate::{SinglePickable, pickable_needed::*}; - -impl SinglePickable for String { - fn pick_single(str: Option<&str>) -> PickerArgResult<Self> { - match str { - Some(str) => PickerArgResult::Parsed(str.to_string()), - None => NotFound, - } - } -} diff --git a/mingling_picker/src/corebind.rs b/mingling_picker/src/corebind.rs deleted file mode 100644 index 3581871..0000000 --- a/mingling_picker/src/corebind.rs +++ /dev/null @@ -1,2 +0,0 @@ -mod entry_picker; -pub use entry_picker::*; diff --git a/mingling_picker/src/corebind/entry_picker.rs b/mingling_picker/src/corebind/entry_picker.rs deleted file mode 100644 index 69bc4d8..0000000 --- a/mingling_picker/src/corebind/entry_picker.rs +++ /dev/null @@ -1,131 +0,0 @@ -use std::marker::PhantomData; - -use mingling_core::{ChainProcess, Groupped, ProgramCollect}; - -use crate::{Pickable, Picker, PickerArg, PickerArgs, PickerPattern1}; - -/// Trait for converting Mingling entry types (types that implement `Groupped<R>` and `Into<Vec<String>>`) -/// into [`Picker`] instances for a given route type `R`. -/// -/// This trait provides a bridge between entry definitions created with the `mingling` framework -/// and the picker argument system used for CLI argument parsing and routing. -/// -/// # Type Parameters -/// -/// * `'a` — The lifetime of the picker and its references to argument definitions. -/// * `This` — The program type used for dispatching runtime routes; must implement [`ProgramCollect`]. -/// * `Route` — The route type used for dispatching; must implement [`Groupped<This>`]. -pub trait EntryPicker<'a, This> { - /// Converts `self` into a [`Picker`] for the given route type `Route`. - fn to_picker(self) -> Picker<'a, ChainProcess<This>>; - - /// Starts building a picker pattern with the first argument. - /// - /// Returns a [`PickerPattern1`] that can be further chained with additional - /// arguments, defaults, routes, and post-processing. - /// - /// # Type Parameters - /// - /// * `Next` — The nominal type of the first argument; must implement [`Pickable`]. - /// - /// # Parameters - /// - /// * `arg` — The argument definition, typically obtained from [`crate::arg`]. - fn pick<Next>( - self, - arg: impl Into<&'a PickerArg<'a, Next>>, - ) -> PickerPattern1<'a, Next, ChainProcess<This>> - where - Self: Sized, - Next: Pickable<'a> + Default + Sized, - { - let picker = Self::to_picker(self); - Picker::build_pattern1(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::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, ChainProcess<This>> - where - Self: Sized, - Next: Pickable<'a> + Default + 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::arg`]. - fn pick_or_default<Next>( - self, - arg: impl Into<&'a PickerArg<'a, Next>>, - ) -> PickerPattern1<'a, Next, ChainProcess<This>> - 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::arg`]. - /// * `func` — A closure that produces a route value if the arg is not provided by the user. - fn pick_or_route<Next, F>( - self, - arg: impl Into<&'a PickerArg<'a, Next>>, - func: F, - ) -> PickerPattern1<'a, Next, ChainProcess<This>> - where - Self: Sized, - Next: Pickable<'a> + Default + Sized, - F: FnMut() -> ChainProcess<This> + 'static, - { - self.pick(arg).or_route(func) - } -} - -impl<'a, This, Bind> EntryPicker<'a, This> for Bind -where - This: ProgramCollect<Enum = This>, - Bind: Groupped<This> + Into<Vec<String>>, -{ - fn to_picker(self) -> Picker<'a, ChainProcess<This>> { - let args = self.into(); - Picker { - route_phantom: PhantomData, - args: PickerArgs::Owned(args), - } - } -} diff --git a/mingling_picker/src/infos.rs b/mingling_picker/src/infos.rs deleted file mode 100644 index d2a0fce..0000000 --- a/mingling_picker/src/infos.rs +++ /dev/null @@ -1,446 +0,0 @@ -use crate::{Pickable, PickerArg}; - -/// 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`](PickerArgResult::Unparsed): The value has not yet been parsed (default). -/// - [`Parsed`](PickerArgResult::Parsed): The value was successfully parsed into `Type`. -/// - [`NotFound`](PickerArgResult::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)`](PickerArgResult::Parsed). - /// - `Err(_)` maps to [`NotFound`](PickerArgResult::NotFound). - fn from(result: Result<Type, E>) -> Self { - match result { - Ok(value) => PickerArgResult::Parsed(value), - Err(_) => PickerArgResult::NotFound, - } - } -} - -impl<Type> From<Option<Type>> for PickerArgResult<Type> { - /// Converts an `Option<Type>` into a `PickerArgResult<Type>`. - /// - /// - `Some(value)` maps to [`Parsed(value)`](PickerArgResult::Parsed). - /// - `None` maps to [`NotFound`](PickerArgResult::NotFound). - fn from(option: Option<Type>) -> Self { - match option { - Some(value) => PickerArgResult::Parsed(value), - None => PickerArgResult::NotFound, - } - } -} - -impl<Type> PickerArgResult<Type> { - /// Returns `true` if the result is [`Parsed`](PickerArgResult::Parsed). - /// - /// # Examples - /// - /// ``` - /// use mingling_picker::PickerArgResult; - /// - /// let result: PickerArgResult<i32> = PickerArgResult::Parsed(42); - /// assert!(result.is_parsed()); - /// - /// let result: PickerArgResult<i32> = PickerArgResult::NotFound; - /// assert!(!result.is_parsed()); - /// ``` - pub fn is_parsed(&self) -> bool { - matches!(self, PickerArgResult::Parsed(_)) - } - - /// Returns `true` if the result is [`Parsed`](PickerArgResult::Parsed) or [`NotFound`](PickerArgResult::NotFound). - /// i.e., the value exists (was either found or not yet parsed). - /// Typically indicates the value was "found" in some sense. - /// - /// # Examples - /// - /// ``` - /// use mingling_picker::PickerArgResult; - /// - /// let result: PickerArgResult<i32> = PickerArgResult::Parsed(42); - /// assert!(result.is_found()); - /// - /// let result: PickerArgResult<i32> = PickerArgResult::NotFound; - /// assert!(result.is_found()); - /// ``` - pub fn is_found(&self) -> bool { - matches!(self, PickerArgResult::Parsed(_) | PickerArgResult::NotFound) - } - - /// Returns `true` if the result is [`Unparsed`](PickerArgResult::Unparsed) or [`NotFound`](PickerArgResult::NotFound). - /// - /// # Examples - /// - /// ``` - /// use mingling_picker::PickerArgResult; - /// - /// let result: PickerArgResult<i32> = PickerArgResult::Unparsed; - /// assert!(result.is_err()); - /// - /// let result: PickerArgResult<i32> = PickerArgResult::Parsed(10); - /// assert!(!result.is_err()); - /// ``` - pub fn is_err(&self) -> bool { - !matches!(self, PickerArgResult::Parsed(_)) - } - - /// Returns `Some(&Type)` if [`Parsed`](PickerArgResult::Parsed), otherwise `None`. - /// - /// # Examples - /// - /// ``` - /// use mingling_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 fn parsed(&self) -> Option<&Type> { - if let PickerArgResult::Parsed(value) = self { - Some(value) - } else { - None - } - } - - /// Returns the contained [`Parsed`](PickerArgResult::Parsed) value or panics with a given message. - /// - /// # Panics - /// Panics if the value is not [`Parsed`](PickerArgResult::Parsed), with a message including the provided `msg`. - /// - /// # Examples - /// - /// ```should_panic - /// use mingling_picker::PickerArgResult; - /// - /// let result: PickerArgResult<i32> = PickerArgResult::NotFound; - /// result.expect("expected a parsed value"); - /// ``` - pub fn expect(self, msg: &str) -> Type { - match self { - PickerArgResult::Parsed(value) => value, - _ => panic!("{}", msg), - } - } - - /// Returns the contained [`Parsed`](PickerArgResult::Parsed) value or panics. - /// - /// # Panics - /// Panics if the value is not [`Parsed`](PickerArgResult::Parsed). - /// - /// # Examples - /// - /// ``` - /// use mingling_picker::PickerArgResult; - /// - /// let result: PickerArgResult<i32> = PickerArgResult::Parsed(42); - /// assert_eq!(result.unwrap(), 42); - /// ``` - /// - /// ```should_panic - /// use mingling_picker::PickerArgResult; - /// - /// let result: PickerArgResult<i32> = PickerArgResult::NotFound; - /// result.unwrap(); - /// ``` - pub fn unwrap(self) -> Type { - match self { - PickerArgResult::Parsed(value) => value, - PickerArgResult::Unparsed => { - panic!("called `PickerArgResult::unwrap()` on an `Unparsed` value") - } - PickerArgResult::NotFound => { - panic!("called `PickerArgResult::unwrap()` on a `NotFound` value") - } - } - } - - /// Returns the contained [`Parsed`](PickerArgResult::Parsed) value or a provided `default`. - /// - /// # Examples - /// - /// ``` - /// use mingling_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 { - PickerArgResult::Parsed(value) => value, - _ => default, - } - } - - /// Returns the contained [`Parsed`](PickerArgResult::Parsed) value or computes it from a closure. - /// - /// # Examples - /// - /// ``` - /// use mingling_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 { - PickerArgResult::Parsed(value) => value, - _ => f(), - } - } - - /// Returns the contained [`Parsed`](PickerArgResult::Parsed) value or the default value of `Type`. - /// - /// # Examples - /// - /// ``` - /// use mingling_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 { - PickerArgResult::Parsed(value) => value, - _ => Type::default(), - } - } - - /// Converts `PickerArgResult<Type>` into `Option<Type>`. - /// - /// Returns `Some(Type)` if [`Parsed`](PickerArgResult::Parsed), otherwise `None`. - /// - /// # Examples - /// - /// ``` - /// use mingling_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 { - PickerArgResult::Parsed(value) => Some(value), - _ => None, - } - } -} - -/// 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 { - let (long, alias) = match value.full.len() { - 0 => (None, None), - _ => { - 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, T: Pickable<'a>> From<&'a PickerArg<'a, T>> for PickerArgInfo<'a> { - fn from(value: &'a PickerArg<'a, T>) -> Self { - let (long, alias) = match value.full.len() { - 0 => (None, None), - _ => { - 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. - pub 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`). - pub fn with_short(mut self, short: char) -> Self { - self.short = Some(short); - self - } - - /// Set the long flag (e.g., `"name"` for `--name`). - pub fn with_long(mut self, long: &'a str) -> Self { - self.long = Some(long); - self - } - - /// Set aliases for the tag. - pub fn with_alias(mut self, alias: Vec<&'a str>) -> Self { - self.alias = Some(alias); - self - } - - /// Mark the tag as positional. - pub fn with_positional(mut self, positional: bool) -> Self { - self.positional = positional; - self - } - - /// Mark the tag as optional. - pub fn with_optional(mut self, optional: bool) -> Self { - self.optional = optional; - self - } - - /// Mark the tag as multi-value. - pub fn with_multi(mut self, multi: bool) -> Self { - self.multi = multi; - self - } - - /// Mark the tag as a flag that participates in parsing after `--`. - pub 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 fn set_short(&mut self, short: char) -> &mut Self { - self.short = Some(short); - self - } - - /// Set the long flag (e.g., `"name"` for `--name`). - pub 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 fn set_positional(&mut self, positional: bool) -> &mut Self { - self.positional = positional; - self - } - - /// Set whether this tag is optional. - pub fn set_optional(&mut self, optional: bool) -> &mut Self { - self.optional = optional; - self - } - - /// Set whether this tag accepts multiple values. - pub fn set_multi(&mut self, multi: bool) -> &mut Self { - self.multi = multi; - self - } - - /// Set whether this tag participates in parsing after a `--` separator. - pub fn set_is_flag(&mut self, is_flag: bool) -> &mut Self { - self.is_flag = is_flag; - self - } -} - -impl<'a> Default for PickerArgInfo<'a> { - fn default() -> Self { - Self::new() - } -} diff --git a/mingling_picker/src/lib.rs b/mingling_picker/src/lib.rs deleted file mode 100644 index deb266e..0000000 --- a/mingling_picker/src/lib.rs +++ /dev/null @@ -1,62 +0,0 @@ -#![doc = include_str!("../README.md")] - -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 mingling_picker::prelude::*; -/// ``` -pub mod prelude { - pub use crate::macros::arg; - - #[cfg(not(feature = "mingling_support"))] - pub use crate::IntoPicker; - - #[cfg(feature = "mingling_support")] - pub use crate::corebind::EntryPicker; -} - -/// Re-export of the `mingling_picker_macros` crate -pub mod macros { - pub use mingling_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}; -} - -#[cfg(feature = "mingling_support")] -mod corebind; - -#[allow(unused_imports)] -#[cfg(feature = "mingling_support")] -pub use corebind::*; diff --git a/mingling_picker/src/parselib.rs b/mingling_picker/src/parselib.rs deleted file mode 100644 index 7fbd606..0000000 --- a/mingling_picker/src/parselib.rs +++ /dev/null @@ -1,144 +0,0 @@ -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`. - fn match_one<'a>(ctx: MatcherContext<'a>) -> 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`. - fn match_all<'a>(ctx: MatcherContext<'a>) -> 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, - } - } -} - -#[inline(always)] -fn is_masked(mask: &[u8], idx: usize) -> bool { - idx < mask.len() && mask[idx] != 0 -} - -#[inline(always)] -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) { - Some(MaskedArg { - raw: r, - raw_idx: idx, - }) - } else { - None - } - }) - .collect() -} diff --git a/mingling_picker/src/parselib/arg_matcher.rs b/mingling_picker/src/parselib/arg_matcher.rs deleted file mode 100644 index 38bb9cc..0000000 --- a/mingling_picker/src/parselib/arg_matcher.rs +++ /dev/null @@ -1,142 +0,0 @@ -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(always)] - 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(always)] - 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/mingling_picker/src/parselib/flag_matcher.rs b/mingling_picker/src/parselib/flag_matcher.rs deleted file mode 100644 index e93d35a..0000000 --- a/mingling_picker/src/parselib/flag_matcher.rs +++ /dev/null @@ -1,82 +0,0 @@ -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(|s| s.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(|s| s.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 matched = if case_sensitive { - flag_refs.contains(&arg.raw) - } else { - flag_refs.iter().any(|s| arg.raw.eq_ignore_ascii_case(s)) - }; - if matched { - matches.push(arg.raw_idx); - } - } - } - - matches -} diff --git a/mingling_picker/src/parselib/multi_arg_matcher.rs b/mingling_picker/src/parselib/multi_arg_matcher.rs deleted file mode 100644 index 748b1be..0000000 --- a/mingling_picker/src/parselib/multi_arg_matcher.rs +++ /dev/null @@ -1,141 +0,0 @@ -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(always)] - 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(always)] - 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/mingling_picker/src/parselib/pos_matcher.rs b/mingling_picker/src/parselib/pos_matcher.rs deleted file mode 100644 index 279e01e..0000000 --- a/mingling_picker/src/parselib/pos_matcher.rs +++ /dev/null @@ -1,71 +0,0 @@ -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(always)] - 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/mingling_picker/src/parselib/single_matcher.rs b/mingling_picker/src/parselib/single_matcher.rs deleted file mode 100644 index 25c4741..0000000 --- a/mingling_picker/src/parselib/single_matcher.rs +++ /dev/null @@ -1,59 +0,0 @@ -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(always)] - pub fn tag(ctx: TagPhaseContext) -> Vec<usize> { - if ctx.arg_info.positional { - PositionalMatcher::match_one(ctx.into()) - .map(|i| vec![i]) - .unwrap_or_default() - } 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/mingling_picker/src/parselib/style.rs b/mingling_picker/src/parselib/style.rs deleted file mode 100644 index dd01125..0000000 --- a/mingling_picker/src/parselib/style.rs +++ /dev/null @@ -1,258 +0,0 @@ -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 mingling_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(always)] - 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 mingling_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<'a> From<char> for FlagStr<'a> { - /// 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 mingling_picker::IntoPicker; -/// use mingling_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, - /// Title Case format: words are separated by spaces, each word capitalized. - /// - /// Example: `Brew Coffee` - Title, - /// lower case format: words are separated by spaces, all lowercase. - /// - /// Example: `brew coffee` - Lower, - /// UPPER CASE format: words are separated by spaces, all uppercase. - /// - /// Example: `BREW COFFEE` - Upper, -} - -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 mingling_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 { - ParserStyleNamingCase::Camel => just_fmt::camel_case!(s.into()).into(), - ParserStyleNamingCase::Pascal => just_fmt::pascal_case!(s.into()).into(), - ParserStyleNamingCase::Kebab => just_fmt::kebab_case!(s.into()).into(), - ParserStyleNamingCase::Snake => just_fmt::snake_case!(s.into()).into(), - ParserStyleNamingCase::Dot => just_fmt::dot_case!(s.into()).into(), - ParserStyleNamingCase::Title => just_fmt::title_case!(s.into()).into(), - ParserStyleNamingCase::Lower => just_fmt::lower_case!(s.into()).into(), - ParserStyleNamingCase::Upper => just_fmt::upper_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<'a> ParserStyle<'a> { - /// 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/mingling_picker/src/parselib/utils.rs b/mingling_picker/src/parselib/utils.rs deleted file mode 100644 index 47c5b55..0000000 --- a/mingling_picker/src/parselib/utils.rs +++ /dev/null @@ -1,276 +0,0 @@ -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. -#[inline(always)] -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; - if let Some(pos) = s.rfind(sep) { - Some(&s[pos + 1..]) - } else { - Some(s) - } - } - _ => 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(always)] -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(always)] -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(always)] -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(always)] -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(always)] -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(always)] -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(always)] -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(always)] -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(always)] -#[doc(hidden)] -pub fn vec_string_to_vec_str(input: &[String]) -> Vec<&str> { - input.iter().map(|s| s.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(always)] -pub fn get_seeked_first(seeked: Vec<usize>) -> Option<usize> { - seeked.into_iter().next() -} diff --git a/mingling_picker/src/pickable.rs b/mingling_picker/src/pickable.rs deleted file mode 100644 index 758ae9a..0000000 --- a/mingling_picker/src/pickable.rs +++ /dev/null @@ -1,91 +0,0 @@ -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/mingling_picker/src/pickable/multi_pickable.rs b/mingling_picker/src/pickable/multi_pickable.rs deleted file mode 100644 index 84a8068..0000000 --- a/mingling_picker/src/pickable/multi_pickable.rs +++ /dev/null @@ -1,76 +0,0 @@ -use crate::{ - Pickable, PickerArg, PickerArgAttr, PickerArgResult, SinglePickable, TagPhaseContext, - matcher_needed::Matcher, - parselib::{MultiArgMatcher, ParserStyle}, -}; - -/// Boundary check for multi-value positional parameters. -pub trait BoundaryCheck { - fn check_boundary(raw: &str) -> bool; -} - -/// Trait for multi-value parameters. -pub trait MultiPickableWithBoundary: Sized { - type Checker: BoundaryCheck; - fn pick_multi(raw: Vec<String>) -> PickerArgResult<Self>; -} - -/// Marker: unit type that always accepts — no boundary. -pub struct NoBoundary; - -impl BoundaryCheck for NoBoundary { - #[inline(always)] - 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 = Vec::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(); - <Vec<T> as MultiPickableWithBoundary>::pick_multi(owned) - } -} diff --git a/mingling_picker/src/pickable/single_pickable.rs b/mingling_picker/src/pickable/single_pickable.rs deleted file mode 100644 index 8a5b3e6..0000000 --- a/mingling_picker/src/pickable/single_pickable.rs +++ /dev/null @@ -1,69 +0,0 @@ -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/mingling_picker/src/picker.rs b/mingling_picker/src/picker.rs deleted file mode 100644 index 7cf1525..0000000 --- a/mingling_picker/src/picker.rs +++ /dev/null @@ -1,432 +0,0 @@ -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). - pub fn from_args() -> Picker<'a, ()> { - 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. - pub fn from_args_skip(skip: usize) -> Picker<'a, ()> { - 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. - 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> Default for PickerArgs<'a> { - fn default() -> Self { - Self::Vec(vec![]) - } -} - -impl<'a> PickerArgs<'a> { - /// Returns the number of arguments. - pub 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. - pub fn is_empty(&self) -> bool { - self.len() == 0 - } - - /// Returns an iterator over the arguments, yielding `&str` values. - 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. - 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(|s| s.as_str()), - } - } -} - -impl<'a> Index<usize> for PickerArgs<'a> { - 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<&'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<'a, Route> From<Vec<String>> for Picker<'a, 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`. - pub fn args(&self) -> &PickerArgs<'a> { - &self.args - } - - /// Returns a mutable reference to the internal `PickerArgs`. - pub fn args_mut(&mut self) -> &mut PickerArgs<'a> { - &mut self.args - } - - /// Consumes `self` and returns the internal `PickerArgs`. - pub fn into_args(self) -> PickerArgs<'a> { - self.args - } - - /// Returns the number of arguments. - pub fn len(&self) -> usize { - self.args.len() - } - - /// Returns `true` if there are no arguments. - pub fn is_empty(&self) -> bool { - self.args.is_empty() - } - - /// Returns an iterator over the arguments, yielding `&str` values. - pub fn iter(&'a self) -> PickerIter<'a> { - self.args.iter() - } -} - -impl<'a, Route> Index<usize> for Picker<'a, Route> { - type Output = str; - - fn index(&self, index: usize) -> &Self::Output { - &self.args[index] - } -} - -impl<'a, Route> Index<usize> for &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) => iter.next().copied(), - PickerIter::Vec(iter) => iter.next().copied(), - PickerIter::Owned(iter) => iter.next().map(|s| s.as_str()), - } - } - - fn size_hint(&self) -> (usize, Option<usize>) { - match self { - PickerIter::Slice(iter) => iter.size_hint(), - PickerIter::Vec(iter) => iter.size_hint(), - PickerIter::Owned(iter) => iter.size_hint(), - } - } -} - -impl<'a> ExactSizeIterator for PickerIter<'a> {} - -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 mingling_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> + Default + Sized, - { - Picker::build_pattern1(self.to_picker().args, arg.into(), None::<()>) - } - - /// 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(|s| s.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(|s| s.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), - } - } -} - -// Private helper: shared construction logic for `PickerPattern1`. -// Both `Picker::pick` and `IntoPicker::pick` delegate to this. -impl<'a, Route> Picker<'a, Route> { - pub(crate) 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/mingling_picker/src/picker/parse.rs b/mingling_picker/src/picker/parse.rs deleted file mode 100644 index 698baac..0000000 --- a/mingling_picker/src/picker/parse.rs +++ /dev/null @@ -1,177 +0,0 @@ -// -------------------------------------------------------------------------------------------- -// 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 mingling_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. - /// - /// # Panics - /// - /// Panics if a route was selected. - pub fn unwrap(self) -> ((T$,+)) { - let p = self.parse(); - ((p.v$.unwrap(),+)) - } - - /// 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/mingling_picker/src/picker/patterns.rs b/mingling_picker/src/picker/patterns.rs deleted file mode 100644 index 78ae2b0..0000000 --- a/mingling_picker/src/picker/patterns.rs +++ /dev/null @@ -1,205 +0,0 @@ -use mingling_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: FnMut() -> T$, - F: 'static, - { - 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: FnMut() -> Route, - F: 'static, - { - 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: FnMut(T$) -> T$, - F: 'static, - { - 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, - F: '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, - F: 'static, - { - self.pick(arg).or_route(func) - } - } -}); diff --git a/mingling_picker/src/picker/result.rs b/mingling_picker/src/picker/result.rs deleted file mode 100644 index 9cd78ae..0000000 --- a/mingling_picker/src/picker/result.rs +++ /dev/null @@ -1,56 +0,0 @@ -#![allow(clippy::type_complexity)] // Aha, Type Gymnastics! - -use mingling_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. - /// - /// # Panics - /// - /// Panics if `self.route` is `Some(...)`. - pub fn unwrap(self) -> ((T$,+)) { - ((self.v$.unwrap(),+)) - } - - /// 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/mingling_picker/src/value.rs b/mingling_picker/src/value.rs deleted file mode 100644 index 995aa00..0000000 --- a/mingling_picker/src/value.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod flag; -pub use flag::*; - -mod vec_until; -pub use vec_until::*; diff --git a/mingling_picker/src/value/flag.rs b/mingling_picker/src/value/flag.rs deleted file mode 100644 index ee0d6ee..0000000 --- a/mingling_picker/src/value/flag.rs +++ /dev/null @@ -1,145 +0,0 @@ -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 mingling_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 mingling_picker::value::Flag; - /// assert!(Flag::Active.bool()); - /// assert!(!Flag::Inactive.bool()); - /// ``` - /// - /// [`Active`]: Flag::Active - /// [`Inactive`]: Flag::Inactive - #[must_use] - #[inline(always)] - pub fn bool(&self) -> bool { - *self == Flag::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 { Flag::Active } else { Flag::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 mingling_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 { - Flag::Active => &true, - Flag::Inactive => &false, - } - } -} - -impl Not for Flag { - type Output = Flag; - - fn not(self) -> Flag { - match self { - Flag::Active => Flag::Inactive, - Flag::Inactive => Flag::Active, - } - } -} diff --git a/mingling_picker/src/value/vec_until.rs b/mingling_picker/src/value/vec_until.rs deleted file mode 100644 index 1b79641..0000000 --- a/mingling_picker/src/value/vec_until.rs +++ /dev/null @@ -1,134 +0,0 @@ -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> { - pub fn into_inner(self) -> Vec<T> { - self.inner - } -} - -impl<T> From<Vec<T>> for VecUntil<T> { - fn from(v: Vec<T>) -> Self { - VecUntil { - 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(VecUntil { - 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 = if is_positional { 0 } else { 1 }; - 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(); - <VecUntil<T> 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/mingling_picker/test/Cargo.lock b/mingling_picker/test/Cargo.lock deleted file mode 100644 index 0fef84b..0000000 --- a/mingling_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 = "just_fmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6170dccbc3ea15dfb7f2da964097f814aba1dd8f746d4ffc56f33245c38e6d96" - -[[package]] -name = "mingling_picker" -version = "0.3.0" -dependencies = [ - "just_fmt", - "mingling_picker_macros", -] - -[[package]] -name = "mingling_picker_macros" -version = "0.3.0" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "proc-macro2" -version = "1.0.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.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 = "test-mingling-picker" -version = "0.1.0" -dependencies = [ - "mingling_picker", -] - -[[package]] -name = "unicode-ident" -version = "1.0.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" diff --git a/mingling_picker/test/Cargo.toml b/mingling_picker/test/Cargo.toml deleted file mode 100644 index 127546c..0000000 --- a/mingling_picker/test/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "test-mingling-picker" -version = "0.1.0" -edition = "2024" - -[workspace] - -[dependencies] -mingling_picker = { path = "../" } diff --git a/mingling_picker/test/src/lib.rs b/mingling_picker/test/src/lib.rs deleted file mode 100644 index eac6cad..0000000 --- a/mingling_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 mingling_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/mingling_picker/test/src/test.rs b/mingling_picker/test/src/test.rs deleted file mode 100644 index 9c53514..0000000 --- a/mingling_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/mingling_picker/test/src/test/arg_matcher_test.rs b/mingling_picker/test/src/test/arg_matcher_test.rs deleted file mode 100644 index 7b37bc8..0000000 --- a/mingling_picker/test/src/test/arg_matcher_test.rs +++ /dev/null @@ -1,289 +0,0 @@ -use mingling_picker::PickerArgInfo; -use mingling_picker::parselib::{ArgMatcher, Matcher, POWERSHELL_STYLE, UNIX_STYLE}; - -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/mingling_picker/test/src/test/basic_test.rs b/mingling_picker/test/src/test/basic_test.rs deleted file mode 100644 index 78b154e..0000000 --- a/mingling_picker/test/src/test/basic_test.rs +++ /dev/null @@ -1,223 +0,0 @@ -use mingling_picker::{IntoPicker, macros::arg}; - -// 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/mingling_picker/test/src/test/multi_arg_test.rs b/mingling_picker/test/src/test/multi_arg_test.rs deleted file mode 100644 index 377357e..0000000 --- a/mingling_picker/test/src/test/multi_arg_test.rs +++ /dev/null @@ -1,181 +0,0 @@ -use mingling_picker::parselib::{Matcher, MultiArgMatcher, UNIX_STYLE}; -use mingling_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/mingling_picker/test/src/test/multi_value_test.rs b/mingling_picker/test/src/test/multi_value_test.rs deleted file mode 100644 index 0f56517..0000000 --- a/mingling_picker/test/src/test/multi_value_test.rs +++ /dev/null @@ -1,66 +0,0 @@ -use mingling_picker::value::{Flag, VecUntil}; -use mingling_picker::{IntoPicker, macros::arg}; - -#[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/mingling_picker/test/src/test/pos_matcher_test.rs b/mingling_picker/test/src/test/pos_matcher_test.rs deleted file mode 100644 index b5319f5..0000000 --- a/mingling_picker/test/src/test/pos_matcher_test.rs +++ /dev/null @@ -1,141 +0,0 @@ -use mingling_picker::PickerArgInfo; -use mingling_picker::parselib::{MaskedArg, Matcher, PositionalMatcher, UNIX_STYLE, WINDOWS_STYLE}; - -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/mingling_picker/test/src/test/priority_test.rs b/mingling_picker/test/src/test/priority_test.rs deleted file mode 100644 index 8179389..0000000 --- a/mingling_picker/test/src/test/priority_test.rs +++ /dev/null @@ -1,85 +0,0 @@ -use mingling_picker::value::Flag; -use mingling_picker::{IntoPicker, macros::arg}; - -// 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/mingling_picker/test/src/test/route_test.rs b/mingling_picker/test/src/test/route_test.rs deleted file mode 100644 index 9261db1..0000000 --- a/mingling_picker/test/src/test/route_test.rs +++ /dev/null @@ -1,200 +0,0 @@ -use mingling_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 = "called `Option::unwrap()` on a `None` value")] -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::<String>() 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/mingling_picker/test/src/test/style_test.rs b/mingling_picker/test/src/test/style_test.rs deleted file mode 100644 index 3c337b9..0000000 --- a/mingling_picker/test/src/test/style_test.rs +++ /dev/null @@ -1,300 +0,0 @@ -use mingling_picker::PickerArgInfo; -use mingling_picker::parselib::{ - FlagMatcher, Matcher, POWERSHELL_STYLE, ParserStyle, ParserStyleNamingCase, UNIX_STYLE, - WINDOWS_STYLE, build_possible_flags, -}; - -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/mingling_picker/test/src/test/value_flag_test.rs b/mingling_picker/test/src/test/value_flag_test.rs deleted file mode 100644 index d267a27..0000000 --- a/mingling_picker/test/src/test/value_flag_test.rs +++ /dev/null @@ -1,174 +0,0 @@ -use mingling_picker::value::Flag; -use mingling_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!(bool::from(flag)); - - let flag = Flag::Inactive; - assert!(!bool::from(flag)); -} - -#[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!(*active); - - let inactive = Flag::Inactive; - assert!(!*inactive); -} - -// 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/mingling_picker/test/src/test/value_string_test.rs b/mingling_picker/test/src/test/value_string_test.rs deleted file mode 100644 index b1e5c0b..0000000 --- a/mingling_picker/test/src/test/value_string_test.rs +++ /dev/null @@ -1,171 +0,0 @@ -use mingling_picker::{IntoPicker, macros::arg}; - -// 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"); -} |
