Skip to main content

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    result
53}
54
55/// Helper function to create a ProcessRunner from a command string
56pub fn create_runner(command: String) -> crate::ProcessRunner {
57    crate::ProcessRunner::new(
58        command,
59        crate::RunOptions {
60            mirror: true,
61            capture: true,
62            ..Default::default()
63        },
64    )
65}
66
67/// Helper function to create a ProcessRunner with custom options
68pub fn create_runner_with_options(
69    command: String,
70    options: crate::RunOptions,
71) -> crate::ProcessRunner {
72    crate::ProcessRunner::new(command, options)
73}
74
75/// The `cmd!` macro for ergonomic shell command execution
76///
77/// This macro provides a similar experience to JavaScript's `$` tagged template literal.
78/// Values interpolated into the command are automatically quoted for shell safety.
79///
80/// Note: Consider using the shorter `s!` or `sh!` aliases for more concise code.
81///
82/// # Examples
83///
84/// ```rust,no_run
85/// use command_stream::s;
86///
87/// # async fn example() -> Result<(), command_stream::Error> {
88/// // Simple command (returns a future that can be awaited)
89/// let result = s!("echo hello").await?;
90///
91/// // With string interpolation
92/// let name = "world";
93/// let result = s!("echo hello {}", name).await?;
94///
95/// // With multiple values
96/// let src = "source.txt";
97/// let dst = "dest.txt";
98/// let result = s!("cp {} {}", src, dst).await?;
99///
100/// // Values with special characters are automatically quoted
101/// let filename = "file with spaces.txt";
102/// let result = s!("cat {}", filename).await?; // Safely handles spaces
103/// # Ok(())
104/// # }
105/// ```
106///
107/// # Safety
108///
109/// All interpolated values are automatically quoted using shell-safe quoting,
110/// preventing command injection attacks.
111#[macro_export]
112macro_rules! cmd {
113    // No interpolation - just a plain command string
114    ($cmd:expr) => {{
115        async {
116            $crate::run($cmd).await
117        }
118    }};
119
120    // With format-style interpolation
121    ($fmt:expr, $($arg:expr),+ $(,)?) => {{
122        // Build command with quoted values
123        let mut result = String::new();
124        let values: Vec<String> = vec![$(format!("{}", $arg)),+];
125        let values_ref: Vec<&str> = values.iter().map(|s| s.as_str()).collect();
126        let fmt_parts: Vec<&str> = $fmt.split("{}").collect();
127        for (i, part) in fmt_parts.iter().enumerate() {
128            result.push_str(part);
129            if i < values_ref.len() {
130                result.push_str(&$crate::quote::quote(values_ref[i]));
131            }
132        }
133
134        async move {
135            $crate::run(result).await
136        }
137    }};
138}
139
140/// The `sh!` macro - alias for `cmd!`
141///
142/// This is an alternative name for `cmd!` that some users may find
143/// more intuitive for shell command execution.
144#[macro_export]
145macro_rules! sh {
146    ($($args:tt)*) => {
147        $crate::cmd!($($args)*)
148    };
149}
150
151/// The `s!` macro - short alias for `cmd!`
152///
153/// This is a concise alternative to `cmd!` for quick shell command execution.
154/// Recommended for use in documentation and examples.
155#[macro_export]
156macro_rules! s {
157    ($($args:tt)*) => {
158        $crate::cmd!($($args)*)
159    };
160}
161
162/// The `cs!` macro - alias for `cmd!`
163///
164/// Short for "command-stream", this provides another alternative
165/// for shell command execution.
166#[macro_export]
167macro_rules! cs {
168    ($($args:tt)*) => {
169        $crate::cmd!($($args)*)
170    };
171}