From 79ec6878877f0fd9246d67d3cd4f8cc2d1200150 Mon Sep 17 00:00:00 2001 From: 魏曹先生 <1992414357@qq.com> Date: Fri, 17 Jul 2026 11:08:07 +0800 Subject: refactor: rename `mingling_picker` to `arg_picker` --- arg_picker/test/Cargo.lock | 68 ++++++ arg_picker/test/Cargo.toml | 9 + arg_picker/test/src/lib.rs | 23 ++ arg_picker/test/src/test.rs | 10 + arg_picker/test/src/test/arg_matcher_test.rs | 289 +++++++++++++++++++++++++ arg_picker/test/src/test/basic_test.rs | 223 +++++++++++++++++++ arg_picker/test/src/test/multi_arg_test.rs | 186 ++++++++++++++++ arg_picker/test/src/test/multi_value_test.rs | 66 ++++++ arg_picker/test/src/test/pos_matcher_test.rs | 141 ++++++++++++ arg_picker/test/src/test/priority_test.rs | 85 ++++++++ arg_picker/test/src/test/route_test.rs | 200 +++++++++++++++++ arg_picker/test/src/test/style_test.rs | 300 ++++++++++++++++++++++++++ arg_picker/test/src/test/value_flag_test.rs | 174 +++++++++++++++ arg_picker/test/src/test/value_string_test.rs | 171 +++++++++++++++ 14 files changed, 1945 insertions(+) create mode 100644 arg_picker/test/Cargo.lock create mode 100644 arg_picker/test/Cargo.toml create mode 100644 arg_picker/test/src/lib.rs create mode 100644 arg_picker/test/src/test.rs create mode 100644 arg_picker/test/src/test/arg_matcher_test.rs create mode 100644 arg_picker/test/src/test/basic_test.rs create mode 100644 arg_picker/test/src/test/multi_arg_test.rs create mode 100644 arg_picker/test/src/test/multi_value_test.rs create mode 100644 arg_picker/test/src/test/pos_matcher_test.rs create mode 100644 arg_picker/test/src/test/priority_test.rs create mode 100644 arg_picker/test/src/test/route_test.rs create mode 100644 arg_picker/test/src/test/style_test.rs create mode 100644 arg_picker/test/src/test/value_flag_test.rs create mode 100644 arg_picker/test/src/test/value_string_test.rs (limited to 'arg_picker/test') diff --git a/arg_picker/test/Cargo.lock b/arg_picker/test/Cargo.lock new file mode 100644 index 0000000..5d44838 --- /dev/null +++ b/arg_picker/test/Cargo.lock @@ -0,0 +1,68 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "arg-picker" +version = "0.1.0" +dependencies = [ + "arg-picker-macros", + "just_fmt", +] + +[[package]] +name = "arg-picker-macros" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "arg-picker-test" +version = "0.1.0" +dependencies = [ + "arg-picker", +] + +[[package]] +name = "just_fmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6170dccbc3ea15dfb7f2da964097f814aba1dd8f746d4ffc56f33245c38e6d96" + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" diff --git a/arg_picker/test/Cargo.toml b/arg_picker/test/Cargo.toml new file mode 100644 index 0000000..2c366c7 --- /dev/null +++ b/arg_picker/test/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "arg-picker-test" +version = "0.1.0" +edition = "2024" + +[workspace] + +[dependencies] +arg-picker = { path = "../" } diff --git a/arg_picker/test/src/lib.rs b/arg_picker/test/src/lib.rs new file mode 100644 index 0000000..0cfa575 --- /dev/null +++ b/arg_picker/test/src/lib.rs @@ -0,0 +1,23 @@ +// Using `assert_eq!(x, true)` is clearer than `assert!(x)` for expressing expected values +// +// BECAUSE `assert!` only checks if the boolean value is true, +// while `assert_eq!` explicitly shows the expected value +#![allow(clippy::bool_assert_comparison)] + +#[cfg(test)] +mod test; + +use arg_picker::parselib::MaskedArg; + +/// Create a single `MaskedArg` from a raw string and its original index. +pub fn make_masked(raw: &str, idx: usize) -> MaskedArg<'_> { + MaskedArg { raw, raw_idx: idx } +} + +/// Create a `Vec` from an array of `(raw, raw_idx)` pairs. +pub fn make_args<'a>(pairs: &'a [(&'a str, usize)]) -> Vec> { + pairs + .iter() + .map(|&(raw, idx)| MaskedArg { raw, raw_idx: idx }) + .collect() +} diff --git a/arg_picker/test/src/test.rs b/arg_picker/test/src/test.rs new file mode 100644 index 0000000..9c53514 --- /dev/null +++ b/arg_picker/test/src/test.rs @@ -0,0 +1,10 @@ +mod arg_matcher_test; +mod basic_test; +mod multi_arg_test; +mod multi_value_test; +mod pos_matcher_test; +mod priority_test; +mod route_test; +mod style_test; +mod value_flag_test; +mod value_string_test; diff --git a/arg_picker/test/src/test/arg_matcher_test.rs b/arg_picker/test/src/test/arg_matcher_test.rs new file mode 100644 index 0000000..d1d363e --- /dev/null +++ b/arg_picker/test/src/test/arg_matcher_test.rs @@ -0,0 +1,289 @@ +use arg_picker::parselib::{ArgMatcher, Matcher, POWERSHELL_STYLE, UNIX_STYLE}; +use arg_picker::PickerArgInfo; + +use crate::make_args; + +// on_match_one — Named + +#[test] +fn test_match_one_named_basic() { + let mut info = PickerArgInfo::new(); + info.set_long("name"); + let args = make_args(&[("--name", 0), ("Alice", 1)]); + let result = ArgMatcher::on_match_one(&args, &UNIX_STYLE, &info); + assert_eq!(result, Some(0)); +} + +#[test] +fn test_match_one_named_eq_mode() { + let mut info = PickerArgInfo::new(); + info.set_long("name"); + let args = make_args(&[("--name=Alice", 0)]); + let result = ArgMatcher::on_match_one(&args, &UNIX_STYLE, &info); + assert_eq!(result, Some(0)); +} + +#[test] +fn test_match_one_named_no_match() { + let mut info = PickerArgInfo::new(); + info.set_long("name"); + let args = make_args(&[("--other", 0), ("Alice", 1)]); + let result = ArgMatcher::on_match_one(&args, &UNIX_STYLE, &info); + assert_eq!(result, None); +} + +#[test] +fn test_match_one_named_short_flag() { + let mut info = PickerArgInfo::new(); + info.set_long("name"); + info.set_short('n'); + let args = make_args(&[("-n", 0), ("Alice", 1)]); + let result = ArgMatcher::on_match_one(&args, &UNIX_STYLE, &info); + assert_eq!(result, Some(0)); +} + +#[test] +fn test_match_one_named_after_end_of_options() { + // Flags after `--` should not be matched. + let mut info = PickerArgInfo::new(); + info.set_long("name"); + let args = make_args(&[("--", 0), ("--name", 1), ("Alice", 2)]); + let result = ArgMatcher::on_match_one(&args, &UNIX_STYLE, &info); + assert_eq!(result, None); +} + +// on_match_one — Positional + +#[test] +fn test_match_one_positional_basic() { + let mut info = PickerArgInfo::new(); + info.set_positional(true); + let args = make_args(&[("file.txt", 0)]); + let result = ArgMatcher::on_match_one(&args, &UNIX_STYLE, &info); + assert_eq!(result, Some(0)); +} + +#[test] +fn test_match_one_positional_takes_first() { + let mut info = PickerArgInfo::new(); + info.set_positional(true); + let args = make_args(&[("a.txt", 0), ("b.txt", 1)]); + let result = ArgMatcher::on_match_one(&args, &UNIX_STYLE, &info); + assert_eq!(result, Some(0)); +} + +// on_match_all — Named, single occurrence + +#[test] +fn test_match_all_named_flag_plus_value() { + let mut info = PickerArgInfo::new(); + info.set_long("name"); + let args = make_args(&[("--name", 0), ("Alice", 1)]); + let result = ArgMatcher::on_match_all(&args, &UNIX_STYLE, &info); + assert_eq!(result, vec![0, 1]); +} + +#[test] +fn test_match_all_named_eq_mode() { + // --name=Alice: value is inline, only tag the flag position. + let mut info = PickerArgInfo::new(); + info.set_long("name"); + let args = make_args(&[("--name=Alice", 0)]); + let result = ArgMatcher::on_match_all(&args, &UNIX_STYLE, &info); + assert_eq!(result, vec![0]); +} + +#[test] +fn test_match_all_named_no_value() { + // Flag at end with no following arg: only tag the flag. + let mut info = PickerArgInfo::new(); + info.set_long("name"); + let args = make_args(&[("--name", 0)]); + let result = ArgMatcher::on_match_all(&args, &UNIX_STYLE, &info); + assert_eq!(result, vec![0]); +} + +#[test] +fn test_match_all_named_value_looks_like_flag() { + // The next arg looks like a flag — still tag it. + // Validation is the Pickable's responsibility. + let mut info = PickerArgInfo::new(); + info.set_long("name"); + let args = make_args(&[("--name", 0), ("--other", 1)]); + let result = ArgMatcher::on_match_all(&args, &UNIX_STYLE, &info); + assert_eq!(result, vec![0, 1]); +} + +// on_match_all — Named, multiple occurrences (Single per flag) + +#[test] +fn test_match_all_named_two_occurrences() { + // --name Alice --name Bob → each occurrence gets one value. + let mut info = PickerArgInfo::new(); + info.set_long("name"); + let args = make_args(&[("--name", 0), ("Alice", 1), ("--name", 2), ("Bob", 3)]); + let result = ArgMatcher::on_match_all(&args, &UNIX_STYLE, &info); + assert_eq!(result, vec![0, 1, 2, 3]); +} + +#[test] +fn test_match_all_named_skips_non_matching_args() { + // --val a b --val d → only pairs (0,1) and (3,4); idx 2 ("b") left free. + let mut info = PickerArgInfo::new(); + info.set_long("val"); + let args = make_args(&[("--val", 0), ("a", 1), ("b", 2), ("--val", 3), ("d", 4)]); + let result = ArgMatcher::on_match_all(&args, &UNIX_STYLE, &info); + assert_eq!(result, vec![0, 1, 3, 4]); +} + +// on_match_all — Named, short flag + +#[test] +fn test_match_all_named_short_flag() { + let mut info = PickerArgInfo::new(); + info.set_long("name"); + info.set_short('n'); + let args = make_args(&[("-n", 0), ("Alice", 1)]); + let result = ArgMatcher::on_match_all(&args, &UNIX_STYLE, &info); + assert_eq!(result, vec![0, 1]); +} + +// on_match_all — Named, eq + non-eq mixed + +#[test] +fn test_match_all_named_mixed_eq_and_regular() { + let mut info = PickerArgInfo::new(); + info.set_long("name"); + let args = make_args(&[("--name=Alice", 0), ("--name", 1), ("Bob", 2)]); + let result = ArgMatcher::on_match_all(&args, &UNIX_STYLE, &info); + assert_eq!(result, vec![0, 1, 2]); +} + +// on_match_all — Named, case insensitive (PowerShell) + +#[test] +fn test_match_all_named_powershell_case_insensitive() { + let mut info = PickerArgInfo::new(); + info.set_long("Name"); + let args = make_args(&[("-name", 0), ("Alice", 1)]); + let result = ArgMatcher::on_match_all(&args, &POWERSHELL_STYLE, &info); + assert_eq!(result, vec![0, 1]); +} + +// on_match_all — Positional + +#[test] +fn test_match_all_positional_single() { + let mut info = PickerArgInfo::new(); + info.set_positional(true); + let args = make_args(&[("file.txt", 0)]); + let result = ArgMatcher::on_match_all(&args, &UNIX_STYLE, &info); + assert_eq!(result, vec![0]); +} + +#[test] +fn test_match_all_positional_multiple() { + let mut info = PickerArgInfo::new(); + info.set_positional(true); + let args = make_args(&[("a.txt", 0), ("b.txt", 1)]); + let result = ArgMatcher::on_match_all(&args, &UNIX_STYLE, &info); + assert_eq!(result, vec![0, 1]); +} + +// End-of-options marker (`--`) + +#[test] +fn test_match_all_named_stops_at_end_of_options() { + // --name before `--` should match, --name after should not. + let mut info = PickerArgInfo::new(); + info.set_long("name"); + let args = make_args(&[ + ("--name", 0), + ("Alice", 1), + ("--", 2), + ("--name", 3), + ("Bob", 4), + ]); + let result = ArgMatcher::on_match_all(&args, &UNIX_STYLE, &info); + assert_eq!(result, vec![0, 1]); +} + +#[test] +fn test_match_all_positional_stops_at_end_of_options() { + let mut info = PickerArgInfo::new(); + info.set_positional(true); + let args = make_args(&[("before", 0), ("--", 1), ("after", 2)]); + let result = ArgMatcher::on_match_all(&args, &UNIX_STYLE, &info); + assert_eq!(result, vec![0]); +} + +// Empty args + +#[test] +fn test_match_one_empty() { + let mut info = PickerArgInfo::new(); + info.set_long("name"); + let args = vec![]; + let result = ArgMatcher::on_match_one(&args, &UNIX_STYLE, &info); + assert_eq!(result, None); +} + +#[test] +fn test_match_all_empty() { + let mut info = PickerArgInfo::new(); + info.set_long("name"); + let args = vec![]; + let result = ArgMatcher::on_match_all(&args, &UNIX_STYLE, &info); + assert!(result.is_empty()); +} + +// Verify that -- itself is never matched as a flag + +#[test] +fn test_match_all_end_of_options_not_matched() { + // `--` should neither match as a flag nor take a value. + let mut info = PickerArgInfo::new(); + info.set_long("name"); + let args = make_args(&[("--", 0)]); + let result = ArgMatcher::on_match_all(&args, &UNIX_STYLE, &info); + assert!(result.is_empty()); +} + +// `--` should never be consumed as a value by a named flag. + +#[test] +fn test_match_all_named_does_not_consume_end_marker() { + // `--flag` before `--`, but the next position IS `--`. + // Only tag the flag, don't consume the end-of-options marker. + let mut info = PickerArgInfo::new(); + info.set_long("flag"); + let args = make_args(&[("--flag", 0), ("--", 1), ("value", 2)]); + let result = ArgMatcher::on_match_all(&args, &UNIX_STYLE, &info); + assert_eq!(result, vec![0], "should NOT consume -- as a value"); +} + +#[test] +fn test_match_all_named_does_not_consume_end_marker_flag_before_end() { + // Simulates: --flag -- you where -- is end-of-options. + // The flag should be tagged but -- should NOT be consumed as its value. + let mut info = PickerArgInfo::new(); + info.set_long("flag"); + + let args = make_args(&[("--flag", 0), ("--", 1), ("you", 2)]); + let result = ArgMatcher::on_match_all(&args, &UNIX_STYLE, &info); + assert_eq!( + result, + vec![0], + "flag before --: only tag flag, leave -- for positional" + ); +} + +#[test] +fn test_match_all_flag_after_end_has_value() { + // Flag after `--` should not match at all. + let mut info = PickerArgInfo::new(); + info.set_long("flag"); + let args = make_args(&[("--", 0), ("--flag", 1), ("value", 2)]); + let result = ArgMatcher::on_match_all(&args, &UNIX_STYLE, &info); + assert!(result.is_empty()); +} diff --git a/arg_picker/test/src/test/basic_test.rs b/arg_picker/test/src/test/basic_test.rs new file mode 100644 index 0000000..b01adb6 --- /dev/null +++ b/arg_picker/test/src/test/basic_test.rs @@ -0,0 +1,223 @@ +use arg_picker::{macros::arg, IntoPicker}; + +// Basic bool flag — present / absent + +#[test] +fn test_bool_flag_present() { + let args = vec!["--verbose"]; + let parsed = args + .to_picker() + .pick(&arg![verbose: bool]) + .or_default() + .unwrap(); + assert_eq!(parsed, true); +} + +#[test] +fn test_bool_flag_absent() { + let args: Vec<&str> = vec![]; + let parsed = args + .to_picker() + .pick(&arg![verbose: bool]) + .or_default() + .unwrap(); + assert_eq!(parsed, false); +} + +// Short flag — '-v' + +#[test] +fn test_bool_short_flag_present() { + let args = vec!["-v"]; + let parsed = args + .to_picker() + .pick(&arg![verbose: bool, 'v']) + .or_default() + .unwrap(); + assert_eq!(parsed, true); +} + +// Multiple bool flags at once + +#[test] +fn test_two_bool_flags_both_present() { + // UNIX_STYLE now uses Kebab naming: `flag_a` → "flag-a" → --flag-a + let args = vec!["--flag-a", "--flag-b"]; + let (a, b) = args + .to_picker() + .pick(&arg![flag_a: bool]) + .or_default() + .pick(&arg![flag_b: bool]) + .or_default() + .unwrap(); + assert_eq!(a, true); + assert_eq!(b, true); +} + +#[test] +fn test_two_bool_flags_one_present() { + let args = vec!["--flag-a"]; + let (a, b) = args + .to_picker() + .pick(&arg![flag_a: bool]) + .or_default() + .pick(&arg![flag_b: bool]) + .or_default() + .unwrap(); + assert_eq!(a, true); + assert_eq!(b, false); +} + +#[test] +fn test_two_bool_flags_neither_present() { + let args: Vec<&str> = vec![]; + let (a, b) = args + .to_picker() + .pick(&arg![flag_a: bool]) + .or_default() + .pick(&arg![flag_b: bool]) + .or_default() + .unwrap(); + assert_eq!(a, false); + assert_eq!(b, false); +} + +// Mixed short and long flags + +#[test] +fn test_short_and_long_flags() { + let args = vec!["-a", "--long-b"]; + let (a, b) = args + .to_picker() + .pick(&arg![flag_a: bool, 'a']) + .or_default() + .pick(&arg![long_b: bool]) + .or_default() + .unwrap(); + assert_eq!(a, true); + assert_eq!(b, true); +} + +// Flags after `--` (end-of-options marker) should not be parsed. + +#[test] +fn test_flag_after_end_of_options() { + let args = vec!["--", "--verbose"]; + let parsed = args + .to_picker() + .pick(&arg![verbose: bool]) + .or_default() + .unwrap(); + assert_eq!(parsed, false); +} + +// Alias matching for bool flags + +#[test] +fn test_bool_flag_with_alias() { + let args = vec!["--cfg"]; + let parsed = args + .to_picker() + .pick(&arg![config: bool, "cfg"]) + .or_default() + .unwrap(); + assert_eq!(parsed, true); +} + +#[test] +fn test_bool_flag_primary_name() { + let args = vec!["--config"]; + let parsed = args + .to_picker() + .pick(&arg![config: bool, "cfg"]) + .or_default() + .unwrap(); + assert_eq!(parsed, true); +} + +// Short flag + alias for bool flag + +#[test] +fn test_bool_flag_short_and_alias() { + let args = vec!["-v"]; + let parsed = args + .to_picker() + .pick(&arg![verbose: bool, 'v', "cfg"]) + .or_default() + .unwrap(); + assert_eq!(parsed, true); +} + +// Default values: .or() / .or_default() + +#[test] +fn test_or_default_without_args() { + let args: Vec<&str> = vec![]; + let parsed = args + .to_picker() + .pick(&arg![verbose: bool]) + .or_default() + .unwrap(); + assert_eq!(parsed, false); +} + +#[test] +fn test_or_custom_default() { + let args: Vec<&str> = vec![]; + let parsed = args + .to_picker() + .pick(&arg![verbose: bool]) + .or(|| true) + .unwrap(); + assert_eq!(parsed, true); +} + +// to_result / to_option interface + +#[test] +fn test_to_result_ok() { + let args = vec!["--verbose"]; + let result = args + .to_picker() + .pick(&arg![verbose: bool]) + .or_default() + .to_result(); + assert_eq!(result, Ok(true)); +} + +#[test] +fn test_to_option_some() { + let args = vec!["--verbose"]; + let opt = args + .to_picker() + .pick(&arg![verbose: bool]) + .or_default() + .to_option(); + assert_eq!(opt, Some(true)); +} + +// Chain with_route passthrough + +#[test] +fn test_with_route_chain() { + let args = vec!["--flag"]; + let parsed = args + .with_route::() + .pick(&arg![flag: bool]) + .or_default() + .unwrap(); + assert_eq!(parsed, true); +} + +// Unrelated flag should not match + +#[test] +fn test_unrelated_flag_does_not_match() { + let args = vec!["--other"]; + let parsed = args + .to_picker() + .pick(&arg![verbose: bool]) + .or_default() + .unwrap(); + assert_eq!(parsed, false); +} diff --git a/arg_picker/test/src/test/multi_arg_test.rs b/arg_picker/test/src/test/multi_arg_test.rs new file mode 100644 index 0000000..49517f1 --- /dev/null +++ b/arg_picker/test/src/test/multi_arg_test.rs @@ -0,0 +1,186 @@ +use arg_picker::parselib::{Matcher, MultiArgMatcher, UNIX_STYLE}; +use arg_picker::PickerArgInfo; + +use crate::make_args; + +// on_match_one — basic + +#[test] +fn test_multi_one_finds_first_flag() { + let mut info = PickerArgInfo::new(); + info.set_long("val"); + let args = make_args(&[("--other", 0), ("--val", 1), ("a", 2), ("b", 3)]); + let result = MultiArgMatcher::on_match_one(&args, &UNIX_STYLE, &info); + assert_eq!(result, Some(1)); +} + +#[test] +fn test_multi_one_no_match() { + let mut info = PickerArgInfo::new(); + info.set_long("val"); + let args = make_args(&[("--other", 0)]); + let result = MultiArgMatcher::on_match_one(&args, &UNIX_STYLE, &info); + assert_eq!(result, None); +} + +// on_match_all — named, basic multi-value + +#[test] +fn test_multi_all_flag_takes_all_values_until_next_flag() { + // --val a b c → all three values belong to --val + let mut info = PickerArgInfo::new(); + info.set_long("val"); + let args = make_args(&[("--val", 0), ("a", 1), ("b", 2), ("c", 3)]); + let result = MultiArgMatcher::on_match_all(&args, &UNIX_STYLE, &info); + assert_eq!(result, vec![0, 1, 2, 3]); +} + +#[test] +fn test_multi_all_stops_at_next_flag() { + // --val a b --other c d → only a,b belong to --val + let mut info = PickerArgInfo::new(); + info.set_long("val"); + let args = make_args(&[("--val", 0), ("a", 1), ("b", 2), ("--other", 3), ("c", 4)]); + let result = MultiArgMatcher::on_match_all(&args, &UNIX_STYLE, &info); + assert_eq!(result, vec![0, 1, 2]); +} + +#[test] +fn test_multi_all_flag_no_values() { + // --val at end with no values → just the flag + let mut info = PickerArgInfo::new(); + info.set_long("val"); + let args = make_args(&[("--val", 0)]); + let result = MultiArgMatcher::on_match_all(&args, &UNIX_STYLE, &info); + assert_eq!(result, vec![0]); +} + +#[test] +fn test_multi_all_empty() { + let mut info = PickerArgInfo::new(); + info.set_long("val"); + let args = vec![]; + let result = MultiArgMatcher::on_match_all(&args, &UNIX_STYLE, &info); + assert!(result.is_empty()); +} + +// on_match_all — named, multiple occurrences of same flag + +#[test] +fn test_multi_all_two_occurrences() { + // --val a b --val c d → two groups + let mut info = PickerArgInfo::new(); + info.set_long("val"); + let args = make_args(&[ + ("--val", 0), + ("a", 1), + ("b", 2), + ("--val", 3), + ("c", 4), + ("d", 5), + ]); + let result = MultiArgMatcher::on_match_all(&args, &UNIX_STYLE, &info); + assert_eq!(result, vec![0, 1, 2, 3, 4, 5]); +} + +#[test] +fn test_multi_all_skips_non_matching_args() { + // --val a --other b --val c → only --val groups + let mut info = PickerArgInfo::new(); + info.set_long("val"); + let args = make_args(&[ + ("--val", 0), + ("a", 1), + ("--other", 2), + ("b", 3), + ("--val", 4), + ("c", 5), + ]); + let result = MultiArgMatcher::on_match_all(&args, &UNIX_STYLE, &info); + assert_eq!(result, vec![0, 1, 4, 5]); +} + +// on_match_all — named, eq mode + +#[test] +fn test_multi_all_eq_mode() { + // --val=a b → eq mode + one extra value + let mut info = PickerArgInfo::new(); + info.set_long("val"); + let args = make_args(&[("--val=a", 0), ("b", 1)]); + let result = MultiArgMatcher::on_match_all(&args, &UNIX_STYLE, &info); + assert_eq!(result, vec![0, 1]); +} + +#[test] +fn test_multi_all_eq_mode_no_extra() { + // --val=a alone → just the eq flag + let mut info = PickerArgInfo::new(); + info.set_long("val"); + let args = make_args(&[("--val=a", 0)]); + let result = MultiArgMatcher::on_match_all(&args, &UNIX_STYLE, &info); + assert_eq!(result, vec![0]); +} + +#[test] +fn test_multi_all_mixed_eq_and_regular() { + // --val=a b --val c d + let mut info = PickerArgInfo::new(); + info.set_long("val"); + let args = make_args(&[("--val=a", 0), ("b", 1), ("--val", 2), ("c", 3), ("d", 4)]); + let result = MultiArgMatcher::on_match_all(&args, &UNIX_STYLE, &info); + assert_eq!(result, vec![0, 1, 2, 3, 4]); +} + +// on_match_all — short flag + +#[test] +fn test_multi_all_short_flag() { + let mut info = PickerArgInfo::new(); + info.set_short('v'); + info.set_long("val"); + let args = make_args(&[("-v", 0), ("a", 1), ("b", 2)]); + let result = MultiArgMatcher::on_match_all(&args, &UNIX_STYLE, &info); + assert_eq!(result, vec![0, 1, 2]); +} + +// on_match_all — end-of-options marker + +#[test] +fn test_multi_all_stops_at_end_of_options() { + // --val a -- b → stops before `--` + let mut info = PickerArgInfo::new(); + info.set_long("val"); + let args = make_args(&[("--val", 0), ("a", 1), ("--", 2), ("b", 3)]); + let result = MultiArgMatcher::on_match_all(&args, &UNIX_STYLE, &info); + assert_eq!(result, vec![0, 1]); +} + +#[test] +fn test_multi_all_flag_after_end_ignored() { + let mut info = PickerArgInfo::new(); + info.set_long("val"); + let args = make_args(&[("--", 0), ("--val", 1), ("a", 2)]); + let result = MultiArgMatcher::on_match_all(&args, &UNIX_STYLE, &info); + assert!(result.is_empty()); +} + +// on_match_all — positional + +#[test] +fn test_multi_all_positional() { + let mut info = PickerArgInfo::new(); + info.set_positional(true); + let args = make_args(&[("a.txt", 0), ("b.txt", 1)]); + let result = MultiArgMatcher::on_match_all(&args, &UNIX_STYLE, &info); + assert_eq!(result, vec![0, 1]); +} + +#[test] +fn test_multi_all_positional_stops_at_end_of_options() { + let mut info = PickerArgInfo::new(); + info.set_positional(true); + let args = make_args(&[("a.txt", 0), ("--", 1), ("b.txt", 2)]); + let result = MultiArgMatcher::on_match_all(&args, &UNIX_STYLE, &info); + assert_eq!(result, vec![0]); +} diff --git a/arg_picker/test/src/test/multi_value_test.rs b/arg_picker/test/src/test/multi_value_test.rs new file mode 100644 index 0000000..cbae645 --- /dev/null +++ b/arg_picker/test/src/test/multi_value_test.rs @@ -0,0 +1,66 @@ +use arg_picker::value::{Flag, VecUntil}; +use arg_picker::{macros::arg, IntoPicker}; + +#[test] +fn test_vec_until_i16_named() { + let (nums, rest): (VecUntil, Flag) = vec!["--nums", "1", "2", "3", "abc"] + .to_picker() + .pick(&arg![nums: VecUntil]) + .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 = vec!["--nums", "42", "abc", "100"] + .to_picker() + .pick(&arg![nums: VecUntil]) + .unwrap(); + assert_eq!(*nums, vec![42i16]); +} + +#[test] +fn test_vec_until_i16_empty() { + let nums: VecUntil = vec!["--nums"] + .to_picker() + .pick(&arg![nums: VecUntil]) + .unwrap(); + assert!(nums.is_empty()); +} + +#[test] +fn test_vec_until_i16_stops_at_next_flag() { + let nums: VecUntil = vec!["--nums", "1", "2", "--other", "3"] + .to_picker() + .pick(&arg![nums: VecUntil]) + .unwrap(); + assert_eq!(*nums, vec![1i16, 2]); +} + +#[test] +fn test_vec_until_i16_stops_at_end_of_options() { + let nums: VecUntil = vec!["--nums", "1", "2", "--", "3"] + .to_picker() + .pick(&arg![nums: VecUntil]) + .unwrap(); + assert_eq!(*nums, vec![1i16, 2]); +} + +// Two VecUntil 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, VecUntil) = vec!["1.5", "2.5", "3.5", "1", "2", "3"] + .to_picker() + .pick(&arg![VecUntil]) + .pick(&arg![VecUntil]) + .unwrap(); + assert_eq!(*floats, vec![1.5, 2.5, 3.5]); + assert_eq!(*ints, vec![1i16, 2, 3]); +} diff --git a/arg_picker/test/src/test/pos_matcher_test.rs b/arg_picker/test/src/test/pos_matcher_test.rs new file mode 100644 index 0000000..0d4f87e --- /dev/null +++ b/arg_picker/test/src/test/pos_matcher_test.rs @@ -0,0 +1,141 @@ +use arg_picker::parselib::{MaskedArg, Matcher, PositionalMatcher, UNIX_STYLE, WINDOWS_STYLE}; +use arg_picker::PickerArgInfo; + +fn make_args<'a>(pairs: &'a [(&'a str, usize)]) -> Vec> { + pairs + .iter() + .map(|&(raw, idx)| MaskedArg { raw, raw_idx: idx }) + .collect() +} + +// on_match_one — basic + +#[test] +fn test_pos_one_takes_first_non_flag() { + let info = PickerArgInfo::new(); + let args = make_args(&[("--verbose", 0), ("file.txt", 1)]); + let result = PositionalMatcher::on_match_one(&args, &UNIX_STYLE, &info); + assert_eq!(result, Some(1)); +} + +#[test] +fn test_pos_one_takes_first_if_no_flag() { + let info = PickerArgInfo::new(); + let args = make_args(&[("file.txt", 0)]); + let result = PositionalMatcher::on_match_one(&args, &UNIX_STYLE, &info); + assert_eq!(result, Some(0)); +} + +#[test] +fn test_pos_one_all_flags_returns_none() { + let info = PickerArgInfo::new(); + let args = make_args(&[("--verbose", 0), ("--name", 1)]); + let result = PositionalMatcher::on_match_one(&args, &UNIX_STYLE, &info); + assert_eq!(result, None); +} + +#[test] +fn test_pos_one_empty_returns_none() { + let info = PickerArgInfo::new(); + let args = vec![]; + let result = PositionalMatcher::on_match_one(&args, &UNIX_STYLE, &info); + assert_eq!(result, None); +} + +// on_match_one — after `--` + +#[test] +fn test_pos_one_after_end_takes_even_flag_like() { + // After `--`, accept everything including `--verbose`. + let info = PickerArgInfo::new(); + let args = make_args(&[("--", 0), ("--verbose", 1), ("file.txt", 2)]); + let result = PositionalMatcher::on_match_one(&args, &UNIX_STYLE, &info); + assert_eq!(result, Some(1)); +} + +#[test] +fn test_pos_one_only_end_returns_none() { + let info = PickerArgInfo::new(); + let args = make_args(&[("--", 0)]); + let result = PositionalMatcher::on_match_one(&args, &UNIX_STYLE, &info); + assert_eq!(result, None); +} + +// on_match_one — Windows style prefix + +#[test] +fn test_pos_one_windows_skips_slash_prefix() { + let info = PickerArgInfo::new(); + let args = make_args(&[("/Verbose", 0), ("file.txt", 1)]); + let result = PositionalMatcher::on_match_one(&args, &WINDOWS_STYLE, &info); + assert_eq!(result, Some(1)); +} + +// on_match_all — basic + +#[test] +fn test_pos_all_collects_non_flags() { + let info = PickerArgInfo::new(); + let args = make_args(&[("--verbose", 0), ("a.txt", 1), ("--name", 2), ("b.txt", 3)]); + let result = PositionalMatcher::on_match_all(&args, &UNIX_STYLE, &info); + assert_eq!(result, vec![1, 3]); +} + +#[test] +fn test_pos_all_only_flags_returns_empty() { + let info = PickerArgInfo::new(); + let args = make_args(&[("--a", 0), ("--b", 1)]); + let result = PositionalMatcher::on_match_all(&args, &UNIX_STYLE, &info); + assert!(result.is_empty()); +} + +#[test] +fn test_pos_all_empty() { + let info = PickerArgInfo::new(); + let args = vec![]; + let result = PositionalMatcher::on_match_all(&args, &UNIX_STYLE, &info); + assert!(result.is_empty()); +} + +// on_match_all — after `--` + +#[test] +fn test_pos_all_after_end_accepts_everything() { + // After `--`, even `--verbose` is accepted as positional. + let info = PickerArgInfo::new(); + let args = make_args(&[ + ("--verbose", 0), + ("a.txt", 1), + ("--", 2), + ("--flag", 3), + ("b.txt", 4), + ]); + let result = PositionalMatcher::on_match_all(&args, &UNIX_STYLE, &info); + assert_eq!(result, vec![1, 3, 4]); +} + +#[test] +fn test_pos_all_only_after_end() { + let info = PickerArgInfo::new(); + let args = make_args(&[("--", 0), ("arg1", 1), ("--arg2", 2)]); + let result = PositionalMatcher::on_match_all(&args, &UNIX_STYLE, &info); + assert_eq!(result, vec![1, 2]); +} + +// on_match_all — mixed before/after `--` + +#[test] +fn test_pos_all_mixed() { + let info = PickerArgInfo::new(); + let args = make_args(&[ + ("infile", 0), + ("--verbose", 1), + ("--", 2), + ("outfile", 3), + ("--extra", 4), + ]); + let result = PositionalMatcher::on_match_all(&args, &UNIX_STYLE, &info); + // Before `--`: "infile" (skip --verbose). + // After `--`: everything — "outfile", "--extra". + assert_eq!(result, vec![0, 3, 4]); +} diff --git a/arg_picker/test/src/test/priority_test.rs b/arg_picker/test/src/test/priority_test.rs new file mode 100644 index 0000000..8f0a452 --- /dev/null +++ b/arg_picker/test/src/test/priority_test.rs @@ -0,0 +1,85 @@ +use arg_picker::value::Flag; +use arg_picker::{macros::arg, IntoPicker}; + +// Same flag name, different Pickable types +// +// PickerArgAttr priority: Flag < Single +// So Single and Multi should parse BEFORE Flag when sharing +// the same flag name. + +#[test] +fn test_single_takes_flag_when_sharing_name() { + // --name Alice: String consumes it, Flag sees nothing. + let (name, verbose): (String, Flag) = vec!["--name", "Alice"] + .to_picker() + .pick(&arg![name: String]) // Single, parsed first + .pick(&arg![name: Flag]) // Flag, parsed second, nothing left + .unwrap(); + assert_eq!(name, "Alice"); + assert_eq!(verbose, Flag::Inactive); +} + +#[test] +fn test_flag_only_triggers_when_single_missing() { + // --verbose: only Flag matches, String gets default. + let (name, verbose): (String, Flag) = vec!["--verbose"] + .to_picker() + .pick(&arg![name: String]) // Single, no match → default "" + .or_default() + .pick(&arg![name: Flag]) // Flag, no --name in args → Inactive + .unwrap(); + assert_eq!(name, ""); + assert_eq!(verbose, Flag::Inactive); +} + +#[test] +fn test_flag_gets_leftovers_after_single_consumes_value() { + // --name Alice --name: String takes "--name Alice", Flag takes "--name". + let (name, verbose): (String, Flag) = vec!["--name", "Alice", "--name"] + .to_picker() + .pick(&arg![name: String]) // Single: tag [0, 1], consumes --name Alice + .pick(&arg![name: Flag]) // Flag: tags position 2 (--name) + .unwrap(); + assert_eq!(name, "Alice"); + assert_eq!( + verbose, + Flag::Active, + "Flag should see --name at position 2 after String consumed positions 0-1" + ); +} + +#[test] +fn test_short_flag_sharing_same_letter() { + // -n Alice: String takes it, Flag misses. + let (name, verbose): (String, Flag) = vec!["-n", "Alice"] + .to_picker() + .pick(&arg![name: String, 'n']) // Single, parsed first + .pick(&arg![name: Flag, 'n']) // Flag, parsed second + .unwrap(); + assert_eq!(name, "Alice"); + assert_eq!(verbose, Flag::Inactive); +} + +#[test] +fn test_flag_captures_remaining_after_single_partial_consume() { + // --name Alice --verbose: String takes --name Alice, Flag takes --verbose. + let (name, verbose): (String, Flag) = vec!["--name", "Alice", "--verbose"] + .to_picker() + .pick(&arg![name: String]) // Single: tag [0, 1] + .pick(&arg![verbose: Flag]) // Flag: tag [2] + .unwrap(); + assert_eq!(name, "Alice"); + assert_eq!(verbose, Flag::Active); +} + +#[test] +fn test_single_skips_already_claimed_positions() { + // --verbose --name Alice: Flag takes --verbose, String takes --name Alice. + let (verbose, name): (Flag, String) = vec!["--verbose", "--name", "Alice"] + .to_picker() + .pick(&arg![verbose: Flag]) // Flag: tag [0] + .pick(&arg![name: String]) // Single: tag [1, 2] + .unwrap(); + assert_eq!(verbose, Flag::Active); + assert_eq!(name, "Alice"); +} diff --git a/arg_picker/test/src/test/route_test.rs b/arg_picker/test/src/test/route_test.rs new file mode 100644 index 0000000..7594c6e --- /dev/null +++ b/arg_picker/test/src/test/route_test.rs @@ -0,0 +1,200 @@ +use arg_picker::{macros::arg, IntoPicker}; + +// 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 = 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 = 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 = 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 = 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 = args + .with_route::() + .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 = args + .with_route::() + .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 = args + .with_route::() + .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::() + .pick(&arg![verbose: bool]) + .or_route(|| "string_route".to_string()) + .with_route::() + .pick(&arg![other: bool]) + .or_route(|| -1) + .to_result(); + // The first flag is missing, but its or_route is cleared when with_route switches (route_$ = None) + // The second flag is missing and has or_route → Err(-1) + assert_eq!(result, Err(-1)); +} diff --git a/arg_picker/test/src/test/style_test.rs b/arg_picker/test/src/test/style_test.rs new file mode 100644 index 0000000..b470d4f --- /dev/null +++ b/arg_picker/test/src/test/style_test.rs @@ -0,0 +1,300 @@ +use arg_picker::parselib::{ + build_possible_flags, FlagMatcher, Matcher, ParserStyle, ParserStyleNamingCase, + POWERSHELL_STYLE, UNIX_STYLE, WINDOWS_STYLE, +}; +use arg_picker::PickerArgInfo; + +use crate::make_masked; + +// Style: formatting utilities + +#[test] +fn test_unix_style_flag_string() { + assert_eq!(UNIX_STYLE.flag_string('v'), "-v"); + assert_eq!(UNIX_STYLE.flag_string("verbose"), "--verbose"); +} + +#[test] +fn test_windows_style_flag_string() { + assert_eq!(WINDOWS_STYLE.flag_string('v'), "/v"); + assert_eq!(WINDOWS_STYLE.flag_string("verbose"), "/verbose"); +} + +#[test] +fn test_powershell_style_flag_string() { + assert_eq!(POWERSHELL_STYLE.flag_string('v'), "-v"); + assert_eq!(POWERSHELL_STYLE.flag_string("Verbose"), "-Verbose"); +} + +#[test] +fn test_build_possible_flags_windows() { + // Build PickerArgInfo from a flag definition: `verbose: bool` + let mut info = PickerArgInfo::new(); + info.set_long("verbose"); + let flags = build_possible_flags(&WINDOWS_STYLE, &info); + assert_eq!(flags, vec!["/Verbose"]); +} + +#[test] +fn test_build_possible_flags_with_short_and_alias() { + let mut info = PickerArgInfo::new(); + info.set_short('n'); + info.set_long("name"); + info.set_alias(vec!["nickname"]); + let flags = build_possible_flags(&UNIX_STYLE, &info); + assert_eq!(flags, vec!["-n", "--name", "--nickname"]); +} + +// Style: matching with different styles via Matcher trait + +#[test] +fn test_windows_style_match() { + // Windows style: /verbose (case insensitive) + let mut info = PickerArgInfo::new(); + info.set_long("verbose"); + + let args = vec![make_masked("/verbose", 0)]; + let result = FlagMatcher::on_match_one(&args, &WINDOWS_STYLE, &info); + assert_eq!(result, Some(0)); +} + +#[test] +fn test_windows_style_match_case_insensitive() { + // Windows style is case-insensitive: /VERBOSE should match "verbose" + let mut info = PickerArgInfo::new(); + info.set_long("verbose"); + + let args = vec![make_masked("/VERBOSE", 0)]; + let result = FlagMatcher::on_match_one(&args, &WINDOWS_STYLE, &info); + assert_eq!(result, Some(0)); +} + +#[test] +fn test_windows_style_no_match_on_unrelated_flag() { + // Different flag should not match + let mut info = PickerArgInfo::new(); + info.set_long("verbose"); + + let args = vec![make_masked("/output", 0)]; + let result = FlagMatcher::on_match_one(&args, &WINDOWS_STYLE, &info); + assert_eq!(result, None); +} + +#[test] +fn test_powershell_style_match() { + // PowerShell style: -Verbose (case insensitive) + let mut info = PickerArgInfo::new(); + info.set_long("Verbose"); + + let args = vec![make_masked("-Verbose", 0)]; + let result = FlagMatcher::on_match_one(&args, &POWERSHELL_STYLE, &info); + assert_eq!(result, Some(0)); +} + +#[test] +fn test_powershell_style_match_case_insensitive() { + let mut info = PickerArgInfo::new(); + info.set_long("Verbose"); + + let args = vec![make_masked("-VERBOSE", 0)]; + let result = FlagMatcher::on_match_one(&args, &POWERSHELL_STYLE, &info); + assert_eq!(result, Some(0)); +} + +#[test] +fn test_unix_style_case_sensitive_no_match() { + // UNIX style is case-sensitive: --VERBOSE should NOT match --verbose + let mut info = PickerArgInfo::new(); + info.set_long("verbose"); + + let args = vec![make_masked("--VERBOSE", 0)]; + let result = FlagMatcher::on_match_one(&args, &UNIX_STYLE, &info); + assert_eq!(result, None); +} + +#[test] +fn test_windows_style_match_all() { + // on_match_all should find all matching flags + let mut info = PickerArgInfo::new(); + info.set_long("verbose"); + info.set_short('v'); + + let args = vec![ + make_masked("/v", 0), + make_masked("/output", 1), + make_masked("/VERBOSE", 2), + ]; + let result = FlagMatcher::on_match_all(&args, &WINDOWS_STYLE, &info); + assert_eq!(result, vec![0, 2]); +} + +#[test] +fn test_windows_style_match_after_end_of_options() { + // end_of_options is always "--" regardless of style + // Flags after -- should not match + let mut info = PickerArgInfo::new(); + info.set_long("verbose"); + + let args = vec![ + make_masked("/verbose", 0), + make_masked("--", 1), + make_masked("/verbose", 2), + ]; + let result = FlagMatcher::on_match_all(&args, &WINDOWS_STYLE, &info); + // end_of_options is always "--" regardless of style + assert_eq!(result, vec![0]); +} + +// Naming case conversion + +/// A Unix-like style with kebab-case naming. +const KEBAB_STYLE: ParserStyle<'static> = ParserStyle { + naming_case: ParserStyleNamingCase::Kebab, + ..UNIX_STYLE +}; + +#[test] +fn test_kebab_case_naming_for_multiword_flag() { + // flag name `flag_a` → Kebab → `flag-a` → `--flag-a` + let mut info = PickerArgInfo::new(); + info.set_long("flag_a"); + + let args = vec![make_masked("--flag-a", 0)]; + let result = FlagMatcher::on_match_one(&args, &KEBAB_STYLE, &info); + assert_eq!( + result, + Some(0), + "--flag-a should match flag_a via kebab-case conversion" + ); +} + +#[test] +fn test_snake_case_should_not_match_as_long_flag() { + // With kebab naming, `--flag_a` (snake) should NOT match `flag_a` + let mut info = PickerArgInfo::new(); + info.set_long("flag_a"); + + let args = vec![make_masked("--flag_a", 0)]; + let result = FlagMatcher::on_match_one(&args, &KEBAB_STYLE, &info); + assert_eq!( + result, None, + "--flag_a should NOT match in kebab-style context" + ); +} + +#[test] +fn test_kebab_case_naming_for_unix_style() { + // UNIX_STYLE now uses Kebab case: `flag_a` → `flag-a` → `--flag-a` + let mut info = PickerArgInfo::new(); + info.set_long("flag_a"); + + let args = vec![make_masked("--flag-a", 0)]; + let result = FlagMatcher::on_match_one(&args, &UNIX_STYLE, &info); + assert_eq!( + result, + Some(0), + "--flag-a should match with kebab-style UNIX_STYLE" + ); +} + +#[test] +fn test_snake_case_rejected_by_unix_style() { + // UNIX_STYLE uses Kebab: `--flag_a` (snake) should NOT match + let mut info = PickerArgInfo::new(); + info.set_long("flag_a"); + + let args = vec![make_masked("--flag_a", 0)]; + let result = FlagMatcher::on_match_one(&args, &UNIX_STYLE, &info); + assert_eq!( + result, None, + "--flag_a should NOT match under kebab-style UNIX_STYLE" + ); +} + +#[test] +fn test_powershell_pascal_case_naming() { + // POWERSHELL_STYLE uses Pascal case: `verbose` → `Verbose` → `-Verbose` + let mut info = PickerArgInfo::new(); + info.set_long("verbose"); + + let args = vec![make_masked("-Verbose", 0)]; + let result = FlagMatcher::on_match_one(&args, &POWERSHELL_STYLE, &info); + assert_eq!( + result, + Some(0), + "-Verbose should match verbose via Pascal case" + ); +} + +#[test] +fn test_flag_naming_kebab_matches_my_name() { + // UNIX_STYLE now uses Kebab: `my_name` → `my-name` → `--my-name` + let mut info = PickerArgInfo::new(); + info.set_long("my_name"); + + let args = vec![make_masked("--my-name", 0)]; + let result = FlagMatcher::on_match_one(&args, &UNIX_STYLE, &info); + assert_eq!( + result, + Some(0), + "--my-name should match via kebab conversion" + ); +} + +#[test] +fn test_flag_naming_kebab_rejects_my_name_underscore() { + // Kebab style: `--my_name` (snake) should NOT match + let mut info = PickerArgInfo::new(); + info.set_long("my_name"); + + let args = vec![make_masked("--my_name", 0)]; + let result = FlagMatcher::on_match_one(&args, &UNIX_STYLE, &info); + assert_eq!( + result, None, + "--my_name should NOT match under kebab-style UNIX_STYLE" + ); +} + +#[test] +fn test_flag_naming_pascal_matches_my_name() { + // `my_name` under Pascal → `MyName` → `-MyName` + let mut info = PickerArgInfo::new(); + info.set_long("my_name"); + + let args = vec![make_masked("-MyName", 0)]; + let result = FlagMatcher::on_match_one(&args, &POWERSHELL_STYLE, &info); + assert_eq!( + result, + Some(0), + "-MyName should match via Pascal conversion" + ); +} + +#[test] +fn test_flag_naming_pascal_matches_lowercase() { + // PowerShell is case-insensitive: `-myname` should also match + let mut info = PickerArgInfo::new(); + info.set_long("my_name"); + + let args = vec![make_masked("-myname", 0)]; + let result = FlagMatcher::on_match_one(&args, &POWERSHELL_STYLE, &info); + assert_eq!( + result, + Some(0), + "-myname (lowercase) should match via case-insensitive Pascal" + ); +} + +#[test] +fn test_flag_naming_pascal_rejects_my_name_underscore() { + // Pascal style: `-my_name` (underscore) should NOT match + let mut info = PickerArgInfo::new(); + info.set_long("my_name"); + + let args = vec![make_masked("-my_name", 0)]; + let result = FlagMatcher::on_match_one(&args, &POWERSHELL_STYLE, &info); + assert_eq!( + result, None, + "-my_name should NOT match under Pascal-style naming" + ); +} diff --git a/arg_picker/test/src/test/value_flag_test.rs b/arg_picker/test/src/test/value_flag_test.rs new file mode 100644 index 0000000..3df053e --- /dev/null +++ b/arg_picker/test/src/test/value_flag_test.rs @@ -0,0 +1,174 @@ +use arg_picker::value::Flag; +use arg_picker::{macros::arg, IntoPicker}; + +// 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 = 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 = 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 = Vec::<&str>::new() + .with_route::<&str>() + .pick(&arg![verbose: Flag]) + .or_route(|| "should_not_fire") + .to_result(); + assert_eq!(result, Ok(Flag::Inactive)); +} diff --git a/arg_picker/test/src/test/value_string_test.rs b/arg_picker/test/src/test/value_string_test.rs new file mode 100644 index 0000000..2c3b50b --- /dev/null +++ b/arg_picker/test/src/test/value_string_test.rs @@ -0,0 +1,171 @@ +use arg_picker::{macros::arg, IntoPicker}; + +// Basic named String — present / absent + +#[test] +fn test_string_named_present() { + let val: String = vec!["--name", "Alice"] + .to_picker() + .pick(&arg![name: String]) + .or_default() + .unwrap(); + assert_eq!(val, "Alice"); +} + +#[test] +fn test_string_named_absent_uses_default() { + let val: String = Vec::<&str>::new() + .to_picker() + .pick(&arg![name: String]) + .or_default() + .unwrap(); + assert_eq!(val, ""); +} + +// Named String — eq mode + +#[test] +fn test_string_named_eq_mode() { + let val: String = vec!["--name=Alice"] + .to_picker() + .pick(&arg![name: String]) + .or_default() + .unwrap(); + assert_eq!(val, "Alice"); +} + +// Named String — short flag + +#[test] +fn test_string_named_short_flag() { + let val: String = vec!["-n", "Alice"] + .to_picker() + .pick(&arg![name: String, 'n']) + .or_default() + .unwrap(); + assert_eq!(val, "Alice"); +} + +// Named String — no value after flag + +#[test] +fn test_string_named_missing_value_triggers_default() { + // --name at end with no following arg → pick returns NotFound → or_default gives "" + let val: String = vec!["--name"] + .to_picker() + .pick(&arg![name: String]) + .or_default() + .unwrap(); + assert_eq!(val, ""); +} + +// Positional String + +#[test] +fn test_string_positional() { + let val: String = vec!["file.txt"] + .to_picker() + .pick(&arg![String]) + .or_default() + .unwrap(); + assert_eq!(val, "file.txt"); +} + +#[test] +fn test_string_positional_takes_first() { + let val: String = vec!["first", "second"] + .to_picker() + .pick(&arg![String]) + .or_default() + .unwrap(); + assert_eq!(val, "first"); +} + +// Multiple occurrences (Single only tags one occurrence per Parseable) + +#[test] +fn test_string_two_named_flags() { + let (a, b): (String, String) = vec!["--name", "Alice", "--greeting", "Hello"] + .to_picker() + .pick(&arg![name: String]) + .or_default() + .pick(&arg![greeting: String]) + .or_default() + .unwrap(); + assert_eq!(a, "Alice"); + assert_eq!(b, "Hello"); +} + +// Mixed named + positional + +#[test] +fn test_string_named_and_positional() { + let (name, file): (String, String) = vec!["--name", "Alice", "file.txt"] + .to_picker() + .pick(&arg![name: String]) + .or_default() + .pick(&arg![String]) + .or_default() + .unwrap(); + assert_eq!(name, "Alice"); + assert_eq!(file, "file.txt"); +} + +// After `--` (end-of-options) + +#[test] +fn test_string_named_after_end_of_options() { + // Named arg after `--` should not be matched → default + let val: String = vec!["--", "--name", "Alice"] + .to_picker() + .pick(&arg![name: String]) + .or_default() + .unwrap(); + assert_eq!(val, ""); +} + +// Unrelated flag should not match → default + +#[test] +fn test_string_unrelated_flag() { + let val: String = vec!["--other", "value"] + .to_picker() + .pick(&arg![name: String]) + .or_default() + .unwrap(); + assert_eq!(val, ""); +} + +// to_result / to_option + +#[test] +fn test_string_to_result() { + let result: Result = 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 = 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"); +} -- cgit