feat: Add the ability to set current dir and env vars
This commit is contained in:
@@ -3,7 +3,8 @@ name = "comlexr"
|
|||||||
description = "Dynamically build Command objects with conditional expressions"
|
description = "Dynamically build Command objects with conditional expressions"
|
||||||
repository = "https://gitlab.com/wunker-bunker/comlexr"
|
repository = "https://gitlab.com/wunker-bunker/comlexr"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
edition = "2018"
|
edition = "2021"
|
||||||
|
rust-version = "1.60"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
@@ -12,7 +13,7 @@ proc-macro = true
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
proc-macro2 = "1"
|
proc-macro2 = "1"
|
||||||
quote = "1"
|
quote = "1"
|
||||||
syn = { version = "2", features = ["full"] }
|
syn = { version = "2", features = ["full", "derive"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
rstest = "0.24"
|
rstest = "0.24"
|
||||||
|
|||||||
56
README.md
56
README.md
@@ -150,6 +150,62 @@ let command = cmd!(
|
|||||||
assert_eq!(format!("{command:?}"), r#""echo" "test" "2" "4" "6""#.to_string());
|
assert_eq!(format!("{command:?}"), r#""echo" "test" "2" "4" "6""#.to_string());
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Set Current Directory
|
||||||
|
Specify the directory to run the command.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use comlexr::cmd;
|
||||||
|
|
||||||
|
let command = cmd!(
|
||||||
|
cd "~/";
|
||||||
|
"echo",
|
||||||
|
"test",
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(format!("{command:?}"), r#"cd "~/" && "echo" "test""#);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Setting Environment Variables
|
||||||
|
Set environment variables for the command.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
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 Env Variable Order Matters
|
||||||
|
Environment variable declarations **MUST** come after the current directory declaration.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use comlexr::cmd;
|
||||||
|
|
||||||
|
let command = cmd!(
|
||||||
|
cd "~/";
|
||||||
|
env {
|
||||||
|
"TEST": "test",
|
||||||
|
};
|
||||||
|
"echo",
|
||||||
|
"test",
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
format!("{command:?}"),
|
||||||
|
r#"cd "~/" && TEST="test" "echo" "test""#
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
- Conditional expressions (`if`, `if let`)
|
- Conditional expressions (`if`, `if let`)
|
||||||
- Iteration constructs (`for`, `for in`)
|
- Iteration constructs (`for`, `for in`)
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ command = [
|
|||||||
env.RUSTFLAGS="-Zmacro-backtrace"
|
env.RUSTFLAGS="-Zmacro-backtrace"
|
||||||
need_stdout = true
|
need_stdout = true
|
||||||
default_watch = false
|
default_watch = false
|
||||||
watch = ["src", "Cargo.toml", "tests"]
|
watch = ["src", "Cargo.toml", "tests", "README.md"]
|
||||||
|
|
||||||
[jobs.test-all]
|
[jobs.test-all]
|
||||||
command = [
|
command = [
|
||||||
@@ -56,7 +56,7 @@ command = [
|
|||||||
env.RUSTFLAGS="-Zmacro-backtrace"
|
env.RUSTFLAGS="-Zmacro-backtrace"
|
||||||
need_stdout = true
|
need_stdout = true
|
||||||
default_watch = false
|
default_watch = false
|
||||||
watch = ["src", "Cargo.toml", "tests"]
|
watch = ["src", "Cargo.toml", "tests", "README.md"]
|
||||||
|
|
||||||
[jobs.doc]
|
[jobs.doc]
|
||||||
command = ["cargo", "doc", "--color", "always", "--no-deps"]
|
command = ["cargo", "doc", "--color", "always", "--no-deps"]
|
||||||
|
|||||||
134
src/cmd.rs
134
src/cmd.rs
@@ -7,22 +7,30 @@ use syn::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub struct Command {
|
pub struct Command {
|
||||||
|
cd: CurrentDir,
|
||||||
|
env_vars: EnvVars,
|
||||||
program: Value,
|
program: Value,
|
||||||
args: Option<Punctuated<LogicArg, Token![,]>>,
|
args: Option<Punctuated<LogicArg, Token![,]>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parse for Command {
|
impl Parse for Command {
|
||||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||||
|
let cd = input.parse()?;
|
||||||
|
let env_vars = input.parse()?;
|
||||||
let program = input.parse()?;
|
let program = input.parse()?;
|
||||||
|
|
||||||
if input.is_empty() {
|
if input.is_empty() {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
|
cd,
|
||||||
|
env_vars,
|
||||||
program,
|
program,
|
||||||
args: None,
|
args: None,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
_ = input.parse::<Token![,]>()?;
|
_ = input.parse::<Token![,]>()?;
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
|
cd,
|
||||||
|
env_vars,
|
||||||
program,
|
program,
|
||||||
args: Some(Punctuated::parse_terminated(input)?),
|
args: Some(Punctuated::parse_terminated(input)?),
|
||||||
})
|
})
|
||||||
@@ -32,22 +40,27 @@ impl Parse for Command {
|
|||||||
|
|
||||||
impl ToTokens for Command {
|
impl ToTokens for Command {
|
||||||
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
||||||
let Self { program, args } = self;
|
let Self {
|
||||||
|
cd,
|
||||||
|
env_vars,
|
||||||
|
program,
|
||||||
|
args,
|
||||||
|
} = self;
|
||||||
let program = quote! { ::std::process::Command::new(#program) };
|
let program = quote! { ::std::process::Command::new(#program) };
|
||||||
let args = args.as_ref().map(Punctuated::iter);
|
let args = args
|
||||||
|
.as_ref()
|
||||||
|
.map(Punctuated::iter)
|
||||||
|
.map_or_else(Vec::new, Iterator::collect);
|
||||||
|
|
||||||
tokens.extend(args.map_or_else(
|
tokens.extend(quote! {
|
||||||
|| quote! { #program },
|
{
|
||||||
|args| {
|
let mut _c = #program;
|
||||||
quote! {
|
#cd
|
||||||
{
|
#env_vars
|
||||||
let mut _c = #program;
|
#(#args)*
|
||||||
#(#args)*
|
_c
|
||||||
_c
|
}
|
||||||
}
|
});
|
||||||
}
|
|
||||||
},
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -400,6 +413,99 @@ impl ToTokens for SingleArg {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
enum Value {
|
||||||
Lit(syn::Lit),
|
Lit(syn::Lit),
|
||||||
Ident(syn::Ident),
|
Ident(syn::Ident),
|
||||||
|
|||||||
51
src/lib.rs
51
src/lib.rs
@@ -28,6 +28,57 @@ mod cmd;
|
|||||||
/// assert_eq!(format!("{command:?}"), r#""echo" "test""#.to_string());
|
/// 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
|
/// ## Conditional Arguments
|
||||||
/// ```
|
/// ```
|
||||||
/// use comlexr::cmd;
|
/// use comlexr::cmd;
|
||||||
|
|||||||
41
tests/cmd.rs
41
tests/cmd.rs
@@ -11,6 +11,47 @@ fn expression() {
|
|||||||
assert_eq!(format!("{command:?}"), r#""echo" "test""#.to_string());
|
assert_eq!(format!("{command:?}"), r#""echo" "test""#.to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn current_dir() {
|
||||||
|
let command = cmd!(
|
||||||
|
cd "~/";
|
||||||
|
"echo",
|
||||||
|
"test",
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(format!("{command:?}"), r#"cd "~/" && "echo" "test""#);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn env_vars() {
|
||||||
|
let command = cmd!(
|
||||||
|
env {
|
||||||
|
"TEST": "test",
|
||||||
|
};
|
||||||
|
"echo",
|
||||||
|
"test",
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(format!("{command:?}"), r#"TEST="test" "echo" "test""#);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cd_env_vars() {
|
||||||
|
let command = cmd!(
|
||||||
|
cd "~/";
|
||||||
|
env {
|
||||||
|
"TEST": "test",
|
||||||
|
};
|
||||||
|
"echo",
|
||||||
|
"test",
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
format!("{command:?}"),
|
||||||
|
r#"cd "~/" && TEST="test" "echo" "test""#
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
#[case(false, false, r#""echo" "test""#)]
|
#[case(false, false, r#""echo" "test""#)]
|
||||||
#[case(true, false, r#""echo" "test" "single""#)]
|
#[case(true, false, r#""echo" "test" "single""#)]
|
||||||
|
|||||||
Reference in New Issue
Block a user