feat: Pipe

This commit is contained in:
2025-01-29 00:04:00 -05:00
parent 6fa4b21c4f
commit 9d944c3218
15 changed files with 888 additions and 235 deletions

174
macro/src/pipe.rs Normal file
View File

@@ -0,0 +1,174 @@
use quote::{quote, ToTokens};
use syn::{
parse::{discouraged::Speculative, Parse},
punctuated::Punctuated,
spanned::Spanned,
Error, Token,
};
use crate::{cmd::Value, macros::enum_to_tokens};
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: #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",
)
})
}
}
enum_to_tokens! {CommandExpr: Macro, Reference, Function, Block}