diff options
| author | 魏曹先生 <1992414357@qq.com> | 2026-07-15 03:52:25 +0800 |
|---|---|---|
| committer | 魏曹先生 <1992414357@qq.com> | 2026-07-15 03:52:25 +0800 |
| commit | 559ff1a4d5d164c2fdbc29a9957e3db38651705f (patch) | |
| tree | 956ce3a9974a6ebbdaa41d9332a18834b61d306c | |
| parent | 963c0937abbb985021ccb6319b8734f143c7603f (diff) | |
refactor(pickable): redesign Pickable trait with two-phase parse API
Introduce `get_attr`, `tag`, and `pick` as the new trait methods,
replacing
the single `tag` method that returned a `PickerTag`. This enables the
parser to first collect all argument position requirements and then
resolve
them in a second pass.
Rename `PickerTag` to `PickerArgInfo` and reorder `PickerFlagAttr`
variants
to reflect parse priority (`Positional < Flag < Single < Multi`). Add
`positional_or_else` helper to `PickerFlagAttr`.
| -rw-r--r-- | mingling_picker/src/flag.rs | 54 | ||||
| -rw-r--r-- | mingling_picker/src/infos.rs | 24 | ||||
| -rw-r--r-- | mingling_picker/src/parselib.rs | 3 | ||||
| -rw-r--r-- | mingling_picker/src/pickable.rs | 105 | ||||
| -rw-r--r-- | mingling_picker/src/pickable/implements.rs | 50 |
5 files changed, 134 insertions, 102 deletions
diff --git a/mingling_picker/src/flag.rs b/mingling_picker/src/flag.rs index 3fa3dc1..aebbc81 100644 --- a/mingling_picker/src/flag.rs +++ b/mingling_picker/src/flag.rs @@ -123,31 +123,49 @@ where /// Describes the attribute (behavior) of a command-line parameter. /// -/// This enum specifies how a parameter is parsed and processed: -/// -/// - `Single`: The parameter accepts a single value. For example, `--name Alice`. -/// -/// - `Multi`: The parameter accepts multiple values. For example, `--file a.txt --file b.txt`. -/// -/// - `Flag`: The parameter is a boolean flag with no associated value. -/// For example, `--verbose` sets a flag to `true`. -/// -/// - `Positional`: The parameter is matched by its position in the command line, -/// without requiring a `--name` or `-n` flag. For example, an input filename as the first argument. +/// The ordering reflects parse priority (higher = parsed first): +/// `Positional < Flag < Single < Multi` #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub enum PickerFlagAttr { - /// Accepts multiple values (e.g., `--file a.txt --file b.txt`). - Multi, + /// Positional argument matched by its position (e.g., an input file). + #[default] + Positional, + + /// Boolean flag with no associated value (e.g., `--verbose`). + Flag, /// Accepts a single value (e.g., `--name Alice`). Single, - /// Boolean flag with no associated value (e.g., `--verbose`). - Flag, + /// Accepts multiple values (e.g., `--file a.txt --file b.txt`). + Multi, +} - /// Positional argument matched by its position (e.g., an input file). - #[default] - Positional, +impl PickerFlagAttr { + /// Determines if the given `PickerFlag` represents a positional parameter. + /// + /// If the flag is positional (determined by `flag.is_positional()`), returns + /// `PickerFlagAttr::Positional`. Otherwise, invokes the `other` closure to + /// produce and return a `PickerFlagAttr`. + /// + /// # Parameters + /// + /// - `flag`: A reference to the [`PickerFlag`] to evaluate. + /// - `other`: A closure that returns a [`PickerFlagAttr`] when the flag is + /// **not** positional. + pub fn positional_or_else<'a, T>( + flag: &PickerFlag<'a, T>, + other: fn() -> PickerFlagAttr, + ) -> PickerFlagAttr + where + T: Pickable<'a> + Default, + { + if flag.is_positional() { + PickerFlagAttr::Positional + } else { + other() + } + } } #[cfg(test)] diff --git a/mingling_picker/src/infos.rs b/mingling_picker/src/infos.rs index 6270483..f820c9a 100644 --- a/mingling_picker/src/infos.rs +++ b/mingling_picker/src/infos.rs @@ -246,21 +246,7 @@ impl<Type> PickerResult<Type> { } } -/// Represents a command-line argument/flag tag for a picker. -/// -/// This struct defines how a picker argument should be parsed from the command line, -/// including its short name (e.g., `-n`), long name (e.g., `--name`), and aliases. -/// -/// # Fields -/// -/// * `short` - The short form of the tag, typically a single character (e.g., `-n`). -/// * `long` - The long form of the tag (e.g., `--name`). -/// * `alias` - Alternative names for the tag (e.g., `["-N", "--nickname"]`). -/// * `positional` - Whether this tag is a positional argument (no `-` or `--` prefix). -/// * `optional` - Whether this tag is optional or required. -/// * `multi` - Whether this tag can accept multiple values. -/// * `is_flag` - Whether this tag participates in parsing after a `--` separator. -pub struct PickerTag<'a> { +pub struct PickerArgInfo<'a> { /// The short form of the tag, e.g. `'n'` for `-n`. pub short: Option<char>, /// The long form of the tag, e.g. `"name"` for `--name`. @@ -277,7 +263,7 @@ pub struct PickerTag<'a> { pub is_flag: bool, } -impl<'a, T> From<PickerFlag<'a, T>> for PickerTag<'a> +impl<'a, T> From<PickerFlag<'a, T>> for PickerArgInfo<'a> where T: Pickable<'a> + Default, { @@ -307,7 +293,7 @@ where } } -impl<'a, T: Pickable<'a> + Default> From<&'a PickerFlag<'a, T>> for PickerTag<'a> { +impl<'a, T: Pickable<'a> + Default> From<&'a PickerFlag<'a, T>> for PickerArgInfo<'a> { fn from(value: &'a PickerFlag<'a, T>) -> Self { let (long, alias) = match value.full.len() { 0 => (None, None), @@ -334,7 +320,7 @@ impl<'a, T: Pickable<'a> + Default> From<&'a PickerFlag<'a, T>> for PickerTag<'a } } -impl<'a> PickerTag<'a> { +impl<'a> PickerArgInfo<'a> { /// Create a new `PickerTag` with default values. pub fn new() -> Self { Self { @@ -433,7 +419,7 @@ impl<'a> PickerTag<'a> { } } -impl<'a> Default for PickerTag<'a> { +impl<'a> Default for PickerArgInfo<'a> { fn default() -> Self { Self::new() } diff --git a/mingling_picker/src/parselib.rs b/mingling_picker/src/parselib.rs index 21f0ba2..132045b 100644 --- a/mingling_picker/src/parselib.rs +++ b/mingling_picker/src/parselib.rs @@ -1,5 +1,2 @@ -mod seek; -// pub use seek::*; - mod style; pub use style::*; diff --git a/mingling_picker/src/pickable.rs b/mingling_picker/src/pickable.rs index 7a89631..9d3f054 100644 --- a/mingling_picker/src/pickable.rs +++ b/mingling_picker/src/pickable.rs @@ -1,47 +1,88 @@ -use crate::{PickerFlag, PickerResult, PickerTag}; +use crate::{PickerArgInfo, PickerArgs, PickerFlag, PickerFlagAttr, PickerResult}; mod implements; -/// A trait for types that can be constructed from a raw string representation. +/// `Pickable` trait defines how to parse a type instance from command-line arguments. /// -/// Implementing this trait allows a type to be "picked" or parsed from a string, -/// enabling deserialization or configuration loading from textual input. +/// This trait is the core abstraction of the `Picker` argument parsing system, dividing the +/// parsing process into two phases: /// -/// # Requirements +/// 1. **Tag phase ([`Pickable::tag`])**: Determines which argument positions the `Pickable` needs to handle. +/// 2. **Pick phase ([`Pickable::pick`])**: Converts the raw strings at the tagged positions into the actual type. /// -/// - The implementing type must be [`Sized`] and implement [`Default`]. -/// - The [`pick`] method performs the actual parsing and may fail. +/// Types implementing this trait must also implement [`Default`], so that a default value +/// can be used as a fallback when parsing fails. /// -/// # Errors +/// # Type Parameters /// -/// 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, PickerFlag, PickerTag}; -/// #[derive(Default)] -/// struct MyType(String); -/// -/// impl<'a> Pickable<'a> for MyType { -/// fn tag(flag: &'a PickerFlag<'a, Self>) -> PickerTag<'a> { -/// let tag = PickerTag::from(flag); -/// } -/// -/// fn pick(raw_strs: &[&str]) -> PickerResult<Self> { -/// PickerResult::Parsed(MyType(raw_strs.join(" "))) -/// } -/// } -/// ``` +/// * `'a` - Lifetime parameter, used to associate references in [`PickerFlag`]. pub trait Pickable<'a> where Self: Sized + Default, { - /// Given a [`PickerFlag`], returns a [`PickerTag`] that tells the parser - /// about the argument's characteristics (e.g., positional, optional, multi). - fn tag(flag: &'a PickerFlag<'a, Self>) -> PickerTag<'a>; + /// Returns the parse-order attribute of this flag. + /// + /// This attribute is used to inform the parser about the parse order + /// between different `Pickable` types. + /// See [`PickerFlagAttr`] for specific ordering definitions. + /// + /// # Parameters + /// + /// * `flag` - The current flag instance, which contains a reference to `Self`. + /// + /// # Returns + /// + /// Returns a [`PickerFlagAttr`] describing the parse-order attribute of this flag. + fn get_attr(flag: &'a PickerFlag<'a, Self>) -> PickerFlagAttr; + + /// Tag phase: Determines which argument positions the `Pickable` needs to handle. + /// + /// This function receives a [`TagPhaseContext`] containing argument context information. + /// During this phase, the parser invokes each `Pickable` and collects the position indices + /// they return, in order to determine which arguments to parse later. + /// + /// # Parameters + /// + /// * `ctx` - The tag phase context, containing argument information, all parameters of the + /// current Picker, and an availability mask. + /// + /// # Returns + /// + /// Returns a `Vec<usize>` representing the indices of the arguments in the argument list + /// that this `Pickable` needs to handle. + fn tag(ctx: TagPhaseContext) -> Vec<usize>; - /// Parses a `Self` value from the given raw string input. + /// Pick phase: Converts the raw string arguments tagged during the `tag` phase into + /// the actual expected type. + /// + /// This function receives a slice of the raw strings that were tagged in the `tag` step + /// and converts them into an instance of `Self`. + /// + /// # Parameters + /// + /// * `raw_strs` - A slice of strings containing the raw argument values to parse. + /// + /// # Returns + /// + /// Returns [`PickerResult<Self>`], i.e., the `Self` instance on success, or an appropriate + /// error message on failure. fn pick(raw_strs: &[&str]) -> PickerResult<Self>; } + +/// Tag phase context, providing the necessary argument and state information for +/// [`Pickable::tag`]. +pub struct TagPhaseContext<'a> { + /// Argument information describing the structure and metadata of the argument + /// to be parsed. + pub arg_info: &'a PickerArgInfo<'a>, + + /// A read-only list of all arguments in the current [`Picker`]. + pub args: &'a PickerArgs<'a>, + + /// Availability mask, recording positions already occupied by other `Pickable` tags. + /// + /// `mask.len()` equals `args.len()`. Where: + /// - `0` means the position is currently available (unoccupied); + /// - `1` means the position is already occupied and cannot be used again. + pub mask: &'a [usize], +} diff --git a/mingling_picker/src/pickable/implements.rs b/mingling_picker/src/pickable/implements.rs index 665bde5..eb46334 100644 --- a/mingling_picker/src/pickable/implements.rs +++ b/mingling_picker/src/pickable/implements.rs @@ -1,39 +1,29 @@ -use crate::{Pickable, PickerFlag, PickerResult, PickerTag}; +use crate::{Pickable, PickerFlagAttr}; -macro_rules! impl_pickable { - ($($t:ty),*) => { - $(impl<'a> Pickable<'a> for $t { - fn tag(flag: &'a PickerFlag<'a, Self>) -> PickerTag<'a> { - flag.into() - } +impl<'a> Pickable<'a> for String { + fn get_attr(flag: &'a crate::PickerFlag<'a, Self>) -> PickerFlagAttr { + PickerFlagAttr::positional_or_else(flag, || PickerFlagAttr::Single) + } + + fn tag(_ctx: super::TagPhaseContext) -> Vec<usize> { + vec![] + } - fn pick(raw_strs: &[&str]) -> PickerResult<Self> { - raw_strs - .first() - .and_then(|s| s.parse().ok()) - .map(PickerResult::Parsed) - .unwrap_or(PickerResult::NotFound) - } - })* - }; + fn pick(_raw_strs: &[&str]) -> crate::PickerResult<Self> { + todo!() + } } -impl_pickable!( - i8, i16, i32, i64, i128, u8, u16, u32, u64, u128, usize, f32, f64, isize -); +impl<'a> Pickable<'a> for Vec<String> { + fn get_attr(flag: &'a crate::PickerFlag<'a, Self>) -> PickerFlagAttr { + PickerFlagAttr::positional_or_else(flag, || PickerFlagAttr::Multi) + } -impl<'a> Pickable<'a> for String { - fn tag(flag: &'a PickerFlag<'a, Self>) -> PickerTag<'a> { - let mut tag: PickerTag = flag.into(); - tag.set_optional(false); - tag.set_multi(false); - tag + fn tag(_ctx: super::TagPhaseContext) -> Vec<usize> { + vec![] } - fn pick(raw_strs: &[&str]) -> PickerResult<Self> { - raw_strs - .first() - .map(|s| PickerResult::Parsed(s.to_string())) - .unwrap_or(PickerResult::NotFound) + fn pick(_raw_strs: &[&str]) -> crate::PickerResult<Self> { + todo!() } } |
