command_stream/macros.rs
1//! Macros for ergonomic command execution
2//!
3//! This module provides command execution macros that offer a similar experience
4//! to JavaScript's `$` tagged template literal for shell command execution.
5//!
6//! ## Available Macros
7//!
8//! - `s!` - Short, concise macro (recommended for most use cases)
9//! - `sh!` - Shell macro (alternative short form)
10//! - `cmd!` - Command macro (explicit name)
11//! - `cs!` - Command-stream macro (another alternative)
12//!
13//! All macros are aliases and provide identical functionality.
14//!
15//! ## Usage
16//!
17//! ```rust,no_run
18//! use command_stream::s;
19//!
20//! #[tokio::main]
21//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
22//! // Simple command
23//! let result = s!("echo hello world").await?;
24//!
25//! // With interpolation (values are automatically quoted for safety)
26//! let name = "John Doe";
27//! let result = s!("echo Hello, {}", name).await?;
28//!
29//! // Multiple arguments
30//! let file = "test.txt";
31//! let dir = "/tmp";
32//! let result = s!("cp {} {}", file, dir).await?;
33//!
34//! Ok(())
35//! }
36//! ```
37
38/// Build a shell command with interpolated values safely quoted
39///
40/// This function is used internally by the `cmd!` macro to build
41/// shell commands with properly quoted interpolated values.
42pub fn build_shell_command(parts: &[&str], values: &[&str]) -> String {
43 let mut result = String::new();
44
45 for (i, part) in parts.iter().enumerate() {
46 result.push_str(part);
47 if i < values.len() {
48 result.push_str(&crate::quote::quote(values[i]));
49 }
50 }
51
52 crate::quote::warn_on_split_template(&result);
53 result
54}
55
56/// Helper function to create a ProcessRunner from a command string
57pub fn create_runner(command: String) -> crate::ProcessRunner {
58 crate::ProcessRunner::new(
59 command,
60 crate::RunOptions {
61 mirror: true,
62 capture: true,
63 ..Default::default()
64 },
65 )
66}
67
68/// Helper function to create a ProcessRunner with custom options
69pub fn create_runner_with_options(
70 command: String,
71 options: crate::RunOptions,
72) -> crate::ProcessRunner {
73 crate::ProcessRunner::new(command, options)
74}
75
76/// The `cmd!` macro for ergonomic shell command execution
77///
78/// This macro provides a similar experience to JavaScript's `$` tagged template literal.
79/// Values interpolated into the command are automatically quoted for shell safety.
80///
81/// Note: Consider using the shorter `s!` or `sh!` aliases for more concise code.
82///
83/// # Examples
84///
85/// ```rust,no_run
86/// use command_stream::s;
87///
88/// # async fn example() -> Result<(), command_stream::Error> {
89/// // Simple command (returns a future that can be awaited)
90/// let result = s!("echo hello").await?;
91///
92/// // With string interpolation
93/// let name = "world";
94/// let result = s!("echo hello {}", name).await?;
95///
96/// // With multiple values
97/// let src = "source.txt";
98/// let dst = "dest.txt";
99/// let result = s!("cp {} {}", src, dst).await?;
100///
101/// // Values with special characters are automatically quoted
102/// let filename = "file with spaces.txt";
103/// let result = s!("cat {}", filename).await?; // Safely handles spaces
104/// # Ok(())
105/// # }
106/// ```
107///
108/// # Safety
109///
110/// All interpolated values are automatically quoted using shell-safe quoting,
111/// preventing command injection attacks.
112#[macro_export]
113macro_rules! cmd {
114 // No interpolation - just a plain command string
115 ($cmd:expr) => {{
116 async {
117 $crate::run($cmd).await
118 }
119 }};
120
121 // With format-style interpolation
122 ($fmt:expr, $($arg:expr),+ $(,)?) => {{
123 // Build command with quoted values
124 let mut result = String::new();
125 let values: Vec<String> = vec![$(format!("{}", $arg)),+];
126 let values_ref: Vec<&str> = values.iter().map(|s| s.as_str()).collect();
127 let fmt_parts: Vec<&str> = $fmt.split("{}").collect();
128 for (i, part) in fmt_parts.iter().enumerate() {
129 result.push_str(part);
130 if i < values_ref.len() {
131 result.push_str(&$crate::quote::quote(values_ref[i]));
132 }
133 }
134 $crate::quote::warn_on_split_template(&result);
135
136 async move {
137 $crate::run(result).await
138 }
139 }};
140}
141
142/// The `sh!` macro - alias for `cmd!`
143///
144/// This is an alternative name for `cmd!` that some users may find
145/// more intuitive for shell command execution.
146#[macro_export]
147macro_rules! sh {
148 ($($args:tt)*) => {
149 $crate::cmd!($($args)*)
150 };
151}
152
153/// The `s!` macro - short alias for `cmd!`
154///
155/// This is a concise alternative to `cmd!` for quick shell command execution.
156/// Recommended for use in documentation and examples.
157#[macro_export]
158macro_rules! s {
159 ($($args:tt)*) => {
160 $crate::cmd!($($args)*)
161 };
162}
163
164/// The `cs!` macro - alias for `cmd!`
165///
166/// Short for "command-stream", this provides another alternative
167/// for shell command execution.
168#[macro_export]
169macro_rules! cs {
170 ($($args:tt)*) => {
171 $crate::cmd!($($args)*)
172 };
173}