use quote::{quote, ToTokens}; use syn::{ braced, bracketed, parse::{discouraged::Speculative, Parse}, punctuated::Punctuated, token, Token, }; pub struct Command { cd: CurrentDir, env_vars: EnvVars, program: Value, args: Option>, } impl Parse for Command { fn parse(input: syn::parse::ParseStream) -> syn::Result { let cd = input.parse()?; let env_vars = input.parse()?; let program = input.parse()?; if input.is_empty() { Ok(Self { cd, env_vars, program, args: None, }) } else { _ = input.parse::()?; Ok(Self { cd, env_vars, program, args: Some(Punctuated::parse_terminated(input)?), }) } } } impl ToTokens for Command { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { let Self { cd, env_vars, program, args, } = self; let program = quote! { ::std::process::Command::new(#program) }; let args = args .as_ref() .map(Punctuated::iter) .map_or_else(Vec::new, Iterator::collect); tokens.extend(quote! { { let mut _c = #program; #cd #env_vars #(#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]) { let pat_fork = input.fork(); _ = pat_fork.parse::()?; if pat_fork.call(syn::Pat::parse_single).is_ok() && pat_fork.peek(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, if_expr: Option, args: Arguments, } impl Parse for MatchArm { fn parse(input: syn::parse::ParseStream) -> syn::Result { let pattern = input.call(syn::Pat::parse_multi)?; let if_expr = if input.peek(Token![if]) { _ = input.parse::()?; Some(input.parse()?) } else { None }; _ = input.parse::]>()?; let args = input.parse()?; Ok(Self { pattern, if_expr, args, }) } } impl ToTokens for MatchArm { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { let Self { pattern, if_expr, args, } = self; tokens.extend(if_expr.as_ref().map_or_else( || { quote! { #pattern => { #args } } }, |if_expr| { quote! { #pattern if #if_expr => { #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); }); } } struct EnvVars(Option>); impl Parse for EnvVars { fn parse(input: syn::parse::ParseStream) -> syn::Result { let fork = input.fork(); let ident = fork.cursor().ident(); match ident { Some((ident, _)) if ident == "env" => { _ = fork.parse::()?; let envs; braced!(envs in fork); Punctuated::parse_terminated(&envs) .and_then(|envs| { _ = fork.parse::()?; input.advance_to(&fork); Ok(Self(Some(envs))) }) .or_else(|_| Ok(Self(None))) } _ => Ok(Self(None)), } } } impl ToTokens for EnvVars { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { let Self(envs) = self; let envs = envs .as_ref() .map_or_else(Vec::new, |envs| envs.iter().collect()); tokens.extend(quote! { #(#envs)* }); } } struct EnvVar { key: Value, value: Value, } impl Parse for EnvVar { fn parse(input: syn::parse::ParseStream) -> syn::Result { let key = input.parse()?; _ = input.parse::()?; let value = input.parse()?; Ok(Self { key, value }) } } impl ToTokens for EnvVar { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { let Self { key, value } = self; tokens.extend(quote! { _c.env(#key, #value); }); } } struct CurrentDir(Option); impl Parse for CurrentDir { fn parse(input: syn::parse::ParseStream) -> syn::Result { let fork = input.fork(); let ident = fork.cursor().ident(); match ident { Some((ident, _)) if ident == "cd" => { _ = fork.parse::(); fork.parse() .and_then(|value| { _ = fork.parse::()?; input.advance_to(&fork); Ok(Self(Some(value))) }) .or_else(|_| Ok(Self(None))) } _ => Ok(Self(None)), } } } impl ToTokens for CurrentDir { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { let Self(cd) = self; let cd = cd.iter(); tokens.extend(quote! { #(_c.current_dir(#cd);)* }); } } enum Value { Lit(syn::Lit), Ident(syn::Ident), Expr(syn::Expr), } impl Parse for Value { fn parse(input: syn::parse::ParseStream) -> syn::Result { let expr_fork = input.fork(); expr_fork .parse() .map(|expr| { input.advance_to(&expr_fork); Self::Expr(expr) }) .or_else(|_| { if input.peek(syn::Ident) { input.parse().map(Self::Ident) } else if input.peek(syn::Lit) { input.parse().map(Self::Lit) } else { Err(syn::Error::new( input.span(), "Expected an expression, ident, or literal", )) } }) } } 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 }, }); } }