Add match and closure syntax
This commit is contained in:
10
Cargo.lock
generated
10
Cargo.lock
generated
@@ -25,7 +25,7 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "comlexr"
|
name = "comlexr"
|
||||||
version = "0.1.0"
|
version = "1.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -133,9 +133,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.92"
|
version = "1.0.93"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
|
checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
@@ -274,9 +274,9 @@ checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winnow"
|
name = "winnow"
|
||||||
version = "0.6.22"
|
version = "0.6.24"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "39281189af81c07ec09db316b302a3e67bf9bd7cbf6c820b50e35fee9c2fa980"
|
checksum = "c8d71a593cc5c42ad7876e2c1fda56f314f3754c084128833e64f1345ff8a03a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "comlexr"
|
name = "comlexr"
|
||||||
version = "0.1.0"
|
version = "1.0.0"
|
||||||
edition = "2021"
|
edition = "2018"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
proc-macro = true
|
proc-macro = true
|
||||||
|
|||||||
163
README.md
Normal file
163
README.md
Normal file
@@ -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).
|
||||||
395
src/cmd.rs
Normal file
395
src/cmd.rs
Normal 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 },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
400
src/lib.rs
400
src/lib.rs
@@ -1,313 +1,103 @@
|
|||||||
use quote::{quote, ToTokens};
|
#![doc = include_str!("../README.md")]
|
||||||
use syn::{bracketed, parse::Parse, parse_macro_input, punctuated::Punctuated, token, Token};
|
|
||||||
|
|
||||||
struct Command {
|
extern crate quote;
|
||||||
program: Value,
|
extern crate syn;
|
||||||
args: Option<Punctuated<Arg, Token![,]>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Parse for Command {
|
use quote::quote;
|
||||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
use syn::parse_macro_input;
|
||||||
let program = input.parse()?;
|
|
||||||
|
|
||||||
if input.is_empty() {
|
mod cmd;
|
||||||
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 },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/// 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]
|
#[proc_macro]
|
||||||
pub fn cmd(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
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()
|
quote! { #command }.into()
|
||||||
}
|
}
|
||||||
|
|||||||
76
tests/cmd.rs
76
tests/cmd.rs
@@ -1,3 +1,6 @@
|
|||||||
|
extern crate comlexr;
|
||||||
|
extern crate rstest;
|
||||||
|
|
||||||
use comlexr::cmd;
|
use comlexr::cmd;
|
||||||
use rstest::rstest;
|
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());
|
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());
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user