548 lines
13 KiB
Rust
548 lines
13 KiB
Rust
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<Punctuated<LogicArg, Token![,]>>,
|
|
}
|
|
|
|
impl Parse for Command {
|
|
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
|
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::<Token![,]>()?;
|
|
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<Self> {
|
|
if input.peek(Token![for]) {
|
|
let pat_fork = input.fork();
|
|
_ = pat_fork.parse::<Token![for]>()?;
|
|
|
|
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<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,
|
|
if_expr: Option<Value>,
|
|
args: Arguments,
|
|
}
|
|
|
|
impl Parse for MatchArm {
|
|
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
|
let pattern = input.call(syn::Pat::parse_multi)?;
|
|
let if_expr = if input.peek(Token![if]) {
|
|
_ = input.parse::<Token![if]>()?;
|
|
Some(input.parse()?)
|
|
} else {
|
|
None
|
|
};
|
|
_ = input.parse::<Token![=>]>()?;
|
|
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<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);
|
|
});
|
|
}
|
|
}
|
|
|
|
struct EnvVars(Option<Punctuated<EnvVar, Token![,]>>);
|
|
|
|
impl Parse for EnvVars {
|
|
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
|
let fork = input.fork();
|
|
let ident = fork.cursor().ident();
|
|
|
|
match ident {
|
|
Some((ident, _)) if ident == "env" => {
|
|
_ = fork.parse::<syn::Ident>()?;
|
|
let envs;
|
|
braced!(envs in fork);
|
|
Punctuated::parse_terminated(&envs)
|
|
.and_then(|envs| {
|
|
_ = fork.parse::<Token![;]>()?;
|
|
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<Self> {
|
|
let key = input.parse()?;
|
|
_ = input.parse::<Token![:]>()?;
|
|
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<Value>);
|
|
|
|
impl Parse for CurrentDir {
|
|
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
|
let fork = input.fork();
|
|
let ident = fork.cursor().ident();
|
|
|
|
match ident {
|
|
Some((ident, _)) if ident == "cd" => {
|
|
_ = fork.parse::<syn::Ident>();
|
|
fork.parse()
|
|
.and_then(|value| {
|
|
_ = fork.parse::<Token![;]>()?;
|
|
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<Self> {
|
|
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 },
|
|
});
|
|
}
|
|
}
|