| 1 | use thiserror::Error; |
| 2 | use crate::Context; |
| 3 | |
| 4 | #[derive(Error, Debug)] |
| 5 | pub enum CommandSubstError { |
| 6 | #[error("Failed to execute command: {0}")] |
| 7 | ExecutionFailed(String), |
| 8 | |
| 9 | #[error("I/O error: {0}")] |
| 10 | IoError(#[from] std::io::Error), |
| 11 | } |
| 12 | |
| 13 | /// Execute a command and capture its stdout |
| 14 | /// This implements $(command) substitution |
| 15 | pub fn execute_command_substitution(cmd: &str, context: &mut Context) -> Result<String, CommandSubstError> { |
| 16 | // Use internal command executor |
| 17 | // The executor is registered during Context creation in rush-cli |
| 18 | // Clone the Rc to avoid borrow checker issues |
| 19 | if let Some(executor) = context.command_executor.0.clone() { |
| 20 | return executor(cmd, context) |
| 21 | .map_err(|e| CommandSubstError::ExecutionFailed(e)); |
| 22 | } |
| 23 | |
| 24 | // No executor configured - this should not happen in normal rush usage |
| 25 | // Rush is fully bespoke and does not delegate to external shells |
| 26 | Err(CommandSubstError::ExecutionFailed( |
| 27 | "Internal command executor not configured. Command substitution requires rush's internal executor.".to_string() |
| 28 | )) |
| 29 | } |
| 30 | |
| 31 | #[cfg(test)] |
| 32 | mod tests { |
| 33 | use super::*; |
| 34 | |
| 35 | #[test] |
| 36 | fn test_simple_command_substitution() { |
| 37 | let mut context = Context::empty(); |
| 38 | let result = execute_command_substitution("echo hello", &mut context).unwrap(); |
| 39 | assert_eq!(result, "hello"); |
| 40 | } |
| 41 | |
| 42 | #[test] |
| 43 | fn test_command_with_trailing_newline() { |
| 44 | let mut context = Context::empty(); |
| 45 | // echo adds a newline, which should be trimmed |
| 46 | let result = execute_command_substitution("printf 'hello\n'", &mut context).unwrap(); |
| 47 | assert_eq!(result, "hello"); |
| 48 | } |
| 49 | |
| 50 | #[test] |
| 51 | fn test_multiple_trailing_newlines() { |
| 52 | let mut context = Context::empty(); |
| 53 | let result = execute_command_substitution("printf 'hello\n\n\n'", &mut context).unwrap(); |
| 54 | assert_eq!(result, "hello"); |
| 55 | } |
| 56 | |
| 57 | #[test] |
| 58 | fn test_pwd() { |
| 59 | let mut context = Context::empty(); |
| 60 | // Just make sure it doesn't error |
| 61 | let result = execute_command_substitution("pwd", &mut context); |
| 62 | assert!(result.is_ok()); |
| 63 | assert!(!result.unwrap().is_empty()); |
| 64 | } |
| 65 | } |
| 66 |