diff options
| author | 魏曹先生 <1992414357@qq.com> | 2026-07-17 11:08:07 +0800 |
|---|---|---|
| committer | 魏曹先生 <1992414357@qq.com> | 2026-07-17 11:11:28 +0800 |
| commit | 79ec6878877f0fd9246d67d3cd4f8cc2d1200150 (patch) | |
| tree | e3cd8cfc7ef5cd2a6a7ceb93d9a4b1764fa21b61 /mingling_picker/src/parselib | |
| parent | e6136f22cff446b16dbebf3b26a6fdea6dbc0e83 (diff) | |
refactor: rename `mingling_picker` to `arg_picker`
Diffstat (limited to 'mingling_picker/src/parselib')
| -rw-r--r-- | mingling_picker/src/parselib/arg_matcher.rs | 142 | ||||
| -rw-r--r-- | mingling_picker/src/parselib/flag_matcher.rs | 82 | ||||
| -rw-r--r-- | mingling_picker/src/parselib/multi_arg_matcher.rs | 141 | ||||
| -rw-r--r-- | mingling_picker/src/parselib/pos_matcher.rs | 71 | ||||
| -rw-r--r-- | mingling_picker/src/parselib/single_matcher.rs | 59 | ||||
| -rw-r--r-- | mingling_picker/src/parselib/style.rs | 258 | ||||
| -rw-r--r-- | mingling_picker/src/parselib/utils.rs | 276 |
7 files changed, 0 insertions, 1029 deletions
diff --git a/mingling_picker/src/parselib/arg_matcher.rs b/mingling_picker/src/parselib/arg_matcher.rs deleted file mode 100644 index 38bb9cc..0000000 --- a/mingling_picker/src/parselib/arg_matcher.rs +++ /dev/null @@ -1,142 +0,0 @@ -use crate::{ - matcher_needed::*, - parselib::{build_possible_flags, seek_end_of_options}, -}; - -/// `ArgMatcher` is used for parameters that carry a single value. -/// -/// It handles two scenarios: -/// -/// **Named** — `--name Alice` or `--name=Alice`. -/// Each flag occurrence consumes **one** following argument as its value, -/// regardless of what it is (even if it looks like a flag). -/// This ensures the mask correctly claims the value slot; validation is -/// the `Pickable`'s responsibility. -/// -/// **Positional** — no flag prefix, matched by position. -/// -/// # Examples -/// -/// | Input | `on_match_one` | `on_match_all` | -/// |-------|----------------|----------------| -/// | `--name Alice` | `[0, 1]` (via Pickable tag) | `[0, 1]` | -/// | `--name=Alice` | `[0]` | `[0]` | -/// | `--val a --val b` | `[0, 1]` | `[0, 1, 2, 3]` | -/// -/// Args after `--` are ignored. -pub struct ArgMatcher; - -impl ArgMatcher { - /// Check whether `raw` matches `flag_str`, optionally with an inline value - /// separated by the style's value separator (`=` for Unix, `:` for PowerShell). - #[inline(always)] - fn matches(raw: &str, flag_str: &str, case_sensitive: bool, sep: char) -> bool { - let eq_match = - |r: &str, f: &str| r.len() > f.len() && r.as_bytes().get(f.len()) == Some(&(sep as u8)); - - if case_sensitive { - raw == flag_str || (raw.starts_with(flag_str) && eq_match(raw, flag_str)) - } else { - raw.eq_ignore_ascii_case(flag_str) - || (raw.len() > flag_str.len() - && raw[..flag_str.len()].eq_ignore_ascii_case(flag_str) - && raw.as_bytes()[flag_str.len()] == sep as u8) - } - } - - /// Check whether the argument contains its value inline via the style's - /// value separator (eq mode), so no extra mask slot is needed. - #[inline(always)] - fn is_inline_value(raw: &str, flag_str: &str, sep: char) -> bool { - raw.len() > flag_str.len() && raw.as_bytes().get(flag_str.len()) == Some(&(sep as u8)) - } -} - -impl Matcher for ArgMatcher { - fn on_match_one( - args: &[MaskedArg], - style: &ParserStyle, - arg_info: &PickerArgInfo, - ) -> Option<usize> { - if arg_info.positional { - return args.first().map(|a| a.raw_idx); - } - - let possible_flags = build_possible_flags(style, arg_info); - let end = seek_end_of_options(args, style); - let sep = style.value_separator; - - for arg in args { - if end.is_some_and(|e| arg.raw_idx >= e) { - break; - } - - let matched = possible_flags - .iter() - .any(|f| Self::matches(arg.raw, f, style.case_sensitive, sep)); - if matched { - return Some(arg.raw_idx); - } - } - - None - } - - fn on_match_all( - args: &[MaskedArg], - style: &ParserStyle, - arg_info: &PickerArgInfo, - ) -> Vec<usize> { - if arg_info.positional { - let end = seek_end_of_options(args, style); - return args - .iter() - .take_while(|a| end.is_none_or(|e| a.raw_idx < e)) - .map(|a| a.raw_idx) - .collect(); - } - - let possible_flags = build_possible_flags(style, arg_info); - let end = seek_end_of_options(args, style); - let sep = style.value_separator; - - let mut result = Vec::new(); - let mut i = 0; - while i < args.len() { - if end.is_some_and(|e| args[i].raw_idx >= e) { - break; - } - - let matched = possible_flags - .iter() - .any(|f| Self::matches(args[i].raw, f, style.case_sensitive, sep)); - - if matched { - let flag_str = possible_flags - .iter() - .find(|f| Self::matches(args[i].raw, f, style.case_sensitive, sep)) - .expect("already matched"); - - result.push(args[i].raw_idx); - - if !Self::is_inline_value(args[i].raw, flag_str, sep) { - if i + 1 < args.len() - // Don't consume `--` (end-of-options marker) as a value. - && end.is_none_or(|e| args[i + 1].raw_idx < e) - { - result.push(args[i + 1].raw_idx); - i += 2; - continue; - } - i += 1; - continue; - } - i += 1; - continue; - } - i += 1; - } - - result - } -} diff --git a/mingling_picker/src/parselib/flag_matcher.rs b/mingling_picker/src/parselib/flag_matcher.rs deleted file mode 100644 index e93d35a..0000000 --- a/mingling_picker/src/parselib/flag_matcher.rs +++ /dev/null @@ -1,82 +0,0 @@ -use crate::{ - matcher_needed::*, - parselib::{build_possible_flags, get_seeked_first, multi_seek_eq, seek_end_of_options}, -}; - -/// `FlagMatcher` is used to match flags in command-line arguments. -/// -/// Flags typically start with `-` or `--` (e.g., `-h`, `--help`), -/// and do not carry additional values. This matcher is responsible for finding -/// these flags in the argument list, taking into account that flags after `--` -/// (end-of-options marker) should not be matched. -pub struct FlagMatcher; - -impl Matcher for FlagMatcher { - fn on_match_one( - args: &[MaskedArg], - style: &ParserStyle, - arg_info: &PickerArgInfo, - ) -> Option<usize> { - let possible_flags = build_possible_flags(style, arg_info); - let flag_refs: Vec<&str> = possible_flags.iter().map(|s| s.as_str()).collect(); - let end_of_options = seek_end_of_options(args, style); - - let result = get_seeked_first(multi_seek_eq(args, &flag_refs, style.case_sensitive)); - - match (end_of_options, result) { - (Some(end), Some(current)) if current > end => None, - _ => result, - } - } - - fn on_match_all( - args: &[MaskedArg], - style: &ParserStyle, - arg_info: &PickerArgInfo, - ) -> Vec<usize> { - let possible_flags = build_possible_flags(style, arg_info); - single_pass_match_all(args, style, &possible_flags) - } -} - -/// Single-pass match: finds the `--` marker and matching flags in one iteration. -fn single_pass_match_all( - args: &[MaskedArg], - style: &ParserStyle, - possible_flags: &[String], -) -> Vec<usize> { - let flag_refs: Vec<&str> = possible_flags.iter().map(|s| s.as_str()).collect(); - let eoo = style.end_of_options; - let case_sensitive = style.case_sensitive; - - let mut end_pos: Option<usize> = None; - let mut matches: Vec<usize> = Vec::new(); - - for arg in args { - if end_pos.is_none() { - let is_eoo = if case_sensitive { - arg.raw == eoo - } else { - arg.raw.eq_ignore_ascii_case(eoo) - }; - if is_eoo { - end_pos = Some(arg.raw_idx); - continue; - } - } - - // Only match flags before the end-of-options marker. - if end_pos.is_none() { - let matched = if case_sensitive { - flag_refs.contains(&arg.raw) - } else { - flag_refs.iter().any(|s| arg.raw.eq_ignore_ascii_case(s)) - }; - if matched { - matches.push(arg.raw_idx); - } - } - } - - matches -} diff --git a/mingling_picker/src/parselib/multi_arg_matcher.rs b/mingling_picker/src/parselib/multi_arg_matcher.rs deleted file mode 100644 index 748b1be..0000000 --- a/mingling_picker/src/parselib/multi_arg_matcher.rs +++ /dev/null @@ -1,141 +0,0 @@ -use crate::{ - matcher_needed::*, - parselib::{build_possible_flags, seek_end_of_options}, -}; - -/// `MultiArgMatcher` matches a named flag and **all** consecutive arguments -/// that follow it, stopping at the next flag, the `--` marker, or the end -/// of the argument list. -/// -/// This is the tag implementation for `Multi` and `GreedyMulti` types -/// such as `Vec<String>` (`--files a.txt b.txt`). -/// -/// # Behavior -/// -/// | Input | `on_match_all` | -/// |-------|----------------| -/// | `--val a b --val d e` | `[0, 1, 2, 5, 6]` (two groups) | -/// | `--val=1 2` | `[0, 1]` (eq mode + one extra value) | -/// -/// Args after `--` are ignored. -pub struct MultiArgMatcher; - -impl Matcher for MultiArgMatcher { - fn on_match_one( - args: &[MaskedArg], - style: &ParserStyle, - arg_info: &PickerArgInfo, - ) -> Option<usize> { - if arg_info.positional { - return args.first().map(|a| a.raw_idx); - } - - let possible_flags = build_possible_flags(style, arg_info); - let end = seek_end_of_options(args, style); - let sep = style.value_separator; - - for arg in args { - if end.is_some_and(|e| arg.raw_idx >= e) { - break; - } - let matched = possible_flags - .iter() - .any(|f| Self::flag_match(arg.raw, f, style.case_sensitive, sep)); - if matched { - return Some(arg.raw_idx); - } - } - None - } - - fn on_match_all( - args: &[MaskedArg], - style: &ParserStyle, - arg_info: &PickerArgInfo, - ) -> Vec<usize> { - if arg_info.positional { - let end = seek_end_of_options(args, style); - return args - .iter() - .take_while(|a| end.is_none_or(|e| a.raw_idx < e)) - .map(|a| a.raw_idx) - .collect(); - } - - let possible_flags = build_possible_flags(style, arg_info); - let end = seek_end_of_options(args, style); - let sep = style.value_separator; - let is_flag = - |raw: &str| raw.starts_with(style.long_prefix) || raw.starts_with(style.short_prefix); - let is_our_flag = |raw: &str| { - possible_flags - .iter() - .any(|f| Self::flag_match(raw, f, style.case_sensitive, sep)) - }; - - let mut result = Vec::new(); - let mut i = 0; - while i < args.len() { - if end.is_some_and(|e| args[i].raw_idx >= e) { - break; - } - - let matched = is_our_flag(args[i].raw); - - if matched { - result.push(args[i].raw_idx); - - if Self::is_eq_match(args[i].raw, &possible_flags, style.case_sensitive, sep) { - i += 1; - while i < args.len() - && end.is_none_or(|e| args[i].raw_idx < e) - && !is_flag(args[i].raw) - { - result.push(args[i].raw_idx); - i += 1; - } - continue; - } - - i += 1; - while i < args.len() - && end.is_none_or(|e| args[i].raw_idx < e) - && !is_flag(args[i].raw) - { - result.push(args[i].raw_idx); - i += 1; - } - continue; - } - i += 1; - } - - result - } -} - -impl MultiArgMatcher { - #[inline(always)] - fn flag_match(raw: &str, flag_str: &str, case_sensitive: bool, sep: char) -> bool { - let eq = - |r: &str, f: &str| r.len() > f.len() && r.as_bytes().get(f.len()) == Some(&(sep as u8)); - - if case_sensitive { - raw == flag_str || (raw.starts_with(flag_str) && eq(raw, flag_str)) - } else { - raw.eq_ignore_ascii_case(flag_str) - || (raw.len() > flag_str.len() - && raw[..flag_str.len()].eq_ignore_ascii_case(flag_str) - && raw.as_bytes()[flag_str.len()] == sep as u8) - } - } - - #[inline(always)] - fn is_eq_match(raw: &str, flags: &[String], case_sensitive: bool, sep: char) -> bool { - flags.iter().any(|f| { - Self::flag_match(raw, f, case_sensitive, sep) - && raw.len() > f.len() - && raw.as_bytes().get(f.len()) == Some(&(sep as u8)) - }) - } -} diff --git a/mingling_picker/src/parselib/pos_matcher.rs b/mingling_picker/src/parselib/pos_matcher.rs deleted file mode 100644 index 279e01e..0000000 --- a/mingling_picker/src/parselib/pos_matcher.rs +++ /dev/null @@ -1,71 +0,0 @@ -use crate::{matcher_needed::*, parselib::seek_end_of_options}; - -/// `PositionalMatcher` matches positional arguments — values not associated -/// with any named flag. -/// -/// # Rules -/// -/// * Before `--`: skips any argument that starts with the style's long or short -/// prefix (those belong to named matchers). -/// * After `--`: takes **everything** — the `--` marker signals that all -/// remaining values are positional, even if they look like flags. -/// * Runs at the lowest priority (see [`PickerArgAttr::Positional`](crate::PickerArgAttr::Positional)). -pub struct PositionalMatcher; - -impl PositionalMatcher { - /// Check whether `raw` looks like a named flag (starts with a prefix). - #[inline(always)] - fn is_flag_like(raw: &str, style: &ParserStyle) -> bool { - raw.starts_with(style.long_prefix) || raw.starts_with(style.short_prefix) - } -} - -impl Matcher for PositionalMatcher { - fn on_match_one( - args: &[MaskedArg], - style: &ParserStyle, - _arg_info: &PickerArgInfo, - ) -> Option<usize> { - let end = seek_end_of_options(args, style); - - for arg in args { - if end.is_some_and(|e| arg.raw_idx == e) { - // Hit `--`: everything from here on is positional, - // including the first arg after `--`. - continue; - } - if end.is_some_and(|e| arg.raw_idx > e) { - // After `--`: accept everything. - return Some(arg.raw_idx); - } - // Before `--`: skip flag-like args. - if !Self::is_flag_like(arg.raw, style) { - return Some(arg.raw_idx); - } - } - - None - } - - fn on_match_all( - args: &[MaskedArg], - style: &ParserStyle, - _arg_info: &PickerArgInfo, - ) -> Vec<usize> { - let end = seek_end_of_options(args, style); - let mut after_end = false; - let mut result = Vec::new(); - - for arg in args { - if end.is_some_and(|e| arg.raw_idx == e) { - after_end = true; - continue; - } - if after_end || !Self::is_flag_like(arg.raw, style) { - result.push(arg.raw_idx); - } - } - - result - } -} diff --git a/mingling_picker/src/parselib/single_matcher.rs b/mingling_picker/src/parselib/single_matcher.rs deleted file mode 100644 index 25c4741..0000000 --- a/mingling_picker/src/parselib/single_matcher.rs +++ /dev/null @@ -1,59 +0,0 @@ -use crate::TagPhaseContext; -use crate::parselib::{ArgMatcher, Matcher, ParserStyle, PositionalMatcher}; - -/// `SingleMatcher` is a composite matcher for single-value parameters. -/// -/// It delegates to [`PositionalMatcher`] for positional args and -/// [`ArgMatcher`] for named args, adding a guard: if a named flag -/// captures only itself with no inline value (eq mode), the result -/// is cleared so that [`Pickable::pick`](crate::Pickable::pick) receives `[]` → `NotFound`. -/// -/// This is the standard tag implementation for all `Single`-type -/// `Pickable` implementations (e.g., `String`, `i32`, `u64`). -pub struct SingleMatcher; - -impl SingleMatcher { - /// Match a single positional value or a named flag+value pair. - /// - /// For named args, only complete pairs (flag + value) are kept. - /// Flag occurrences without a following value or inline separator - /// are dropped so they remain available for other matchers. - #[inline(always)] - pub fn tag(ctx: TagPhaseContext) -> Vec<usize> { - if ctx.arg_info.positional { - PositionalMatcher::match_one(ctx.into()) - .map(|i| vec![i]) - .unwrap_or_default() - } else { - let args = ctx.args; - let positions = ArgMatcher::match_all(ctx.into()); - let sep = ParserStyle::global_style().value_separator; - - // Walk pairs: [flag, value, flag, value, ...] - // Drop any flag that has no following value and no inline separator. - let mut i = 0; - let mut result = Vec::with_capacity(positions.len()); - while i < positions.len() { - let flag_idx = positions[i]; - if let Some(raw) = args.get(flag_idx) - && raw.contains(sep) - { - // Eq mode: value is inline, keep just the flag. - result.push(flag_idx); - i += 1; - continue; - } - if i + 1 < positions.len() { - // Pair: flag + value. - result.push(flag_idx); - result.push(positions[i + 1]); - i += 2; - } else { - // Flag without value — drop it. - i += 1; - } - } - result - } - } -} diff --git a/mingling_picker/src/parselib/style.rs b/mingling_picker/src/parselib/style.rs deleted file mode 100644 index dd01125..0000000 --- a/mingling_picker/src/parselib/style.rs +++ /dev/null @@ -1,258 +0,0 @@ -use std::sync::OnceLock; -use std::sync::atomic::{AtomicBool, Ordering}; - -use crate::parselib::ParserStyleNamingCase::{Kebab, Pascal}; - -/// Defines the style of command-line argument parsing (prefixes, separators, etc.). -#[derive(Clone, Copy, PartialEq, Eq)] -pub struct ParserStyle<'a> { - /// End-of-options marker (e.g., `--`) - pub end_of_options: &'a str, - - /// Prefix for long options (e.g., `--` or `/`) - pub long_prefix: &'a str, - - /// Prefix for short options (e.g., `-` or `/`) - pub short_prefix: &'a str, - - /// Prefix for combined short flags (e.g., `-abc`) - pub combine_prefix: &'a str, - - /// Separator between name and value (e.g., `=` or `:`) - pub value_separator: char, - - /// Whether option names are case-sensitive - pub case_sensitive: bool, - - /// Whether combining short flags is allowed (e.g., `-abc` for `-a -b -c`) - pub allow_combine: bool, - - /// Naming case - pub naming_case: ParserStyleNamingCase, -} - -impl<'a> ParserStyle<'a> { - /// Formats a flag (short or long) into a full command-line option string. - /// - /// This method takes any type that can be converted into a `FlagStr` and produces - /// a complete option string by prepending the appropriate prefix. - /// - /// # Examples - /// - /// ``` - /// # use mingling_picker::parselib::{ParserStyle, FlagStr, UNIX_STYLE}; - /// let style = &UNIX_STYLE; - /// - /// assert_eq!(style.flag_string('v'), "-v"); - /// assert_eq!(style.flag_string("verbose"), "--verbose"); - /// ``` - /// - /// # Parameters - /// - /// * `flag` - A value that can be converted to `FlagStr`, either a `char` for short flags - /// or a `&str` for long flags. - /// - /// # Returns - /// - /// A `String` with the prefix and the flag name combined. - #[must_use] - #[inline(always)] - pub fn flag_string<F>(&self, flag: F) -> String - where - F: Into<FlagStr<'a>>, - { - match flag.into() { - FlagStr::Short(short) => format!("{}{}", self.short_prefix, short), - FlagStr::Long(long) => format!("{}{}", self.long_prefix, long), - } - } -} - -/// Represents a flag name for command-line argument parsing. -/// -/// This enum can hold either a short flag (a single character, e.g., `'v'` for `-v`) -/// or a long flag (a string, e.g., `"verbose"` for `--verbose`). -/// -/// # Examples -/// -/// ``` -/// use mingling_picker::parselib::FlagStr; -/// -/// let short: FlagStr = 'v'.into(); -/// let long: FlagStr = "verbose".into(); -/// ``` -pub enum FlagStr<'a> { - /// A short flag represented by a single character. - Short(char), - /// A long flag represented by a string slice. - Long(&'a str), -} - -impl<'a> From<char> for FlagStr<'a> { - /// Converts a single character into a `FlagStr::Short`. - fn from(c: char) -> Self { - FlagStr::Short(c) - } -} - -impl<'a> From<&'a str> for FlagStr<'a> { - /// Converts a string slice into a `FlagStr::Long`. - fn from(s: &'a str) -> Self { - FlagStr::Long(s) - } -} - -impl<'a> From<&'a String> for FlagStr<'a> { - /// Converts a reference to a `String` into a `FlagStr::Long`. - fn from(s: &'a String) -> Self { - FlagStr::Long(s.as_str()) - } -} - -/// Defines the naming convention for command-line option names. -/// -/// Each variant represents a different case format that can be applied -/// to option names (e.g., long option names) during parsing or generation. -/// -/// # Examples -/// -/// ``` -/// # use mingling_picker::IntoPicker; -/// use mingling_picker::parselib::ParserStyleNamingCase; -/// -/// let case = ParserStyleNamingCase::Kebab; -/// assert_eq!( -/// case.convert("brew_coffee".to_string()), -/// "brew-coffee".to_string() -/// ); -/// ``` -#[repr(u8)] -#[derive(Default, Clone, Copy, PartialEq, Eq)] -pub enum ParserStyleNamingCase { - /// snake_case format: words are separated by underscores, all lowercase. - /// - /// Example: `brew_coffee` - #[default] - Snake, - /// camelCase format: first word is lowercase, subsequent words are capitalized. - /// - /// Example: `brewCoffee` - Camel, - /// PascalCase format: every word starts with an uppercase letter. - /// - /// Example: `BrewCoffee` - Pascal, - /// kebab-case format: words are separated by hyphens, all lowercase. - /// - /// Example: `brew-coffee` - Kebab, - /// dot.case format: words are separated by dots, all lowercase. - /// - /// Example: `brew.coffee` - Dot, - /// Title Case format: words are separated by spaces, each word capitalized. - /// - /// Example: `Brew Coffee` - Title, - /// lower case format: words are separated by spaces, all lowercase. - /// - /// Example: `brew coffee` - Lower, - /// UPPER CASE format: words are separated by spaces, all uppercase. - /// - /// Example: `BREW COFFEE` - Upper, -} - -impl ParserStyleNamingCase { - /// Converts the input string `s` to the naming case represented by this variant. - /// - /// This method takes any type `S` that can be converted into a `String` and - /// produced from a `String`, applies the corresponding case transformation, - /// and returns the result. - /// - /// # Examples - /// - /// ``` - /// use mingling_picker::parselib::ParserStyleNamingCase; - /// - /// let camel = ParserStyleNamingCase::Camel; - /// assert_eq!(camel.convert("brew_coffee".to_string()), "brewCoffee"); - /// - /// let kebab = ParserStyleNamingCase::Kebab; - /// assert_eq!(kebab.convert("BrewCoffee".to_string()), "brew-coffee"); - /// ``` - pub fn convert<S>(&self, s: S) -> S - where - S: Into<String> + From<String>, - { - match self { - ParserStyleNamingCase::Camel => just_fmt::camel_case!(s.into()).into(), - ParserStyleNamingCase::Pascal => just_fmt::pascal_case!(s.into()).into(), - ParserStyleNamingCase::Kebab => just_fmt::kebab_case!(s.into()).into(), - ParserStyleNamingCase::Snake => just_fmt::snake_case!(s.into()).into(), - ParserStyleNamingCase::Dot => just_fmt::dot_case!(s.into()).into(), - ParserStyleNamingCase::Title => just_fmt::title_case!(s.into()).into(), - ParserStyleNamingCase::Lower => just_fmt::lower_case!(s.into()).into(), - ParserStyleNamingCase::Upper => just_fmt::upper_case!(s.into()).into(), - } - } -} - -/// Unix-like style (e.g., `--verbose`, `-v`, `--name=value`) -pub const UNIX_STYLE: ParserStyle = ParserStyle { - end_of_options: "--", - long_prefix: "--", - short_prefix: "-", - combine_prefix: "-", - value_separator: '=', - case_sensitive: true, - allow_combine: true, - naming_case: Kebab, -}; - -/// PowerShell style (e.g., `-Verbose`, `-Name:value`) -pub const POWERSHELL_STYLE: ParserStyle = ParserStyle { - end_of_options: "--", - long_prefix: "-", - short_prefix: "-", - combine_prefix: "-", - value_separator: ':', - case_sensitive: false, - allow_combine: false, - naming_case: Pascal, -}; - -/// Windows-style command-line (e.g., `/Verbose`, `/Name:value`) -pub const WINDOWS_STYLE: ParserStyle = ParserStyle { - end_of_options: "--", - long_prefix: "/", - short_prefix: "/", - combine_prefix: "/", - value_separator: ':', - case_sensitive: false, - allow_combine: false, - naming_case: Pascal, -}; - -static GLOBAL_STYLE: OnceLock<ParserStyle<'static>> = OnceLock::new(); -static GLOBAL_STYLE_SET: AtomicBool = AtomicBool::new(false); - -impl<'a> ParserStyle<'a> { - /// Sets the global parser style. - /// - /// This function can only be called once. Subsequent calls will have no effect. - /// The style is stored as a static reference; the provided style must be a static - /// constant (e.g., `&'static ParserStyle`). Use the built-in constants like - /// `UNIX_STYLE`, `POWERSHELL_STYLE`, or `WINDOWS_STYLE`. - pub fn set_global_style(style: &'static ParserStyle<'static>) { - if !GLOBAL_STYLE_SET.load(Ordering::Acquire) && GLOBAL_STYLE.set(*style).is_ok() { - GLOBAL_STYLE_SET.store(true, Ordering::Release); - } - } - - /// Returns the global parser style, falling back to `UNIX_STYLE` if not set. - pub fn global_style() -> &'static ParserStyle<'static> { - GLOBAL_STYLE.get().unwrap_or(&UNIX_STYLE) - } -} diff --git a/mingling_picker/src/parselib/utils.rs b/mingling_picker/src/parselib/utils.rs deleted file mode 100644 index 47c5b55..0000000 --- a/mingling_picker/src/parselib/utils.rs +++ /dev/null @@ -1,276 +0,0 @@ -use crate::{ - PickerArgInfo, - parselib::{MaskedArg, ParserStyle}, -}; - -/// Builds a list of possible flag strings for the given argument info -/// -/// This function generates formatted flag strings (e.g., `-h`, `--help`) from the short flag, -/// long flag, and any aliases defined in the argument info. The long flag and alias names -/// are converted according to the style's naming case convention before being formatted. -#[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() -} |
