aboutsummaryrefslogtreecommitdiff
path: root/mingling_picker_macros
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-07-17 11:08:07 +0800
committer魏曹先生 <1992414357@qq.com>2026-07-17 11:11:28 +0800
commit79ec6878877f0fd9246d67d3cd4f8cc2d1200150 (patch)
treee3cd8cfc7ef5cd2a6a7ceb93d9a4b1764fa21b61 /mingling_picker_macros
parente6136f22cff446b16dbebf3b26a6fdea6dbc0e83 (diff)
refactor: rename `mingling_picker` to `arg_picker`
Diffstat (limited to 'mingling_picker_macros')
-rw-r--r--mingling_picker_macros/Cargo.toml20
-rw-r--r--mingling_picker_macros/README.md40
-rw-r--r--mingling_picker_macros/src/arg.rs205
-rw-r--r--mingling_picker_macros/src/internal_repeat.rs315
-rw-r--r--mingling_picker_macros/src/lib.rs32
5 files changed, 0 insertions, 612 deletions
diff --git a/mingling_picker_macros/Cargo.toml b/mingling_picker_macros/Cargo.toml
deleted file mode 100644
index 58488ea..0000000
--- a/mingling_picker_macros/Cargo.toml
+++ /dev/null
@@ -1,20 +0,0 @@
-[package]
-name = "mingling_picker_macros"
-version.workspace = true
-edition.workspace = true
-license.workspace = true
-repository.workspace = true
-authors = ["Weicao-CatilGrass"]
-readme = "README.md"
-description = "Mingling's lightweight argument parser macros"
-
-[lib]
-proc-macro = true
-
-[features]
-mingling_support = []
-
-[dependencies]
-syn.workspace = true
-quote.workspace = true
-proc-macro2.workspace = true
diff --git a/mingling_picker_macros/README.md b/mingling_picker_macros/README.md
deleted file mode 100644
index 5a3f220..0000000
--- a/mingling_picker_macros/README.md
+++ /dev/null
@@ -1,40 +0,0 @@
-# Mingling Picker Macros
-
-Procedural macros for [Mingling Picker](https://github.com/mingling-rs/mingling/tree/main/mingling_picker), enabled by the `mingling/picker` feature.
-
-```toml
-[dependencies.mingling]
-version = "0.3.0"
-features = [
- "picker"
-]
-```
-
-## Provided Macros
-
-### Macro `arg!`
-
-Declares a parameter definition for use with `Picker`'s `.pick()` method:
-
-```rust,ignore
-use mingling_picker_macros::arg;
-
-// Named flag with a value
-let flag = arg![name: String];
-
-// Named flag with short form
-let flag = arg![name: String, 'n'];
-
-// Named flag with alias
-let flag = arg![name: String, 'n', "nickname"];
-
-// Positional parameter
-let flag = arg![String];
-
-// Flag-only parameter (boolean)
-let flag = arg![verbose: Flag];
-```
-
-### Macro `internal_repeat!` (Internal)
-
-Internal macro used by Picker to generate `PickerPattern1..=32` and their parsing logic. Not intended for direct use.
diff --git a/mingling_picker_macros/src/arg.rs b/mingling_picker_macros/src/arg.rs
deleted file mode 100644
index 70b27b0..0000000
--- a/mingling_picker_macros/src/arg.rs
+++ /dev/null
@@ -1,205 +0,0 @@
-use proc_macro::{TokenStream, TokenTree};
-use proc_macro2::TokenStream as TS2;
-use quote::quote;
-
-pub(crate) fn arg(input: TokenStream) -> TokenStream {
- let tokens: Vec<TokenTree> = input.into_iter().collect();
- let args = split_at_commas(&tokens);
- if args.is_empty() {
- return quote! { compile_error!("arg! flaguires at least one argument") }.into();
- }
-
- let first = &args[0];
- let rest = &args[1..];
-
- // Validate: at most one char literal
- let char_count = rest.iter().filter(|a| is_char_literal(a)).count();
- if char_count > 1 {
- return quote! { compile_error!("arg! only supports at most one short name") }.into();
- }
-
- // Extract short char and string aliases
- let short_char: Option<char> = rest
- .iter()
- .find(|a| is_char_literal(a))
- .and_then(|a| extract_char(a));
- let aliases: Vec<String> = rest
- .iter()
- .filter(|a| is_string_literal(a))
- .filter_map(|a| extract_string(a))
- .collect();
-
- // Parse first argument
- // arg![name: Type] → name, with explicit type
- // arg![Type] → positional type, no name
- let colon_pos = first
- .iter()
- .position(|t| matches!(t, TokenTree::Punct(p) if p.as_char() == ':'));
- let has_named_colon = colon_pos.is_some_and(|pos| pos > 0 && !is_double_colon(first, pos));
-
- let first_slice = first.as_slice();
- let (name, ty, is_named) = if has_named_colon {
- let pos = colon_pos.unwrap();
- // `name : Type`
- (
- Some(&first_slice[..pos]),
- Some(&first_slice[pos + 1..]),
- true,
- )
- } else {
- // No `name:` prefix → the entire first argument is the type
- (None, Some(first_slice), false)
- };
-
- // Build full names
- let full_names: Vec<String> = match name {
- Some(n) => {
- let mut v = vec![join_idents(n)];
- v.extend(aliases);
- v
- }
- None => aliases,
- };
-
- let path: TS2 = {
- let ty_ts: TS2 = ty
- .map(|t| {
- let ts: TokenStream = t.iter().cloned().collect();
- TS2::from(ts)
- })
- .unwrap_or(TS2::new());
-
- #[cfg(feature = "mingling_support")]
- let import = quote! { ::mingling::picker::PickerArg };
-
- #[cfg(not(feature = "mingling_support"))]
- let import = quote! { ::mingling_picker::PickerArg };
-
- if ty.is_some() {
- quote! { #import::<#ty_ts> }
- } else {
- quote! { #import::<_> }
- }
- };
-
- // full: &["name", "alias", ...] or &[]
- let full_value: TS2 = if !full_names.is_empty() {
- let strs: Vec<proc_macro2::Literal> = full_names
- .iter()
- .map(|s| proc_macro2::Literal::string(s))
- .collect();
- quote! { &[#(#strs),*] }
- } else {
- quote! { &[] }
- };
-
- // short: Some('c') or None
- let short_value: TS2 = match short_char {
- Some(c) => {
- let lit = proc_macro2::Literal::character(c);
- quote! { ::std::option::Option::Some(#lit) }
- }
- None => quote! { ::std::option::Option::None },
- };
-
- let positional_value: bool = !is_named;
-
- let result = quote! {
- #path {
- full: #full_value,
- short: #short_value,
- positional: #positional_value,
- internal_type: ::std::marker::PhantomData,
- }
- };
-
- result.into()
-}
-
-fn split_at_commas(tokens: &[TokenTree]) -> Vec<Vec<TokenTree>> {
- let mut result = vec![Vec::new()];
- let mut depth = 0u32;
- for t in tokens {
- match t {
- TokenTree::Group(_g) => {
- depth += 1;
- result.last_mut().unwrap().push(t.clone());
- depth -= 1;
- }
- TokenTree::Punct(p) if p.as_char() == ',' && depth == 0 => {
- result.push(Vec::new());
- }
- _ => result.last_mut().unwrap().push(t.clone()),
- }
- }
- result
-}
-
-fn is_double_colon(tokens: &[TokenTree], pos: usize) -> bool {
- pos > 0 && matches!(&tokens[pos - 1], TokenTree::Punct(p) if p.as_char() == ':')
-}
-
-fn is_char_literal(tokens: &[TokenTree]) -> bool {
- tokens.len() == 1
- && matches!(&tokens[0], TokenTree::Literal(l) if {
- let s = l.to_string();
- s.starts_with('\'') && s.len() >= 3
- })
-}
-
-fn is_string_literal(tokens: &[TokenTree]) -> bool {
- tokens.len() == 1
- && matches!(&tokens[0], TokenTree::Literal(l) if {
- l.to_string().starts_with('"')
- })
-}
-
-fn extract_char(tokens: &[TokenTree]) -> Option<char> {
- match &tokens[0] {
- TokenTree::Literal(l) => {
- let s = l.to_string();
- let cs: Vec<char> = s.chars().collect();
- if cs.len() >= 3 && cs[0] == '\'' && cs[cs.len() - 1] == '\'' {
- let inner: String = cs[1..cs.len() - 1].iter().collect();
- // inner is the character between the quotes of a char literal.
- // For a literal `'n'`, inner is `"n"` (the character n).
- // For an escape `'\n'`, inner is the actual newline character.
- // The catch-all handles both literal single chars and escape
- // sequences (the escaped char IS the actual control character).
- match inner.as_str() {
- "\\\\" => Some('\\'),
- "\\'" => Some('\''),
- _ => inner.chars().next(),
- }
- } else {
- None
- }
- }
- _ => None,
- }
-}
-
-fn extract_string(tokens: &[TokenTree]) -> Option<String> {
- match &tokens[0] {
- TokenTree::Literal(l) => {
- let s = l.to_string();
- let cs: Vec<char> = s.chars().collect();
- if cs.len() >= 2 && cs[0] == '"' && cs[cs.len() - 1] == '"' {
- Some(cs[1..cs.len() - 1].iter().collect())
- } else {
- None
- }
- }
- _ => None,
- }
-}
-
-fn join_idents(tokens: &[TokenTree]) -> String {
- tokens
- .iter()
- .map(|t| match t {
- TokenTree::Ident(id) => id.to_string(),
- _ => String::new(),
- })
- .collect()
-}
diff --git a/mingling_picker_macros/src/internal_repeat.rs b/mingling_picker_macros/src/internal_repeat.rs
deleted file mode 100644
index 4b2242b..0000000
--- a/mingling_picker_macros/src/internal_repeat.rs
+++ /dev/null
@@ -1,315 +0,0 @@
-use proc_macro::{Delimiter, Group, Ident, Literal, TokenStream, TokenTree};
-
-pub(crate) fn internal_repeat(input: TokenStream) -> TokenStream {
- let tokens: Vec<TokenTree> = input.into_iter().collect();
- let (range_start, range_end, body_start) = parse_range(&tokens);
-
- let mut body: Vec<TokenTree> = tokens[body_start..].to_vec();
- if body.len() == 1
- && let TokenTree::Group(g) = &body[0]
- && g.delimiter() == Delimiter::Brace
- {
- body = g.stream().into_iter().collect();
- }
-
- let mut result = Vec::new();
- for i in range_start..=range_end {
- result.extend(expand_body(&body, i, range_start, range_end));
- }
- result.into_iter().collect()
-}
-
-/// Parse `start .. end =>` or `start ..= end =>` or `count =>` (backward compat).
-/// Returns `(start, end_inclusive, body_start_index)`.
-fn parse_range(tokens: &[TokenTree]) -> (usize, usize, usize) {
- // Find => separator
- let arrow_pos = tokens.windows(2).position(|w| {
- matches!(&w[0], TokenTree::Punct(p) if p.as_char() == '=')
- && matches!(&w[1], TokenTree::Punct(p) if p.as_char() == '>')
- });
-
- let (arrow_pos, body_start) = match arrow_pos {
- Some(p) => (p, p + 2),
- None => return (1, 12, 0), // fallback
- };
-
- let before: Vec<&TokenTree> = tokens[..arrow_pos].iter().collect();
-
- // Try to find `..` or `..=` pattern
- // `..` is two Punct('.') tokens
- let dotdot = before.windows(2).position(|w| {
- matches!(w[0], TokenTree::Punct(p) if p.as_char() == '.')
- && matches!(w[1], TokenTree::Punct(p) if p.as_char() == '.')
- });
-
- if let Some(dd) = dotdot {
- // Start value: tokens before `..`
- let start = parse_usize_tokens(&before[..dd]);
- let after_dd = &before[dd + 2..];
-
- // Check for `..=` (inclusive range)
- let (inclusive, end_tokens) = if after_dd
- .first()
- .is_some_and(|t| matches!(t, TokenTree::Punct(p) if p.as_char() == '='))
- {
- (true, &after_dd[1..])
- } else {
- (false, after_dd)
- };
-
- let end = parse_usize_tokens(end_tokens);
-
- if inclusive {
- (start, end, body_start)
- } else {
- // Exclusive end: if end >= start, iterate start..end, so end_inclusive = end - 1
- if end > start {
- (start, end - 1, body_start)
- } else {
- (1, 12, body_start) // fallback
- }
- }
- } else {
- // No `..` found — fallback to simple count
- let count = parse_usize_tokens(&before);
- (1, count, body_start)
- }
-}
-
-/// Parse a sequence of tokens as a single usize value.
-fn parse_usize_tokens(tokens: &[&TokenTree]) -> usize {
- let s: String = tokens
- .iter()
- .map(|t| match t {
- TokenTree::Literal(l) => l.to_string(),
- TokenTree::Ident(id) => id.to_string(),
- _ => String::new(),
- })
- .collect::<Vec<_>>()
- .join("")
- .replace(' ', "");
-
- s.parse().unwrap_or(12)
-}
-
-/// Walk tokens, replacing:
-/// `$` → current
-/// `^$` → max
-/// `$^` → min
-/// `$+` → current + 1 (clamped)
-/// `$-` → current - 1 (clamped)
-/// `ident$` → ident{current}
-/// and expanding `( … )+` / `( … ,)+` / `( … ;)+` groups.
-fn expand_body(tokens: &[TokenTree], current: usize, min: usize, max: usize) -> Vec<TokenTree> {
- let mut out = Vec::new();
- let mut i = 0;
- while i < tokens.len() {
- // Check for a parenthesized repetition group: ( ... ) sep? +
- if let Some(exp) = try_expand_paren_group(tokens, i, current, min, max) {
- let (items, consumed) = exp;
- out.extend(items);
- i += consumed;
- continue;
- }
-
- // `^$` — max value
- if let TokenTree::Punct(p) = &tokens[i]
- && p.as_char() == '^'
- && i + 1 < tokens.len()
- && let TokenTree::Punct(p2) = &tokens[i + 1]
- && p2.as_char() == '$'
- {
- out.push(TokenTree::Literal(Literal::usize_suffixed(max)));
- i += 2;
- continue;
- }
-
- // `ident$` / `ident$+` / `ident$-` / `ident$^`
- // → {ident}{current} / {ident}{current+1} / {ident}{current-1} / {ident}{min}
- if let TokenTree::Ident(id) = &tokens[i] {
- if i + 1 < tokens.len()
- && let TokenTree::Punct(p) = &tokens[i + 1]
- && p.as_char() == '$'
- {
- // Check for ident$+ / ident$- / ident$^
- if i + 2 < tokens.len() {
- match &tokens[i + 2] {
- TokenTree::Punct(p2) if p2.as_char() == '+' => {
- // ident$+ → {ident}{current+1}
- let name = format!("{}{}", id, current + 1);
- out.push(TokenTree::Ident(Ident::new(&name, id.span())));
- i += 3;
- continue;
- }
- TokenTree::Punct(p2) if p2.as_char() == '-' => {
- // ident$- → {ident}{current-1}
- let val = current.saturating_sub(1);
- let name = format!("{}{}", id, val);
- out.push(TokenTree::Ident(Ident::new(&name, id.span())));
- i += 3;
- continue;
- }
- TokenTree::Punct(p2) if p2.as_char() == '^' => {
- let name = format!("{}{}", id, min);
- out.push(TokenTree::Ident(Ident::new(&name, id.span())));
- i += 3;
- continue;
- }
- _ => {}
- }
- }
- // ident$ alone → {ident}{current}
- let name = format!("{}{}", id, current);
- out.push(TokenTree::Ident(Ident::new(&name, id.span())));
- i += 2;
- continue;
- }
- out.push(tokens[i].clone());
- i += 1;
- continue;
- }
-
- match &tokens[i] {
- TokenTree::Punct(p) if p.as_char() == '$' => {
- // lookahead for $^, $+, $-
- if i + 1 < tokens.len() {
- match &tokens[i + 1] {
- TokenTree::Punct(p2) if p2.as_char() == '^' => {
- // $^ → min
- out.push(TokenTree::Literal(Literal::usize_suffixed(min)));
- i += 2;
- continue;
- }
- TokenTree::Punct(p2) if p2.as_char() == '+' => {
- // $+ → current + 1
- out.push(TokenTree::Literal(Literal::usize_suffixed(current + 1)));
- i += 2;
- continue;
- }
- TokenTree::Punct(p2) if p2.as_char() == '-' => {
- // $- → current - 1
- let val = current.saturating_sub(1);
- out.push(TokenTree::Literal(Literal::usize_suffixed(val)));
- i += 2;
- continue;
- }
- _ => {}
- }
- }
- // `$` alone → current
- out.push(TokenTree::Literal(Literal::usize_suffixed(current)));
- i += 1;
- continue;
- }
- TokenTree::Group(g) => {
- let inner = expand_body_vec(&g.stream(), current, min, max);
- out.push(TokenTree::Group(Group::new(
- g.delimiter(),
- inner.into_iter().collect(),
- )));
- }
- other => out.push(other.clone()),
- }
- i += 1;
- }
- out
-}
-
-fn expand_body_vec(stream: &TokenStream, current: usize, min: usize, max: usize) -> Vec<TokenTree> {
- let v: Vec<TokenTree> = stream.clone().into_iter().collect();
- expand_body(&v, current, min, max)
-}
-
-/// Try to expand a repetition group.
-///
-/// New syntax (repetition marker `+` is the LAST token INSIDE the parens):
-/// - `(group,+)` — repeat `*` times (current), `,` is the separator
-/// - `(group,+)` — repeat `*` times, `,` is the separator
-/// - `(group,+)` — repeat `*` times, `;` is the separator
-/// - `(group +)` — repeat `*` times, no separator
-/// - `(group,+)+` — repeat `*+1` times (with separator)
-/// - `(group,+)--` — repeat `*-1` times (with separator) [not yet used]
-///
-/// The `*` is the current counter value. An optional `+` or `-` immediately
-/// after the closing paren shifts the repeat count up or down by one.
-fn try_expand_paren_group(
- tokens: &[TokenTree],
- i: usize,
- current: usize,
- _min: usize,
- _max: usize,
-) -> Option<(Vec<TokenTree>, usize)> {
- let group = match tokens.get(i)? {
- TokenTree::Group(g) if g.delimiter() == Delimiter::Parenthesis => g,
- _ => return None,
- };
-
- let stream: Vec<TokenTree> = group.stream().into_iter().collect();
-
- // Repetition syntax: the LAST token inside the parens MUST be `+`.
- // `(content,+)` — repeat * times, `,` separator
- // `(content;+)` — repeat * times, `;` separator
- // `(content,+)` — repeat * times, no separator
- // `(content,+)+` — repeat *+1 times
- // `(content,+)-` — repeat *-1 times
- // An optional `+` / `-` right after `)` shifts the count by ±1.
- //
- // Any `+` tokens inside `content` are treated as regular Rust syntax
- // (trait bounds, etc.) — the repetition marker is ONLY the final `+`.
-
- let last_is_plus = stream
- .last()
- .is_some_and(|t| matches!(t, TokenTree::Punct(p) if p.as_char() == '+'));
- if !last_is_plus {
- return None;
- }
-
- // Determine separator and inner content.
- let (inner, sep_str): (Vec<TokenTree>, &str) = {
- if stream.len() >= 2 {
- let sep_idx = stream.len() - 2;
- match &stream[sep_idx] {
- TokenTree::Punct(p) if p.as_char() == ',' => {
- // (...,content,+) — inner is everything before the last `,`
- let content: Vec<TokenTree> = stream[..sep_idx].into();
- (content, ",")
- }
- TokenTree::Punct(p) if p.as_char() == ';' => {
- let content: Vec<TokenTree> = stream[..sep_idx].into();
- (content, ";")
- }
- _ => {
- // (content+) — no separator, the `+` is the only special token
- let content: Vec<TokenTree> = stream[..stream.len() - 1].into();
- (content, "")
- }
- }
- } else {
- // (+) — bare repetition marker, empty content
- (vec![], "")
- }
- };
-
- // Handle modifier after `)`
- let rest = &tokens[i + 1..];
- let modifier: isize = match rest.first() {
- Some(TokenTree::Punct(p)) if p.as_char() == '+' => 1,
- Some(TokenTree::Punct(p)) if p.as_char() == '-' => -1,
- _ => 0,
- };
- let consumed = if modifier != 0 { 2 } else { 1 };
- let repeat_count = (current as isize + modifier) as usize;
-
- let mut out = Vec::new();
- for n in 1..=repeat_count {
- if n > 1 && !sep_str.is_empty() {
- out.push(TokenTree::Punct(proc_macro::Punct::new(
- sep_str.chars().next().unwrap(),
- proc_macro::Spacing::Alone,
- )));
- }
- out.extend(expand_body(&inner, n, 1, repeat_count));
- }
-
- Some((out, consumed))
-}
diff --git a/mingling_picker_macros/src/lib.rs b/mingling_picker_macros/src/lib.rs
deleted file mode 100644
index a12e3ed..0000000
--- a/mingling_picker_macros/src/lib.rs
+++ /dev/null
@@ -1,32 +0,0 @@
-#![doc = include_str!("../README.md")]
-
-use proc_macro::TokenStream;
-
-mod arg;
-mod internal_repeat;
-
-/// Core proc-macro: repeats a template body `count` times.
-///
-/// Internal call signature: `internal_repeat!(count => { template })`
-#[proc_macro]
-pub fn internal_repeat(input: TokenStream) -> TokenStream {
- internal_repeat::internal_repeat(input)
-}
-
-/// Quick builder for `PickerArg`.
-///
-/// # Syntax
-///
-/// ```ignore
-/// use mingling_picker_macros::flag;
-///
-/// let basic = arg![name: String];
-/// let with_short_name = arg![name: String, 'n'];
-/// let with_short_alias = arg![name: String, 'n', "alias"];
-/// let positional = arg![String];
-/// let positional_with_name = arg![String, 'n', "alias"];
-/// ```
-#[proc_macro]
-pub fn arg(input: TokenStream) -> TokenStream {
- arg::arg(input)
-}