aboutsummaryrefslogtreecommitdiff
path: root/mingling_picker/src
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-07-14 01:36:27 +0800
committer魏曹先生 <1992414357@qq.com>2026-07-14 01:36:27 +0800
commit4665fc94ab9508d115298dd988e2381354f46c01 (patch)
treea2b14f5684c68d88a0a77570d0ac3ac8d863867b /mingling_picker/src
parent5ac0f0e98d05e95f7e3c58995f8d79670cbac772 (diff)
feat(picker): add core trait, types, and builder macro for argument
parsing Implement the foundational `mingling_picker` library along with its companion `mingling_picker_macros` crate. The picker provides: - `Pickable` trait for parsing types from raw strings - `PickerResult` enum modeling parse outcomes - `Picker` struct for storing and indexing command-line arguments - `PickerRequirement` struct for declarative parameter definitions - `PickerPattern` family (1..32) of typed pattern structs via the `internal_repeat` proc macro - `req!` proc macro as a succinct builder for `PickerRequirement` Re-export `mingling_picker::macros::*` from the `mingling` crate when the `picker` feature is enabled, replacing the previous wildcard re-export of `mingling_macros`.
Diffstat (limited to 'mingling_picker/src')
-rw-r--r--mingling_picker/src/lib.rs20
-rw-r--r--mingling_picker/src/pickable.rs39
-rw-r--r--mingling_picker/src/pickable/implements.rs43
-rw-r--r--mingling_picker/src/picker.rs232
-rw-r--r--mingling_picker/src/picker/patterns.rs12
-rw-r--r--mingling_picker/src/requirement.rs150
-rw-r--r--mingling_picker/src/result.rs59
7 files changed, 554 insertions, 1 deletions
diff --git a/mingling_picker/src/lib.rs b/mingling_picker/src/lib.rs
index 3c3251d..97020a4 100644
--- a/mingling_picker/src/lib.rs
+++ b/mingling_picker/src/lib.rs
@@ -1 +1,19 @@
-pub mod prelude {}
+mod picker;
+pub use picker::*;
+
+mod pickable;
+pub use pickable::*;
+
+mod requirement;
+pub use requirement::*;
+
+mod result;
+pub use result::*;
+
+pub mod prelude {
+ pub use crate::IntoPicker;
+}
+
+pub mod macros {
+ pub use mingling_picker_macros::*;
+}
diff --git a/mingling_picker/src/pickable.rs b/mingling_picker/src/pickable.rs
new file mode 100644
index 0000000..9fe2c9d
--- /dev/null
+++ b/mingling_picker/src/pickable.rs
@@ -0,0 +1,39 @@
+use crate::PickerResult;
+
+mod implements;
+
+/// A trait for types that can be constructed from a raw string representation.
+///
+/// Implementing this trait allows a type to be "picked" or parsed from a string,
+/// enabling deserialization or configuration loading from textual input.
+///
+/// # Requirements
+///
+/// - The implementing type must be [`Sized`] and implement [`Default`].
+/// - The [`pick`] method performs the actual parsing and may fail.
+///
+/// # Errors
+///
+/// Returns a [`PickerResult`] which encapsulates either a successful parse
+/// or an error indicating why the input could not be parsed.
+///
+/// # Examples
+///
+/// ```
+/// # use mingling_picker::{Pickable, PickerResult};
+/// #[derive(Default)]
+/// struct MyType(String);
+///
+/// impl Pickable for MyType {
+/// fn pick(raw_str: &str) -> PickerResult<Self> {
+/// PickerResult::Parsed(MyType(raw_str.to_string()))
+/// }
+/// }
+/// ```
+pub trait Pickable
+where
+ Self: Sized + Default,
+{
+ /// Parses a `Self` value from the given raw string input.
+ fn pick(raw_str: &str) -> PickerResult<Self>;
+}
diff --git a/mingling_picker/src/pickable/implements.rs b/mingling_picker/src/pickable/implements.rs
new file mode 100644
index 0000000..34f9727
--- /dev/null
+++ b/mingling_picker/src/pickable/implements.rs
@@ -0,0 +1,43 @@
+use crate::{Pickable, PickerResult};
+use std::path::PathBuf;
+
+macro_rules! impl_pickable {
+ ($($t:ty),*) => {
+ $(
+ impl Pickable for $t {
+ fn pick(raw_str: &str) -> PickerResult<Self> {
+ raw_str.parse::<$t>().into()
+ }
+ }
+ )*
+ };
+}
+
+impl_pickable!(
+ i8, i16, i32, i64, i128, u8, u16, u32, u64, u128, usize, f32, f64, isize
+);
+
+impl Pickable for String {
+ fn pick(raw_str: &str) -> PickerResult<Self> {
+ PickerResult::Parsed(raw_str.to_string())
+ }
+}
+
+impl Pickable for PathBuf {
+ fn pick(raw_str: &str) -> PickerResult<Self> {
+ PickerResult::Parsed(PathBuf::from(raw_str))
+ }
+}
+
+impl<T: Pickable> Pickable for Option<T> {
+ fn pick(raw_str: &str) -> PickerResult<Self> {
+ if raw_str.is_empty() {
+ PickerResult::Parsed(None)
+ } else {
+ match T::pick(raw_str) {
+ PickerResult::Parsed(v) => PickerResult::Parsed(Some(v)),
+ _ => PickerResult::Parsed(None),
+ }
+ }
+ }
+}
diff --git a/mingling_picker/src/picker.rs b/mingling_picker/src/picker.rs
new file mode 100644
index 0000000..f8d3cb1
--- /dev/null
+++ b/mingling_picker/src/picker.rs
@@ -0,0 +1,232 @@
+use std::ops::Index;
+
+mod patterns;
+pub use patterns::*;
+
+/// Picker, used to record all states of a parameter parsing
+///
+/// Includes the following:
+///
+/// - Basic arguments
+/// - Parsing states
+/// - Parsing results
+pub struct Picker<'a> {
+ /// Internal arguments of Picker
+ args: PickerArguments<'a>,
+}
+
+/// Internal arguments of Picker
+///
+/// - `Slice` - borrowed slice of string slices
+/// - `Vec` - owned vector of borrowed string slices
+/// - `Owned` - owned vector of owned strings
+pub enum PickerArguments<'a> {
+ /// Borrowed slice of string slices
+ Slice(&'a [&'a str]),
+ /// Owned vector of borrowed string slices
+ Vec(Vec<&'a str>),
+ /// Owned vector of owned strings
+ Owned(Vec<String>),
+}
+
+impl<'a> From<&'a [&'a str]> for Picker<'a> {
+ fn from(value: &'a [&'a str]) -> Self {
+ Picker {
+ args: PickerArguments::Slice(value),
+ }
+ }
+}
+
+impl<'a> From<Vec<&'a str>> for Picker<'a> {
+ fn from(value: Vec<&'a str>) -> Self {
+ Picker {
+ args: PickerArguments::Vec(value),
+ }
+ }
+}
+
+impl<'a> From<Vec<String>> for Picker<'a> {
+ fn from(value: Vec<String>) -> Self {
+ Picker {
+ args: PickerArguments::Owned(value),
+ }
+ }
+}
+
+impl<'a> Picker<'a> {
+ /// Returns the number of arguments in the picker.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use mingling_picker::Picker;
+ ///
+ /// let args = Picker::from(&["hello", "world"][..]);
+ /// assert_eq!(args.len(), 2);
+ ///
+ /// let args = Picker::from(vec!["foo", "bar"]);
+ /// assert_eq!(args.len(), 2);
+ ///
+ /// let args = Picker::from(vec!["a".to_string(), "b".to_string()]);
+ /// assert_eq!(args.len(), 2);
+ ///
+ /// let empty: Picker = Picker::from(&[][..]);
+ /// assert_eq!(empty.len(), 0);
+ /// ```
+ pub fn len(&self) -> usize {
+ match &self.args {
+ PickerArguments::Slice(items) => items.len(),
+ PickerArguments::Vec(items) => items.len(),
+ PickerArguments::Owned(items) => items.len(),
+ }
+ }
+
+ /// Returns an iterator over the arguments, yielding owned `String` values.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use mingling_picker::Picker;
+ ///
+ /// let args = Picker::from(&["hello", "world"][..]);
+ /// let collected: Vec<String> = args.iter().collect();
+ /// assert_eq!(collected, vec!["hello", "world"]);
+ ///
+ /// let args = Picker::from(vec!["foo", "bar"]);
+ /// let collected: Vec<String> = args.iter().collect();
+ /// assert_eq!(collected, vec!["foo", "bar"]);
+ ///
+ /// let args = Picker::from(vec!["a".to_string(), "b".to_string()]);
+ /// let collected: Vec<String> = args.iter().collect();
+ /// assert_eq!(collected, vec!["a", "b"]);
+ /// ```
+ pub fn iter(&'a self) -> PickerIter<'a> {
+ match &self.args {
+ PickerArguments::Slice(items) => PickerIter::Slice(items.iter()),
+ PickerArguments::Vec(items) => PickerIter::Vec(items.iter()),
+ PickerArguments::Owned(items) => PickerIter::Owned(items.iter()),
+ }
+ }
+}
+
+impl<'a> Index<usize> for Picker<'a> {
+ type Output = str;
+
+ fn index(&self, index: usize) -> &Self::Output {
+ match &self.args {
+ PickerArguments::Slice(items) => items[index],
+ PickerArguments::Vec(items) => items[index],
+ PickerArguments::Owned(items) => &items[index],
+ }
+ }
+}
+
+impl<'a> Index<usize> for &Picker<'a> {
+ type Output = str;
+
+ fn index(&self, index: usize) -> &Self::Output {
+ match &self.args {
+ PickerArguments::Slice(items) => items[index],
+ PickerArguments::Vec(items) => items[index],
+ PickerArguments::Owned(items) => &items[index],
+ }
+ }
+}
+
+impl<'a> IntoIterator for &'a Picker<'a> {
+ type Item = String;
+ type IntoIter = PickerIter<'a>;
+
+ fn into_iter(self) -> Self::IntoIter {
+ match &self.args {
+ PickerArguments::Slice(items) => PickerIter::Slice(items.iter()),
+ PickerArguments::Vec(items) => PickerIter::Vec(items.iter()),
+ PickerArguments::Owned(items) => PickerIter::Owned(items.iter()),
+ }
+ }
+}
+
+/// Iterator for `Picker`, yielding owned `String` values.
+///
+/// This enum wraps the underlying slice iterators for each variant of
+/// `PickerArguments`, allowing uniform iteration over borrowed or owned data.
+pub enum PickerIter<'a> {
+ /// Iterates over a borrowed slice (`&[&str]`)
+ Slice(std::slice::Iter<'a, &'a str>),
+ /// Iterates over an owned vector of borrowed string slices (`Vec<&str>`)
+ Vec(std::slice::Iter<'a, &'a str>),
+ /// Iterates over an owned vector of owned strings (`Vec<String>`)
+ Owned(std::slice::Iter<'a, String>),
+}
+
+impl<'a> Iterator for PickerIter<'a> {
+ type Item = String;
+
+ 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(),
+ }
+ }
+
+ fn size_hint(&self) -> (usize, Option<usize>) {
+ match self {
+ PickerIter::Slice(iter) => iter.size_hint(),
+ PickerIter::Vec(iter) => iter.size_hint(),
+ PickerIter::Owned(iter) => iter.size_hint(),
+ }
+ }
+}
+
+impl<'a> ExactSizeIterator for PickerIter<'a> {}
+
+/// Trait for converting types into a `Picker`
+///
+/// Implemented for:
+/// - `&[&str]` (borrowed slice)
+/// - `Vec<&str>` (owned vector of borrowed strings)
+/// - `Vec<String>` (owned vector of owned strings)
+pub trait IntoPicker<'a> {
+ /// Converts the value into a `Picker`
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use mingling_picker::{IntoPicker, Picker};
+ ///
+ /// let args: Picker = (&["hello", "world"][..]).to_picker();
+ /// assert_eq!(args.len(), 2);
+ ///
+ /// let args: Picker = vec!["foo", "bar"].to_picker();
+ /// assert_eq!(args.len(), 2);
+ ///
+ /// let args: Picker = vec!["a".to_string(), "b".to_string()].to_picker();
+ /// assert_eq!(args.len(), 2);
+ /// ```
+ fn to_picker(self) -> Picker<'a>;
+}
+
+impl<'a> IntoPicker<'a> for &'a [&'a str] {
+ fn to_picker(self) -> Picker<'a> {
+ Picker {
+ args: PickerArguments::Slice(self),
+ }
+ }
+}
+
+impl<'a> IntoPicker<'a> for Vec<&'a str> {
+ fn to_picker(self) -> Picker<'a> {
+ Picker {
+ args: PickerArguments::Vec(self),
+ }
+ }
+}
+
+impl<'a> IntoPicker<'a> for Vec<String> {
+ fn to_picker(self) -> Picker<'a> {
+ Picker {
+ args: PickerArguments::Owned(self),
+ }
+ }
+}
diff --git a/mingling_picker/src/picker/patterns.rs b/mingling_picker/src/picker/patterns.rs
new file mode 100644
index 0000000..74a8cd8
--- /dev/null
+++ b/mingling_picker/src/picker/patterns.rs
@@ -0,0 +1,12 @@
+use crate::{Pickable, PickerArguments, PickerRequirement};
+
+mingling_picker_macros::internal_repeat! (1..=32 => {
+ pub struct PickerPattern$<'a, (Type$,)+>
+ where (Type$: Pickable + Default,)+
+ {
+ pub args: PickerArguments<'a>,
+ (
+ pub require_$: PickerRequirement<'a, Type$>,
+ )+
+ }
+});
diff --git a/mingling_picker/src/requirement.rs b/mingling_picker/src/requirement.rs
new file mode 100644
index 0000000..32fefff
--- /dev/null
+++ b/mingling_picker/src/requirement.rs
@@ -0,0 +1,150 @@
+use crate::{Pickable, PickerResult};
+
+/// Represents a constraint definition for a parameter selection.
+///
+/// This structure describes the constraints that a command-line parameter (Picker parameter item)
+/// should satisfy, including its full name list (with aliases), short name form, and whether it is
+/// positional.
+///
+/// # Field Descriptions
+///
+/// - `full`: Full name or alias list. For example, `["config", "cfg"]` means the parameter can be
+/// matched with either `--config` or `--cfg`. Must contain at least one non-empty string.
+///
+/// - `short`: Short name (single character). For example, `Some('c')` means it can be passed using
+/// the `-c` form. If set to `None`, the short name form is not supported.
+///
+/// - `positional`: Whether the parameter is positional (i.e., an argument without a flag).
+/// - `true`: The parameter is positional; it is matched by its position in the command line rather
+/// than by a `--name` or `-n` flag.
+/// - `false`: The parameter is a named (flag-based) parameter.
+///
+/// - `result`: The parsed result of this parameter requirement. Initially set to
+/// `PickerResult::Unparsed`. After parsing, contains either the successfully parsed value or an
+/// error.
+#[derive(Default)]
+pub struct PickerRequirement<'a, Type>
+where
+ Type: Default + Pickable,
+{
+ /// Full name, may include variant names (aliases), e.g., `["config", "cfg"]`.
+ full: &'a [&'a str],
+
+ /// Short name, e.g., `'c'`.
+ short: Option<char>,
+
+ /// Whether the parameter is positional (no flag, matched by position).
+ positional: bool,
+
+ /// The parsed result of this parameter requirement.
+ result: PickerResult<Type>,
+}
+
+impl<'a, Type> PickerRequirement<'a, Type>
+where
+ Type: Default + Pickable,
+{
+ /// Creates a new `PickerRequirement` with the provided parameters.
+ pub fn new(full: &'a [&'a str], short: Option<char>, positional: bool) -> Self {
+ Self {
+ full,
+ short,
+ positional,
+ result: PickerResult::Unparsed,
+ }
+ }
+
+ /// Returns the full name list (including aliases).
+ pub fn full(&self) -> &'a [&'a str] {
+ self.full
+ }
+
+ /// Returns the short name, if any.
+ pub fn short(&self) -> Option<char> {
+ self.short
+ }
+
+ /// Returns whether the parameter is positional.
+ ///
+ /// If `full` is empty or `short` is `None`, the parameter is considered positional
+ /// regardless of the stored value.
+ pub fn is_positional(&self) -> bool {
+ if self.full.is_empty() && self.short.is_none() {
+ true
+ } else {
+ self.positional
+ }
+ }
+
+ /// Sets the full name list.
+ pub fn set_full(&mut self, full: &'a [&'a str]) {
+ self.full = full;
+ }
+
+ /// Sets the short name.
+ pub fn set_short(&mut self, short: Option<char>) {
+ self.short = short;
+ }
+
+ /// Sets whether the parameter is positional.
+ pub fn set_positional(&mut self, positional: bool) {
+ self.positional = positional;
+ }
+
+ /// Sets the full name list and returns self.
+ pub fn with_full(mut self, full: &'a [&'a str]) -> Self {
+ self.full = full;
+ self
+ }
+
+ /// Clears the full name list (sets it to an empty slice) and returns self.
+ pub fn without_full(mut self) -> Self {
+ self.full = &[];
+ self
+ }
+
+ /// Sets the short name to the given character and returns self.
+ pub fn with_short(mut self, short: char) -> Self {
+ self.short = Some(short);
+ self
+ }
+
+ /// Clears the short name (sets it to None) and returns self.
+ pub fn without_short(mut self) -> Self {
+ self.short = None;
+ self
+ }
+
+ /// Sets whether the parameter is positional and returns self.
+ pub fn with_positional(mut self, positional: bool) -> Self {
+ self.positional = positional;
+ self
+ }
+
+ /// Returns a reference to the current parse result.
+ pub fn result(&self) -> &PickerResult<Type> {
+ &self.result
+ }
+
+ /// Returns a mutable reference to the current parse result.
+ pub fn result_mut(&mut self) -> &mut PickerResult<Type> {
+ &mut self.result
+ }
+
+ /// Sets the parse result.
+ pub fn set_result(&mut self, result: PickerResult<Type>) {
+ self.result = result;
+ }
+
+ /// Replaces the parse result with `PickerResult::Unparsed` and returns self.
+ pub fn reset_result(mut self) -> Self {
+ self.result = PickerResult::Unparsed;
+ self
+ }
+
+ /// Sets the parse result and returns self.
+ pub fn with_result(mut self, result: PickerResult<Type>) -> Self {
+ self.result = result;
+ self
+ }
+}
diff --git a/mingling_picker/src/result.rs b/mingling_picker/src/result.rs
new file mode 100644
index 0000000..1fad4f5
--- /dev/null
+++ b/mingling_picker/src/result.rs
@@ -0,0 +1,59 @@
+use crate::Pickable;
+
+/// Represents the result of parsing or looking up a value.
+///
+/// This enum is generic over the type being parsed. It models four possible outcomes:
+/// - [`Unparsed`](PickerResult::Unparsed): The value has not yet been parsed (default).
+/// - [`Parsed`](PickerResult::Parsed): The value was successfully parsed into `Type`.
+/// - [`NotFound`](PickerResult::NotFound): The requested value could not be found.
+/// - [`FormatError`](PickerResult::FormatError): The input could not be parsed due to a format error.
+#[derive(Default)]
+pub enum PickerResult<Type>
+where
+ Type: Default + Pickable,
+{
+ /// The value has not yet been parsed (default).
+ #[default]
+ Unparsed,
+
+ /// The value was successfully parsed into `Type`.
+ Parsed(Type),
+
+ /// The requested value could not be found.
+ NotFound,
+
+ /// The input could not be parsed due to a format error.
+ FormatError,
+}
+
+impl<Type, E> From<Result<Type, E>> for PickerResult<Type>
+where
+ Type: Default + Pickable,
+{
+ /// Converts a `Result<Type, E>` into a `PickerResult<Type>`.
+ ///
+ /// - `Ok(value)` maps to [`Parsed(value)`](PickerResult::Parsed).
+ /// - `Err(_)` maps to [`FormatError`](PickerResult::FormatError).
+ fn from(result: Result<Type, E>) -> Self {
+ match result {
+ Ok(value) => PickerResult::Parsed(value),
+ Err(_) => PickerResult::FormatError,
+ }
+ }
+}
+
+impl<Type> From<Option<Type>> for PickerResult<Type>
+where
+ Type: Default + Pickable,
+{
+ /// Converts an `Option<Type>` into a `PickerResult<Type>`.
+ ///
+ /// - `Some(value)` maps to [`Parsed(value)`](PickerResult::Parsed).
+ /// - `None` maps to [`NotFound`](PickerResult::NotFound).
+ fn from(option: Option<Type>) -> Self {
+ match option {
+ Some(value) => PickerResult::Parsed(value),
+ None => PickerResult::NotFound,
+ }
+ }
+}