diff options
| author | 魏曹先生 <1992414357@qq.com> | 2026-07-17 03:24:51 +0800 |
|---|---|---|
| committer | 魏曹先生 <1992414357@qq.com> | 2026-07-17 03:25:24 +0800 |
| commit | b9f208deed7b8e012fbcd84202e2fb1d5eb8eeb9 (patch) | |
| tree | 2e0f00651b0cff09a2f6bafd6ce35046532ffa81 /mingling_picker/src/parselib/utils.rs | |
| parent | 8b2c209c2f111e0e208de5dde9df6c4bd3ea1052 (diff) | |
feat(picker2): complete Picker2 prototype
Picker2 replaces the original Picker1 with a two-phase (tag → pick) +
mask-bitmap architecture, decoupling argument matching from type
conversion via a composable matcher pipeline.
Architecture
- FlagMatcher — boolean flags (--verbose)
- ArgMatcher — single flag+value pairs (--name Alice)
- MultiArgMatcher — multi-value flag groups (--files a.txt b.txt)
- PositionalMatcher — positional arguments, respects `--`
- SingleMatcher — composite for Single-type Pickables
Types
- Flag (Active/Inactive) — semantic bool flag value
- String + 14 numeric types via SinglePickable trait
- Vec<T> — greedy multi-value (all SinglePickable types)
- VecUntil<T> — bounded multi-value via BoundaryCheck trait
- VecUntil<T>, pick_string, pick_numbers, pick_bool, pick_flag
Style system
- UNIX_STYLE (kebab), POWERSHELL_STYLE (Pascal), WINDOWS_STYLE (Pascal)
- naming_case auto-conversion via just_fmt integration
- Style-aware separator (= for Unix, : for PS/Windows)
Infrastructure
- internal_repeat! macro generates PickerPattern1..=32
- SinglePickable blanket impl → Pickable
- MultiPickableWithBoundary trait with greedy/bounded variants
- 151 integration tests
- Docs updated for parser feature
Diffstat (limited to 'mingling_picker/src/parselib/utils.rs')
| -rw-r--r-- | mingling_picker/src/parselib/utils.rs | 271 |
1 files changed, 271 insertions, 0 deletions
diff --git a/mingling_picker/src/parselib/utils.rs b/mingling_picker/src/parselib/utils.rs new file mode 100644 index 0000000..726346c --- /dev/null +++ b/mingling_picker/src/parselib/utils.rs @@ -0,0 +1,271 @@ +use crate::{ + PickerArgInfo, + parselib::{MaskedArg, ParserStyle}, +}; + +#[inline(always)] +pub fn build_possible_flags(style: &ParserStyle, arg_info: &PickerArgInfo) -> Vec<String> { + let mut possible_flags = vec![]; + + if let Some(short) = arg_info.short { + possible_flags.push(style.flag_string(short)); + } + + if let Some(long) = arg_info.long { + let converted = style.naming_case.convert(long.to_string()); + possible_flags.push(style.flag_string(&converted)); + } + + if let Some(aliases) = &arg_info.alias { + for alias in aliases { + let converted = style.naming_case.convert(alias.to_string()); + possible_flags.push(style.flag_string(&converted)); + } + } + + possible_flags +} + +/// Extract a single value from the raw strings tagged by [`SingleMatcher`](crate::parselib::SingleMatcher). +/// +/// Returns `None` if no value is available (empty slice), +/// the inline value after the style separator if present (eq mode), +/// or the value directly (positional or flag-following). +/// +/// This is the standard `pick` helper for all `Single`-type +/// [`Pickable`](crate::Pickable) implementations. +#[must_use] +pub fn seek_single<'a>(raw_strs: &'a [&'a str]) -> Option<&'a str> { + match raw_strs.len() { + 0 => None, + 1 => { + let s = raw_strs[0]; + let sep = ParserStyle::global_style().value_separator; + if let Some(pos) = s.rfind(sep) { + Some(&s[pos + 1..]) + } else { + Some(s) + } + } + _ => Some(raw_strs[1]), + } +} + +/// Seeks the index of the end-of-options marker (`--`) in the argument list. +/// +/// This function searches for the standard end-of-options separator (`--`) +/// in the given argument list, respecting the parser's style settings +/// (e.g., case sensitivity). The end-of-options marker indicates that all +/// subsequent arguments should be treated as positional arguments, not flags. +#[must_use] +pub fn seek_end_of_options(args: &[MaskedArg], style: &ParserStyle) -> Option<usize> { + 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`. +/// +/// Returns the indices of matching arguments. +#[must_use] +#[inline(always)] +pub fn seek_eq(args: &[MaskedArg], string: &str, case_sensitive: bool) -> Vec<usize> { + args.iter() + .filter(|arg| { + if case_sensitive { + arg.raw == string + } else { + arg.raw.eq_ignore_ascii_case(string) + } + }) + .map(|arg| arg.raw_idx) + .collect() +} + +/// Seeks arguments in `args` that contain the given `string` as a substring. +/// +/// Returns the indices of matching arguments. +#[must_use] +#[inline(always)] +pub fn seek_contains(args: &[MaskedArg], string: &str, case_sensitive: bool) -> Vec<usize> { + args.iter() + .filter(|arg| { + if case_sensitive { + arg.raw.contains(string) + } else { + arg.raw.to_lowercase().contains(&string.to_lowercase()) + } + }) + .map(|arg| arg.raw_idx) + .collect() +} + +/// Seeks arguments in `args` that start with the given `string`. +/// +/// Returns the indices of matching arguments. +#[must_use] +#[inline(always)] +pub fn seek_start_with(args: &[MaskedArg], string: &str, case_sensitive: bool) -> Vec<usize> { + args.iter() + .filter(|arg| { + if case_sensitive { + arg.raw.starts_with(string) + } else { + arg.raw.to_lowercase().starts_with(&string.to_lowercase()) + } + }) + .map(|arg| arg.raw_idx) + .collect() +} + +/// Seeks arguments in `args` that end with the given `string`. +/// +/// Returns the indices of matching arguments. +#[must_use] +#[inline(always)] +pub fn seek_end_with(args: &[MaskedArg], string: &str, case_sensitive: bool) -> Vec<usize> { + args.iter() + .filter(|arg| { + if case_sensitive { + arg.raw.ends_with(string) + } else { + arg.raw.to_lowercase().ends_with(&string.to_lowercase()) + } + }) + .map(|arg| arg.raw_idx) + .collect() +} + +/// Seeks arguments in `args` that are exactly equal to any of the given `strings`. +/// +/// Returns the indices of matching arguments. +#[must_use] +#[inline(always)] +pub fn multi_seek_eq(args: &[MaskedArg], strings: &[&str], case_sensitive: bool) -> Vec<usize> { + args.iter() + .filter(|arg| { + if case_sensitive { + strings.contains(&arg.raw) + } else { + strings.iter().any(|s| arg.raw.eq_ignore_ascii_case(s)) + } + }) + .map(|arg| arg.raw_idx) + .collect() +} + +/// Seeks arguments in `args` that contain any of the given `strings` as a substring. +/// +/// Returns the indices of matching arguments. +#[must_use] +#[inline(always)] +pub fn multi_seek_contains( + args: &[MaskedArg], + strings: &[&str], + case_sensitive: bool, +) -> Vec<usize> { + args.iter() + .filter(|arg| { + if case_sensitive { + strings.iter().any(|s| arg.raw.contains(s)) + } else { + let lower_raw = arg.raw.to_lowercase(); + strings + .iter() + .any(|s| lower_raw.contains(&s.to_lowercase())) + } + }) + .map(|arg| arg.raw_idx) + .collect() +} + +/// Seeks arguments in `args` that start with any of the given `strings`. +/// +/// Returns the indices of matching arguments. +#[must_use] +#[inline(always)] +pub fn multi_seek_start_with( + args: &[MaskedArg], + strings: &[&str], + case_sensitive: bool, +) -> Vec<usize> { + args.iter() + .filter(|arg| { + if case_sensitive { + strings.iter().any(|s| arg.raw.starts_with(s)) + } else { + let lower_raw = arg.raw.to_lowercase(); + strings + .iter() + .any(|s| lower_raw.starts_with(&s.to_lowercase())) + } + }) + .map(|arg| arg.raw_idx) + .collect() +} + +/// Seeks arguments in `args` that end with any of the given `strings`. +/// +/// Returns the indices of matching arguments. +#[must_use] +#[inline(always)] +pub fn multi_seek_end_with( + args: &[MaskedArg], + strings: &[&str], + case_sensitive: bool, +) -> Vec<usize> { + args.iter() + .filter(|arg| { + if case_sensitive { + strings.iter().any(|s| arg.raw.ends_with(s)) + } else { + let lower_raw = arg.raw.to_lowercase(); + strings + .iter() + .any(|s| lower_raw.ends_with(&s.to_lowercase())) + } + }) + .map(|arg| arg.raw_idx) + .collect() +} + +/// Converts a `&Vec<String>` into a `Vec<&str>` by borrowing each string's slice. +/// +/// This is useful for converting owned `String` vectors into borrowed `&str` slices +/// for functions that take `&[&str]` or similar parameters. +#[must_use] +#[inline(always)] +#[doc(hidden)] +pub fn vec_string_to_vec_str(input: &[String]) -> Vec<&str> { + input.iter().map(|s| s.as_str()).collect() +} + +/// Converts a `&Vec<String>` into a `Vec<&str>` by borrowing each string's slice. +/// +/// This is useful for converting owned `String` vectors into borrowed `&str` slices +/// for functions that take `&[&str]` or similar parameters. +#[macro_export] +#[doc(hidden)] +macro_rules! vec_string_slice { + ($v:expr) => { + $v.iter() + .map(|s| s.as_str()) + .collect::<Vec<&str>>() + .as_slice() + }; +} + +/// Gets the first element from a vector of seek results, if any. +/// +/// Returns `Some(index)` if the vector is non-empty, otherwise `None`. +#[must_use] +#[inline(always)] +pub fn get_seeked_first(seeked: Vec<usize>) -> Option<usize> { + seeked.into_iter().next() +} |
