Add match and closure syntax

This commit is contained in:
2025-01-11 00:20:15 -05:00
parent 5602c66b11
commit 79912c3cc6
6 changed files with 736 additions and 312 deletions

395
src/cmd.rs Normal file
View File

@@ -0,0 +1,395 @@
use quote::{quote, ToTokens};
use syn::{braced, bracketed, parse::Parse, punctuated::Punctuated, token, Token};
pub struct Command {
program: Value,
args: Option<Punctuated<LogicArg, Token![,]>>,
}
impl Parse for Command {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let program = input.parse()?;
if input.is_empty() {
Ok(Self {
program,
args: None,
})
} else {
_ = input.parse::<Token![,]>()?;
Ok(Self {
program,
args: Some(Punctuated::parse_terminated(input)?),
})
}
}
}
impl ToTokens for Command {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let Self { program, args } = self;
let program = quote! { ::std::process::Command::new(#program) };
let args = args.as_ref().map(Punctuated::iter);
tokens.extend(args.map_or_else(
|| quote! { #program },
|args| {
quote! {
{
let mut _c = #program;
#(#args)*
_c
}
}
},
));
}
}
enum LogicArg {
Expr(SingleArg),
ForIter(ForIter),
ForIn(ForIn),
IfLet(IfLet),
If(If),
Match(Match),
Closure(Closure),
}
impl Parse for LogicArg {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
if input.peek(Token![for]) {
if input.peek3(Token![in]) {
input.parse().map(Self::ForIn)
} else {
input.parse().map(Self::ForIter)
}
} else if input.peek(Token![if]) {
if input.peek2(Token![let]) {
input.parse().map(Self::IfLet)
} else {
input.parse().map(Self::If)
}
} else if input.peek(Token![match]) {
input.parse().map(Self::Match)
} else if input.peek(Token![||]) {
input.parse().map(Self::Closure)
} else {
input.parse().map(Self::Expr)
}
}
}
impl ToTokens for LogicArg {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
tokens.extend(match self {
Self::Expr(expr) => quote! { #expr },
Self::ForIter(for_iter) => quote! { #for_iter },
Self::ForIn(for_in) => quote! { #for_in },
Self::IfLet(if_let) => quote! { #if_let },
Self::If(if_) => quote! { #if_ },
Self::Match(match_) => quote! { #match_ },
Self::Closure(closure) => quote! { #closure },
});
}
}
struct ForIter {
expr: Value,
}
impl Parse for ForIter {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
_ = input.parse::<Token![for]>()?;
let expr = input.parse()?;
Ok(Self { expr })
}
}
impl ToTokens for ForIter {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let expr = &self.expr;
tokens.extend(quote! {
for _a in #expr {
_c.arg(_a);
}
});
}
}
struct ForIn {
pattern: syn::Pat,
iter: Value,
args: Arguments,
}
impl Parse for ForIn {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
_ = input.parse::<Token![for]>()?;
let pattern = input.call(syn::Pat::parse_single)?;
_ = input.parse::<Token![in]>()?;
let iter = input.parse()?;
_ = input.parse::<Token![=>]>()?;
let args = input.parse()?;
Ok(Self {
pattern,
iter,
args,
})
}
}
impl ToTokens for ForIn {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let Self {
pattern,
iter,
args,
} = self;
tokens.extend(quote! {
for #pattern in #iter {
#args
}
});
}
}
struct IfLet {
pattern: syn::Pat,
expr: Value,
args: Arguments,
}
impl Parse for IfLet {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
_ = input.parse::<Token![if]>()?;
_ = input.parse::<Token![let]>()?;
let pattern = input.call(syn::Pat::parse_single)?;
_ = input.parse::<Token![=]>()?;
let expr = input.parse()?;
_ = input.parse::<Token![=>]>()?;
let args = input.parse()?;
Ok(Self {
pattern,
expr,
args,
})
}
}
impl ToTokens for IfLet {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let Self {
pattern,
expr,
args,
} = &self;
tokens.extend(quote! {
if let #pattern = #expr {
#args
}
});
}
}
struct If {
expr: Value,
args: Arguments,
}
impl Parse for If {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
_ = input.parse::<Token![if]>()?;
let expr = input.parse()?;
_ = input.parse::<Token![=>]>()?;
let args = input.parse()?;
Ok(Self { expr, args })
}
}
impl ToTokens for If {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let Self { expr, args } = self;
tokens.extend(quote! {
if #expr {
#args
}
});
}
}
struct Match {
expr: Value,
match_arms: Punctuated<MatchArm, Token![,]>,
}
impl Parse for Match {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
_ = input.parse::<Token![match]>()?;
let expr = input.parse()?;
let arms;
braced!(arms in input);
let match_arms = Punctuated::parse_terminated(&arms)?;
Ok(Self { expr, match_arms })
}
}
impl ToTokens for Match {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let Self { expr, match_arms } = self;
let match_arms = match_arms.iter();
tokens.extend(quote! {
match #expr {
#(#match_arms)*
}
});
}
}
struct MatchArm {
pattern: syn::Pat,
args: Arguments,
}
impl Parse for MatchArm {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let pattern = input.call(syn::Pat::parse_multi)?;
_ = input.parse::<Token![=>]>()?;
let args = input.parse()?;
Ok(Self { pattern, args })
}
}
impl ToTokens for MatchArm {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let Self { pattern, args } = self;
tokens.extend(quote! {
#pattern => {
#args
}
});
}
}
struct Closure(syn::Expr);
impl Parse for Closure {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
_ = input.parse::<Token![||]>()?;
input.parse().map(Self)
}
}
impl ToTokens for Closure {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let Self(block) = self;
tokens.extend(quote! {
let _fn = || #block;
for _a in _fn() {
_c.arg(_a);
}
});
}
}
enum Arguments {
Single(SingleArg),
Multi(MultiArg),
}
impl Parse for Arguments {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
if input.peek(token::Bracket) {
input.parse().map(Self::Multi)
} else {
input.parse().map(Self::Single)
}
}
}
impl ToTokens for Arguments {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
tokens.extend(match self {
Self::Single(arg) => quote! { #arg },
Self::Multi(args) => quote! { #args },
});
}
}
struct MultiArg(Punctuated<SingleArg, Token![,]>);
impl Parse for MultiArg {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let args;
bracketed!(args in input);
Punctuated::parse_terminated(&args).map(Self)
}
}
impl ToTokens for MultiArg {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let args = self.0.iter().collect::<Vec<_>>();
tokens.extend(quote! { #(#args)* });
}
}
struct SingleArg(Value);
impl Parse for SingleArg {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
input.parse().map(Self)
}
}
impl ToTokens for SingleArg {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let arg = &self.0;
tokens.extend(quote! {
_c.arg(#arg);
});
}
}
enum Value {
Lit(syn::Lit),
Ident(syn::Ident),
Expr(syn::Expr),
}
impl Parse for Value {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
if input.peek(syn::Lit) {
input.parse().map(Self::Lit)
} else if input.peek(syn::Ident) {
input.parse().map(Self::Ident)
} else {
input.parse().map(Self::Expr)
}
}
}
impl ToTokens for Value {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
tokens.extend(match self {
Self::Lit(lit) => quote! { #lit },
Self::Ident(ident) => quote! { #ident },
Self::Expr(expr) => quote! { #expr },
});
}
}

View File

@@ -1,313 +1,103 @@
use quote::{quote, ToTokens};
use syn::{bracketed, parse::Parse, parse_macro_input, punctuated::Punctuated, token, Token};
#![doc = include_str!("../README.md")]
struct Command {
program: Value,
args: Option<Punctuated<Arg, Token![,]>>,
}
extern crate quote;
extern crate syn;
impl Parse for Command {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let program = input.parse()?;
use quote::quote;
use syn::parse_macro_input;
if input.is_empty() {
Ok(Self {
program,
args: None,
})
} else {
_ = input.parse::<Token![,]>()?;
Ok(Self {
program,
args: Some(Punctuated::parse_terminated(input)?),
})
}
}
}
impl ToTokens for Command {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let Self { program, args } = self;
let program = quote! { ::std::process::Command::new(#program) };
let args = args.as_ref().map(|args| args.iter());
tokens.extend(args.map_or_else(
|| quote! { #program },
|args| {
quote! {
{
let mut command = #program;
#(#args)*
command
}
}
},
));
}
}
enum Arg {
Expr(SingleArg),
ForIter(ForIter),
ForIn(ForIn),
IfLet(IfLet),
If(If),
}
impl Parse for Arg {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
if input.peek(Token![for]) {
if input.peek3(Token![in]) {
input.parse().map(Self::ForIn)
} else {
input.parse().map(Self::ForIter)
}
} else if input.peek(Token![if]) {
if input.peek2(Token![let]) {
input.parse().map(Self::IfLet)
} else {
input.parse().map(Self::If)
}
} else {
input.parse().map(Self::Expr)
}
}
}
impl ToTokens for Arg {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
tokens.extend(match self {
Self::Expr(expr) => quote! { #expr },
Self::ForIter(for_iter) => quote! { #for_iter },
Self::ForIn(for_in) => quote! { #for_in },
Self::IfLet(if_let) => quote! { #if_let },
Self::If(if_) => quote! { #if_ },
});
}
}
struct ForIter {
expr: Value,
}
impl Parse for ForIter {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
_ = input.parse::<Token![for]>()?;
let expr = input.parse()?;
Ok(Self { expr })
}
}
impl ToTokens for ForIter {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let expr = &self.expr;
tokens.extend(quote! {
for arg in #expr {
command.arg(arg);
}
});
}
}
struct ForIn {
pattern: syn::Pat,
iter: Value,
args: SingleMultiArg,
}
impl Parse for ForIn {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
_ = input.parse::<Token![for]>()?;
let pattern = input.call(syn::Pat::parse_single)?;
_ = input.parse::<Token![in]>()?;
let iter = input.parse()?;
_ = input.parse::<Token![=>]>()?;
let args = input.parse()?;
Ok(Self {
pattern,
iter,
args,
})
}
}
impl ToTokens for ForIn {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let Self {
pattern,
iter,
args,
} = self;
tokens.extend(quote! {
for #pattern in #iter {
#args
}
});
}
}
struct IfLet {
pattern: syn::Pat,
expr: Value,
args: SingleMultiArg,
}
impl Parse for IfLet {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
_ = input.parse::<Token![if]>()?;
_ = input.parse::<Token![let]>()?;
let pattern = input.call(syn::Pat::parse_single)?;
_ = input.parse::<Token![=]>()?;
let expr = input.parse()?;
_ = input.parse::<Token![=>]>()?;
let args = input.parse()?;
Ok(Self {
pattern,
expr,
args,
})
}
}
impl ToTokens for IfLet {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let Self {
pattern,
expr,
args,
} = &self;
tokens.extend(quote! {
if let #pattern = #expr {
#args
}
});
}
}
struct If {
expr: Value,
args: SingleMultiArg,
}
impl Parse for If {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
_ = input.parse::<Token![if]>()?;
let expr = input.parse()?;
_ = input.parse::<Token![=>]>()?;
let args = input.parse()?;
Ok(Self { expr, args })
}
}
impl ToTokens for If {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let Self { expr, args } = self;
tokens.extend(quote! {
if #expr {
#args
}
});
}
}
enum SingleMultiArg {
Single(SingleArg),
Multi(MultiArg),
}
impl Parse for SingleMultiArg {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
if input.peek(token::Bracket) {
input.parse().map(Self::Multi)
} else {
input.parse().map(Self::Single)
}
}
}
impl ToTokens for SingleMultiArg {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
tokens.extend(match self {
Self::Single(arg) => quote! { #arg },
Self::Multi(args) => quote! { #args },
});
}
}
struct MultiArg(Punctuated<SingleArg, Token![,]>);
impl Parse for MultiArg {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let args;
bracketed!(args in input);
Punctuated::parse_terminated(&args).map(Self)
}
}
impl ToTokens for MultiArg {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let args = self.0.iter().collect::<Vec<_>>();
tokens.extend(quote! { #(#args)* });
}
}
struct SingleArg(Value);
impl Parse for SingleArg {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
input.parse().map(Self)
}
}
impl ToTokens for SingleArg {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let arg = &self.0;
tokens.extend(quote! {
command.arg(#arg);
});
}
}
enum Value {
Lit(syn::Lit),
Ident(syn::Ident),
Expr(syn::Expr),
}
impl Parse for Value {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
if input.peek(syn::Lit) {
input.parse().map(Self::Lit)
} else if input.peek(syn::Ident) {
input.parse().map(Self::Ident)
} else {
input.parse().map(Self::Expr)
}
}
}
impl ToTokens for Value {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
tokens.extend(match self {
Self::Lit(lit) => quote! { #lit },
Self::Ident(ident) => quote! { #ident },
Self::Expr(expr) => quote! { #expr },
});
}
}
mod cmd;
/// Generates a command expression by combining static strings, conditional logic, loops,
/// pattern matching, and closures to dynamically build and format command-line arguments.
///
/// The `cmd!` macro supports flexible syntax constructs such as:
/// - **Static arguments**: Basic string arguments.
/// - **Conditional inclusion**: Using `if` or `if let` to conditionally include arguments.
/// - **Iterative inclusion**: Using `for` loops to include multiple arguments from collections.
/// - **Pattern matching**: Using `match` expressions for dynamic argument selection.
/// - **Closures**: Dynamically generating arguments at runtime.
///
/// # Examples
///
/// ## Basic Usage
/// ```
/// use comlexr::cmd;
///
/// let command = cmd!("echo", "test");
/// assert_eq!(format!("{command:?}"), r#""echo" "test""#.to_string());
/// ```
///
/// ## Conditional Arguments
/// ```
/// use comlexr::cmd;
///
/// let include_arg = true;
///
/// let command = cmd!("echo", "test", if include_arg => "optional_arg");
/// assert_eq!(format!("{command:?}"), r#""echo" "test" "optional_arg""#.to_string());
/// ```
///
/// ## Conditional Pattern Matching
/// ```
/// use comlexr::cmd;
///
/// let single_option = Some("single");
/// let multi_option: Option<&str> = None;
///
/// let command = cmd!(
/// "echo",
/// "test",
/// if let Some(arg) = single_option => arg,
/// if let Some(arg) = multi_option => [
/// "multi",
/// arg,
/// ],
/// );
/// assert_eq!(format!("{command:?}"), r#""echo" "test" "single""#.to_string());
/// ```
///
/// ## Iterative Argument Inclusion
/// ```
/// use comlexr::cmd;
///
/// let args = &["arg1", "arg2"];
///
/// let command = cmd!("echo", for args);
/// assert_eq!(format!("{command:?}"), r#""echo" "arg1" "arg2""#.to_string());
/// ```
///
/// ## Iteration with `for in`
/// ```
/// use comlexr::cmd;
///
/// let single_iter = &["arg1", "arg2"];
/// let multi_iter = &["multi1", "multi2"];
///
/// let command = cmd!(
/// "echo",
/// "test",
/// for arg in single_iter => arg,
/// for arg in multi_iter => [
/// "multi",
/// arg,
/// ],
/// );
/// assert_eq!(format!("{command:?}"), r#""echo" "test" "arg1" "arg2" "multi" "multi1" "multi" "multi2""#.to_string());
/// ```
///
/// ## Dynamic Closures
/// ```
/// use comlexr::cmd;
///
/// let numbers = vec![1, 2, 3];
/// let multiplier = 2;
///
/// let command = cmd!("echo", || numbers.into_iter().map(|n| format!("{}", n * multiplier)));
/// assert_eq!(format!("{command:?}"), r#""echo" "2" "4" "6""#.to_string());
/// ```
#[proc_macro]
pub fn cmd(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let command = parse_macro_input!(input as Command);
let command = parse_macro_input!(input as cmd::Command);
quote! { #command }.into()
}