diff options
| author | 魏曹先生 <1992414357@qq.com> | 2026-07-16 18:58:31 +0800 |
|---|---|---|
| committer | 魏曹先生 <1992414357@qq.com> | 2026-07-16 19:04:10 +0800 |
| commit | 41fe0580212b3a681fd767d331fd8875ee59b019 (patch) | |
| tree | 4032d50ecbcb1d7ce566012ade310b00a3b3d921 /mingling_picker/src | |
| parent | 7ab6216fd2c056ef0c155f37e4c95df90b88701c (diff) | |
feat(picker): add `Flag` value type and `Pickable` implementation
Add a new `Flag` enum in `mingling_picker::value` that explicitly
distinguishes between an absent flag (`Inactive`) and a present flag
(`Active`), along with its `Pickable` implementation. Rename internal
fields from `flag_*` to `arg_*` for consistency, update the default
naming case to `Kebab`, and enable the `mingling_support` feature in
workspace settings.
Diffstat (limited to 'mingling_picker/src')
| -rw-r--r-- | mingling_picker/src/arg.rs | 14 | ||||
| -rw-r--r-- | mingling_picker/src/builtin.rs | 1 | ||||
| -rw-r--r-- | mingling_picker/src/builtin/pick_flag.rs | 21 | ||||
| -rw-r--r-- | mingling_picker/src/lib.rs | 3 | ||||
| -rw-r--r-- | mingling_picker/src/parselib.rs | 3 | ||||
| -rw-r--r-- | mingling_picker/src/parselib/arg_matcher.rs | 21 | ||||
| -rw-r--r-- | mingling_picker/src/parselib/style.rs | 4 | ||||
| -rw-r--r-- | mingling_picker/src/picker.rs | 8 | ||||
| -rw-r--r-- | mingling_picker/src/picker/parse.rs | 10 | ||||
| -rw-r--r-- | mingling_picker/src/picker/patterns.rs | 52 | ||||
| -rw-r--r-- | mingling_picker/src/value.rs | 134 |
11 files changed, 234 insertions, 37 deletions
diff --git a/mingling_picker/src/arg.rs b/mingling_picker/src/arg.rs index 3b5f8b1..a18a616 100644 --- a/mingling_picker/src/arg.rs +++ b/mingling_picker/src/arg.rs @@ -39,6 +39,20 @@ where 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>, diff --git a/mingling_picker/src/builtin.rs b/mingling_picker/src/builtin.rs index d6c4085..00fe1e5 100644 --- a/mingling_picker/src/builtin.rs +++ b/mingling_picker/src/builtin.rs @@ -1 +1,2 @@ mod pick_bool; +mod pick_flag; diff --git a/mingling_picker/src/builtin/pick_flag.rs b/mingling_picker/src/builtin/pick_flag.rs new file mode 100644 index 0000000..b642a9a --- /dev/null +++ b/mingling_picker/src/builtin/pick_flag.rs @@ -0,0 +1,21 @@ +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/lib.rs b/mingling_picker/src/lib.rs index c3c15b9..afc7aca 100644 --- a/mingling_picker/src/lib.rs +++ b/mingling_picker/src/lib.rs @@ -14,6 +14,8 @@ pub use infos::*; pub mod parselib; +pub mod value; + pub mod prelude { pub use crate::IntoPicker; } @@ -36,5 +38,6 @@ pub mod matcher_needed { #[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 index cf550b7..49e3cc2 100644 --- a/mingling_picker/src/parselib.rs +++ b/mingling_picker/src/parselib.rs @@ -1,6 +1,9 @@ mod flag_matcher; pub use flag_matcher::*; +mod arg_matcher; +pub use arg_matcher::*; + mod style; pub use style::*; diff --git a/mingling_picker/src/parselib/arg_matcher.rs b/mingling_picker/src/parselib/arg_matcher.rs new file mode 100644 index 0000000..e8be999 --- /dev/null +++ b/mingling_picker/src/parselib/arg_matcher.rs @@ -0,0 +1,21 @@ +use crate::matcher_needed::*; + +pub struct ArgMatcher; + +impl Matcher for ArgMatcher { + fn on_match_one( + _args: &[MaskedArg], + _style: &ParserStyle, + _arg_info: &PickerArgInfo, + ) -> Option<usize> { + todo!() + } + + fn on_match_all( + _args: &[MaskedArg], + _style: &ParserStyle, + _arg_info: &PickerArgInfo, + ) -> Vec<usize> { + todo!() + } +} diff --git a/mingling_picker/src/parselib/style.rs b/mingling_picker/src/parselib/style.rs index 434b545..4ea161f 100644 --- a/mingling_picker/src/parselib/style.rs +++ b/mingling_picker/src/parselib/style.rs @@ -1,7 +1,7 @@ use std::sync::OnceLock; use std::sync::atomic::{AtomicBool, Ordering}; -use crate::parselib::ParserStyleNamingCase::{Pascal, Snake}; +use crate::parselib::ParserStyleNamingCase::{Kebab, Pascal}; /// Defines the style of command-line argument parsing (prefixes, separators, etc.). #[derive(Clone, Copy, PartialEq, Eq)] @@ -175,7 +175,7 @@ pub const UNIX_STYLE: ParserStyle = ParserStyle { value_separator: '=', case_sensitive: true, allow_combine: true, - naming_case: Snake, + naming_case: Kebab, }; /// PowerShell style (e.g., `-Verbose`, `-Name:value`) diff --git a/mingling_picker/src/picker.rs b/mingling_picker/src/picker.rs index c6e40c8..4473d76 100644 --- a/mingling_picker/src/picker.rs +++ b/mingling_picker/src/picker.rs @@ -270,18 +270,18 @@ pub trait IntoPicker<'a> { /// ``` fn to_picker(self) -> Picker<'a, ()>; - /// Creates a `PickerPattern1` from the given flag for the `pick` method. + /// 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 flag. The result is initially `Unparsed`. - fn pick<N>(self, flag: &'a PickerArg<'a, N>) -> PickerPattern1<'a, N, ()> + /// 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, { PickerPattern1 { args: self.to_picker().args, - flag_1: flag, + arg_1: arg.into(), result_1: PickerArgResult::Unparsed, default_1: None, route_1: None, diff --git a/mingling_picker/src/picker/parse.rs b/mingling_picker/src/picker/parse.rs index 69de583..8f7d514 100644 --- a/mingling_picker/src/picker/parse.rs +++ b/mingling_picker/src/picker/parse.rs @@ -67,13 +67,13 @@ internal_repeat!(1..=32 => { // ArgInfos let arg_infos: [PickerArgInfo; $] = [ ( - PickerArgInfo::from(self.flag_$), + PickerArgInfo::from(self.arg_$), +) ]; let mut bundle: [ ( - // Flag Attr + // Arg Attr PickerArgAttr, // Tag Func @@ -88,8 +88,8 @@ internal_repeat!(1..=32 => { ; $] = [ ( ( - // Flag Attr - T$::get_attr(self.flag_$), + // Arg Attr + T$::get_attr(self.arg_$), // Tag Func Box::new(|args, mask| { @@ -143,7 +143,7 @@ internal_repeat!(1..=32 => { // Sort by Bundle Ord (descending) bundle.sort_by(|a, b| b.0.cmp(&a.0)); - // Mask — size = number of args (not flags), so use args length + // Mask — size = number of args (not args), so use args length let mut mask: Vec<u8> = vec![0u8; self.args.len()]; // Parsing diff --git a/mingling_picker/src/picker/patterns.rs b/mingling_picker/src/picker/patterns.rs index df04ab9..3c6e73f 100644 --- a/mingling_picker/src/picker/patterns.rs +++ b/mingling_picker/src/picker/patterns.rs @@ -10,7 +10,7 @@ internal_repeat!(1..=32 => { pub args: PickerArgs<'a>, pub error_route: Option<Route>, ( - pub flag_$: &'a PickerArg<'a, T$>, + pub arg_$: &'a PickerArg<'a, T$>, pub result_$: PickerArgResult<T$>, pub default_$: Option<Box<dyn FnOnce() -> T$>>, pub route_$: Option<Box<dyn FnOnce() -> Route>>, @@ -23,16 +23,16 @@ internal_repeat!(1..=32 => { impl<'a, (T$,+), Route> PickerPattern$<'a, (T$,+), Route> where (T$: Pickable<'a>,+) { - /// Sets a default value provider for this flag. + /// Sets a default value provider for this arg. /// - /// If the flag is not provided by the user at runtime, the given closure will be + /// 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 /// /// ```ignore /// let pattern = picker - /// .pick(&my_flag) + /// .pick(&my_arg) /// .or(|| 42); /// ``` #[allow(clippy::type_complexity)] @@ -45,16 +45,16 @@ internal_repeat!(1..=32 => { self } - /// Uses the default value for this flag's type if the flag is not provided. + /// Uses the default value for this arg's type if the arg is not provided. /// - /// If the flag is not provided by the user at runtime, the default value for `T$` + /// 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 /// /// ```ignore /// let pattern = picker - /// .pick(&my_flag) + /// .pick(&my_arg) /// .or_default(); /// ``` #[allow(clippy::type_complexity)] @@ -66,16 +66,16 @@ internal_repeat!(1..=32 => { self } - /// Sets a route for when the flag is not provided. + /// Sets a route for when the arg is not provided. /// - /// If the flag is not provided by the user at runtime, the given closure will be + /// 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 /// /// ```ignore /// let pattern = picker - /// .pick(&my_flag) + /// .pick(&my_arg) /// .or_route(|| Redirect::home()); /// ``` pub fn or_route<F>(mut self, func: F) -> Self @@ -91,7 +91,7 @@ internal_repeat!(1..=32 => { /// 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 flag configurations, defaults, and post- + /// 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`). /// @@ -104,7 +104,7 @@ internal_repeat!(1..=32 => { args: self.args, error_route: None, ( - flag_$: self.flag_$, + arg_$: self.arg_$, result_$: self.result_$, default_$: self.default_$, route_$: None, @@ -113,9 +113,9 @@ internal_repeat!(1..=32 => { } } - /// Attaches a post-processing function to this flag. + /// Attaches a post-processing function to this arg. /// - /// After the flag's value is parsed (or defaulted), the given closure will be + /// 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. /// @@ -123,7 +123,7 @@ internal_repeat!(1..=32 => { /// /// ```ignore /// let pattern = picker - /// .pick(&my_flag) + /// .pick(&my_arg) /// .post(|val| val * 2); /// ``` #[allow(clippy::type_complexity)] @@ -143,12 +143,12 @@ internal_repeat!(1..32 => { where (T$: Pickable<'a>,+) { #[allow(clippy::type_complexity)] - /// Adds a new flag to the picking chain, returning a new `PickerPattern` with one more type parameter. + /// 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 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<N>(self, flag: &'a PickerArg<'a, N>) -> PickerPattern$+<'a, (T$,+), N, Route> + /// 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>, { @@ -158,7 +158,7 @@ internal_repeat!(1..32 => { error_route: self.error_route, // Current - flag_$+: flag, + arg_$+: arg.into(), result_$+: PickerArgResult::Unparsed, default_$+: None, route_$+: None, @@ -166,7 +166,7 @@ internal_repeat!(1..32 => { // Prev ( - flag_$: self.flag_$, + arg_$: self.arg_$, result_$: self.result_$, default_$: self.default_$, route_$: self.route_$, @@ -178,18 +178,18 @@ internal_repeat!(1..32 => { }); impl<'a, Route> Picker<'a, Route> { - /// Creates a `PickerPattern1` from the given flag to start a picking chain. + /// Creates a `PickerPattern1` from the given arg to start a picking chain. /// - /// This method initiates a parameter picking chain with one flag. + /// This method initiates a parameter picking chain with one arg. /// The result is initially `Unparsed`. - pub fn pick<N>(self, flag: &'a PickerArg<'a, N>) -> PickerPattern1<'a, N, Route> + pub fn pick<N>(self, arg: impl Into<&'a PickerArg<'a, N>>) -> PickerPattern1<'a, N, Route> where N: Pickable<'a>, { PickerPattern1 { args: self.args, error_route: None::<Route>, - flag_1: flag, + arg_1: arg.into(), result_1: PickerArgResult::Unparsed, route_1: None, default_1: None, diff --git a/mingling_picker/src/value.rs b/mingling_picker/src/value.rs new file mode 100644 index 0000000..7d52442 --- /dev/null +++ b/mingling_picker/src/value.rs @@ -0,0 +1,134 @@ +use std::{ + fmt::{Debug, Display}, + ops::Deref, +}; + +/// 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, + } + } +} |
