feat: Pipe
This commit is contained in:
547
src/cmd.rs
547
src/cmd.rs
@@ -1,547 +0,0 @@
|
||||
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 },
|
||||
});
|
||||
}
|
||||
}
|
||||
177
src/lib.rs
177
src/lib.rs
@@ -1,177 +1,8 @@
|
||||
#![doc = include_str!("../README.md")]
|
||||
|
||||
extern crate quote;
|
||||
extern crate syn;
|
||||
extern crate comlexr_macro;
|
||||
|
||||
use quote::quote;
|
||||
use syn::parse_macro_input;
|
||||
pub use comlexr_macro::*;
|
||||
pub use pipe::*;
|
||||
|
||||
mod cmd;
|
||||
|
||||
/// Generates a command expression by combining static strings, conditional logic, loops,
|
||||
/// pattern matching, and closures to dynamically build and format command-line arguments.
|
||||
///
|
||||
/// The `cmd!` macro supports flexible syntax constructs such as:
|
||||
/// - **Static arguments**: Basic string arguments.
|
||||
/// - **Conditional inclusion**: Using `if` or `if let` to conditionally include arguments.
|
||||
/// - **Iterative inclusion**: Using `for` loops to include multiple arguments from collections.
|
||||
/// - **Pattern matching**: Using `match` expressions for dynamic argument selection.
|
||||
/// - **Closures**: Dynamically generating arguments at runtime.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ## Basic Usage
|
||||
/// ```
|
||||
/// use comlexr::cmd;
|
||||
///
|
||||
/// let command = cmd!("echo", "test");
|
||||
/// assert_eq!(format!("{command:?}"), r#""echo" "test""#.to_string());
|
||||
/// ```
|
||||
///
|
||||
/// ## Current Directory
|
||||
/// ```
|
||||
/// use comlexr::cmd;
|
||||
///
|
||||
/// let command = cmd!(
|
||||
/// cd "~/";
|
||||
/// "echo",
|
||||
/// "test",
|
||||
/// );
|
||||
///
|
||||
/// assert_eq!(format!("{command:?}"), r#"cd "~/" && "echo" "test""#);
|
||||
/// ```
|
||||
///
|
||||
/// ## Environment Vars
|
||||
/// ```
|
||||
/// use comlexr::cmd;
|
||||
///
|
||||
/// const NEW_VAR: &str = "NEW_VAR";
|
||||
///
|
||||
/// let command = cmd!(
|
||||
/// env {
|
||||
/// "TEST": "test",
|
||||
/// NEW_VAR: "new_var"
|
||||
/// };
|
||||
/// "echo",
|
||||
/// "test",
|
||||
/// );
|
||||
///
|
||||
/// assert_eq!(format!("{command:?}"), r#"NEW_VAR="new_var" TEST="test" "echo" "test""#);
|
||||
/// ```
|
||||
///
|
||||
/// ### Current Directory and Environment Variable Order
|
||||
/// ```
|
||||
/// use comlexr::cmd;
|
||||
///
|
||||
/// let command = cmd!(
|
||||
/// cd "~/";
|
||||
/// env {
|
||||
/// "TEST": "test",
|
||||
/// };
|
||||
/// "echo",
|
||||
/// "test",
|
||||
/// );
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// format!("{command:?}"),
|
||||
/// r#"cd "~/" && TEST="test" "echo" "test""#
|
||||
/// );
|
||||
///
|
||||
/// ```
|
||||
///
|
||||
/// ## Conditional Arguments
|
||||
/// ```
|
||||
/// use comlexr::cmd;
|
||||
///
|
||||
/// let include_arg = true;
|
||||
///
|
||||
/// let command = cmd!("echo", "test", if include_arg => "optional_arg");
|
||||
/// assert_eq!(format!("{command:?}"), r#""echo" "test" "optional_arg""#.to_string());
|
||||
/// ```
|
||||
///
|
||||
/// ## Conditional Pattern Matching
|
||||
/// ```
|
||||
/// use comlexr::cmd;
|
||||
///
|
||||
/// let single_option = Some("single");
|
||||
/// let multi_option: Option<&str> = None;
|
||||
///
|
||||
/// let command = cmd!(
|
||||
/// "echo",
|
||||
/// "test",
|
||||
/// if let Some(arg) = single_option => arg,
|
||||
/// if let Some(arg) = multi_option => [
|
||||
/// "multi",
|
||||
/// arg,
|
||||
/// ],
|
||||
/// );
|
||||
/// assert_eq!(format!("{command:?}"), r#""echo" "test" "single""#.to_string());
|
||||
/// ```
|
||||
///
|
||||
/// ## Iterative Argument Inclusion
|
||||
/// ```
|
||||
/// use comlexr::cmd;
|
||||
///
|
||||
/// let args = &["arg1", "arg2"];
|
||||
///
|
||||
/// let command = cmd!("echo", for args);
|
||||
/// assert_eq!(format!("{command:?}"), r#""echo" "arg1" "arg2""#.to_string());
|
||||
/// ```
|
||||
///
|
||||
/// ## Iteration with `for in`
|
||||
/// ```
|
||||
/// use comlexr::cmd;
|
||||
///
|
||||
/// let single_iter = &["arg1", "arg2"];
|
||||
/// let multi_iter = &["multi1", "multi2"];
|
||||
///
|
||||
/// let command = cmd!(
|
||||
/// "echo",
|
||||
/// "test",
|
||||
/// for arg in single_iter => arg,
|
||||
/// for arg in multi_iter => [
|
||||
/// "multi",
|
||||
/// arg,
|
||||
/// ],
|
||||
/// );
|
||||
/// assert_eq!(format!("{command:?}"), r#""echo" "test" "arg1" "arg2" "multi" "multi1" "multi" "multi2""#.to_string());
|
||||
/// ```
|
||||
///
|
||||
/// ## Match Statements
|
||||
/// ```
|
||||
/// use comlexr::cmd;
|
||||
///
|
||||
/// enum TestArgs {
|
||||
/// Arg1,
|
||||
/// Arg2,
|
||||
/// Arg3,
|
||||
/// }
|
||||
///
|
||||
/// let match_arg = TestArgs::Arg2;
|
||||
/// let command = cmd!(
|
||||
/// "echo",
|
||||
/// "test",
|
||||
/// match match_arg {
|
||||
/// TestArgs::Arg1 => "arg1",
|
||||
/// TestArgs::Arg2 => ["arg1", "arg2"],
|
||||
/// TestArgs::Arg3 => ["arg1", "arg2", "arg3"],
|
||||
/// }
|
||||
/// );
|
||||
/// assert_eq!(format!("{command:?}"), r#""echo" "test" "arg1" "arg2""#.to_string());
|
||||
/// ```
|
||||
///
|
||||
/// ## Dynamic Closures
|
||||
/// ```
|
||||
/// use comlexr::cmd;
|
||||
///
|
||||
/// let numbers = vec![1, 2, 3];
|
||||
/// let multiplier = 2;
|
||||
///
|
||||
/// let command = cmd!("echo", || numbers.into_iter().map(|n| format!("{}", n * multiplier)));
|
||||
/// assert_eq!(format!("{command:?}"), r#""echo" "2" "4" "6""#.to_string());
|
||||
/// ```
|
||||
#[proc_macro]
|
||||
pub fn cmd(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
let command = parse_macro_input!(input as cmd::Command);
|
||||
quote! { #command }.into()
|
||||
}
|
||||
mod pipe;
|
||||
|
||||
73
src/pipe.rs
Normal file
73
src/pipe.rs
Normal file
@@ -0,0 +1,73 @@
|
||||
use std::{
|
||||
ffi::OsStr,
|
||||
process::Command,
|
||||
process::{Child, ExitStatus, Output},
|
||||
};
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
/// Errors that can be created when running the `PipeExecutor`
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ExecutorError {
|
||||
/// IO Error
|
||||
#[error(transparent)]
|
||||
Io(#[from] std::io::Error),
|
||||
|
||||
/// Failed Command Error
|
||||
#[error("Failed to run command '{} {}', exit code {exit_code}",
|
||||
.command
|
||||
.get_program()
|
||||
.to_string_lossy(),
|
||||
.command
|
||||
.get_args()
|
||||
.map(OsStr::to_string_lossy)
|
||||
.collect::<Vec<_>>()
|
||||
.join(" "),
|
||||
)]
|
||||
FailedCommand { command: Command, exit_code: i32 },
|
||||
|
||||
/// No stdin Error
|
||||
#[error("Unable to get mutable stdin")]
|
||||
NoStdIn,
|
||||
}
|
||||
|
||||
/// A lazy piped command executor.
|
||||
pub struct Executor<'a, S>
|
||||
where
|
||||
S: AsRef<[u8]> + ?Sized,
|
||||
{
|
||||
stdin: Option<&'a S>,
|
||||
piped_commands: Box<dyn FnMut(Option<&'a S>) -> Result<Child, ExecutorError> + 'a>,
|
||||
}
|
||||
|
||||
impl<'a, S> Executor<'a, S>
|
||||
where
|
||||
S: AsRef<[u8]> + ?Sized,
|
||||
{
|
||||
/// Construct a `PipeExecutor`.
|
||||
pub fn new(
|
||||
stdin: Option<&'a S>,
|
||||
piped_commands: impl FnMut(Option<&'a S>) -> Result<Child, ExecutorError> + 'a,
|
||||
) -> Self {
|
||||
Self {
|
||||
stdin,
|
||||
piped_commands: Box::new(piped_commands),
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrieves the `ExitStatus` of the last command.
|
||||
///
|
||||
/// # Errors
|
||||
/// Will error if the exit status of any chained commands fail.
|
||||
pub fn status(&mut self) -> Result<ExitStatus, ExecutorError> {
|
||||
Ok((self.piped_commands)(self.stdin)?.wait()?)
|
||||
}
|
||||
|
||||
/// Retrieves the `Output` of the last command.
|
||||
///
|
||||
/// # Errors
|
||||
/// Will error if the exit status of any chained commands fail.
|
||||
pub fn output(&mut self) -> Result<Output, ExecutorError> {
|
||||
Ok((self.piped_commands)(self.stdin)?.wait_with_output()?)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user