diff options
Diffstat (limited to 'arg_picker')
| -rw-r--r-- | arg_picker/Cargo.toml | 3 | ||||
| -rw-r--r-- | arg_picker/src/arg.rs | 26 | ||||
| -rw-r--r-- | arg_picker/src/builtin.rs | 1 | ||||
| -rw-r--r-- | arg_picker/src/builtin/pick_picker_args.rs | 21 | ||||
| -rw-r--r-- | arg_picker/src/corebind/entry_picker.rs | 131 | ||||
| -rw-r--r-- | arg_picker/src/lib.rs | 14 | ||||
| -rw-r--r-- | arg_picker/src/parselib.rs | 23 | ||||
| -rw-r--r-- | arg_picker/src/picker.rs | 34 | ||||
| -rw-r--r-- | arg_picker/src/picker/parse.rs | 8 | ||||
| -rw-r--r-- | arg_picker/src/picker/result.rs | 8 | ||||
| -rw-r--r-- | arg_picker/test/src/test/route_test.rs | 4 |
11 files changed, 113 insertions, 160 deletions
diff --git a/arg_picker/Cargo.toml b/arg_picker/Cargo.toml index f7672c1..d87a844 100644 --- a/arg_picker/Cargo.toml +++ b/arg_picker/Cargo.toml @@ -9,9 +9,8 @@ readme = "README.md" description = "A lightweight, type-safe CLI argument parser" [features] -mingling_support = ["dep:mingling_core", "arg-picker-macros/mingling_support"] +mingling_support = ["arg-picker-macros/mingling_support"] [dependencies] -mingling_core = { workspace = true, optional = true } arg-picker-macros.workspace = true just_fmt.workspace = true diff --git a/arg_picker/src/arg.rs b/arg_picker/src/arg.rs index 78ad539..a352418 100644 --- a/arg_picker/src/arg.rs +++ b/arg_picker/src/arg.rs @@ -138,9 +138,27 @@ where /// Describes the attribute (behavior) of a command-line parameter. /// /// The ordering reflects parse priority (higher = parsed first): -/// `PositionalMulti < Positional < Flag < Single < Multi` +/// `Postprocess < Final < PositionalMulti < Positional < Flag < Single < Multi < Begin < Preprocess` +/// +/// # Variants +/// +/// - `Postprocess` — Reserved lowest priority, used only in special cases. +/// - `Final` — Reserved post-processing priority, used only in special cases. +/// - `PositionalMulti` — Positional argument that accepts multiple values (e.g., multiple input files). +/// - `Positional` — Positional argument matched by its position (e.g., an input file). +/// - `Flag` — Boolean flag with no associated value (e.g., `--verbose`). +/// - `Single` — Accepts a single value (e.g., `--name Alice`). +/// - `Multi` — Accepts multiple values (e.g., `--file a.txt --file b.txt`). +/// - `Begin` — Reserved pre-processing priority, used only in special cases. +/// - `Preprocess` — Reserved highest priority, used only in special cases. #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub enum PickerArgAttr { + /// Reserved lowest priority, used only in special cases. + Postprocess, + + /// Reserved post-processing priority, used only in special cases. + Final, + /// Positional argument that accepts multiple values (e.g., multiple input files). PositionalMulti, @@ -156,6 +174,12 @@ pub enum PickerArgAttr { /// Accepts multiple values (e.g., `--file a.txt --file b.txt`). Multi, + + /// Reserved pre-processing priority, used only in special cases. + Begin, + + /// Reserved highest priority, used only in special cases. + Preprocess, } impl PickerArgAttr { diff --git a/arg_picker/src/builtin.rs b/arg_picker/src/builtin.rs index e855b08..1c698ba 100644 --- a/arg_picker/src/builtin.rs +++ b/arg_picker/src/builtin.rs @@ -1,4 +1,5 @@ mod pick_bool; mod pick_flag; mod pick_numbers; +mod pick_picker_args; mod pick_string; diff --git a/arg_picker/src/builtin/pick_picker_args.rs b/arg_picker/src/builtin/pick_picker_args.rs new file mode 100644 index 0000000..419cbc8 --- /dev/null +++ b/arg_picker/src/builtin/pick_picker_args.rs @@ -0,0 +1,21 @@ +use crate::{PickerArgResult::Parsed, PickerArgs, parselib::build_masked_args, pickable_needed::*}; + +impl<'a> Pickable<'a> for PickerArgs<'a> { + fn get_attr(_flag: &'a PickerArg<'a, Self>) -> PickerArgAttr { + // Use the lowest priority attribute + PickerArgAttr::Postprocess + } + + fn tag(ctx: TagPhaseContext) -> Vec<usize> { + // Collect all remaining raw index values + build_masked_args(ctx.args, ctx.mask) + .iter() + .map(|m| m.raw_idx) + .collect() + } + + fn pick(raw_strs: &[&str]) -> PickerArgResult<Self> { + let remains: Vec<String> = raw_strs.iter().map(|s| s.to_string()).collect(); + Parsed(PickerArgs::Owned(remains)) + } +} diff --git a/arg_picker/src/corebind/entry_picker.rs b/arg_picker/src/corebind/entry_picker.rs deleted file mode 100644 index d543f98..0000000 --- a/arg_picker/src/corebind/entry_picker.rs +++ /dev/null @@ -1,131 +0,0 @@ -use std::marker::PhantomData; - -use mingling_core::{ChainProcess, Groupped, ProgramCollect}; - -use crate::{Pickable, Picker, PickerArg, PickerArgs, PickerPattern1}; - -/// Trait for converting Mingling entry types (types that implement `Groupped<R>` and `Into<Vec<String>>`) -/// into [`Picker`] instances for a given route type `R`. -/// -/// This trait provides a bridge between entry definitions created with the `mingling` framework -/// and the picker argument system used for CLI argument parsing and routing. -/// -/// # Type Parameters -/// -/// * `'a` — The lifetime of the picker and its references to argument definitions. -/// * `This` — The program type used for dispatching runtime routes; must implement [`ProgramCollect`]. -/// * `Route` — The route type used for dispatching; must implement [`Groupped<This>`]. -pub trait EntryPicker<'a, This> { - /// Converts `self` into a [`Picker`] for the given route type `Route`. - fn to_picker(self) -> Picker<'a, ChainProcess<This>>; - - /// Starts building a picker pattern with the first argument. - /// - /// Returns a [`PickerPattern1`] that can be further chained with additional - /// arguments, defaults, routes, and post-processing. - /// - /// # Type Parameters - /// - /// * `Next` — The nominal type of the first argument; must implement [`Pickable`]. - /// - /// # Parameters - /// - /// * `arg` — The argument definition, typically obtained from [`crate::macros::arg`]. - fn pick<Next>( - self, - arg: impl Into<&'a PickerArg<'a, Next>>, - ) -> PickerPattern1<'a, Next, ChainProcess<This>> - where - Self: Sized, - Next: Pickable<'a> + Default + Sized, - { - let picker = Self::to_picker(self); - Picker::build_pattern1(picker.args, arg.into(), None) - } - - /// Starts building a picker pattern with the first argument, using a default value provider. - /// - /// This is a shorthand for calling `.pick(arg).or(func)`. - /// - /// # Type Parameters - /// - /// * `Next` — The nominal type of the first argument; must implement [`Pickable`]. - /// - /// # Parameters - /// - /// * `arg` — The argument definition, typically obtained from [`crate::macros::arg`]. - /// * `func` — A closure that provides a default value if the arg is not provided by the user. - fn pick_or<Next, F>( - self, - arg: impl Into<&'a PickerArg<'a, Next>>, - func: F, - ) -> PickerPattern1<'a, Next, ChainProcess<This>> - where - Self: Sized, - Next: Pickable<'a> + Default + Sized, - F: FnMut() -> Next + 'static, - { - self.pick(arg).or(func) - } - - /// Starts building a picker pattern with the first argument, using a default value. - /// - /// This is a shorthand for calling `.pick(arg).or_default()`. - /// - /// # Type Parameters - /// - /// * `Next` — The nominal type of the first argument; must implement [`Pickable`] and [`Default`]. - /// - /// # Parameters - /// - /// * `arg` — The argument definition, typically obtained from [`crate::macros::arg`]. - fn pick_or_default<Next>( - self, - arg: impl Into<&'a PickerArg<'a, Next>>, - ) -> PickerPattern1<'a, Next, ChainProcess<This>> - where - Self: Sized, - Next: Pickable<'a> + Default + Sized, - { - self.pick(arg).or_default() - } - - /// Starts building a picker pattern with the first argument, using a route if the arg is not provided. - /// - /// This is a shorthand for calling `.pick(arg).or_route(func)`. - /// - /// # Type Parameters - /// - /// * `Next` — The nominal type of the first argument; must implement [`Pickable`]. - /// - /// # Parameters - /// - /// * `arg` — The argument definition, typically obtained from [`crate::macros::arg`]. - /// * `func` — A closure that produces a route value if the arg is not provided by the user. - fn pick_or_route<Next, F>( - self, - arg: impl Into<&'a PickerArg<'a, Next>>, - func: F, - ) -> PickerPattern1<'a, Next, ChainProcess<This>> - where - Self: Sized, - Next: Pickable<'a> + Default + Sized, - F: FnMut() -> ChainProcess<This> + 'static, - { - self.pick(arg).or_route(func) - } -} - -impl<'a, This, Bind> EntryPicker<'a, This> for Bind -where - This: ProgramCollect<Enum = This>, - Bind: Groupped<This> + Into<Vec<String>>, -{ - fn to_picker(self) -> Picker<'a, ChainProcess<This>> { - let args = self.into(); - Picker { - route_phantom: PhantomData, - args: PickerArgs::Owned(args), - } - } -} diff --git a/arg_picker/src/lib.rs b/arg_picker/src/lib.rs index 21a0d35..c65e793 100644 --- a/arg_picker/src/lib.rs +++ b/arg_picker/src/lib.rs @@ -29,13 +29,8 @@ pub mod value; /// use arg_picker::prelude::*; /// ``` pub mod prelude { - pub use crate::macros::arg; - - #[cfg(not(feature = "mingling_support"))] pub use crate::IntoPicker; - - #[cfg(feature = "mingling_support")] - pub use crate::corebind::EntryPicker; + pub use crate::macros::arg; } /// Re-export of the `arg_picker_macros` crate @@ -53,10 +48,3 @@ pub mod matcher_needed { pub use crate::PickerArgInfo; pub use crate::parselib::{MaskedArg, Matcher, ParserStyle}; } - -#[cfg(feature = "mingling_support")] -mod corebind; - -#[allow(unused_imports)] -#[cfg(feature = "mingling_support")] -pub use corebind::*; diff --git a/arg_picker/src/parselib.rs b/arg_picker/src/parselib.rs index 7fbd606..0fcd583 100644 --- a/arg_picker/src/parselib.rs +++ b/arg_picker/src/parselib.rs @@ -117,13 +117,32 @@ impl<'a> From<crate::TagPhaseContext<'a>> for MatcherContext<'a> { } } +/// Checks whether the argument at index `idx` is already claimed (masked). +/// +/// Returns `true` if `idx` is within the mask bounds and the mask value is non-zero, +/// indicating the argument has been claimed by a previous matcher. +/// +/// # Arguments +/// +/// * `mask` - A byte slice where non-zero values indicate claimed arguments. +/// * `idx` - The index to check in the mask. #[inline(always)] -fn is_masked(mask: &[u8], idx: usize) -> bool { +pub fn is_masked(mask: &[u8], idx: usize) -> bool { idx < mask.len() && mask[idx] != 0 } +/// Builds a vector of [`MaskedArg`] from the given `PickerArgs` and mask. +/// +/// Only arguments whose mask entry is `0` (i.e., available/not yet claimed) are included. +/// Each resulting [`MaskedArg`] retains its original raw string and its index in the full +/// argument list for later reference. +/// +/// # Arguments +/// +/// * `args` - The full set of parsed arguments. +/// * `mask` - A byte slice where `0` means available and non-zero means already claimed. #[inline(always)] -fn build_masked_args<'a>(args: &'a PickerArgs, mask: &'a [u8]) -> Vec<MaskedArg<'a>> { +pub fn build_masked_args<'a>(args: &'a PickerArgs, mask: &'a [u8]) -> Vec<MaskedArg<'a>> { let mut cidx = 0; args.iter() .filter_map(|r| { diff --git a/arg_picker/src/picker.rs b/arg_picker/src/picker.rs index ecab648..f31a5b6 100644 --- a/arg_picker/src/picker.rs +++ b/arg_picker/src/picker.rs @@ -73,6 +73,26 @@ pub enum PickerArgs<'a> { Owned(Vec<String>), } +impl<'a> From<PickerArgs<'a>> for Vec<String> { + fn from(value: PickerArgs<'a>) -> Self { + match value { + PickerArgs::Slice(items) => items.iter().map(|s| s.to_string()).collect(), + PickerArgs::Vec(items) => items.into_iter().map(|s| s.to_string()).collect(), + PickerArgs::Owned(items) => items, + } + } +} + +impl<'a> From<&'a PickerArgs<'a>> for Vec<&'a str> { + fn from(value: &'a PickerArgs<'a>) -> Self { + match value { + PickerArgs::Slice(items) => items.to_vec(), + PickerArgs::Vec(items) => items.clone(), + PickerArgs::Owned(items) => items.iter().map(|s| s.as_str()).collect(), + } + } +} + impl<'a> Default for PickerArgs<'a> { fn default() -> Self { Self::Vec(vec![]) @@ -138,6 +158,15 @@ impl<'a> IntoIterator for &'a PickerArgs<'a> { } } +impl<'a, Route> From<PickerArgs<'a>> for Picker<'a, Route> { + fn from(args: PickerArgs<'a>) -> Self { + Picker { + route_phantom: PhantomData, + args, + } + } +} + impl<'a, Route> From<&'a [&'a str]> for Picker<'a, Route> { fn from(value: &'a [&'a str]) -> Self { Picker { @@ -408,10 +437,9 @@ impl<'a> IntoPicker<'a> for Vec<String> { } } -// Private helper: shared construction logic for `PickerPattern1`. -// Both `Picker::pick` and `IntoPicker::pick` delegate to this. impl<'a, Route> Picker<'a, Route> { - pub(crate) fn build_pattern1<N>( + /// Build the PickerPattern via Arguments + pub fn build_pattern1<N>( args: PickerArgs<'a>, arg: &'a PickerArg<'a, N>, error_route: Option<Route>, diff --git a/arg_picker/src/picker/parse.rs b/arg_picker/src/picker/parse.rs index 366028b..9db5bd9 100644 --- a/arg_picker/src/picker/parse.rs +++ b/arg_picker/src/picker/parse.rs @@ -21,14 +21,16 @@ internal_repeat!(1..=32 => { internal_repeat!(1..=32 => { impl<'a, (T$,+), Route> PickerPattern$<'a, (T$,+), Route> where (T$: Pickable<'a>,+) { - /// Unwraps the result, panicking if a route was selected. + /// Unwraps the result, panicking if a route was selected or a required + /// value is missing. /// /// # Panics /// - /// Panics if a route was selected. + /// Panics if a route was selected, or if a required argument was not + /// provided by the user. pub fn unwrap(self) -> ((T$,+)) { let p = self.parse(); - ((p.v$.unwrap(),+)) + ((p.v$.expect(concat!("missing required argument at position ", $)),+)) } /// Returns the individual option values without checking the route. diff --git a/arg_picker/src/picker/result.rs b/arg_picker/src/picker/result.rs index 83ca2cd..2099825 100644 --- a/arg_picker/src/picker/result.rs +++ b/arg_picker/src/picker/result.rs @@ -21,13 +21,15 @@ internal_repeat!(1..=32 => { internal_repeat!(1..=32 => { impl<(T$,+), Route> PickerResult$<(T$,+), Route> { - /// Unwraps the result, panicking if a route was selected. + /// Unwraps the result, panicking if a route was selected or a required + /// value is missing. /// /// # Panics /// - /// Panics if `self.route` is `Some(...)`. + /// Panics if `self.route` is `Some(...)`, or if a required argument was + /// not provided by the user. pub fn unwrap(self) -> ((T$,+)) { - ((self.v$.unwrap(),+)) + ((self.v$.expect(concat!("missing required argument at position ", $)),+)) } /// Returns the individual option values without checking the route. diff --git a/arg_picker/test/src/test/route_test.rs b/arg_picker/test/src/test/route_test.rs index 7594c6e..c9cd5ab 100644 --- a/arg_picker/test/src/test/route_test.rs +++ b/arg_picker/test/src/test/route_test.rs @@ -1,4 +1,4 @@ -use arg_picker::{macros::arg, IntoPicker}; +use arg_picker::{IntoPicker, macros::arg}; // Route mechanism — or_route @@ -53,7 +53,7 @@ fn test_or_route_to_option_returns_none() { } #[test] -#[should_panic(expected = "called `Option::unwrap()` on a `None` value")] +#[should_panic(expected = "missing required argument")] fn test_or_route_unwrap_panics() { let args: Vec<&str> = vec![]; args.with_route::<&'static str>() |
