aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-07-15 19:31:23 +0800
committer魏曹先生 <1992414357@qq.com>2026-07-15 19:33:45 +0800
commit8b2bbdef88f47838ed0326f5c9cf86bac84babde (patch)
treecab7e7d492e4778a8084919459e0e8f40cc0827f
parent5894aa72a8e63f8b6d095678fe58e88fd64836a1 (diff)
feat(picker): add builtin bool pickable and parselib matcher framework
Add `Pickable` implementation for `bool` and the supporting `Matcher` trait with utility functions for argument matching. Expose `pickable_needed` and `matcher_needed` modules for downstream trait implementors.
-rw-r--r--Cargo.lock1
-rw-r--r--mingling_picker/Cargo.toml1
-rw-r--r--mingling_picker/src/builtin.rs1
-rw-r--r--mingling_picker/src/builtin/pick_bool.rs15
-rw-r--r--mingling_picker/src/infos.rs5
-rw-r--r--mingling_picker/src/lib.rs13
-rw-r--r--mingling_picker/src/parselib.rs127
-rw-r--r--mingling_picker/src/parselib/bool_matcher.rs45
-rw-r--r--mingling_picker/src/parselib/style.rs143
-rw-r--r--mingling_picker/src/parselib/utils.rs235
-rw-r--r--mingling_picker/src/picker.rs18
11 files changed, 595 insertions, 9 deletions
diff --git a/Cargo.lock b/Cargo.lock
index f160c1d..93f6efb 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -465,6 +465,7 @@ dependencies = [
name = "mingling_picker"
version = "0.3.0"
dependencies = [
+ "just_fmt",
"mingling_core",
"mingling_picker_macros",
]
diff --git a/mingling_picker/Cargo.toml b/mingling_picker/Cargo.toml
index 4cbab39..ddab33c 100644
--- a/mingling_picker/Cargo.toml
+++ b/mingling_picker/Cargo.toml
@@ -14,3 +14,4 @@ mingling_support = ["dep:mingling_core", "mingling_picker_macros/mingling_suppor
[dependencies]
mingling_core = { workspace = true, optional = true }
mingling_picker_macros.workspace = true
+just_fmt.workspace = true
diff --git a/mingling_picker/src/builtin.rs b/mingling_picker/src/builtin.rs
new file mode 100644
index 0000000..d6c4085
--- /dev/null
+++ b/mingling_picker/src/builtin.rs
@@ -0,0 +1 @@
+mod pick_bool;
diff --git a/mingling_picker/src/builtin/pick_bool.rs b/mingling_picker/src/builtin/pick_bool.rs
new file mode 100644
index 0000000..ad93a83
--- /dev/null
+++ b/mingling_picker/src/builtin/pick_bool.rs
@@ -0,0 +1,15 @@
+use crate::{parselib::Matcher, pickable_needed::*};
+
+impl<'a> Pickable<'a> for bool {
+ fn get_attr(_: &'a PickerFlag<'a, Self>) -> PickerFlagAttr {
+ PickerFlagAttr::Flag
+ }
+
+ fn tag(ctx: TagPhaseContext) -> Vec<usize> {
+ <bool as Matcher>::match_all(ctx.into())
+ }
+
+ fn pick(raw_strs: &[&str]) -> PickerArgResult<Self> {
+ PickerArgResult::Parsed(!raw_strs.is_empty())
+ }
+}
diff --git a/mingling_picker/src/infos.rs b/mingling_picker/src/infos.rs
index ff78b0a..c0309e0 100644
--- a/mingling_picker/src/infos.rs
+++ b/mingling_picker/src/infos.rs
@@ -261,6 +261,11 @@ impl<Type> PickerArgResult<Type> {
}
}
+/// Represents metadata about a command-line argument or flag.
+///
+/// This struct stores all relevant information about a tag/argument that can be used
+/// for parsing command-line inputs. It includes the short form (e.g., `-n`), long form
+/// (e.g., `--name`), aliases, and various flags that control parsing behavior.
pub struct PickerArgInfo<'a> {
/// The short form of the tag, e.g. `'n'` for `-n`.
pub short: Option<char>,
diff --git a/mingling_picker/src/lib.rs b/mingling_picker/src/lib.rs
index bee2c50..7e48db7 100644
--- a/mingling_picker/src/lib.rs
+++ b/mingling_picker/src/lib.rs
@@ -1,3 +1,5 @@
+mod builtin;
+
mod picker;
pub use picker::*;
@@ -20,6 +22,17 @@ pub mod macros {
pub use mingling_picker_macros::*;
}
+/// Provides the types necessary for implementing the `Pickable` trait
+pub mod pickable_needed {
+ pub use crate::{Pickable, PickerArgResult, PickerFlag, PickerFlagAttr, TagPhaseContext};
+}
+
+/// Provides the types necessary for implementing the `Matcher` trait
+pub mod matcher_needed {
+ pub use crate::PickerArgInfo;
+ pub use crate::parselib::{MaskedArg, Matcher, ParserStyle};
+}
+
#[cfg(feature = "mingling_support")]
mod corebind;
diff --git a/mingling_picker/src/parselib.rs b/mingling_picker/src/parselib.rs
index 132045b..74c53d0 100644
--- a/mingling_picker/src/parselib.rs
+++ b/mingling_picker/src/parselib.rs
@@ -1,2 +1,129 @@
+mod bool_matcher;
+
mod style;
pub use style::*;
+
+mod utils;
+pub use utils::*;
+
+use crate::{PickerArgInfo, PickerArgs};
+
+/// Represents a single argument with its original raw string and index.
+///
+/// This is used during pattern matching to provide context about
+/// which argument is being processed.
+#[derive(Clone, Copy, PartialEq, Eq)]
+pub struct MaskedArg<'a> {
+ /// The raw string value of the argument.
+ pub raw: &'a str,
+ /// The original index of the argument in the full argument list.
+ pub raw_idx: usize,
+}
+
+/// Trait for defining matching logic against masked arguments.
+///
+/// Implementors can define custom strategies for matching one or all
+/// arguments that pass through a mask filter.
+pub trait Matcher {
+ /// Called when only one match is needed.
+ ///
+ /// Returns the index of the first matched argument, or `None` if no match.
+ fn on_match_one(
+ args: &[MaskedArg],
+ style: &ParserStyle,
+ arg_info: &PickerArgInfo,
+ ) -> Option<usize>;
+
+ /// Called when all matches are needed.
+ ///
+ /// Returns a vector of indices of all matched arguments.
+ fn on_match_all(
+ args: &[MaskedArg],
+ style: &ParserStyle,
+ arg_info: &PickerArgInfo,
+ ) -> Vec<usize>;
+
+ /// Convenience method that builds masked arguments from `PickerArgs` and a mask,
+ /// then calls `on_match_one`.
+ fn match_one<'a>(ctx: MatcherContext<'a>) -> Option<usize> {
+ let masked_args = build_masked_args(ctx.args, ctx.mask);
+ Self::on_match_one(masked_args.as_slice(), ctx.style, ctx.arg_info)
+ }
+
+ /// Convenience method that builds masked arguments from `PickerArgs` and a mask,
+ /// then calls `on_match_all`.
+ fn match_all<'a>(ctx: MatcherContext<'a>) -> Vec<usize> {
+ let masked_args = build_masked_args(ctx.args, ctx.mask);
+ Self::on_match_all(masked_args.as_slice(), ctx.style, ctx.arg_info)
+ }
+}
+
+/// Context for matcher operations
+///
+/// This struct bundles together the key pieces of data needed during matching:
+/// - `args`: The full set of parsed arguments.
+/// - `mask`: A byte mask indicating which arguments are currently active (non-zero = active).
+/// - `style`: The parsing style configuration.
+pub struct MatcherContext<'a> {
+ /// The full set of parsed arguments.
+ pub args: &'a PickerArgs<'a>,
+
+ /// A byte mask where non-zero values indicate the argument at that position is active/should be matched.
+ pub mask: &'a [u8],
+
+ /// The parsing style configuration.
+ pub style: &'a ParserStyle<'a>,
+
+ /// Metadata about the command-line argument/flag being processed.
+ ///
+ /// Contains information such as short form (`-n`), long form (`--name`),
+ /// aliases, and parsing flags (positional, optional, multi, is_flag).
+ /// Used by matchers to make decisions based on argument characteristics.
+ pub arg_info: &'a PickerArgInfo<'a>,
+}
+
+impl<'a> From<&'a crate::TagPhaseContext<'a>> for MatcherContext<'a> {
+ fn from(ctx: &'a crate::TagPhaseContext<'a>) -> Self {
+ MatcherContext {
+ args: ctx.args,
+ mask: ctx.mask,
+ style: ParserStyle::global_style(),
+ arg_info: ctx.arg_info,
+ }
+ }
+}
+
+impl<'a> From<crate::TagPhaseContext<'a>> for MatcherContext<'a> {
+ fn from(ctx: crate::TagPhaseContext<'a>) -> Self {
+ MatcherContext {
+ args: ctx.args,
+ mask: ctx.mask,
+ style: ParserStyle::global_style(),
+ arg_info: ctx.arg_info,
+ }
+ }
+}
+
+#[inline(always)]
+fn is_masked(mask: &[u8], idx: usize) -> bool {
+ idx < mask.len() && mask[idx] != 0
+}
+
+#[inline(always)]
+fn build_masked_args<'a>(args: &'a PickerArgs, mask: &'a [u8]) -> Vec<MaskedArg<'a>> {
+ let mut cidx = 0;
+ args.iter()
+ .filter_map(|r| {
+ let idx = cidx;
+ cidx += 1;
+ if is_masked(mask, cidx) {
+ Some(MaskedArg {
+ raw: r,
+ raw_idx: idx,
+ })
+ } else {
+ None
+ }
+ })
+ .collect()
+}
diff --git a/mingling_picker/src/parselib/bool_matcher.rs b/mingling_picker/src/parselib/bool_matcher.rs
new file mode 100644
index 0000000..cbcb111
--- /dev/null
+++ b/mingling_picker/src/parselib/bool_matcher.rs
@@ -0,0 +1,45 @@
+use crate::{
+ matcher_needed::*,
+ parselib::{build_possible_flags, get_seeked_first, multi_seek_eq, seek_end_of_options},
+ vec_string_slice,
+};
+
+impl Matcher for bool {
+ fn on_match_one(
+ args: &[MaskedArg],
+ style: &ParserStyle,
+ arg_info: &PickerArgInfo,
+ ) -> Option<usize> {
+ let possible_flags = build_possible_flags(style, arg_info);
+ let end_of_options = seek_end_of_options(args, style);
+ let result = get_seeked_first(multi_seek_eq(
+ args,
+ vec_string_slice!(possible_flags),
+ 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);
+ let end_of_options = seek_end_of_options(args, style);
+ let result = multi_seek_eq(
+ args,
+ vec_string_slice!(possible_flags),
+ style.case_sensitive,
+ );
+
+ match end_of_options {
+ Some(end) => result.into_iter().filter(|&idx| idx <= end).collect(),
+ None => result,
+ }
+ }
+}
diff --git a/mingling_picker/src/parselib/style.rs b/mingling_picker/src/parselib/style.rs
index 3ce12f7..a2a7393 100644
--- a/mingling_picker/src/parselib/style.rs
+++ b/mingling_picker/src/parselib/style.rs
@@ -1,6 +1,8 @@
use std::sync::OnceLock;
use std::sync::atomic::{AtomicBool, Ordering};
+use crate::parselib::ParserStyleNamingCase::{Pascal, Snake};
+
/// Defines the style of command-line argument parsing (prefixes, separators, etc.).
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct ParserStyle<'a> {
@@ -24,6 +26,144 @@ pub struct ParserStyle<'a> {
/// 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
+ ///
+ /// ```ignore
+ /// 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())
+ }
+}
+
+#[repr(u8)]
+#[derive(Default, Clone, Copy, PartialEq, Eq)]
+pub enum ParserStyleNamingCase {
+ /// snake_case format (e.g., `brew_coffee`)
+ #[default]
+ Snake,
+ /// camelCase format (e.g., `brewCoffee`)
+ Camel,
+ /// PascalCase format (e.g., `BrewCoffee`)
+ Pascal,
+ /// kebab-case format (e.g., `brew-coffee`)
+ Kebab,
+ /// dot.case format (e.g., `brew.coffee`)
+ Dot,
+ /// Title Case format (e.g., `Brew Coffee`)
+ Title,
+ /// lower case format (e.g., `brew coffee`)
+ Lower,
+ /// UPPER CASE format (e.g., `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"), "brewCoffee");
+ ///
+ /// let kebab = ParserStyleNamingCase::Kebab;
+ /// assert_eq!(kebab.convert("BrewCoffee"), "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`)
@@ -35,6 +175,7 @@ pub const UNIX_STYLE: ParserStyle = ParserStyle {
value_separator: '=',
case_sensitive: true,
allow_combine: true,
+ naming_case: Snake,
};
/// PowerShell style (e.g., `-Verbose`, `-Name:value`)
@@ -46,6 +187,7 @@ pub const POWERSHELL_STYLE: ParserStyle = ParserStyle {
value_separator: ':',
case_sensitive: false,
allow_combine: false,
+ naming_case: Pascal,
};
/// Windows-style command-line (e.g., `/Verbose`, `/Name:value`)
@@ -57,6 +199,7 @@ pub const WINDOWS_STYLE: ParserStyle = ParserStyle {
value_separator: ':',
case_sensitive: false,
allow_combine: false,
+ naming_case: Pascal,
};
static GLOBAL_STYLE: OnceLock<ParserStyle<'static>> = OnceLock::new();
diff --git a/mingling_picker/src/parselib/utils.rs b/mingling_picker/src/parselib/utils.rs
new file mode 100644
index 0000000..eefbe2f
--- /dev/null
+++ b/mingling_picker/src/parselib/utils.rs
@@ -0,0 +1,235 @@
+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 {
+ possible_flags.push(style.flag_string(long));
+ }
+
+ if let Some(aliases) = &arg_info.alias {
+ for alias in aliases {
+ possible_flags.push(style.flag_string(*alias));
+ }
+ }
+
+ possible_flags
+}
+
+/// 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> {
+ get_seeked_first(seek_eq(args, style.end_of_options, style.case_sensitive))
+}
+
+/// 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]
+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()
+}
diff --git a/mingling_picker/src/picker.rs b/mingling_picker/src/picker.rs
index b1e8527..eea9611 100644
--- a/mingling_picker/src/picker.rs
+++ b/mingling_picker/src/picker.rs
@@ -59,7 +59,7 @@ impl<'a> PickerArgs<'a> {
self.len() == 0
}
- /// Returns an iterator over the arguments, yielding owned `String` values.
+ /// Returns an iterator over the arguments, yielding `&str` values.
pub fn iter(&'a self) -> PickerIter<'a> {
match self {
PickerArgs::Slice(items) => PickerIter::Slice(items.iter()),
@@ -91,7 +91,7 @@ impl<'a> Index<usize> for PickerArgs<'a> {
}
impl<'a> IntoIterator for &'a PickerArgs<'a> {
- type Item = String;
+ type Item = &'a str;
type IntoIter = PickerIter<'a>;
fn into_iter(self) -> Self::IntoIter {
@@ -156,7 +156,7 @@ impl<'a, Route> Picker<'a, Route> {
self.args.is_empty()
}
- /// Returns an iterator over the arguments, yielding owned `String` values.
+ /// Returns an iterator over the arguments, yielding `&str` values.
pub fn iter(&'a self) -> PickerIter<'a> {
self.args.iter()
}
@@ -179,7 +179,7 @@ impl<'a, Route> Index<usize> for &Picker<'a, Route> {
}
impl<'a, Route> IntoIterator for &'a Picker<'a, Route> {
- type Item = String;
+ type Item = &'a str;
type IntoIter = PickerIter<'a>;
fn into_iter(self) -> Self::IntoIter {
@@ -187,7 +187,7 @@ impl<'a, Route> IntoIterator for &'a Picker<'a, Route> {
}
}
-/// Iterator for `Picker` (and `PickerArgs`), yielding owned `String` values.
+/// Iterator for `Picker` (and `PickerArgs`), yielding `&'a str` values.
pub enum PickerIter<'a> {
/// Iterates over a borrowed slice (`&[&str]`)
Slice(std::slice::Iter<'a, &'a str>),
@@ -198,13 +198,13 @@ pub enum PickerIter<'a> {
}
impl<'a> Iterator for PickerIter<'a> {
- type Item = String;
+ type Item = &'a str;
fn next(&mut self) -> Option<Self::Item> {
match self {
- PickerIter::Slice(iter) => iter.next().map(|s| s.to_string()),
- PickerIter::Vec(iter) => iter.next().map(|s| s.to_string()),
- PickerIter::Owned(iter) => iter.next().cloned(),
+ PickerIter::Slice(iter) => iter.next().copied(),
+ PickerIter::Vec(iter) => iter.next().copied(),
+ PickerIter::Owned(iter) => iter.next().map(|s| s.as_str()),
}
}