aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.vscode/settings.json1
-rw-r--r--.zed/settings.json3
-rw-r--r--mingling_picker/src/arg.rs14
-rw-r--r--mingling_picker/src/builtin.rs1
-rw-r--r--mingling_picker/src/builtin/pick_flag.rs21
-rw-r--r--mingling_picker/src/lib.rs3
-rw-r--r--mingling_picker/src/parselib.rs3
-rw-r--r--mingling_picker/src/parselib/arg_matcher.rs21
-rw-r--r--mingling_picker/src/parselib/style.rs4
-rw-r--r--mingling_picker/src/picker.rs8
-rw-r--r--mingling_picker/src/picker/parse.rs10
-rw-r--r--mingling_picker/src/picker/patterns.rs52
-rw-r--r--mingling_picker/src/value.rs134
-rw-r--r--mingling_picker/test/src/test.rs1
-rw-r--r--mingling_picker/test/src/test/basic_test.rs8
-rw-r--r--mingling_picker/test/src/test/route_test.rs2
-rw-r--r--mingling_picker/test/src/test/style_test.rs95
-rw-r--r--mingling_picker/test/src/test/value_flag_test.rs174
18 files changed, 509 insertions, 46 deletions
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 3f1ff1d..7472e1c 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -7,4 +7,5 @@
"mingling_pathf/test/Cargo.toml",
"mingling_picker/test/Cargo.toml",
],
+ "rust-analyzer.cargo.features": ["mingling_support"],
}
diff --git a/.zed/settings.json b/.zed/settings.json
index 3ecbbe3..12c901e 100644
--- a/.zed/settings.json
+++ b/.zed/settings.json
@@ -10,6 +10,9 @@
"mingling_pathf/test/Cargo.toml",
"mingling_picker/test/Cargo.toml",
],
+ "cargo": {
+ "features": ["mingling_support"],
+ },
},
},
},
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,
+ }
+ }
+}
diff --git a/mingling_picker/test/src/test.rs b/mingling_picker/test/src/test.rs
index d3e19b3..29d52ec 100644
--- a/mingling_picker/test/src/test.rs
+++ b/mingling_picker/test/src/test.rs
@@ -1,3 +1,4 @@
mod basic_test;
mod route_test;
mod style_test;
+mod value_flag_test;
diff --git a/mingling_picker/test/src/test/basic_test.rs b/mingling_picker/test/src/test/basic_test.rs
index bb67b8f..78b154e 100644
--- a/mingling_picker/test/src/test/basic_test.rs
+++ b/mingling_picker/test/src/test/basic_test.rs
@@ -41,8 +41,8 @@ fn test_bool_short_flag_present() {
#[test]
fn test_two_bool_flags_both_present() {
- // The `arg!` macro expands the identifier `flag_a` into the string "flag_a" → --flag_a
- let args = vec!["--flag_a", "--flag_b"];
+ // 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])
@@ -56,7 +56,7 @@ fn test_two_bool_flags_both_present() {
#[test]
fn test_two_bool_flags_one_present() {
- let args = vec!["--flag_a"];
+ let args = vec!["--flag-a"];
let (a, b) = args
.to_picker()
.pick(&arg![flag_a: bool])
@@ -86,7 +86,7 @@ fn test_two_bool_flags_neither_present() {
#[test]
fn test_short_and_long_flags() {
- let args = vec!["-a", "--long_b"];
+ let args = vec!["-a", "--long-b"];
let (a, b) = args
.to_picker()
.pick(&arg![flag_a: bool, 'a'])
diff --git a/mingling_picker/test/src/test/route_test.rs b/mingling_picker/test/src/test/route_test.rs
index af5f169..9261db1 100644
--- a/mingling_picker/test/src/test/route_test.rs
+++ b/mingling_picker/test/src/test/route_test.rs
@@ -127,7 +127,7 @@ fn test_two_flags_second_missing_triggers_route() {
#[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 args = vec!["--flag-a", "--flag-b"];
let result: Result<(bool, bool), &'static str> = args
.with_route::<&'static str>()
.pick(&arg![flag_a: bool])
diff --git a/mingling_picker/test/src/test/style_test.rs b/mingling_picker/test/src/test/style_test.rs
index dbe34b5..efb2185 100644
--- a/mingling_picker/test/src/test/style_test.rs
+++ b/mingling_picker/test/src/test/style_test.rs
@@ -185,17 +185,31 @@ fn test_snake_case_should_not_match_as_long_flag() {
}
#[test]
-fn test_snake_case_naming_for_unix_style() {
- // UNIX_STYLE uses snake case: `flag_a` stays `flag_a` → `--flag_a`
+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 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 snake-style UNIX_STYLE"
+ "--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"
);
}
@@ -213,3 +227,76 @@ fn test_powershell_pascal_case_naming() {
"-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
new file mode 100644
index 0000000..d267a27
--- /dev/null
+++ b/mingling_picker/test/src/test/value_flag_test.rs
@@ -0,0 +1,174 @@
+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));
+}