aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--mingling_picker/src/builtin/pick_string.rs4
-rw-r--r--mingling_picker/src/parselib.rs3
-rw-r--r--mingling_picker/src/parselib/pos_matcher.rs71
-rw-r--r--mingling_picker/test/src/test.rs1
-rw-r--r--mingling_picker/test/src/test/pos_matcher_test.rs141
5 files changed, 218 insertions, 2 deletions
diff --git a/mingling_picker/src/builtin/pick_string.rs b/mingling_picker/src/builtin/pick_string.rs
index f56571a..3363165 100644
--- a/mingling_picker/src/builtin/pick_string.rs
+++ b/mingling_picker/src/builtin/pick_string.rs
@@ -1,4 +1,4 @@
-use crate::parselib::{ArgMatcher, Matcher, ParserStyle};
+use crate::parselib::{ArgMatcher, Matcher, ParserStyle, PositionalMatcher};
use crate::pickable_needed::*;
impl<'a> Pickable<'a> for String {
@@ -8,7 +8,7 @@ impl<'a> Pickable<'a> for String {
fn tag(ctx: TagPhaseContext) -> Vec<usize> {
if ctx.arg_info.positional {
- ArgMatcher::match_one(ctx.into())
+ PositionalMatcher::match_one(ctx.into())
.map(|i| vec![i])
.unwrap_or_default()
} else {
diff --git a/mingling_picker/src/parselib.rs b/mingling_picker/src/parselib.rs
index 49e3cc2..4e9f329 100644
--- a/mingling_picker/src/parselib.rs
+++ b/mingling_picker/src/parselib.rs
@@ -4,6 +4,9 @@ pub use flag_matcher::*;
mod arg_matcher;
pub use arg_matcher::*;
+mod pos_matcher;
+pub use pos_matcher::*;
+
mod style;
pub use style::*;
diff --git a/mingling_picker/src/parselib/pos_matcher.rs b/mingling_picker/src/parselib/pos_matcher.rs
new file mode 100644
index 0000000..a7052d4
--- /dev/null
+++ b/mingling_picker/src/parselib/pos_matcher.rs
@@ -0,0 +1,71 @@
+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`]).
+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/test/src/test.rs b/mingling_picker/test/src/test.rs
index dc251dd..e27615a 100644
--- a/mingling_picker/test/src/test.rs
+++ b/mingling_picker/test/src/test.rs
@@ -1,5 +1,6 @@
mod arg_matcher_test;
mod basic_test;
+mod pos_matcher_test;
mod route_test;
mod style_test;
mod value_flag_test;
diff --git a/mingling_picker/test/src/test/pos_matcher_test.rs b/mingling_picker/test/src/test/pos_matcher_test.rs
new file mode 100644
index 0000000..b5319f5
--- /dev/null
+++ b/mingling_picker/test/src/test/pos_matcher_test.rs
@@ -0,0 +1,141 @@
+use mingling_picker::PickerArgInfo;
+use mingling_picker::parselib::{MaskedArg, Matcher, PositionalMatcher, UNIX_STYLE, WINDOWS_STYLE};
+
+fn make_args<'a>(pairs: &'a [(&'a str, usize)]) -> Vec<MaskedArg<'a>> {
+ pairs
+ .iter()
+ .map(|&(raw, idx)| MaskedArg { raw, raw_idx: idx })
+ .collect()
+}
+
+// on_match_one — basic
+
+#[test]
+fn test_pos_one_takes_first_non_flag() {
+ let info = PickerArgInfo::new();
+ let args = make_args(&[("--verbose", 0), ("file.txt", 1)]);
+ let result = PositionalMatcher::on_match_one(&args, &UNIX_STYLE, &info);
+ assert_eq!(result, Some(1));
+}
+
+#[test]
+fn test_pos_one_takes_first_if_no_flag() {
+ let info = PickerArgInfo::new();
+ let args = make_args(&[("file.txt", 0)]);
+ let result = PositionalMatcher::on_match_one(&args, &UNIX_STYLE, &info);
+ assert_eq!(result, Some(0));
+}
+
+#[test]
+fn test_pos_one_all_flags_returns_none() {
+ let info = PickerArgInfo::new();
+ let args = make_args(&[("--verbose", 0), ("--name", 1)]);
+ let result = PositionalMatcher::on_match_one(&args, &UNIX_STYLE, &info);
+ assert_eq!(result, None);
+}
+
+#[test]
+fn test_pos_one_empty_returns_none() {
+ let info = PickerArgInfo::new();
+ let args = vec![];
+ let result = PositionalMatcher::on_match_one(&args, &UNIX_STYLE, &info);
+ assert_eq!(result, None);
+}
+
+// on_match_one — after `--`
+
+#[test]
+fn test_pos_one_after_end_takes_even_flag_like() {
+ // After `--`, accept everything including `--verbose`.
+ let info = PickerArgInfo::new();
+ let args = make_args(&[("--", 0), ("--verbose", 1), ("file.txt", 2)]);
+ let result = PositionalMatcher::on_match_one(&args, &UNIX_STYLE, &info);
+ assert_eq!(result, Some(1));
+}
+
+#[test]
+fn test_pos_one_only_end_returns_none() {
+ let info = PickerArgInfo::new();
+ let args = make_args(&[("--", 0)]);
+ let result = PositionalMatcher::on_match_one(&args, &UNIX_STYLE, &info);
+ assert_eq!(result, None);
+}
+
+// on_match_one — Windows style prefix
+
+#[test]
+fn test_pos_one_windows_skips_slash_prefix() {
+ let info = PickerArgInfo::new();
+ let args = make_args(&[("/Verbose", 0), ("file.txt", 1)]);
+ let result = PositionalMatcher::on_match_one(&args, &WINDOWS_STYLE, &info);
+ assert_eq!(result, Some(1));
+}
+
+// on_match_all — basic
+
+#[test]
+fn test_pos_all_collects_non_flags() {
+ let info = PickerArgInfo::new();
+ let args = make_args(&[("--verbose", 0), ("a.txt", 1), ("--name", 2), ("b.txt", 3)]);
+ let result = PositionalMatcher::on_match_all(&args, &UNIX_STYLE, &info);
+ assert_eq!(result, vec![1, 3]);
+}
+
+#[test]
+fn test_pos_all_only_flags_returns_empty() {
+ let info = PickerArgInfo::new();
+ let args = make_args(&[("--a", 0), ("--b", 1)]);
+ let result = PositionalMatcher::on_match_all(&args, &UNIX_STYLE, &info);
+ assert!(result.is_empty());
+}
+
+#[test]
+fn test_pos_all_empty() {
+ let info = PickerArgInfo::new();
+ let args = vec![];
+ let result = PositionalMatcher::on_match_all(&args, &UNIX_STYLE, &info);
+ assert!(result.is_empty());
+}
+
+// on_match_all — after `--`
+
+#[test]
+fn test_pos_all_after_end_accepts_everything() {
+ // After `--`, even `--verbose` is accepted as positional.
+ let info = PickerArgInfo::new();
+ let args = make_args(&[
+ ("--verbose", 0),
+ ("a.txt", 1),
+ ("--", 2),
+ ("--flag", 3),
+ ("b.txt", 4),
+ ]);
+ let result = PositionalMatcher::on_match_all(&args, &UNIX_STYLE, &info);
+ assert_eq!(result, vec![1, 3, 4]);
+}
+
+#[test]
+fn test_pos_all_only_after_end() {
+ let info = PickerArgInfo::new();
+ let args = make_args(&[("--", 0), ("arg1", 1), ("--arg2", 2)]);
+ let result = PositionalMatcher::on_match_all(&args, &UNIX_STYLE, &info);
+ assert_eq!(result, vec![1, 2]);
+}
+
+// on_match_all — mixed before/after `--`
+
+#[test]
+fn test_pos_all_mixed() {
+ let info = PickerArgInfo::new();
+ let args = make_args(&[
+ ("infile", 0),
+ ("--verbose", 1),
+ ("--", 2),
+ ("outfile", 3),
+ ("--extra", 4),
+ ]);
+ let result = PositionalMatcher::on_match_all(&args, &UNIX_STYLE, &info);
+ // Before `--`: "infile" (skip --verbose).
+ // After `--`: everything — "outfile", "--extra".
+ assert_eq!(result, vec![0, 3, 4]);
+}