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 path = VirtualUtils::resolve_path("/absolute/path", None);
169        assert_eq!(path, PathBuf::from("/absolute/path"));
170    }
171
172    #[test]
173    fn test_resolve_path_relative() {
174        let cwd = PathBuf::from("/home/user");
175        let path = VirtualUtils::resolve_path("relative/path", Some(&cwd));
176        assert_eq!(path, PathBuf::from("/home/user/relative/path"));
177    }
178
179    #[test]
180    fn test_validate_args_success() {
181        let args = vec!["arg1".to_string()];
182        assert!(VirtualUtils::validate_args(&args, 1, "cmd").is_none());
183    }
184
185    #[test]
186    fn test_validate_args_missing() {
187        let args = vec!["arg1".to_string()];
188        let result = VirtualUtils::validate_args(&args, 2, "cmd");
189        assert!(result.is_some());
190    }
191
192    #[test]
193    fn test_missing_operand_error() {
194        let result = VirtualUtils::missing_operand_error("cat");
195        assert!(!result.is_success());
196        assert!(result.stderr.contains("missing operand"));
197    }
198
199    #[test]
200    fn test_invalid_argument_error() {
201        let result = VirtualUtils::invalid_argument_error("ls", "invalid option");
202        assert!(!result.is_success());
203        assert!(result.stderr.contains("invalid option"));
204    }
205
206    // Re-exported module tests are in their respective modules
207    // These tests verify the re-exports work correctly
208
209    #[test]
210    fn test_reexported_quote() {
211        assert_eq!(quote("hello"), "hello");
212        assert_eq!(quote("hello world"), "'hello world'");
213    }
214
215    #[test]
216    fn test_reexported_ansi_utils() {
217        let text = "\x1b[31mRed text\x1b[0m";
218        assert_eq!(AnsiUtils::strip_ansi(text), "Red text");
219    }
220
221    #[test]
222    fn test_reexported_ansi_config() {
223        let config = AnsiConfig::default();
224        assert!(config.preserve_ansi);
225        assert!(config.preserve_control_chars);
226    }
227}