use quote::{quote, ToTokens};
use syn::{
braced, bracketed,
parse::{discouraged::Speculative, Parse},
punctuated::Punctuated,
token, Token,
};
use crate::macros::enum_to_tokens;
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
}
});
}
}
pub struct CommandMut {
cd: CurrentDir,
env_vars: EnvVars,
cmd: ValueNoLit,
args: Option<Punctuated<LogicArg, Token![,]>>,
}
impl Parse for CommandMut {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let cd = input.parse()?;
let env_vars = input.parse()?;
let cmd = input.parse()?;
if input.is_empty() {
Ok(Self {
cd,
env_vars,
cmd,
args: None,
})
} else {
_ = input.parse::<Token![,]>()?;
Ok(Self {
cd,
env_vars,
cmd,
args: Some(Punctuated::parse_terminated(input)?),
})
}
}
}
impl ToTokens for CommandMut {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let Self {
cd,
env_vars,
cmd,
args,
} = self;
let args = args
.as_ref()
.map(Punctuated::iter)
.map_or_else(Vec::new, Iterator::collect);
tokens.extend(quote! {
{
let _c: &mut ::std::process::Command = #cmd;
#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)
}
}
}
enum_to_tokens! {LogicArg: Expr, ForIter, ForIn, IfLet, If, Match, 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)
}
}
}
enum_to_tokens! {Arguments: Single, Multi}
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,
conditional: bool,
}
impl Parse for EnvVar {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let key = input.parse()?;
_ = input.parse::<Token![:]>()?;
let conditional = if input.lookahead1().peek(Token![?]) {
input.parse::<Token![?]>()?;
true
} else {
false
};
let value = input.parse()?;
Ok(Self {
key,
value,
conditional,
})
}
}
impl ToTokens for EnvVar {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let Self {
key,
value,
conditional,
} = self;
if *conditional {
tokens.extend(quote! {
if ::std::env::var(#key).ok().is_none() {
_c.env(#key, #value);
}
});
} else {
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);)* });
}
}
pub 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",
))
}
})
}
}
enum_to_tokens! {Value: Lit, Ident, Expr}
pub enum ValueNoLit {
Ident(syn::Ident),
Expr(syn::Expr),
}
impl Parse for ValueNoLit {
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 {
Err(syn::Error::new(
input.span(),
"Expected an expression, ident, or literal",
))
}
})
}
}
enum_to_tokens! {ValueNoLit: Ident, Expr}