192 lines
6.0 KiB
Rust
192 lines
6.0 KiB
Rust
use quote::{quote, ToTokens};
|
|
use syn::{
|
|
parse::{discouraged::Speculative, Parse},
|
|
punctuated::Punctuated,
|
|
spanned::Spanned,
|
|
Error, Token,
|
|
};
|
|
|
|
use crate::cmd::Value;
|
|
|
|
pub struct Pipe {
|
|
stdin: Option<Value>,
|
|
commands: Punctuated<CommandExpr, Token![|]>,
|
|
}
|
|
|
|
impl Parse for Pipe {
|
|
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
|
let stdin = match input.cursor().ident() {
|
|
Some((ident, _)) if ident == "stdin" && input.peek2(Token![=]) => {
|
|
_ = input.parse::<syn::Ident>()?;
|
|
_ = input.parse::<Token![=]>()?;
|
|
let stdin = input.parse()?;
|
|
_ = input.parse::<Token![;]>()?;
|
|
Some(stdin)
|
|
}
|
|
_ => None,
|
|
};
|
|
let commands = Punctuated::parse_separated_nonempty(input)?;
|
|
|
|
if commands.is_empty() {
|
|
return Err(Error::new_spanned(
|
|
commands,
|
|
"At least one command is required",
|
|
));
|
|
}
|
|
|
|
Ok(Self { stdin, commands })
|
|
}
|
|
}
|
|
|
|
impl ToTokens for Pipe {
|
|
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
|
let Self { stdin, commands } = self;
|
|
let command_count = commands.len();
|
|
let last_command_span = commands.last().unwrap().span();
|
|
let mut commands_iter = commands.iter();
|
|
let first_command = commands_iter.next().unwrap();
|
|
|
|
let initial = quote! {
|
|
let mut _c_0: #first_command;
|
|
_c_0.stdout(::std::process::Stdio::piped());
|
|
_c_0.stdin(::std::process::Stdio::piped());
|
|
let mut _child_0 = _c_0.spawn()?;
|
|
|
|
if let Some(stdin) = stdin {
|
|
_child_0
|
|
.stdin
|
|
.as_mut()
|
|
.ok_or(::comlexr::ExecutorError::NoStdIn)?
|
|
.write_all(stdin.as_ref())?;
|
|
}
|
|
};
|
|
|
|
let stdin = stdin.as_ref().map_or_else(
|
|
|| {
|
|
quote! {
|
|
None::<&[u8]>
|
|
}
|
|
},
|
|
|stdin| {
|
|
quote! {
|
|
Some(#stdin)
|
|
}
|
|
},
|
|
);
|
|
|
|
let commands = commands_iter.enumerate().map(|(index, command)| {
|
|
let previous_span = if index == 0 {
|
|
first_command.span()
|
|
} else {
|
|
commands[index - 1].span()
|
|
};
|
|
let prev_com_ident = syn::Ident::new(&format!("_c_{index}"), previous_span);
|
|
let prev_child_ident = syn::Ident::new(&format!("_child_{index}"), previous_span);
|
|
let com_ident = syn::Ident::new(&format!("_c_{}", index + 1), command.span());
|
|
let child_ident = syn::Ident::new(&format!("_child_{}", index + 1), command.span());
|
|
|
|
quote! {
|
|
let _output = #prev_child_ident.wait_with_output()?;
|
|
|
|
if !_output.status.success() {
|
|
return Err(::comlexr::ExecutorError::FailedCommand{
|
|
command: std::boxed::Box::new(#prev_com_ident),
|
|
exit_code: _output.status.code().unwrap_or(1),
|
|
});
|
|
}
|
|
|
|
let mut #com_ident: #command;
|
|
#com_ident.stdout(::std::process::Stdio::piped());
|
|
#com_ident.stdin(::std::process::Stdio::piped());
|
|
|
|
let mut #child_ident = #com_ident.spawn()?;
|
|
#child_ident
|
|
.stdin
|
|
.as_mut()
|
|
.ok_or(::comlexr::ExecutorError::NoStdIn)?
|
|
.write_all(&_output.stdout)?;
|
|
}
|
|
});
|
|
|
|
let last_child_ident =
|
|
syn::Ident::new(&format!("_child_{}", command_count - 1), last_command_span);
|
|
|
|
tokens.extend(quote! {
|
|
::comlexr::Executor::new(
|
|
#stdin,
|
|
|stdin| -> ::std::result::Result<
|
|
::std::process::Child,
|
|
::comlexr::ExecutorError,
|
|
> {
|
|
use ::std::io::Write;
|
|
#initial
|
|
#(#commands)*
|
|
Ok(#last_child_ident)
|
|
}
|
|
)
|
|
});
|
|
}
|
|
}
|
|
|
|
enum CommandExpr {
|
|
Macro(syn::ExprMacro),
|
|
Reference(syn::ExprReference),
|
|
Function(syn::ExprCall),
|
|
Block(syn::ExprBlock),
|
|
}
|
|
|
|
impl Parse for CommandExpr {
|
|
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
|
let fork = input.fork();
|
|
|
|
fork.parse()
|
|
.map(|value| {
|
|
input.advance_to(&fork);
|
|
Self::Macro(value)
|
|
})
|
|
.or_else(|_| {
|
|
let fork = input.fork();
|
|
let value = fork.parse()?;
|
|
input.advance_to(&fork);
|
|
Ok(Self::Function(value))
|
|
})
|
|
.or_else(|_: syn::Error| {
|
|
let fork = input.fork();
|
|
let value = fork.parse()?;
|
|
input.advance_to(&fork);
|
|
Ok(Self::Block(value))
|
|
})
|
|
.or_else(|_: syn::Error| {
|
|
let fork = input.fork();
|
|
let value = fork.parse()?;
|
|
input.advance_to(&fork);
|
|
Ok(Self::Reference(value))
|
|
})
|
|
.map_err(|_: syn::Error| {
|
|
syn::Error::new(
|
|
input.span(),
|
|
"Only references, function calls, macro calls, and blocks are allowed",
|
|
)
|
|
})
|
|
}
|
|
}
|
|
|
|
impl ToTokens for CommandExpr {
|
|
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
|
tokens.extend(match self {
|
|
Self::Macro(macr) => quote! {
|
|
::std::process::Command = #macr
|
|
},
|
|
Self::Reference(refer) => quote! {
|
|
&mut ::std::process::Command = #refer
|
|
},
|
|
Self::Function(fun) => quote! {
|
|
::std::process::Command = #fun
|
|
},
|
|
Self::Block(block) => quote! {
|
|
::std::process::Command = #block
|
|
},
|
|
});
|
|
}
|
|
}
|