From 4f4a61d493547660260a5aa1db1af8a6ec42bbb6 Mon Sep 17 00:00:00 2001 From: 魏曹先生 <1992414357@qq.com> Date: Thu, 16 Jul 2026 19:47:01 +0800 Subject: refactor: extract `seek_end_of_options` into utils and add test helpers Deduplicate the `seek_end_of_options` function by moving it from both `arg_matcher` and `flag_matcher` into a single shared implementation in utils. Add `make_masked` and `make_args` test helpers to the test crate --- mingling_picker/src/parselib/arg_matcher.rs | 32 +++++------------------ mingling_picker/src/parselib/flag_matcher.rs | 18 +------------ mingling_picker/src/parselib/utils.rs | 10 ++++++- mingling_picker/test/src/lib.rs | 15 +++++++++++ mingling_picker/test/src/test/arg_matcher_test.rs | 31 ++-------------------- mingling_picker/test/src/test/style_test.rs | 10 +++---- 6 files changed, 37 insertions(+), 79 deletions(-) (limited to 'mingling_picker') diff --git a/mingling_picker/src/parselib/arg_matcher.rs b/mingling_picker/src/parselib/arg_matcher.rs index 931652a..808e4a2 100644 --- a/mingling_picker/src/parselib/arg_matcher.rs +++ b/mingling_picker/src/parselib/arg_matcher.rs @@ -1,4 +1,7 @@ -use crate::{matcher_needed::*, parselib::build_possible_flags}; +use crate::{ + matcher_needed::*, + parselib::{build_possible_flags, seek_end_of_options}, +}; /// `ArgMatcher` is used for parameters that carry a single value. /// @@ -53,7 +56,6 @@ impl Matcher for ArgMatcher { arg_info: &PickerArgInfo, ) -> Option { if arg_info.positional { - // Positional: first available position. return args.first().map(|a| a.raw_idx); } @@ -61,7 +63,6 @@ impl Matcher for ArgMatcher { let end = seek_end_of_options(args, style); for arg in args { - // Stop at end-of-options marker. if end.is_some_and(|e| arg.raw_idx >= e) { break; } @@ -83,7 +84,6 @@ impl Matcher for ArgMatcher { arg_info: &PickerArgInfo, ) -> Vec { if arg_info.positional { - // Positional: all available positions before `--`. let end = seek_end_of_options(args, style); return args .iter() @@ -98,7 +98,6 @@ impl Matcher for ArgMatcher { let mut result = Vec::new(); let mut i = 0; while i < args.len() { - // Stop at end-of-options marker. if end.is_some_and(|e| args[i].raw_idx >= e) { break; } @@ -108,28 +107,22 @@ impl Matcher for ArgMatcher { .any(|f| Self::matches(args[i].raw, f, style.case_sensitive)); if matched { - // Find which flag matched to check eq mode. let flag_str = possible_flags .iter() .find(|f| Self::matches(args[i].raw, f, style.case_sensitive)) .expect("already matched"); - result.push(args[i].raw_idx); // flag position + result.push(args[i].raw_idx); if !Self::is_eq_mode(args[i].raw, flag_str) { - // Non-eq mode: the next argument is the value. - // Always tag it — even if it looks like a flag — so that - // the mask reserves it. Validation is the Pickable's job. if i + 1 < args.len() { result.push(args[i + 1].raw_idx); - i += 2; // skip flag + value + i += 2; continue; } - // No value available: tag just the flag. i += 1; continue; } - // eq mode: value is inline, no extra slot. i += 1; continue; } @@ -139,16 +132,3 @@ impl Matcher for ArgMatcher { result } } - -/// Locate the end-of-options marker (`--`) in the argument list. -fn seek_end_of_options(args: &[MaskedArg], style: &ParserStyle) -> Option { - args.iter() - .find(|arg| { - if style.case_sensitive { - arg.raw == style.end_of_options - } else { - arg.raw.eq_ignore_ascii_case(style.end_of_options) - } - }) - .map(|arg| arg.raw_idx) -} diff --git a/mingling_picker/src/parselib/flag_matcher.rs b/mingling_picker/src/parselib/flag_matcher.rs index 782d0f8..e93d35a 100644 --- a/mingling_picker/src/parselib/flag_matcher.rs +++ b/mingling_picker/src/parselib/flag_matcher.rs @@ -1,6 +1,6 @@ use crate::{ matcher_needed::*, - parselib::{build_possible_flags, get_seeked_first, multi_seek_eq}, + parselib::{build_possible_flags, get_seeked_first, multi_seek_eq, seek_end_of_options}, }; /// `FlagMatcher` is used to match flags in command-line arguments. @@ -80,19 +80,3 @@ fn single_pass_match_all( matches } - -/// Locate the end-of-options marker (`--`) in the argument list. -fn seek_end_of_options(args: &[MaskedArg], style: &ParserStyle) -> Option { - get_seeked_first( - args.iter() - .filter(|arg| { - if style.case_sensitive { - arg.raw == style.end_of_options - } else { - arg.raw.eq_ignore_ascii_case(style.end_of_options) - } - }) - .map(|arg| arg.raw_idx) - .collect(), - ) -} diff --git a/mingling_picker/src/parselib/utils.rs b/mingling_picker/src/parselib/utils.rs index 5219b02..ae3c203 100644 --- a/mingling_picker/src/parselib/utils.rs +++ b/mingling_picker/src/parselib/utils.rs @@ -34,7 +34,15 @@ pub fn build_possible_flags(style: &ParserStyle, arg_info: &PickerArgInfo) -> Ve /// subsequent arguments should be treated as positional arguments, not flags. #[must_use] pub fn seek_end_of_options(args: &[MaskedArg], style: &ParserStyle) -> Option { - get_seeked_first(seek_eq(args, style.end_of_options, style.case_sensitive)) + args.iter() + .find(|arg| { + if style.case_sensitive { + arg.raw == style.end_of_options + } else { + arg.raw.eq_ignore_ascii_case(style.end_of_options) + } + }) + .map(|arg| arg.raw_idx) } /// Seeks arguments in `args` that are exactly equal to the given `string`. diff --git a/mingling_picker/test/src/lib.rs b/mingling_picker/test/src/lib.rs index 9bc0560..eac6cad 100644 --- a/mingling_picker/test/src/lib.rs +++ b/mingling_picker/test/src/lib.rs @@ -6,3 +6,18 @@ #[cfg(test)] mod test; + +use mingling_picker::parselib::MaskedArg; + +/// Create a single `MaskedArg` from a raw string and its original index. +pub fn make_masked(raw: &str, idx: usize) -> MaskedArg<'_> { + MaskedArg { raw, raw_idx: idx } +} + +/// Create a `Vec` 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/mingling_picker/test/src/test/arg_matcher_test.rs b/mingling_picker/test/src/test/arg_matcher_test.rs index 3249079..947e20f 100644 --- a/mingling_picker/test/src/test/arg_matcher_test.rs +++ b/mingling_picker/test/src/test/arg_matcher_test.rs @@ -1,16 +1,9 @@ use mingling_picker::PickerArgInfo; -use mingling_picker::parselib::{ArgMatcher, MaskedArg, Matcher, POWERSHELL_STYLE, UNIX_STYLE}; +use mingling_picker::parselib::{ArgMatcher, Matcher, POWERSHELL_STYLE, UNIX_STYLE}; -fn make_args<'a>(pairs: &'a [(&'a str, usize)]) -> Vec> { - pairs - .iter() - .map(|&(raw, idx)| MaskedArg { raw, raw_idx: idx }) - .collect() -} +use crate::make_args; -// ============================================================ // on_match_one — Named -// ============================================================ #[test] fn test_match_one_named_basic() { @@ -59,9 +52,7 @@ fn test_match_one_named_after_end_of_options() { assert_eq!(result, None); } -// ============================================================ // on_match_one — Positional -// ============================================================ #[test] fn test_match_one_positional_basic() { @@ -81,9 +72,7 @@ fn test_match_one_positional_takes_first() { assert_eq!(result, Some(0)); } -// ============================================================ // on_match_all — Named, single occurrence -// ============================================================ #[test] fn test_match_all_named_flag_plus_value() { @@ -125,9 +114,7 @@ fn test_match_all_named_value_looks_like_flag() { assert_eq!(result, vec![0, 1]); } -// ============================================================ // on_match_all — Named, multiple occurrences (Single per flag) -// ============================================================ #[test] fn test_match_all_named_two_occurrences() { @@ -149,9 +136,7 @@ fn test_match_all_named_skips_non_matching_args() { assert_eq!(result, vec![0, 1, 3, 4]); } -// ============================================================ // on_match_all — Named, short flag -// ============================================================ #[test] fn test_match_all_named_short_flag() { @@ -163,9 +148,7 @@ fn test_match_all_named_short_flag() { assert_eq!(result, vec![0, 1]); } -// ============================================================ // on_match_all — Named, eq + non-eq mixed -// ============================================================ #[test] fn test_match_all_named_mixed_eq_and_regular() { @@ -176,9 +159,7 @@ fn test_match_all_named_mixed_eq_and_regular() { assert_eq!(result, vec![0, 1, 2]); } -// ============================================================ // on_match_all — Named, case insensitive (PowerShell) -// ============================================================ #[test] fn test_match_all_named_powershell_case_insensitive() { @@ -189,9 +170,7 @@ fn test_match_all_named_powershell_case_insensitive() { assert_eq!(result, vec![0, 1]); } -// ============================================================ // on_match_all — Positional -// ============================================================ #[test] fn test_match_all_positional_single() { @@ -211,9 +190,7 @@ fn test_match_all_positional_multiple() { assert_eq!(result, vec![0, 1]); } -// ============================================================ // End-of-options marker (`--`) -// ============================================================ #[test] fn test_match_all_named_stops_at_end_of_options() { @@ -240,9 +217,7 @@ fn test_match_all_positional_stops_at_end_of_options() { assert_eq!(result, vec![0]); } -// ============================================================ // Empty args -// ============================================================ #[test] fn test_match_one_empty() { @@ -262,9 +237,7 @@ fn test_match_all_empty() { assert!(result.is_empty()); } -// ============================================================ // Verify that -- itself is never matched as a flag -// ============================================================ #[test] fn test_match_all_end_of_options_not_matched() { diff --git a/mingling_picker/test/src/test/style_test.rs b/mingling_picker/test/src/test/style_test.rs index efb2185..3c337b9 100644 --- a/mingling_picker/test/src/test/style_test.rs +++ b/mingling_picker/test/src/test/style_test.rs @@ -1,9 +1,11 @@ use mingling_picker::PickerArgInfo; use mingling_picker::parselib::{ - FlagMatcher, MaskedArg, Matcher, POWERSHELL_STYLE, ParserStyle, ParserStyleNamingCase, - UNIX_STYLE, WINDOWS_STYLE, build_possible_flags, + FlagMatcher, Matcher, POWERSHELL_STYLE, ParserStyle, ParserStyleNamingCase, UNIX_STYLE, + WINDOWS_STYLE, build_possible_flags, }; +use crate::make_masked; + // Style: formatting utilities #[test] @@ -45,10 +47,6 @@ fn test_build_possible_flags_with_short_and_alias() { // Style: matching with different styles via Matcher trait -fn make_masked(raw: &str, idx: usize) -> MaskedArg<'_> { - MaskedArg { raw, raw_idx: idx } -} - #[test] fn test_windows_style_match() { // Windows style: /verbose (case insensitive) -- cgit