Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0cdd0bbc07 | |||
| a5d442f7ff | |||
| ac948f193b | |||
| 2d7d6cf641 | |||
| c58cbff4b2 | |||
| 9d944c3218 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
|||||||
/target
|
/target
|
||||||
/expand.rs
|
/expand.rs
|
||||||
|
/.sccache
|
||||||
|
|||||||
3
.gitlab-ci.yml
Normal file
3
.gitlab-ci.yml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
include:
|
||||||
|
- project: wunker-bunker/ci-pipelines
|
||||||
|
file: cargo-lib.yml
|
||||||
6
.helix/languages.toml
Normal file
6
.helix/languages.toml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
[language-server.rust-analyzer.config]
|
||||||
|
cargo.features = "all"
|
||||||
|
|
||||||
|
[language-server.rust-analyzer.config.check]
|
||||||
|
command = "clippy"
|
||||||
|
args = ["--no-deps", "--workspace"]
|
||||||
30
CHANGELOG.md
30
CHANGELOG.md
@@ -2,12 +2,42 @@
|
|||||||
|
|
||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
## [1.4.0] - 2025-04-06
|
||||||
|
|
||||||
|
### 🚀 Features
|
||||||
|
|
||||||
|
- Default env variables
|
||||||
|
|
||||||
|
## [1.3.1] - 2025-01-31
|
||||||
|
|
||||||
|
### 🐛 Bug Fixes
|
||||||
|
|
||||||
|
- Better enforce typing for commands
|
||||||
|
|
||||||
|
### ⚙️ Miscellaneous Tasks
|
||||||
|
|
||||||
|
- Release
|
||||||
|
|
||||||
|
## [1.3.0] - 2025-01-31
|
||||||
|
|
||||||
|
### 🚀 Features
|
||||||
|
|
||||||
|
- Pipe
|
||||||
|
|
||||||
|
### ⚙️ Miscellaneous Tasks
|
||||||
|
|
||||||
|
- Release
|
||||||
|
|
||||||
## [1.2.0] - 2025-01-14
|
## [1.2.0] - 2025-01-14
|
||||||
|
|
||||||
### 🚀 Features
|
### 🚀 Features
|
||||||
|
|
||||||
- Add the ability to set current dir and env vars
|
- Add the ability to set current dir and env vars
|
||||||
|
|
||||||
|
### ⚙️ Miscellaneous Tasks
|
||||||
|
|
||||||
|
- Release comlexr version 1.2.0
|
||||||
|
|
||||||
## [1.1.0] - 2025-01-11
|
## [1.1.0] - 2025-01-11
|
||||||
|
|
||||||
### 🚀 Features
|
### 🚀 Features
|
||||||
|
|||||||
206
Cargo.lock
generated
206
Cargo.lock
generated
@@ -17,6 +17,12 @@ version = "1.4.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "2.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg-if"
|
name = "cfg-if"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
@@ -34,13 +40,23 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "comlexr"
|
name = "comlexr"
|
||||||
version = "1.2.0"
|
version = "1.4.0"
|
||||||
|
dependencies = [
|
||||||
|
"comlexr_macro",
|
||||||
|
"rstest",
|
||||||
|
"rusty-hook",
|
||||||
|
"tempfile",
|
||||||
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "comlexr_macro"
|
||||||
|
version = "1.4.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"rstest",
|
|
||||||
"rusty-hook",
|
|
||||||
"syn",
|
"syn",
|
||||||
|
"tempfile",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -59,6 +75,22 @@ version = "1.0.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "errno"
|
||||||
|
version = "0.3.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fastrand"
|
||||||
|
version = "2.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fsio"
|
name = "fsio"
|
||||||
version = "0.1.3"
|
version = "0.1.3"
|
||||||
@@ -117,6 +149,18 @@ dependencies = [
|
|||||||
"unicode-width",
|
"unicode-width",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"wasi",
|
||||||
|
"windows-targets",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "glob"
|
name = "glob"
|
||||||
version = "0.3.2"
|
version = "0.3.2"
|
||||||
@@ -155,6 +199,18 @@ dependencies = [
|
|||||||
"hashbrown 0.15.2",
|
"hashbrown 0.15.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.169"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "linux-raw-sys"
|
||||||
|
version = "0.4.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.7.4"
|
version = "2.7.4"
|
||||||
@@ -167,6 +223,12 @@ version = "0.5.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ab250442c86f1850815b5d268639dff018c0627022bc1940eb2d642ca1ce12f0"
|
checksum = "ab250442c86f1850815b5d268639dff018c0627022bc1940eb2d642ca1ce12f0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell"
|
||||||
|
version = "1.20.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pin-project-lite"
|
name = "pin-project-lite"
|
||||||
version = "0.2.16"
|
version = "0.2.16"
|
||||||
@@ -280,6 +342,19 @@ dependencies = [
|
|||||||
"semver",
|
"semver",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustix"
|
||||||
|
version = "0.38.44"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"errno",
|
||||||
|
"libc",
|
||||||
|
"linux-raw-sys",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rusty-hook"
|
name = "rusty-hook"
|
||||||
version = "0.11.2"
|
version = "0.11.2"
|
||||||
@@ -338,6 +413,40 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tempfile"
|
||||||
|
version = "3.16.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "38c246215d7d24f48ae091a2902398798e05d978b24315d6efbc00ede9a8bb91"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"fastrand",
|
||||||
|
"getrandom",
|
||||||
|
"once_cell",
|
||||||
|
"rustix",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror"
|
||||||
|
version = "1.0.69"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror-impl",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror-impl"
|
||||||
|
version = "1.0.69"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml"
|
name = "toml"
|
||||||
version = "0.5.11"
|
version = "0.5.11"
|
||||||
@@ -376,6 +485,88 @@ version = "0.1.14"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
|
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.13.3+wasi-0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2"
|
||||||
|
dependencies = [
|
||||||
|
"wit-bindgen-rt",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.59.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-targets"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||||
|
dependencies = [
|
||||||
|
"windows_aarch64_gnullvm",
|
||||||
|
"windows_aarch64_msvc",
|
||||||
|
"windows_i686_gnu",
|
||||||
|
"windows_i686_gnullvm",
|
||||||
|
"windows_i686_msvc",
|
||||||
|
"windows_x86_64_gnu",
|
||||||
|
"windows_x86_64_gnullvm",
|
||||||
|
"windows_x86_64_msvc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_gnullvm"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_msvc"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnu"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnullvm"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_msvc"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnu"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnullvm"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_msvc"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winnow"
|
name = "winnow"
|
||||||
version = "0.6.24"
|
version = "0.6.24"
|
||||||
@@ -384,3 +575,12 @@ checksum = "c8d71a593cc5c42ad7876e2c1fda56f314f3754c084128833e64f1345ff8a03a"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-bindgen-rt"
|
||||||
|
version = "0.33.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
]
|
||||||
|
|||||||
51
Cargo.toml
51
Cargo.toml
@@ -1,28 +1,21 @@
|
|||||||
[package]
|
[workspace]
|
||||||
name = "comlexr"
|
members = ["macro"]
|
||||||
|
|
||||||
|
[workspace.package]
|
||||||
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.2.0"
|
version = "1.4.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.60"
|
rust-version = "1.60"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|
||||||
[lib]
|
[workspace.dependencies]
|
||||||
proc-macro = true
|
tempfile = "3.6"
|
||||||
|
|
||||||
[dependencies]
|
[workspace.lints.rust]
|
||||||
proc-macro2 = "1"
|
|
||||||
quote = "1"
|
|
||||||
syn = { version = "2", features = ["full", "derive"] }
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
rstest = "0.24"
|
|
||||||
rusty-hook = "0.11"
|
|
||||||
|
|
||||||
[lints.rust]
|
|
||||||
unsafe_code = "forbid"
|
unsafe_code = "forbid"
|
||||||
|
|
||||||
[lints.clippy]
|
[workspace.lints.clippy]
|
||||||
correctness = "deny"
|
correctness = "deny"
|
||||||
suspicious = "deny"
|
suspicious = "deny"
|
||||||
perf = "deny"
|
perf = "deny"
|
||||||
@@ -30,8 +23,34 @@ style = "deny"
|
|||||||
nursery = "deny"
|
nursery = "deny"
|
||||||
pedantic = "deny"
|
pedantic = "deny"
|
||||||
|
|
||||||
|
[package]
|
||||||
|
name = "comlexr"
|
||||||
|
|
||||||
|
version.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
rust-version.workspace = true
|
||||||
|
description.workspace = true
|
||||||
|
repository.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
|
||||||
[package.metadata.release]
|
[package.metadata.release]
|
||||||
pre-release-hook = ["git", "cliff", "-o", "CHANGELOG.md", "--tag", "{{version}}"]
|
pre-release-hook = ["git", "cliff", "-o", "CHANGELOG.md", "--tag", "{{version}}"]
|
||||||
pre-release-replacements = [
|
pre-release-replacements = [
|
||||||
{ file = "README.md", search = "comlexr = \"\\d+.\\d+.\\d+\"", replace = "comlexr = \"{{version}}\"" }
|
{ file = "README.md", search = "comlexr = \"\\d+.\\d+.\\d+\"", replace = "comlexr = \"{{version}}\"" }
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[package.metadata."docs.rs"]
|
||||||
|
all-features = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
comlexr_macro = { version = "=1.4.0", path = "./macro" }
|
||||||
|
thiserror = "1.0.65"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
rstest = "0.24"
|
||||||
|
rusty-hook = "0.11"
|
||||||
|
|
||||||
|
tempfile.workspace = true
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|||||||
57
README.md
57
README.md
@@ -8,11 +8,11 @@ Add `comlexr` to your project's `Cargo.toml`:
|
|||||||
|
|
||||||
```toml
|
```toml
|
||||||
[dependencies]
|
[dependencies]
|
||||||
comlexr = "1.2.0"
|
comlexr = "1.4.0"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Rust Edition
|
## MSRV
|
||||||
This project uses Rust **2018 edition** to ensure compatibility and stable language features.
|
The minimum supported Rust version is `1.60.0` for broader support.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
@@ -185,6 +185,27 @@ let command = cmd!(
|
|||||||
assert_eq!(format!("{command:?}"), r#"NEW_VAR="new_var" TEST="test" "echo" "test""#);
|
assert_eq!(format!("{command:?}"), r#"NEW_VAR="new_var" TEST="test" "echo" "test""#);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Conditional
|
||||||
|
You can have a default value set for an environment variable.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use comlexr::cmd;
|
||||||
|
|
||||||
|
const NEW_VAR: &str = "NEW_VAR";
|
||||||
|
std::env::set_var("TEST", "realvalue");
|
||||||
|
|
||||||
|
let command = cmd!(
|
||||||
|
env {
|
||||||
|
"TEST":? "test",
|
||||||
|
NEW_VAR: "new_var"
|
||||||
|
};
|
||||||
|
"echo",
|
||||||
|
"test",
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(format!("{command:?}"), r#"NEW_VAR="new_var" "echo" "test""#);
|
||||||
|
```
|
||||||
|
|
||||||
#### Current Directory and Env Variable Order Matters
|
#### Current Directory and Env Variable Order Matters
|
||||||
Environment variable declarations **MUST** come after the current directory declaration.
|
Environment variable declarations **MUST** come after the current directory declaration.
|
||||||
|
|
||||||
@@ -206,14 +227,42 @@ assert_eq!(
|
|||||||
);
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Piping commands
|
||||||
|
When using the `pipe` feature, you can make use of `pipe!` to chain the stdout of commands to stdin. Execution is lazy so commands aren't run until `status()` or `output()` is called.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use comlexr::{pipe, cmd};
|
||||||
|
|
||||||
|
let dir = tempfile::tempdir().unwrap();
|
||||||
|
let file = dir.path().join("out");
|
||||||
|
let mut pipe = pipe!(cmd!("echo", "test") | cmd!("tee", &file));
|
||||||
|
|
||||||
|
let status = pipe.status().unwrap();
|
||||||
|
assert!(status.success());
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sending variables to stdin
|
||||||
|
You can also send data to the stdin of the first command in the chain.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use comlexr::{pipe, cmd};
|
||||||
|
|
||||||
|
let mut pipe = pipe!(stdin = "test"; cmd!("sed", "s|e|oa|"));
|
||||||
|
let output = pipe.output().unwrap();
|
||||||
|
|
||||||
|
assert!(output.status.success());
|
||||||
|
assert_eq!(String::from_utf8_lossy(&output.stdout), "toast");
|
||||||
|
```
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
- Conditional expressions (`if`, `if let`)
|
- Conditional expressions (`if`, `if let`)
|
||||||
- Iteration constructs (`for`, `for in`)
|
- Iteration constructs (`for`, `for in`)
|
||||||
- Pattern matching (`match`)
|
- Pattern matching (`match`)
|
||||||
- Support for closures and dynamic expressions
|
- Support for closures and dynamic expressions
|
||||||
|
- Piping stdout from one command to the stdin of another
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
See the [tests](./tests/) directory for more examples on how to use `comlexr` effectively in your project.
|
See the [tests](./tests/) directory for more examples on how to use `comlexr` effectively in your project.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
This project is licensed under the [MIT License](LICENSE).
|
This project is licensed under the [MIT License](./LICENSE).
|
||||||
|
|||||||
22
bacon.toml
22
bacon.toml
@@ -11,32 +11,32 @@ default_job = "clippy-all"
|
|||||||
command = ["cargo", "check", "--color", "always"]
|
command = ["cargo", "check", "--color", "always"]
|
||||||
need_stdout = false
|
need_stdout = false
|
||||||
default_watch = false
|
default_watch = false
|
||||||
watch = ["src", "Cargo.toml"]
|
watch = ["src", "Cargo.toml", "macro"]
|
||||||
|
|
||||||
[jobs.check-all]
|
[jobs.check-all]
|
||||||
command = ["cargo", "check", "--all-features", "--color", "always"]
|
command = ["cargo", "check", "--all-features", "--color", "always"]
|
||||||
need_stdout = false
|
need_stdout = false
|
||||||
default_watch = false
|
default_watch = false
|
||||||
watch = ["src", "Cargo.toml"]
|
watch = ["src", "Cargo.toml", "macro"]
|
||||||
|
|
||||||
[jobs.clippy]
|
[jobs.clippy]
|
||||||
command = [
|
command = [
|
||||||
"cargo", "clippy",
|
"cargo", "clippy", "--workspace",
|
||||||
"--color", "always",
|
"--color", "always",
|
||||||
]
|
]
|
||||||
need_stdout = false
|
need_stdout = false
|
||||||
default_watch = false
|
default_watch = false
|
||||||
watch = ["src", "Cargo.toml"]
|
watch = ["src", "Cargo.toml", "macro"]
|
||||||
|
|
||||||
[jobs.clippy-all]
|
[jobs.clippy-all]
|
||||||
command = [
|
command = [
|
||||||
"cargo", "clippy",
|
"cargo", "clippy", "--workspace",
|
||||||
"--all-features",
|
"--all-features",
|
||||||
"--color", "always",
|
"--color", "always",
|
||||||
]
|
]
|
||||||
need_stdout = false
|
need_stdout = false
|
||||||
default_watch = false
|
default_watch = false
|
||||||
watch = ["src", "Cargo.toml"]
|
watch = ["src", "Cargo.toml", "macro"]
|
||||||
|
|
||||||
[jobs.test]
|
[jobs.test]
|
||||||
command = [
|
command = [
|
||||||
@@ -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", "README.md"]
|
watch = ["src", "Cargo.toml", "tests", "README.md", "macro"]
|
||||||
|
|
||||||
[jobs.test-all]
|
[jobs.test-all]
|
||||||
command = [
|
command = [
|
||||||
@@ -56,18 +56,18 @@ 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", "README.md"]
|
watch = ["src", "Cargo.toml", "tests", "README.md", "macro"]
|
||||||
|
|
||||||
[jobs.doc]
|
[jobs.doc]
|
||||||
command = ["cargo", "doc", "--color", "always", "--no-deps"]
|
command = ["cargo", "doc", "--color", "always", "--no-deps", "--workspace", "--all-features"]
|
||||||
need_stdout = false
|
need_stdout = false
|
||||||
default_watch = false
|
default_watch = false
|
||||||
watch = ["src", "Cargo.toml"]
|
watch = ["src", "Cargo.toml", "macro"]
|
||||||
|
|
||||||
# If the doc compiles, then it opens in your browser and bacon switches
|
# If the doc compiles, then it opens in your browser and bacon switches
|
||||||
# to the previous job
|
# to the previous job
|
||||||
[jobs.doc-open]
|
[jobs.doc-open]
|
||||||
command = ["cargo", "doc", "--color", "always", "--no-deps", "--open"]
|
command = ["cargo", "doc", "--color", "always", "--no-deps", "--open", "--workspace", "--all-features"]
|
||||||
need_stdout = false
|
need_stdout = false
|
||||||
on_success = "back" # so that we don't open the browser at each change
|
on_success = "back" # so that we don't open the browser at each change
|
||||||
|
|
||||||
|
|||||||
26
macro/Cargo.toml
Normal file
26
macro/Cargo.toml
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
[package]
|
||||||
|
name = "comlexr_macro"
|
||||||
|
|
||||||
|
version.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
rust-version.workspace = true
|
||||||
|
description.workspace = true
|
||||||
|
repository.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
|
||||||
|
[package.metadata."docs.rs"]
|
||||||
|
all-features = true
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
proc-macro = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
proc-macro2 = "1"
|
||||||
|
quote = "1"
|
||||||
|
syn = { version = "2", features = ["full", "derive"] }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
tempfile.workspace = true
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
@@ -6,6 +6,8 @@ use syn::{
|
|||||||
token, Token,
|
token, Token,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::macros::enum_to_tokens;
|
||||||
|
|
||||||
pub struct Command {
|
pub struct Command {
|
||||||
cd: CurrentDir,
|
cd: CurrentDir,
|
||||||
env_vars: EnvVars,
|
env_vars: EnvVars,
|
||||||
@@ -101,19 +103,7 @@ impl Parse for LogicArg {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToTokens for LogicArg {
|
enum_to_tokens! {LogicArg: Expr, ForIter, ForIn, IfLet, If, Match, Closure}
|
||||||
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 {
|
struct ForIter {
|
||||||
expr: Value,
|
expr: Value,
|
||||||
@@ -368,14 +358,7 @@ impl Parse for Arguments {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToTokens for Arguments {
|
enum_to_tokens! {Arguments: Single, Multi}
|
||||||
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![,]>);
|
struct MultiArg(Punctuated<SingleArg, Token![,]>);
|
||||||
|
|
||||||
@@ -454,25 +437,51 @@ impl ToTokens for EnvVars {
|
|||||||
struct EnvVar {
|
struct EnvVar {
|
||||||
key: Value,
|
key: Value,
|
||||||
value: Value,
|
value: Value,
|
||||||
|
conditional: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parse for EnvVar {
|
impl Parse for EnvVar {
|
||||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||||
let key = input.parse()?;
|
let key = input.parse()?;
|
||||||
|
|
||||||
_ = input.parse::<Token![:]>()?;
|
_ = input.parse::<Token![:]>()?;
|
||||||
|
|
||||||
|
let conditional = if input.lookahead1().peek(Token![?]) {
|
||||||
|
input.parse::<Token![?]>()?;
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
let value = input.parse()?;
|
let value = input.parse()?;
|
||||||
|
|
||||||
Ok(Self { key, value })
|
Ok(Self {
|
||||||
|
key,
|
||||||
|
value,
|
||||||
|
conditional,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToTokens for EnvVar {
|
impl ToTokens for EnvVar {
|
||||||
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
||||||
let Self { key, value } = self;
|
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); });
|
tokens.extend(quote! { _c.env(#key, #value); });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct CurrentDir(Option<Value>);
|
struct CurrentDir(Option<Value>);
|
||||||
|
|
||||||
@@ -506,7 +515,7 @@ impl ToTokens for CurrentDir {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Value {
|
pub enum Value {
|
||||||
Lit(syn::Lit),
|
Lit(syn::Lit),
|
||||||
Ident(syn::Ident),
|
Ident(syn::Ident),
|
||||||
Expr(syn::Expr),
|
Expr(syn::Expr),
|
||||||
@@ -536,12 +545,4 @@ impl Parse for Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToTokens for Value {
|
enum_to_tokens! {Value: Lit, Ident, Expr}
|
||||||
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 },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
220
macro/src/lib.rs
Normal file
220
macro/src/lib.rs
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
extern crate quote;
|
||||||
|
extern crate syn;
|
||||||
|
|
||||||
|
use quote::quote;
|
||||||
|
use syn::parse_macro_input;
|
||||||
|
|
||||||
|
mod cmd;
|
||||||
|
mod macros;
|
||||||
|
mod pipe;
|
||||||
|
|
||||||
|
/// 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_macro::cmd;
|
||||||
|
/// let command = cmd!("echo", "test");
|
||||||
|
/// assert_eq!(format!("{command:?}"), r#""echo" "test""#.to_string());
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## Current Directory
|
||||||
|
/// ```
|
||||||
|
/// # use comlexr_macro::cmd;
|
||||||
|
/// let command = cmd!(
|
||||||
|
/// cd "~/";
|
||||||
|
/// "echo",
|
||||||
|
/// "test",
|
||||||
|
/// );
|
||||||
|
///
|
||||||
|
/// assert_eq!(format!("{command:?}"), r#"cd "~/" && "echo" "test""#);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## Environment Vars
|
||||||
|
/// ```
|
||||||
|
/// # use comlexr_macro::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""#);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// #### Conditional
|
||||||
|
/// You can have a default value set for an environment variable.
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use comlexr_macro::cmd;
|
||||||
|
/// const NEW_VAR: &str = "NEW_VAR";
|
||||||
|
/// std::env::set_var("TEST", "realvalue");
|
||||||
|
///
|
||||||
|
/// let command = cmd!(
|
||||||
|
/// env {
|
||||||
|
/// "TEST":? "test",
|
||||||
|
/// NEW_VAR: "new_var"
|
||||||
|
/// };
|
||||||
|
/// "echo",
|
||||||
|
/// "test",
|
||||||
|
/// );
|
||||||
|
///
|
||||||
|
/// assert_eq!(format!("{command:?}"), r#"NEW_VAR="new_var" "echo" "test""#);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ### Current Directory and Environment Variable Order
|
||||||
|
/// ```
|
||||||
|
/// # use comlexr_macro::cmd;
|
||||||
|
/// let command = cmd!(
|
||||||
|
/// cd "~/";
|
||||||
|
/// env {
|
||||||
|
/// "TEST": "test",
|
||||||
|
/// };
|
||||||
|
/// "echo",
|
||||||
|
/// "test",
|
||||||
|
/// );
|
||||||
|
///
|
||||||
|
/// assert_eq!(
|
||||||
|
/// format!("{command:?}"),
|
||||||
|
/// r#"cd "~/" && TEST="test" "echo" "test""#
|
||||||
|
/// );
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## Conditional Arguments
|
||||||
|
/// ```
|
||||||
|
/// # use comlexr_macro::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_macro::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_macro::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_macro::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_macro::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_macro::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()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Chain the stdout of commands to stdin. Execution is lazy so commands aren't run until `status()` or `output()` is called.
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// use comlexr::{pipe, cmd};
|
||||||
|
///
|
||||||
|
/// let dir = tempfile::tempdir().unwrap();
|
||||||
|
/// let file = dir.path().join("out");
|
||||||
|
/// let mut pipe = pipe!(cmd!("echo", "test") | cmd!("sed", "s|e|oa|") | cmd!("tee", &file));
|
||||||
|
///
|
||||||
|
/// let output = pipe.output().unwrap();
|
||||||
|
/// assert!(output.status.success());
|
||||||
|
/// assert_eq!(String::from_utf8_lossy(&output.stdout), "toast\n");
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Or pass data via stdin.
|
||||||
|
///
|
||||||
|
/// NOTE: Data must implement `AsRef<[u8]>`.
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// use comlexr::{pipe, cmd};
|
||||||
|
///
|
||||||
|
/// let mut pipe = pipe!(stdin = "test"; cmd!("sed", "s|e|oa|"));
|
||||||
|
///
|
||||||
|
/// let output = pipe.output().unwrap();
|
||||||
|
/// assert!(output.status.success());
|
||||||
|
/// assert_eq!(String::from_utf8_lossy(&output.stdout), "toast");
|
||||||
|
/// ```
|
||||||
|
#[proc_macro]
|
||||||
|
pub fn pipe(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||||
|
let pipe = parse_macro_input!(input as pipe::Pipe);
|
||||||
|
quote! { #pipe }.into()
|
||||||
|
}
|
||||||
13
macro/src/macros.rs
Normal file
13
macro/src/macros.rs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
macro_rules! enum_to_tokens {
|
||||||
|
($enum:ident: $($variant:ident),*) => {
|
||||||
|
impl ToTokens for $enum {
|
||||||
|
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
||||||
|
tokens.extend(match self {
|
||||||
|
$(Self::$variant(variant) => quote! { #variant },)*
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) use enum_to_tokens;
|
||||||
191
macro/src/pipe.rs
Normal file
191
macro/src/pipe.rs
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
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: #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
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
177
src/lib.rs
177
src/lib.rs
@@ -1,177 +1,8 @@
|
|||||||
#![doc = include_str!("../README.md")]
|
#![doc = include_str!("../README.md")]
|
||||||
|
|
||||||
extern crate quote;
|
extern crate comlexr_macro;
|
||||||
extern crate syn;
|
|
||||||
|
|
||||||
use quote::quote;
|
pub use comlexr_macro::*;
|
||||||
use syn::parse_macro_input;
|
pub use pipe::*;
|
||||||
|
|
||||||
mod cmd;
|
mod pipe;
|
||||||
|
|
||||||
/// 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()
|
|
||||||
}
|
|
||||||
|
|||||||
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()?)
|
||||||
|
}
|
||||||
|
}
|
||||||
33
tests/cmd.rs
33
tests/cmd.rs
@@ -1,6 +1,8 @@
|
|||||||
extern crate comlexr;
|
extern crate comlexr;
|
||||||
extern crate rstest;
|
extern crate rstest;
|
||||||
|
|
||||||
|
use std::env;
|
||||||
|
|
||||||
use comlexr::cmd;
|
use comlexr::cmd;
|
||||||
use rstest::rstest;
|
use rstest::rstest;
|
||||||
|
|
||||||
@@ -35,6 +37,37 @@ fn env_vars() {
|
|||||||
assert_eq!(format!("{command:?}"), r#"TEST="test" "echo" "test""#);
|
assert_eq!(format!("{command:?}"), r#"TEST="test" "echo" "test""#);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn conditional_env_vars() {
|
||||||
|
env::set_var("TEST", "realvalue");
|
||||||
|
env::set_var("TEST2", "won't see");
|
||||||
|
|
||||||
|
let command = cmd!(
|
||||||
|
env {
|
||||||
|
"TEST":? "test",
|
||||||
|
"TEST2": "test2"
|
||||||
|
};
|
||||||
|
"echo",
|
||||||
|
"test",
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(format!("{command:?}"), r#"TEST2="test2" "echo" "test""#);
|
||||||
|
|
||||||
|
let command = cmd!(
|
||||||
|
env {
|
||||||
|
"TEST": "test",
|
||||||
|
"TEST2": "test2"
|
||||||
|
};
|
||||||
|
"echo",
|
||||||
|
"test",
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
format!("{command:?}"),
|
||||||
|
r#"TEST="test" TEST2="test2" "echo" "test""#
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn cd_env_vars() {
|
fn cd_env_vars() {
|
||||||
let command = cmd!(
|
let command = cmd!(
|
||||||
|
|||||||
104
tests/pipe.rs
Normal file
104
tests/pipe.rs
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
use core::panic;
|
||||||
|
use std::ffi::OsStr;
|
||||||
|
|
||||||
|
use comlexr::{cmd, pipe};
|
||||||
|
use tempfile::tempdir;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pipe_status() {
|
||||||
|
let dir = tempdir().unwrap();
|
||||||
|
let file = dir.path().join("out");
|
||||||
|
let mut pipe = pipe!(cmd!("echo", "test") | cmd!("tee", &file));
|
||||||
|
|
||||||
|
let status = pipe.status().unwrap();
|
||||||
|
assert!(status.success());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pipe_stdin_output() {
|
||||||
|
let dir = tempdir().unwrap();
|
||||||
|
let file = dir.path().join("out");
|
||||||
|
let mut pipe = pipe!(stdin = "test"; cmd!("sed", "s|e|oa|") | cmd!("tee", &file));
|
||||||
|
println!("{}", file.display());
|
||||||
|
|
||||||
|
let output = pipe.output().unwrap();
|
||||||
|
assert!(output.status.success());
|
||||||
|
assert_eq!(String::from_utf8_lossy(&output.stdout), "toast");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pipe_stdin_output_single_command() {
|
||||||
|
let mut pipe = pipe!(stdin = "test"; cmd!("sed", "s|e|oa|"));
|
||||||
|
|
||||||
|
let output = pipe.output().unwrap();
|
||||||
|
assert!(output.status.success());
|
||||||
|
assert_eq!(String::from_utf8_lossy(&output.stdout), "toast");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pipe_expect_fail() {
|
||||||
|
let mut pipe = pipe!(stdin = "test"; cmd!("false") | cmd!("sed", "s|e|oa|"));
|
||||||
|
|
||||||
|
let error = pipe.output().unwrap_err();
|
||||||
|
|
||||||
|
match error {
|
||||||
|
comlexr::ExecutorError::FailedCommand { command, exit_code } => {
|
||||||
|
assert_eq!(command.get_program(), OsStr::new("false"));
|
||||||
|
assert_eq!(exit_code, 1);
|
||||||
|
}
|
||||||
|
err => panic!("Expected exit code, got: {err}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pipe_expect_fail_command_not_exist() {
|
||||||
|
let mut pipe = pipe!(stdin = "test"; cmd!("no_command") | cmd!("sed", "s|e|oa|"));
|
||||||
|
|
||||||
|
let error = pipe.output().unwrap_err();
|
||||||
|
|
||||||
|
match error {
|
||||||
|
comlexr::ExecutorError::Io(err) => assert_eq!(err.raw_os_error().unwrap(), 2),
|
||||||
|
err => panic!("Expected IO Error, got: {err}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pipe_block() {
|
||||||
|
let mut pipe = pipe!(stdin = "test"; {
|
||||||
|
let c = cmd!("sed", "s|e|oa|");
|
||||||
|
println!("{c:?}");
|
||||||
|
c
|
||||||
|
});
|
||||||
|
|
||||||
|
let output = pipe.output().unwrap();
|
||||||
|
assert!(output.status.success());
|
||||||
|
assert_eq!(String::from_utf8_lossy(&output.stdout), "toast");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pipe_ref() {
|
||||||
|
let mut command = {
|
||||||
|
let c = cmd!("sed", "s|e|oa|");
|
||||||
|
println!("{c:?}");
|
||||||
|
c
|
||||||
|
};
|
||||||
|
let mut pipe = pipe!(stdin = "test"; &mut command);
|
||||||
|
|
||||||
|
let output = pipe.output().unwrap();
|
||||||
|
assert!(output.status.success());
|
||||||
|
assert_eq!(String::from_utf8_lossy(&output.stdout), "toast");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pipe_fn() {
|
||||||
|
let command = || {
|
||||||
|
let c = cmd!("sed", "s|e|oa|");
|
||||||
|
println!("{c:?}");
|
||||||
|
c
|
||||||
|
};
|
||||||
|
let mut pipe = pipe!(stdin = "test"; command());
|
||||||
|
|
||||||
|
let output = pipe.output().unwrap();
|
||||||
|
assert!(output.status.success());
|
||||||
|
assert_eq!(String::from_utf8_lossy(&output.stdout), "toast");
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user