From 63730b461c009a33b08f9b98c0a583f08341895c Mon Sep 17 00:00:00 2001 From: 魏曹先生 <1992414357@qq.com> Date: Thu, 16 Jul 2026 17:05:29 +0800 Subject: refactor: rename PickerFlag and PickerFlagAttr to PickerArg and PickerArgAttr --- mingling_picker/src/arg.rs | 122 ++++++++--------- mingling_picker/src/builtin/pick_bool.rs | 4 +- mingling_picker/src/infos.rs | 10 +- mingling_picker/src/lib.rs | 2 +- mingling_picker/src/pickable.rs | 10 +- mingling_picker/src/picker.rs | 4 +- mingling_picker/src/picker/parse.rs | 6 +- mingling_picker/src/picker/patterns.rs | 8 +- mingling_picker/test/src/test/basic_test.rs | 44 +++--- mingling_picker/test/src/test/route_test.rs | 40 +++--- mingling_picker_macros/src/arg.rs | 204 ++++++++++++++++++++++++++++ mingling_picker_macros/src/flag.rs | 204 ---------------------------- mingling_picker_macros/src/lib.rs | 18 +-- 13 files changed, 337 insertions(+), 339 deletions(-) create mode 100644 mingling_picker_macros/src/arg.rs delete mode 100644 mingling_picker_macros/src/flag.rs diff --git a/mingling_picker/src/arg.rs b/mingling_picker/src/arg.rs index 33c7829..3b5f8b1 100644 --- a/mingling_picker/src/arg.rs +++ b/mingling_picker/src/arg.rs @@ -22,7 +22,7 @@ use std::marker::PhantomData; /// /// - `_type`: PhantomData to hold the type parameter. #[derive(Default, Clone, Copy)] -pub struct PickerFlag<'a, Type> +pub struct PickerArg<'a, Type> where Type: Pickable<'a>, { @@ -39,11 +39,11 @@ where pub internal_type: PhantomData, } -impl<'a, Type> PickerFlag<'a, Type> +impl<'a, Type> PickerArg<'a, Type> where Type: Pickable<'a>, { - /// Creates a new `PickerFlag` with the provided parameters. + /// Creates a new `PickerArg` with the provided parameters. pub fn new(full: &'a [&'a str], short: Option, positional: bool) -> Self { Self { full, @@ -126,7 +126,7 @@ where /// The ordering reflects parse priority (higher = parsed first): /// `Positional < PositionalMulti < Flag < Single < Multi` #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub enum PickerFlagAttr { +pub enum PickerArgAttr { /// Positional argument matched by its position (e.g., an input file). #[default] Positional, @@ -144,85 +144,85 @@ pub enum PickerFlagAttr { Multi, } -impl PickerFlagAttr { - /// Determines if the given `PickerFlag` represents a positional parameter. +impl PickerArgAttr { + /// Determines if the given `PickerArg` represents a positional parameter. /// /// If the flag is positional (determined by `flag.is_positional()`), returns - /// `PickerFlagAttr::Positional`. Otherwise, invokes the `other` closure to - /// produce and return a `PickerFlagAttr`. + /// `PickerArgAttr::Positional`. Otherwise, invokes the `other` closure to + /// produce and return a `PickerArgAttr`. /// /// # Parameters /// - /// - `flag`: A reference to the [`PickerFlag`] to evaluate. - /// - `other`: A closure that returns a [`PickerFlagAttr`] when the flag is + /// - `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: &PickerFlag<'a, T>, - other: fn() -> PickerFlagAttr, - ) -> PickerFlagAttr + flag: &PickerArg<'a, T>, + other: fn() -> PickerArgAttr, + ) -> PickerArgAttr where T: Pickable<'a>, { if flag.is_positional() { - PickerFlagAttr::Positional + PickerArgAttr::Positional } else { other() } } - /// Determines if the given `PickerFlag` represents a positional parameter and returns - /// `PickerFlagAttr::Positional` if so. Otherwise, returns the provided `default` attribute. + /// 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 [`PickerFlag`] to evaluate. - /// - `default`: The [`PickerFlagAttr`] to return if the flag is not positional. + /// - `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: &PickerFlag<'a, T>, default: PickerFlagAttr) -> PickerFlagAttr + pub fn positional_or<'a, T>(flag: &PickerArg<'a, T>, default: PickerArgAttr) -> PickerArgAttr where T: Pickable<'a>, { if flag.is_positional() { - PickerFlagAttr::Positional + PickerArgAttr::Positional } else { default } } - /// Determines if the given `PickerFlag` represents a positional parameter and returns - /// `PickerFlagAttr::Positional` if so. Otherwise, returns `PickerFlagAttr::Single`. + /// 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 [`PickerFlag`] to evaluate. + /// - `flag`: A reference to the [`PickerArg`] to evaluate. #[inline(always)] - pub fn positional_or_single<'a, T>(flag: &PickerFlag<'a, T>) -> PickerFlagAttr + pub fn positional_or_single<'a, T>(flag: &PickerArg<'a, T>) -> PickerArgAttr where T: Pickable<'a>, { if flag.is_positional() { - PickerFlagAttr::Positional + PickerArgAttr::Positional } else { - PickerFlagAttr::Single + PickerArgAttr::Single } } - /// Determines if the given `PickerFlag` represents a positional parameter and returns - /// `PickerFlagAttr::PositionalMulti` if so. Otherwise, returns `PickerFlagAttr::Multi`. + /// 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 [`PickerFlag`] to evaluate. + /// - `flag`: A reference to the [`PickerArg`] to evaluate. #[inline(always)] - pub fn positional_or_multi<'a, T>(flag: &PickerFlag<'a, T>) -> PickerFlagAttr + pub fn positional_or_multi<'a, T>(flag: &PickerArg<'a, T>) -> PickerArgAttr where T: Pickable<'a>, { if flag.is_positional() { - PickerFlagAttr::PositionalMulti + PickerArgAttr::PositionalMulti } else { - PickerFlagAttr::Multi + PickerArgAttr::Multi } } } @@ -234,51 +234,51 @@ mod tests { #[test] fn test_picker_flag_attr_ordering() { // Multi > Single > Flag > PositionalMulti > Positional - assert!(PickerFlagAttr::Multi > PickerFlagAttr::Single); - assert!(PickerFlagAttr::Multi > PickerFlagAttr::Flag); - assert!(PickerFlagAttr::Multi > PickerFlagAttr::PositionalMulti); - assert!(PickerFlagAttr::Multi > PickerFlagAttr::Positional); + assert!(PickerArgAttr::Multi > PickerArgAttr::Single); + assert!(PickerArgAttr::Multi > PickerArgAttr::Flag); + assert!(PickerArgAttr::Multi > PickerArgAttr::PositionalMulti); + assert!(PickerArgAttr::Multi > PickerArgAttr::Positional); - assert!(PickerFlagAttr::Single > PickerFlagAttr::Flag); - assert!(PickerFlagAttr::Single > PickerFlagAttr::PositionalMulti); - assert!(PickerFlagAttr::Single > PickerFlagAttr::Positional); + assert!(PickerArgAttr::Single > PickerArgAttr::Flag); + assert!(PickerArgAttr::Single > PickerArgAttr::PositionalMulti); + assert!(PickerArgAttr::Single > PickerArgAttr::Positional); - assert!(PickerFlagAttr::Flag > PickerFlagAttr::PositionalMulti); - assert!(PickerFlagAttr::Flag > PickerFlagAttr::Positional); + assert!(PickerArgAttr::Flag > PickerArgAttr::PositionalMulti); + assert!(PickerArgAttr::Flag > PickerArgAttr::Positional); - assert!(PickerFlagAttr::PositionalMulti > PickerFlagAttr::Positional); + assert!(PickerArgAttr::PositionalMulti > PickerArgAttr::Positional); // PartialOrd - assert!(PickerFlagAttr::Multi >= PickerFlagAttr::Single); - assert!(PickerFlagAttr::Single >= PickerFlagAttr::Flag); - assert!(PickerFlagAttr::Flag >= PickerFlagAttr::PositionalMulti); - assert!(PickerFlagAttr::PositionalMulti >= PickerFlagAttr::Positional); - - assert!(PickerFlagAttr::Positional < PickerFlagAttr::PositionalMulti); - assert!(PickerFlagAttr::PositionalMulti < PickerFlagAttr::Flag); - assert!(PickerFlagAttr::Flag < PickerFlagAttr::Single); - assert!(PickerFlagAttr::Single < PickerFlagAttr::Multi); + assert!(PickerArgAttr::Multi >= PickerArgAttr::Single); + assert!(PickerArgAttr::Single >= PickerArgAttr::Flag); + assert!(PickerArgAttr::Flag >= PickerArgAttr::PositionalMulti); + assert!(PickerArgAttr::PositionalMulti >= PickerArgAttr::Positional); + + assert!(PickerArgAttr::Positional < PickerArgAttr::PositionalMulti); + assert!(PickerArgAttr::PositionalMulti < PickerArgAttr::Flag); + assert!(PickerArgAttr::Flag < PickerArgAttr::Single); + assert!(PickerArgAttr::Single < PickerArgAttr::Multi); } #[test] fn test_picker_flag_attr_sorting() { // Sort let mut values = vec![ - PickerFlagAttr::Flag, - PickerFlagAttr::Single, - PickerFlagAttr::Positional, - PickerFlagAttr::Multi, - PickerFlagAttr::PositionalMulti, + PickerArgAttr::Flag, + PickerArgAttr::Single, + PickerArgAttr::Positional, + PickerArgAttr::Multi, + PickerArgAttr::PositionalMulti, ]; values.sort(); assert_eq!( values, vec![ - PickerFlagAttr::Positional, - PickerFlagAttr::PositionalMulti, - PickerFlagAttr::Flag, - PickerFlagAttr::Single, - PickerFlagAttr::Multi, + PickerArgAttr::Positional, + PickerArgAttr::PositionalMulti, + PickerArgAttr::Flag, + PickerArgAttr::Single, + PickerArgAttr::Multi, ] ); } diff --git a/mingling_picker/src/builtin/pick_bool.rs b/mingling_picker/src/builtin/pick_bool.rs index 673f2a3..ccc4424 100644 --- a/mingling_picker/src/builtin/pick_bool.rs +++ b/mingling_picker/src/builtin/pick_bool.rs @@ -2,8 +2,8 @@ use crate::parselib::{FlagMatcher, Matcher}; use crate::pickable_needed::*; impl<'a> Pickable<'a> for bool { - fn get_attr(_: &'a PickerFlag<'a, Self>) -> PickerFlagAttr { - PickerFlagAttr::Flag + fn get_attr(_: &'a PickerArg<'a, Self>) -> PickerArgAttr { + PickerArgAttr::Flag } fn tag(ctx: TagPhaseContext) -> Vec { diff --git a/mingling_picker/src/infos.rs b/mingling_picker/src/infos.rs index c0309e0..d2a0fce 100644 --- a/mingling_picker/src/infos.rs +++ b/mingling_picker/src/infos.rs @@ -1,4 +1,4 @@ -use crate::{Pickable, PickerFlag}; +use crate::{Pickable, PickerArg}; /// Represents the result of parsing or looking up a value. /// @@ -283,11 +283,11 @@ pub struct PickerArgInfo<'a> { pub is_flag: bool, } -impl<'a, T> From> for PickerArgInfo<'a> +impl<'a, T> From> for PickerArgInfo<'a> where T: Pickable<'a>, { - fn from(value: PickerFlag<'a, T>) -> Self { + fn from(value: PickerArg<'a, T>) -> Self { let (long, alias) = match value.full.len() { 0 => (None, None), _ => { @@ -313,8 +313,8 @@ where } } -impl<'a, T: Pickable<'a>> From<&'a PickerFlag<'a, T>> for PickerArgInfo<'a> { - fn from(value: &'a PickerFlag<'a, T>) -> Self { +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), _ => { diff --git a/mingling_picker/src/lib.rs b/mingling_picker/src/lib.rs index 6f2640b..c3c15b9 100644 --- a/mingling_picker/src/lib.rs +++ b/mingling_picker/src/lib.rs @@ -24,7 +24,7 @@ pub mod macros { /// Provides the types necessary for implementing the `Pickable` trait pub mod pickable_needed { - pub use crate::{Pickable, PickerArgResult, PickerFlag, PickerFlagAttr, TagPhaseContext}; + pub use crate::{Pickable, PickerArg, PickerArgAttr, PickerArgResult, TagPhaseContext}; } /// Provides the types necessary for implementing the `Matcher` trait diff --git a/mingling_picker/src/pickable.rs b/mingling_picker/src/pickable.rs index 4f8c534..c88e49b 100644 --- a/mingling_picker/src/pickable.rs +++ b/mingling_picker/src/pickable.rs @@ -1,4 +1,4 @@ -use crate::{PickerArgInfo, PickerArgResult, PickerArgs, PickerFlag, PickerFlagAttr}; +use crate::{PickerArg, PickerArgAttr, PickerArgInfo, PickerArgResult, PickerArgs}; /// `Pickable` trait defines how to parse a type instance from command-line arguments. /// @@ -13,7 +13,7 @@ use crate::{PickerArgInfo, PickerArgResult, PickerArgs, PickerFlag, PickerFlagAt /// /// # Type Parameters /// -/// * `'a` - Lifetime parameter, used to associate references in [`PickerFlag`]. +/// * `'a` - Lifetime parameter, used to associate references in [`PickerArg`]. pub trait Pickable<'a> where Self: Sized, @@ -22,7 +22,7 @@ where /// /// This attribute is used to inform the parser about the parse order /// between different `Pickable` types. - /// See [`PickerFlagAttr`] for specific ordering definitions. + /// See [`PickerArgAttr`] for specific ordering definitions. /// /// # Parameters /// @@ -30,8 +30,8 @@ where /// /// # Returns /// - /// Returns a [`PickerFlagAttr`] describing the parse-order attribute of this flag. - fn get_attr(flag: &'a PickerFlag<'a, Self>) -> PickerFlagAttr; + /// 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. /// diff --git a/mingling_picker/src/picker.rs b/mingling_picker/src/picker.rs index d065891..c6e40c8 100644 --- a/mingling_picker/src/picker.rs +++ b/mingling_picker/src/picker.rs @@ -8,7 +8,7 @@ pub use patterns::*; mod result; pub use result::*; -use crate::{Pickable, PickerArgResult, PickerFlag}; +use crate::{Pickable, PickerArg, PickerArgResult}; /// Picker, used to record all states of a parameter parsing /// @@ -274,7 +274,7 @@ pub trait IntoPicker<'a> { /// /// This method converts the value into a `Picker` and starts a parameter /// picking chain with one flag. The result is initially `Unparsed`. - fn pick(self, flag: &'a PickerFlag<'a, N>) -> PickerPattern1<'a, N, ()> + fn pick(self, flag: &'a PickerArg<'a, N>) -> PickerPattern1<'a, N, ()> where Self: Sized, N: Pickable<'a> + Default + Sized, diff --git a/mingling_picker/src/picker/parse.rs b/mingling_picker/src/picker/parse.rs index ac4b30e..69de583 100644 --- a/mingling_picker/src/picker/parse.rs +++ b/mingling_picker/src/picker/parse.rs @@ -10,9 +10,7 @@ // Then, I must disable `clippy::type_complexity` — this guy is way too noisy. #![allow(clippy::type_complexity)] -use crate::{ - Pickable, PickerArgInfo, PickerArgResult, PickerArgs, PickerFlagAttr, TagPhaseContext, -}; +use crate::{Pickable, PickerArgAttr, PickerArgInfo, PickerArgResult, PickerArgs, TagPhaseContext}; use mingling_picker_macros::internal_repeat; internal_repeat!(1..=32 => { @@ -76,7 +74,7 @@ internal_repeat!(1..=32 => { let mut bundle: [ ( // Flag Attr - PickerFlagAttr, + PickerArgAttr, // Tag Func Box, &[u8]) -> Vec>, diff --git a/mingling_picker/src/picker/patterns.rs b/mingling_picker/src/picker/patterns.rs index 20faadd..df04ab9 100644 --- a/mingling_picker/src/picker/patterns.rs +++ b/mingling_picker/src/picker/patterns.rs @@ -1,6 +1,6 @@ use mingling_picker_macros::internal_repeat; -use crate::{Pickable, Picker, PickerArgResult, PickerArgs, PickerFlag}; +use crate::{Pickable, Picker, PickerArg, PickerArgResult, PickerArgs}; internal_repeat!(1..=32 => { #[doc(hidden)] @@ -10,7 +10,7 @@ internal_repeat!(1..=32 => { pub args: PickerArgs<'a>, pub error_route: Option, ( - pub flag_$: &'a PickerFlag<'a, T$>, + pub flag_$: &'a PickerArg<'a, T$>, pub result_$: PickerArgResult, pub default_$: Option T$>>, pub route_$: Option Route>>, @@ -148,7 +148,7 @@ internal_repeat!(1..32 => { /// This method extends the current picking pattern by appending an additional flag. /// The previous flags and their results are preserved as part of the new pattern. /// The new flag's result is initially `Unparsed`. - pub fn pick(self, flag: &'a PickerFlag<'a, N>) -> PickerPattern$+<'a, (T$,+), N, Route> + pub fn pick(self, flag: &'a PickerArg<'a, N>) -> PickerPattern$+<'a, (T$,+), N, Route> where N: Pickable<'a>, { @@ -182,7 +182,7 @@ impl<'a, Route> Picker<'a, Route> { /// /// This method initiates a parameter picking chain with one flag. /// The result is initially `Unparsed`. - pub fn pick(self, flag: &'a PickerFlag<'a, N>) -> PickerPattern1<'a, N, Route> + pub fn pick(self, flag: &'a PickerArg<'a, N>) -> PickerPattern1<'a, N, Route> where N: Pickable<'a>, { diff --git a/mingling_picker/test/src/test/basic_test.rs b/mingling_picker/test/src/test/basic_test.rs index 5929e75..3969991 100644 --- a/mingling_picker/test/src/test/basic_test.rs +++ b/mingling_picker/test/src/test/basic_test.rs @@ -7,7 +7,7 @@ fn test_bool_flag_present() { let args = vec!["--verbose"]; let parsed = args .to_picker() - .pick(&flag![verbose: bool]) + .pick(&arg![verbose: bool]) .or_default() .unwrap(); assert_eq!(parsed, true); @@ -18,7 +18,7 @@ fn test_bool_flag_absent() { let args: Vec<&str> = vec![]; let parsed = args .to_picker() - .pick(&flag![verbose: bool]) + .pick(&arg![verbose: bool]) .or_default() .unwrap(); assert_eq!(parsed, false); @@ -31,7 +31,7 @@ fn test_bool_short_flag_present() { let args = vec!["-v"]; let parsed = args .to_picker() - .pick(&flag![verbose: bool, 'v']) + .pick(&arg![verbose: bool, 'v']) .or_default() .unwrap(); assert_eq!(parsed, true); @@ -41,13 +41,13 @@ fn test_bool_short_flag_present() { #[test] fn test_two_bool_flags_both_present() { - // The `flag!` macro expands the identifier `flag_a` into the string "flag_a" → --flag_a + // The `arg!` macro expands the identifier `flag_a` into the string "flag_a" → --flag_a let args = vec!["--flag_a", "--flag_b"]; let (a, b) = args .to_picker() - .pick(&flag![flag_a: bool]) + .pick(&arg![flag_a: bool]) .or_default() - .pick(&flag![flag_b: bool]) + .pick(&arg![flag_b: bool]) .or_default() .unwrap(); assert_eq!(a, true); @@ -59,9 +59,9 @@ fn test_two_bool_flags_one_present() { let args = vec!["--flag_a"]; let (a, b) = args .to_picker() - .pick(&flag![flag_a: bool]) + .pick(&arg![flag_a: bool]) .or_default() - .pick(&flag![flag_b: bool]) + .pick(&arg![flag_b: bool]) .or_default() .unwrap(); assert_eq!(a, true); @@ -73,9 +73,9 @@ fn test_two_bool_flags_neither_present() { let args: Vec<&str> = vec![]; let (a, b) = args .to_picker() - .pick(&flag![flag_a: bool]) + .pick(&arg![flag_a: bool]) .or_default() - .pick(&flag![flag_b: bool]) + .pick(&arg![flag_b: bool]) .or_default() .unwrap(); assert_eq!(a, false); @@ -89,9 +89,9 @@ fn test_short_and_long_flags() { let args = vec!["-a", "--long_b"]; let (a, b) = args .to_picker() - .pick(&flag![flag_a: bool, 'a']) + .pick(&arg![flag_a: bool, 'a']) .or_default() - .pick(&flag![long_b: bool]) + .pick(&arg![long_b: bool]) .or_default() .unwrap(); assert_eq!(a, true); @@ -105,7 +105,7 @@ fn test_flag_after_end_of_options() { let args = vec!["--", "--verbose"]; let parsed = args .to_picker() - .pick(&flag![verbose: bool]) + .pick(&arg![verbose: bool]) .or_default() .unwrap(); assert_eq!(parsed, false); @@ -118,7 +118,7 @@ fn test_bool_flag_with_alias() { let args = vec!["--cfg"]; let parsed = args .to_picker() - .pick(&flag![config: bool, "cfg"]) + .pick(&arg![config: bool, "cfg"]) .or_default() .unwrap(); assert_eq!(parsed, true); @@ -129,7 +129,7 @@ fn test_bool_flag_primary_name() { let args = vec!["--config"]; let parsed = args .to_picker() - .pick(&flag![config: bool, "cfg"]) + .pick(&arg![config: bool, "cfg"]) .or_default() .unwrap(); assert_eq!(parsed, true); @@ -142,7 +142,7 @@ fn test_bool_flag_short_and_alias() { let args = vec!["-v"]; let parsed = args .to_picker() - .pick(&flag![verbose: bool, 'v', "cfg"]) + .pick(&arg![verbose: bool, 'v', "cfg"]) .or_default() .unwrap(); assert_eq!(parsed, true); @@ -155,7 +155,7 @@ fn test_or_default_without_args() { let args: Vec<&str> = vec![]; let parsed = args .to_picker() - .pick(&flag![verbose: bool]) + .pick(&arg![verbose: bool]) .or_default() .unwrap(); assert_eq!(parsed, false); @@ -166,7 +166,7 @@ fn test_or_custom_default() { let args: Vec<&str> = vec![]; let parsed = args .to_picker() - .pick(&flag![verbose: bool]) + .pick(&arg![verbose: bool]) .or(|| true) .unwrap(); assert_eq!(parsed, true); @@ -179,7 +179,7 @@ fn test_to_result_ok() { let args = vec!["--verbose"]; let result = args .to_picker() - .pick(&flag![verbose: bool]) + .pick(&arg![verbose: bool]) .or_default() .to_result(); assert_eq!(result, Ok(true)); @@ -190,7 +190,7 @@ fn test_to_option_some() { let args = vec!["--verbose"]; let opt = args .to_picker() - .pick(&flag![verbose: bool]) + .pick(&arg![verbose: bool]) .or_default() .to_option(); assert_eq!(opt, Some(true)); @@ -203,7 +203,7 @@ fn test_with_route_chain() { let args = vec!["--flag"]; let parsed = args .with_route::() - .pick(&flag![flag: bool]) + .pick(&arg![flag: bool]) .or_default() .unwrap(); assert_eq!(parsed, true); @@ -216,7 +216,7 @@ fn test_unrelated_flag_does_not_match() { let args = vec!["--other"]; let parsed = args .to_picker() - .pick(&flag![verbose: bool]) + .pick(&arg![verbose: bool]) .or_default() .unwrap(); assert_eq!(parsed, false); diff --git a/mingling_picker/test/src/test/route_test.rs b/mingling_picker/test/src/test/route_test.rs index ca07cc0..cd08e91 100644 --- a/mingling_picker/test/src/test/route_test.rs +++ b/mingling_picker/test/src/test/route_test.rs @@ -8,7 +8,7 @@ fn test_or_route_triggered_to_result() { let args: Vec<&str> = vec![]; let result: Result = args .with_route::<&'static str>() - .pick(&flag![verbose: bool]) + .pick(&arg![verbose: bool]) .or_route(|| "missing_verbose") .to_result(); assert_eq!(result, Err("missing_verbose")); @@ -20,7 +20,7 @@ fn test_or_route_not_triggered_when_flag_present() { let args = vec!["--verbose"]; let result: Result = args .with_route::<&'static str>() - .pick(&flag![verbose: bool]) + .pick(&arg![verbose: bool]) .or_route(|| "missing_verbose") .to_result(); assert_eq!(result, Ok(true)); @@ -33,7 +33,7 @@ fn test_or_default_priority_over_or_route() { let args: Vec<&str> = vec![]; let result: Result = args .with_route::<&'static str>() - .pick(&flag![verbose: bool]) + .pick(&arg![verbose: bool]) .or_default() .or_route(|| "should_not_reach") .to_result(); @@ -46,7 +46,7 @@ fn test_or_route_to_option_returns_none() { let args: Vec<&str> = vec![]; let opt: Option = args .with_route::<&'static str>() - .pick(&flag![verbose: bool]) + .pick(&arg![verbose: bool]) .or_route(|| "missing") .to_option(); assert_eq!(opt, None); @@ -57,7 +57,7 @@ fn test_or_route_to_option_returns_none() { fn test_or_route_unwrap_panics() { let args: Vec<&str> = vec![]; args.with_route::<&'static str>() - .pick(&flag![verbose: bool]) + .pick(&arg![verbose: bool]) .or_route(|| "missing") .unwrap(); } @@ -68,7 +68,7 @@ fn test_or_route_with_string_route() { let args: Vec<&str> = vec![]; let result: Result = args .with_route::() - .pick(&flag![verbose: bool]) + .pick(&arg![verbose: bool]) .or_route(|| "route_hit".to_string()) .to_result(); assert_eq!(result, Err("route_hit".to_string())); @@ -87,7 +87,7 @@ fn test_or_route_with_custom_enum() { let args: Vec<&str> = vec![]; let result: Result = args .with_route::() - .pick(&flag![verbose: bool]) + .pick(&arg![verbose: bool]) .or_route(|| Redirect::Help) .to_result(); assert_eq!(result, Err(Redirect::Help)); @@ -102,9 +102,9 @@ fn test_two_flags_first_missing_triggers_route() { let args: Vec<&str> = vec![]; let result: Result<(bool, bool), &'static str> = args .with_route::<&'static str>() - .pick(&flag![flag_a: bool]) + .pick(&arg![flag_a: bool]) .or_route(|| "first_missing") - .pick(&flag![flag_b: bool]) + .pick(&arg![flag_b: bool]) .or_route(|| "second_missing") .to_result(); assert_eq!(result, Err("first_missing")); @@ -116,9 +116,9 @@ fn test_two_flags_second_missing_triggers_route() { let args: Vec<&str> = vec![]; let result: Result<(bool, bool), &'static str> = args .with_route::<&'static str>() - .pick(&flag![flag_a: bool]) + .pick(&arg![flag_a: bool]) .or_default() - .pick(&flag![flag_b: bool]) + .pick(&arg![flag_b: bool]) .or_route(|| "second_missing") .to_result(); assert_eq!(result, Err("second_missing")); @@ -130,9 +130,9 @@ fn test_two_flags_both_present_route_not_triggered() { let args = vec!["--flag_a", "--flag_b"]; let result: Result<(bool, bool), &'static str> = args .with_route::<&'static str>() - .pick(&flag![flag_a: bool]) + .pick(&arg![flag_a: bool]) .or_route(|| "first_missing") - .pick(&flag![flag_b: bool]) + .pick(&arg![flag_b: bool]) .or_route(|| "second_missing") .to_result(); assert_eq!(result, Ok((true, true))); @@ -145,9 +145,9 @@ fn test_two_flags_first_missing_no_route_second_has_route() { let args: Vec<&str> = vec![]; let result: Result<(bool, bool), &'static str> = args .with_route::<&'static str>() - .pick(&flag![flag_a: bool]) + .pick(&arg![flag_a: bool]) // No or_default, no or_route either - .pick(&flag![flag_b: bool]) + .pick(&arg![flag_b: bool]) .or_route(|| "second_missing") .to_result(); assert_eq!(result, Err("second_missing")); @@ -160,8 +160,8 @@ fn test_two_flags_only_second_has_default() { let args: Vec<&str> = vec![]; let (a, b) = args .to_picker() - .pick(&flag![flag_a: bool]) - .pick(&flag![flag_b: bool]) + .pick(&arg![flag_a: bool]) + .pick(&arg![flag_b: bool]) .or_default() .unpack(); assert_eq!(a, None); @@ -176,7 +176,7 @@ fn test_with_route_and_or_route() { let args: Vec<&str> = vec![]; let result: Result = args .with_route::() - .pick(&flag![verbose: bool]) + .pick(&arg![verbose: bool]) .or_route(|| "redirected".to_string()) .to_result(); assert_eq!(result, Err("redirected".to_string())); @@ -188,10 +188,10 @@ fn test_with_route_type_switch() { let args: Vec<&str> = vec![]; let result: Result<(bool, bool), i32> = args .with_route::() - .pick(&flag![verbose: bool]) + .pick(&arg![verbose: bool]) .or_route(|| "string_route".to_string()) .with_route::() - .pick(&flag![other: bool]) + .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) diff --git a/mingling_picker_macros/src/arg.rs b/mingling_picker_macros/src/arg.rs new file mode 100644 index 0000000..e273d23 --- /dev/null +++ b/mingling_picker_macros/src/arg.rs @@ -0,0 +1,204 @@ +use proc_macro::{TokenStream, TokenTree}; +use proc_macro2::TokenStream as TS2; +use quote::quote; + +pub(crate) fn arg(input: TokenStream) -> TokenStream { + let tokens: Vec = input.into_iter().collect(); + let args = split_at_commas(&tokens); + if args.is_empty() { + return quote! { compile_error!("arg! flaguires at least one argument") }.into(); + } + + let first = &args[0]; + let rest = &args[1..]; + + // Validate: at most one char literal + let char_count = rest.iter().filter(|a| is_char_literal(a)).count(); + if char_count > 1 { + return quote! { compile_error!("arg! only supports at most one short name") }.into(); + } + + // Extract short char and string aliases + let short_char: Option = rest + .iter() + .find(|a| is_char_literal(a)) + .and_then(|a| extract_char(a)); + let aliases: Vec = rest + .iter() + .filter(|a| is_string_literal(a)) + .filter_map(|a| extract_string(a)) + .collect(); + + // Parse first argument + // arg![name: Type] → name, with explicit type + // arg![Type] → positional type, no name + let colon_pos = first + .iter() + .position(|t| matches!(t, TokenTree::Punct(p) if p.as_char() == ':')); + let has_named_colon = colon_pos.is_some_and(|pos| pos > 0 && !is_double_colon(first, pos)); + + let first_slice = first.as_slice(); + let (name, ty, is_named) = if has_named_colon { + let pos = colon_pos.unwrap(); + // `name : Type` + ( + Some(&first_slice[..pos]), + Some(&first_slice[pos + 1..]), + true, + ) + } else { + // No `name:` prefix → the entire first argument is the type + (None, Some(first_slice), false) + }; + + // Build full names + let full_names: Vec = match name { + Some(n) => { + let mut v = vec![join_idents(n)]; + v.extend(aliases); + v + } + None => aliases, + }; + + let path: TS2 = { + let ty_ts: TS2 = ty + .map(|t| { + let ts: TokenStream = t.iter().cloned().collect(); + TS2::from(ts) + }) + .unwrap_or(TS2::new()); + + #[cfg(feature = "mingling_support")] + let import = quote! { ::mingling::picker::PickerArg }; + + #[cfg(not(feature = "mingling_support"))] + let import = quote! { ::mingling_picker::PickerArg }; + + if ty.is_some() { + quote! { #import::<#ty_ts> } + } else { + quote! { #import::<_> } + } + }; + + // full: &["name", "alias", ...] or &[] + let full_value: TS2 = if !full_names.is_empty() { + let strs: Vec = full_names + .iter() + .map(|s| proc_macro2::Literal::string(s)) + .collect(); + quote! { &[#(#strs),*] } + } else { + quote! { &[] } + }; + + // short: Some('c') or None + let short_value: TS2 = match short_char { + Some(c) => { + let lit = proc_macro2::Literal::character(c); + quote! { ::std::option::Option::Some(#lit) } + } + None => quote! { ::std::option::Option::None }, + }; + + let positional_value: bool = !is_named; + + let result = quote! { + #path { + full: #full_value, + short: #short_value, + positional: #positional_value, + internal_type: ::std::marker::PhantomData, + } + }; + + result.into() +} + +fn split_at_commas(tokens: &[TokenTree]) -> Vec> { + let mut result = vec![Vec::new()]; + let mut depth = 0u32; + for t in tokens { + match t { + TokenTree::Group(_g) => { + depth += 1; + result.last_mut().unwrap().push(t.clone()); + depth -= 1; + } + TokenTree::Punct(p) if p.as_char() == ',' && depth == 0 => { + result.push(Vec::new()); + } + _ => result.last_mut().unwrap().push(t.clone()), + } + } + result +} + +fn is_double_colon(tokens: &[TokenTree], pos: usize) -> bool { + pos > 0 && matches!(&tokens[pos - 1], TokenTree::Punct(p) if p.as_char() == ':') +} + +fn is_char_literal(tokens: &[TokenTree]) -> bool { + tokens.len() == 1 + && matches!(&tokens[0], TokenTree::Literal(l) if { + let s = l.to_string(); + s.starts_with('\'') && s.len() >= 3 + }) +} + +fn is_string_literal(tokens: &[TokenTree]) -> bool { + tokens.len() == 1 + && matches!(&tokens[0], TokenTree::Literal(l) if { + l.to_string().starts_with('"') + }) +} + +fn extract_char(tokens: &[TokenTree]) -> Option { + match &tokens[0] { + TokenTree::Literal(l) => { + let s = l.to_string(); + let cs: Vec = s.chars().collect(); + if cs.len() >= 3 && cs[0] == '\'' && cs[cs.len() - 1] == '\'' { + let inner: String = cs[1..cs.len() - 1].iter().collect(); + match inner.as_str() { + "n" => Some('\n'), + "t" => Some('\t'), + "r" => Some('\r'), + "0" => Some('\0'), + "\\\\" => Some('\\'), + "\\'" => Some('\''), + _ => inner.chars().next(), + } + } else { + None + } + } + _ => None, + } +} + +fn extract_string(tokens: &[TokenTree]) -> Option { + match &tokens[0] { + TokenTree::Literal(l) => { + let s = l.to_string(); + let cs: Vec = s.chars().collect(); + if cs.len() >= 2 && cs[0] == '"' && cs[cs.len() - 1] == '"' { + Some(cs[1..cs.len() - 1].iter().collect()) + } else { + None + } + } + _ => None, + } +} + +fn join_idents(tokens: &[TokenTree]) -> String { + tokens + .iter() + .map(|t| match t { + TokenTree::Ident(id) => id.to_string(), + _ => String::new(), + }) + .collect() +} diff --git a/mingling_picker_macros/src/flag.rs b/mingling_picker_macros/src/flag.rs deleted file mode 100644 index 71302ae..0000000 --- a/mingling_picker_macros/src/flag.rs +++ /dev/null @@ -1,204 +0,0 @@ -use proc_macro::{TokenStream, TokenTree}; -use proc_macro2::TokenStream as TS2; -use quote::quote; - -pub(crate) fn flag(input: TokenStream) -> TokenStream { - let tokens: Vec = input.into_iter().collect(); - let args = split_at_commas(&tokens); - if args.is_empty() { - return quote! { compile_error!("flag! flaguires at least one argument") }.into(); - } - - let first = &args[0]; - let rest = &args[1..]; - - // Validate: at most one char literal - let char_count = rest.iter().filter(|a| is_char_literal(a)).count(); - if char_count > 1 { - return quote! { compile_error!("flag! only supports at most one short name") }.into(); - } - - // Extract short char and string aliases - let short_char: Option = rest - .iter() - .find(|a| is_char_literal(a)) - .and_then(|a| extract_char(a)); - let aliases: Vec = rest - .iter() - .filter(|a| is_string_literal(a)) - .filter_map(|a| extract_string(a)) - .collect(); - - // Parse first argument - // flag![name: Type] → name, with explicit type - // flag![Type] → positional type, no name - let colon_pos = first - .iter() - .position(|t| matches!(t, TokenTree::Punct(p) if p.as_char() == ':')); - let has_named_colon = colon_pos.is_some_and(|pos| pos > 0 && !is_double_colon(first, pos)); - - let first_slice = first.as_slice(); - let (name, ty, is_named) = if has_named_colon { - let pos = colon_pos.unwrap(); - // `name : Type` - ( - Some(&first_slice[..pos]), - Some(&first_slice[pos + 1..]), - true, - ) - } else { - // No `name:` prefix → the entire first argument is the type - (None, Some(first_slice), false) - }; - - // Build full names - let full_names: Vec = match name { - Some(n) => { - let mut v = vec![join_idents(n)]; - v.extend(aliases); - v - } - None => aliases, - }; - - let path: TS2 = { - let ty_ts: TS2 = ty - .map(|t| { - let ts: TokenStream = t.iter().cloned().collect(); - TS2::from(ts) - }) - .unwrap_or(TS2::new()); - - #[cfg(feature = "mingling_support")] - let import = quote! { ::mingling::picker::PickerFlag }; - - #[cfg(not(feature = "mingling_support"))] - let import = quote! { ::mingling_picker::PickerFlag }; - - if ty.is_some() { - quote! { #import::<#ty_ts> } - } else { - quote! { #import::<_> } - } - }; - - // full: &["name", "alias", ...] or &[] - let full_value: TS2 = if !full_names.is_empty() { - let strs: Vec = full_names - .iter() - .map(|s| proc_macro2::Literal::string(s)) - .collect(); - quote! { &[#(#strs),*] } - } else { - quote! { &[] } - }; - - // short: Some('c') or None - let short_value: TS2 = match short_char { - Some(c) => { - let lit = proc_macro2::Literal::character(c); - quote! { ::std::option::Option::Some(#lit) } - } - None => quote! { ::std::option::Option::None }, - }; - - let positional_value: bool = !is_named; - - let result = quote! { - #path { - full: #full_value, - short: #short_value, - positional: #positional_value, - internal_type: ::std::marker::PhantomData, - } - }; - - result.into() -} - -fn split_at_commas(tokens: &[TokenTree]) -> Vec> { - let mut result = vec![Vec::new()]; - let mut depth = 0u32; - for t in tokens { - match t { - TokenTree::Group(_g) => { - depth += 1; - result.last_mut().unwrap().push(t.clone()); - depth -= 1; - } - TokenTree::Punct(p) if p.as_char() == ',' && depth == 0 => { - result.push(Vec::new()); - } - _ => result.last_mut().unwrap().push(t.clone()), - } - } - result -} - -fn is_double_colon(tokens: &[TokenTree], pos: usize) -> bool { - pos > 0 && matches!(&tokens[pos - 1], TokenTree::Punct(p) if p.as_char() == ':') -} - -fn is_char_literal(tokens: &[TokenTree]) -> bool { - tokens.len() == 1 - && matches!(&tokens[0], TokenTree::Literal(l) if { - let s = l.to_string(); - s.starts_with('\'') && s.len() >= 3 - }) -} - -fn is_string_literal(tokens: &[TokenTree]) -> bool { - tokens.len() == 1 - && matches!(&tokens[0], TokenTree::Literal(l) if { - l.to_string().starts_with('"') - }) -} - -fn extract_char(tokens: &[TokenTree]) -> Option { - match &tokens[0] { - TokenTree::Literal(l) => { - let s = l.to_string(); - let cs: Vec = s.chars().collect(); - if cs.len() >= 3 && cs[0] == '\'' && cs[cs.len() - 1] == '\'' { - let inner: String = cs[1..cs.len() - 1].iter().collect(); - match inner.as_str() { - "n" => Some('\n'), - "t" => Some('\t'), - "r" => Some('\r'), - "0" => Some('\0'), - "\\\\" => Some('\\'), - "\\'" => Some('\''), - _ => inner.chars().next(), - } - } else { - None - } - } - _ => None, - } -} - -fn extract_string(tokens: &[TokenTree]) -> Option { - match &tokens[0] { - TokenTree::Literal(l) => { - let s = l.to_string(); - let cs: Vec = s.chars().collect(); - if cs.len() >= 2 && cs[0] == '"' && cs[cs.len() - 1] == '"' { - Some(cs[1..cs.len() - 1].iter().collect()) - } else { - None - } - } - _ => None, - } -} - -fn join_idents(tokens: &[TokenTree]) -> String { - tokens - .iter() - .map(|t| match t { - TokenTree::Ident(id) => id.to_string(), - _ => String::new(), - }) - .collect() -} diff --git a/mingling_picker_macros/src/lib.rs b/mingling_picker_macros/src/lib.rs index fe1b53c..33bb67d 100644 --- a/mingling_picker_macros/src/lib.rs +++ b/mingling_picker_macros/src/lib.rs @@ -1,6 +1,6 @@ use proc_macro::TokenStream; -mod flag; +mod arg; mod internal_repeat; /// Core proc-macro: repeats a template body `count` times. @@ -11,20 +11,20 @@ pub fn internal_repeat(input: TokenStream) -> TokenStream { internal_repeat::internal_repeat(input) } -/// Quick builder for `PickerFlag`. +/// Quick builder for `PickerArg`. /// /// # Syntax /// /// ```ignore /// use mingling_picker_macros::flag; /// -/// let basic = flag![name: String]; -/// let with_short_name = flag![name: String, 'n']; -/// let with_short_alias = flag![name: String, 'n', "alias"]; -/// let positional = flag![String]; -/// let positional_with_name = flag![String, 'n', "alias"]; +/// let basic = arg![name: String]; +/// let with_short_name = arg![name: String, 'n']; +/// let with_short_alias = arg![name: String, 'n', "alias"]; +/// let positional = arg![String]; +/// let positional_with_name = arg![String, 'n', "alias"]; /// ``` #[proc_macro] -pub fn flag(input: TokenStream) -> TokenStream { - flag::flag(input) +pub fn arg(input: TokenStream) -> TokenStream { + arg::arg(input) } -- cgit