Skip to main content

command_stream/
utils.rs

1//! Utility functions and types for command-stream
2//!
3//! This module provides helper functions for command results, virtual command
4//! utilities, and re-exports from specialized utility modules.
5//!
6//! ## Module Organization
7//!
8//! The utilities are organized into focused modules following the same
9//! modular pattern as the JavaScript implementation:
10//!
11//! - `trace` - Logging and tracing utilities
12//! - `ansi` - ANSI escape code handling
13//! - `quote` - Shell quoting utilities
14//! - `utils` (this module) - Command results and virtual command helpers
15
16use std::env;
17use std::path::{Path, PathBuf};
18
19// Re-export from specialized modules for backwards compatibility
20pub use crate::ansi::{AnsiConfig, AnsiUtils};
21pub use crate::quote::quote;
22pub use crate::trace::{is_trace_enabled, trace, trace_lazy};
23
24/// Result type for virtual command operations
25#[derive(Debug, Clone)]
26pub struct CommandResult {
27    pub stdout: String,
28    pub stderr: String,
29    pub code: i32,
30}
31
32impl CommandResult {
33    /// Create a success result with stdout output
34    pub fn success(stdout: impl Into<String>) -> Self {
35        CommandResult {
36            stdout: stdout.into(),
37            stderr: String::new(),
38            code: 0,
39        }
40    }
41
42    /// Create an empty success result
43    pub fn success_empty() -> Self {
44        CommandResult {
45            stdout: String::new(),
46            stderr: String::new(),
47            code: 0,
48        }
49    }
50
51    /// Create an error result with stderr output
52    pub fn error(stderr: impl Into<String>) -> Self {
53        CommandResult {
54            stdout: String::new(),
55            stderr: stderr.into(),
56            code: 1,
57        }
58    }
59
60    /// Create an error result with custom exit code
61    pub fn error_with_code(stderr: impl Into<String>, code: i32) -> Self {
62        CommandResult {
63            stdout: String::new(),
64            stderr: stderr.into(),
65            code,
66        }
67    }
68
69    /// Check if the command was successful
70    pub fn is_success(&self) -> bool {
71        self.code == 0
72    }
73}
74
75/// Utility functions for virtual commands
76pub struct VirtualUtils;
77
78impl VirtualUtils {
79    /// Create standardized error response for missing operands
80    pub fn missing_operand_error(command_name: &str) -> CommandResult {
81        CommandResult::error(format!("{}: missing operand", command_name))
82    }
83
84    /// Create standardized error response for missing operands with custom message
85    pub fn missing_operand_error_with_message(command_name: &str, message: &str) -> CommandResult {
86        CommandResult::error(format!("{}: {}", command_name, message))
87    }
88
89    /// Create standardized error response for invalid arguments
90    pub fn invalid_argument_error(command_name: &str, message: &str) -> CommandResult {
91        CommandResult::error(format!("{}: {}", command_name, message))
92    }
93
94    /// Create standardized success response
95    pub fn success(stdout: impl Into<String>) -> CommandResult {
96        CommandResult::success(stdout)
97    }
98
99    /// Create standardized error response
100    pub fn error(stderr: impl Into<String>) -> CommandResult {
101        CommandResult::error(stderr)
102    }
103
104    /// Validate that command has required number of arguments
105    pub fn validate_args(
106        args: &[String],
107        min_count: usize,
108        command_name: &str,
109    ) -> Option<CommandResult> {
110        if args.len() < min_count {
111            if min_count == 1 {
112                return Some(Self::missing_operand_error(command_name));
113            } else {
114                return Some(Self::invalid_argument_error(
115                    command_name,
116                    &format!("requires at least {} arguments", min_count),
117                ));
118            }
119        }
120        None // No error
121    }
122
123    /// Resolve file path with optional cwd parameter
124    pub fn resolve_path(file_path: &str, cwd: Option<&Path>) -> PathBuf {
125        let path = Path::new(file_path);
126        if path.is_absolute() {
127            path.to_path_buf()
128        } else {
129            let base_path = cwd
130                .map(|p| p.to_path_buf())
131                .unwrap_or_else(|| env::current_dir().unwrap_or_else(|_| PathBuf::from("/")));
132            base_path.join(path)
133        }
134    }
135}
136
137#[cfg(test)]
138mod tests {
139    use super::*;
140
141    #[test]
142    fn test_command_result_success() {
143        let result = CommandResult::success("hello");
144        assert!(result.is_success());
145        assert_eq!(result.stdout, "hello");
146        assert_eq!(result.stderr, "");
147        assert_eq!(result.code, 0);
148    }
149
150    #[test]
151    fn test_command_result_error() {
152        let result = CommandResult::error("something went wrong");
153        assert!(!result.is_success());
154        assert_eq!(result.stdout, "");
155        assert_eq!(result.stderr, "something went wrong");
156        assert_eq!(result.code, 1);
157    }
158
159    #[test]
160    fn test_command_result_error_with_code() {
161        let result = CommandResult::error_with_code("permission denied", 126);
162        assert!(!result.is_success());
163        assert_eq!(result.code, 126);
164    }
165
166    #[test]
167    fn test_resolve_path_absolute() {
168        let absolute_path = if cfg!(windows) {
169            PathBuf::from(r"C:\absolute\path")
170        } else {
171            PathBuf::from("/absolute/path")
172        };
173        let path = VirtualUtils::resolve_path(absolute_path.to_str().unwrap(), None);
174        assert_eq!(path, absolute_path);
175    }
176
177    #[test]
178    fn test_resolve_path_relative() {
179        let cwd = PathBuf::from("/home/user");
180        let path = VirtualUtils::resolve_path("relative/path", Some(&cwd));
181        assert_eq!(path, PathBuf::from("/home/user/relative/path"));
182    }
183
184    #[test]
185    fn test_validate_args_success() {
186        let args = vec!["arg1".to_string()];
187        assert!(VirtualUtils::validate_args(&args, 1, "cmd").is_none());
188    }
189
190    #[test]
191    fn test_validate_args_missing() {
192        let args = vec!["arg1".to_string()];
193        let result = VirtualUtils::validate_args(&args, 2, "cmd");
194        assert!(result.is_some());
195    }
196
197    #[test]
198    fn test_missing_operand_error() {
199        let result = VirtualUtils::missing_operand_error("cat");
200        assert!(!result.is_success());
201        assert!(result.stderr.contains("missing operand"));
202    }
203
204    #[test]
205    fn test_invalid_argument_error() {
206        let result = VirtualUtils::invalid_argument_error("ls", "invalid option");
207        assert!(!result.is_success());
208        assert!(result.stderr.contains("invalid option"));
209    }
210
211    // Re-exported module tests are in their respective modules
212    // These tests verify the re-exports work correctly
213
214    #[test]
215    fn test_reexported_quote() {
216        assert_eq!(quote("hello"), "hello");
217        assert_eq!(quote("hello world"), "'hello world'");
218    }
219
220    #[test]
221    fn test_reexported_ansi_utils() {
222        let text = "\x1b[31mRed text\x1b[0m";
223        assert_eq!(AnsiUtils::strip_ansi(text), "Red text");
224    }
225
226    #[test]
227    fn test_reexported_ansi_config() {
228        let config = AnsiConfig::default();
229        assert!(config.preserve_ansi);
230        assert!(config.preserve_control_chars);
231    }
232}