Get macro functional with tests
This commit is contained in:
333
src/lib.rs
333
src/lib.rs
@@ -1,7 +1,8 @@
|
||||
use syn::{bracketed, parse::Parse, punctuated::Punctuated, token, Token};
|
||||
use quote::{quote, ToTokens};
|
||||
use syn::{bracketed, parse::Parse, parse_macro_input, punctuated::Punctuated, token, Token};
|
||||
|
||||
struct Command {
|
||||
program: Program,
|
||||
program: Value,
|
||||
args: Option<Punctuated<Arg, Token![,]>>,
|
||||
}
|
||||
|
||||
@@ -10,119 +11,257 @@ impl Parse for Command {
|
||||
let program = input.parse()?;
|
||||
|
||||
if input.is_empty() {
|
||||
Ok(Command {
|
||||
Ok(Self {
|
||||
program,
|
||||
args: None,
|
||||
})
|
||||
} else {
|
||||
_ = input.parse::<Token![,]>()?;
|
||||
Ok(Command {
|
||||
Ok(Self {
|
||||
program,
|
||||
args: Some(input.parse_terminated(Arg::parse, Token![,])?),
|
||||
args: Some(Punctuated::parse_terminated(input)?),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Program(syn::LitStr);
|
||||
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());
|
||||
|
||||
impl Parse for Program {
|
||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||
input.parse().map(Self)
|
||||
tokens.extend(args.map_or_else(
|
||||
|| quote! { #program },
|
||||
|args| {
|
||||
quote! {
|
||||
{
|
||||
let mut command = #program;
|
||||
#(#args)*
|
||||
command
|
||||
}
|
||||
}
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
enum Arg {
|
||||
Expression(syn::Expr),
|
||||
ForIter {
|
||||
for_key: Token![for],
|
||||
expr: syn::Expr,
|
||||
},
|
||||
ForInIterMulti {
|
||||
for_iter: ForIter,
|
||||
arrow_token: Token![=>],
|
||||
args: MultiArg,
|
||||
},
|
||||
ForInIterSingle {
|
||||
for_iter: ForIter,
|
||||
arrow_token: Token![=>],
|
||||
arg: SingleArg,
|
||||
},
|
||||
IfLetMulti {
|
||||
if_let: IfLet,
|
||||
arrow_token: Token![=>],
|
||||
args: MultiArg,
|
||||
},
|
||||
IfLetSingle {
|
||||
if_let: IfLet,
|
||||
arrow_token: Token![=>],
|
||||
arg: SingleArg,
|
||||
},
|
||||
IfMulti {
|
||||
expr: syn::Expr,
|
||||
arrow_token: Token![=>],
|
||||
args: MultiArg,
|
||||
},
|
||||
IfSingle {
|
||||
expr: syn::Expr,
|
||||
arrow_token: Token![=>],
|
||||
arg: SingleArg,
|
||||
},
|
||||
Expr(SingleArg),
|
||||
ForIter(ForIter),
|
||||
ForIn(ForIn),
|
||||
IfLet(IfLet),
|
||||
If(If),
|
||||
}
|
||||
|
||||
impl Parse for Arg {
|
||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||
input.parse()
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Pattern(syn::Pat);
|
||||
|
||||
impl Parse for Pattern {
|
||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||
input.call(syn::Pat::parse_single).map(Self)
|
||||
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 {
|
||||
for_token: Token![for],
|
||||
pattern: Pattern,
|
||||
in_token: Token![in],
|
||||
iter: syn::Expr,
|
||||
expr: Value,
|
||||
}
|
||||
|
||||
impl Parse for ForIter {
|
||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||
Ok(ForIter {
|
||||
for_token: input.parse()?,
|
||||
pattern: input.parse()?,
|
||||
in_token: input.parse()?,
|
||||
iter: input.parse()?,
|
||||
_ = 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 {
|
||||
if_token: Token![if],
|
||||
let_token: Token![let],
|
||||
pattern: Pattern,
|
||||
eq_token: Token![=],
|
||||
expr: syn::Expr,
|
||||
pattern: syn::Pat,
|
||||
expr: Value,
|
||||
args: SingleMultiArg,
|
||||
}
|
||||
|
||||
impl Parse for IfLet {
|
||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||
Ok(IfLet {
|
||||
if_token: input.parse()?,
|
||||
let_token: input.parse()?,
|
||||
pattern: input.parse()?,
|
||||
eq_token: input.parse()?,
|
||||
expr: input.parse()?,
|
||||
_ = 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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct SingleArg(syn::Expr);
|
||||
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> {
|
||||
@@ -130,17 +269,45 @@ impl Parse for SingleArg {
|
||||
}
|
||||
}
|
||||
|
||||
struct MultiArg {
|
||||
bracket: token::Bracket,
|
||||
args: Punctuated<SingleArg, Token![,]>,
|
||||
}
|
||||
|
||||
impl Parse for MultiArg {
|
||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||
let args;
|
||||
Ok(MultiArg {
|
||||
bracket: bracketed!(args in input),
|
||||
args: input.parse_terminated(SingleArg::parse, Token![,])?,
|
||||
})
|
||||
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 },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[proc_macro]
|
||||
pub fn cmd(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
let command = parse_macro_input!(input as Command);
|
||||
quote! { #command }.into()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user