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