1use std::env;
17use std::path::{Path, PathBuf};
18
19pub use crate::ansi::{AnsiConfig, AnsiUtils};
21pub use crate::quote::quote;
22pub use crate::trace::{is_trace_enabled, trace, trace_lazy};
23
24#[derive(Debug, Clone)]
26pub struct CommandResult {
27 pub stdout: String,
28 pub stderr: String,
29 pub code: i32,
30}
31
32impl CommandResult {
33 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 pub fn success_empty() -> Self {
44 CommandResult {
45 stdout: String::new(),
46 stderr: String::new(),
47 code: 0,
48 }
49 }
50
51 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 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 pub fn is_success(&self) -> bool {
71 self.code == 0
72 }
73}
74
75pub struct VirtualUtils;
77
78impl VirtualUtils {
79 pub fn missing_operand_error(command_name: &str) -> CommandResult {
81 CommandResult::error(format!("{}: missing operand", command_name))
82 }
83
84 pub fn missing_operand_error_with_message(command_name: &str, message: &str) -> CommandResult {
86 CommandResult::error(format!("{}: {}", command_name, message))
87 }
88
89 pub fn invalid_argument_error(command_name: &str, message: &str) -> CommandResult {
91 CommandResult::error(format!("{}: {}", command_name, message))
92 }
93
94 pub fn success(stdout: impl Into<String>) -> CommandResult {
96 CommandResult::success(stdout)
97 }
98
99 pub fn error(stderr: impl Into<String>) -> CommandResult {
101 CommandResult::error(stderr)
102 }
103
104 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 }
122
123 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 #[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}