From 79912c3cc6b6995288cadb95977ce6eeab2dbd8d Mon Sep 17 00:00:00 2001 From: Gerald Pinder Date: Sat, 11 Jan 2025 00:20:15 -0500 Subject: [PATCH] Add match and closure syntax --- Cargo.lock | 10 +- Cargo.toml | 4 +- README.md | 163 +++++++++++++++++++++ src/cmd.rs | 395 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 400 ++++++++++++--------------------------------------- tests/cmd.rs | 76 ++++++++++ 6 files changed, 736 insertions(+), 312 deletions(-) create mode 100644 README.md create mode 100644 src/cmd.rs diff --git a/Cargo.lock b/Cargo.lock index 09ea70e..1c09970 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -25,7 +25,7 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "comlexr" -version = "0.1.0" +version = "1.0.0" dependencies = [ "proc-macro2", "quote", @@ -133,9 +133,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.92" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" dependencies = [ "unicode-ident", ] @@ -274,9 +274,9 @@ checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "winnow" -version = "0.6.22" +version = "0.6.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39281189af81c07ec09db316b302a3e67bf9bd7cbf6c820b50e35fee9c2fa980" +checksum = "c8d71a593cc5c42ad7876e2c1fda56f314f3754c084128833e64f1345ff8a03a" dependencies = [ "memchr", ] diff --git a/Cargo.toml b/Cargo.toml index 6bb3291..8b3f0ff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "comlexr" -version = "0.1.0" -edition = "2021" +version = "1.0.0" +edition = "2018" [lib] proc-macro = true diff --git a/README.md b/README.md new file mode 100644 index 0000000..ecf4761 --- /dev/null +++ b/README.md @@ -0,0 +1,163 @@ +# comlexr + +`comlexr` is a Rust procedural macro crate designed to simplify command expression generation by using flexible syntax constructs. It allows you to dynamically build command-line instructions based on conditional statements, loops, pattern matching, closures, and more. + +## Installation + +Add `comlexr` to your project's `Cargo.toml`: + +```toml +[dependencies] +comlexr = "1.0.0" +``` + +### Rust Edition +This project uses Rust **2018 edition** to ensure compatibility and stable language features. + +## Usage + +### Basic Command Construction +Create simple command expressions using the `cmd!` macro. + +```rust +use comlexr::cmd; + +let command = cmd!("echo", "test"); +assert_eq!(format!("{command:?}"), r#""echo" "test""#.to_string()); +``` + +### Conditional Argument Inclusion + +Use `if` statements to conditionally include arguments. + +```rust +use comlexr::cmd; + +let single = true; +let multi = false; + +let command = cmd!( + "echo", + "test", + if single => "single", + if multi => [ + "multi", + "arg", + ], +); +assert_eq!(format!("{command:?}"), r#""echo" "test" "single""#.to_string()); +``` + +### Conditional Pattern Matching + +Use `if let` syntax to conditionally include arguments based on pattern matching. + +```rust +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 the `for` syntax to iterate over collections and include multiple arguments. + +```rust +use comlexr::cmd; + +let iter = &["1", "2"]; +let command = cmd!( + "echo", + "test", + for iter, +); +assert_eq!(format!("{command:?}"), r#""echo" "test" "1" "2""#.to_string()); +``` + +### Iteration with `for in` + +Leverage the `for in` syntax to map collection elements to arguments dynamically. + +```rust +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()); +``` + +### Pattern Matching with `match` +Dynamically choose arguments based on pattern matching. + +```rust +use comlexr::cmd; + +enum TestArgs { + Arg1, + Arg2, + Arg3, +} + +let match_arg = TestArgs::Arg2; +let command = cmd!( + "echo", + "test", + match match_arg { + TestArgs::Arg1 => "arg1", + TestArgs::Arg2 => ["arg1", "arg2"], + TestArgs::Arg3 => ["arg1", "arg2", "arg3"], + } +); +assert_eq!(format!("{command:?}"), r#""echo" "test" "arg1" "arg2""#.to_string()); +``` + +### Closures for Dynamic Argument Generation +Generate arguments on the fly using closures. The closure must return a type that implements `IntoIterator`. + +```rust +use comlexr::cmd; + +let arr = vec![1, 2, 3]; +let input = 2; + +let command = cmd!( + "echo", + "test", + || arr.into_iter().map(|i| format!("{}", i * input)) +); +assert_eq!(format!("{command:?}"), r#""echo" "test" "2" "4" "6""#.to_string()); +``` + +## Features +- Conditional expressions (`if`, `if let`) +- Iteration constructs (`for`, `for in`) +- Pattern matching (`match`) +- Support for closures and dynamic expressions + +## Examples +See the [tests](./tests/) directory for more examples on how to use `comlexr` effectively in your project. + +## License +This project is licensed under the [MIT License](LICENSE). diff --git a/src/cmd.rs b/src/cmd.rs new file mode 100644 index 0000000..86799b1 --- /dev/null +++ b/src/cmd.rs @@ -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>, +} + +impl Parse for Command { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let program = input.parse()?; + + if input.is_empty() { + Ok(Self { + program, + args: None, + }) + } else { + _ = input.parse::()?; + 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 { + 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 { + _ = input.parse::()?; + 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 { + _ = input.parse::()?; + let pattern = input.call(syn::Pat::parse_single)?; + _ = input.parse::()?; + let iter = input.parse()?; + _ = input.parse::]>()?; + 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 { + _ = input.parse::()?; + _ = input.parse::()?; + let pattern = input.call(syn::Pat::parse_single)?; + _ = input.parse::()?; + let expr = input.parse()?; + _ = input.parse::]>()?; + 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 { + _ = input.parse::()?; + let expr = input.parse()?; + _ = input.parse::]>()?; + 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, +} + +impl Parse for Match { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + _ = input.parse::()?; + 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 { + let pattern = input.call(syn::Pat::parse_multi)?; + _ = input.parse::]>()?; + 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 { + _ = input.parse::()?; + + 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 { + 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); + +impl Parse for MultiArg { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + 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::>(); + + tokens.extend(quote! { #(#args)* }); + } +} + +struct SingleArg(Value); + +impl Parse for SingleArg { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + 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 { + 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 }, + }); + } +} diff --git a/src/lib.rs b/src/lib.rs index 609632c..ebc0299 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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>, -} +extern crate quote; +extern crate syn; -impl Parse for Command { - fn parse(input: syn::parse::ParseStream) -> syn::Result { - let program = input.parse()?; +use quote::quote; +use syn::parse_macro_input; - if input.is_empty() { - Ok(Self { - program, - args: None, - }) - } else { - _ = input.parse::()?; - 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 { - 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 { - _ = input.parse::()?; - 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 { - _ = input.parse::()?; - let pattern = input.call(syn::Pat::parse_single)?; - _ = input.parse::()?; - let iter = input.parse()?; - _ = input.parse::]>()?; - 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 { - _ = input.parse::()?; - _ = input.parse::()?; - let pattern = input.call(syn::Pat::parse_single)?; - _ = input.parse::()?; - let expr = input.parse()?; - _ = input.parse::]>()?; - 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 { - _ = input.parse::()?; - let expr = input.parse()?; - _ = input.parse::]>()?; - 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 { - 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); - -impl Parse for MultiArg { - fn parse(input: syn::parse::ParseStream) -> syn::Result { - 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::>(); - - tokens.extend(quote! { #(#args)* }); - } -} - -struct SingleArg(Value); - -impl Parse for SingleArg { - fn parse(input: syn::parse::ParseStream) -> syn::Result { - 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 { - 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() } diff --git a/tests/cmd.rs b/tests/cmd.rs index b7cc246..8154389 100644 --- a/tests/cmd.rs +++ b/tests/cmd.rs @@ -1,3 +1,6 @@ +extern crate comlexr; +extern crate rstest; + use comlexr::cmd; use rstest::rstest; @@ -77,3 +80,76 @@ fn if_let(#[case] single: Option<&str>, #[case] multi: Option<&str>, #[case] exp assert_eq!(format!("{command:?}"), expected.to_string()); } + +enum TestArgs { + Arg1, + Arg2, + Arg3, +} + +#[rstest] +#[case(TestArgs::Arg1, r#""echo" "test" "arg1""#)] +#[case(TestArgs::Arg2, r#""echo" "test" "arg1" "arg2""#)] +#[case(TestArgs::Arg3, r#""echo" "test" "arg1" "arg2" "arg3""#)] +fn match_statement(#[case] match_arg: TestArgs, #[case] expected: &str) { + let command = cmd!( + "echo", + "test", + match match_arg { + TestArgs::Arg1 => "arg1", + TestArgs::Arg2 => ["arg1", "arg2"], + TestArgs::Arg3 => ["arg1", "arg2", "arg3"], + } + ); + + assert_eq!(format!("{command:?}"), expected.to_string()); +} + +#[rstest] +#[case(None, None, r#""echo" "test""#)] +#[case(Some("arg"), None, r#""echo" "test" "arg""#)] +#[case(None, Some("arg"), r#""echo" "test" "multi" "arg""#)] +#[case(Some("1"), Some("2"), r#""echo" "test" "1" "multi" "2""#)] +fn multi_match(#[case] single: Option<&str>, #[case] multi: Option<&str>, #[case] expected: &str) { + let command = cmd!( + "echo", + "test", + match (single, multi) { + (None, None) => [], + (Some(single), None) => single, + (None, Some(multi)) => ["multi", multi], + (Some(single), Some(multi)) => [single, "multi", multi], + }, + ); + + assert_eq!(format!("{command:?}"), expected.to_string()); +} + +#[rstest] +#[case(1, r#""echo" "test" "1" "2" "3""#)] +#[case(2, r#""echo" "test" "2" "4" "6""#)] +#[case(3, r#""echo" "test" "3" "6" "9""#)] +fn closure_expr(#[case] input: usize, #[case] expected: &str) { + let arr = vec![1, 2, 3]; + let command = cmd!("echo", "test", || arr + .into_iter() + .map(|i| format!("{}", i * input))); + + assert_eq!(format!("{command:?}"), expected.to_string()); +} + +#[rstest] +#[case(1, r#""echo" "test" "--test=1a" "--test=2a" "--test=3a""#)] +#[case(2, r#""echo" "test" "--test=2a" "--test=4a" "--test=6a""#)] +#[case(3, r#""echo" "test" "--test=3a" "--test=6a" "--test=9a""#)] +fn closure_block(#[case] input: usize, #[case] expected: &str) { + let arr = vec![1, 2, 3]; + let suffix = "a"; + let command = cmd!("echo", "test", || { + let prefix = "--test"; + arr.into_iter() + .map(move |i| format!("{prefix}={}{suffix}", i * input)) + }); + + assert_eq!(format!("{command:?}"), expected.to_string()); +}