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 pub fn exit_code(&self) -> i32 {
79 self.code
80 }
81}
82
83pub struct VirtualUtils;
85
86impl VirtualUtils {
87 pub fn missing_operand_error(command_name: &str) -> CommandResult {
89 CommandResult::error(format!("{}: missing operand", command_name))
90 }
91
92 pub fn missing_operand_error_with_message(command_name: &str, message: &str) -> CommandResult {
94 CommandResult::error(format!("{}: {}", command_name, message))
95 }
96
97 pub fn invalid_argument_error(command_name: &str, message: &str) -> CommandResult {
99 CommandResult::error(format!("{}: {}", command_name, message))
100 }
101
102 pub fn success(stdout: impl Into<String>) -> CommandResult {
104 CommandResult::success(stdout)
105 }
106
107 pub fn error(stderr: impl Into<String>) -> CommandResult {
109 CommandResult::error(stderr)
110 }
111
112 pub fn validate_args(
114 args: &[String],
115 min_count: usize,
116 command_name: &str,
117 ) -> Option<CommandResult> {
118 if args.len() < min_count {
119 if min_count == 1 {
120 return Some(Self::missing_operand_error(command_name));
121 } else {
122 return Some(Self::invalid_argument_error(
123 command_name,
124 &format!("requires at least {} arguments", min_count),
125 ));
126 }
127 }
128 None }
130
131 pub fn resolve_path(file_path: &str, cwd: Option<&Path>) -> PathBuf {
133 let path = Path::new(file_path);
134 if path.is_absolute() {
135 path.to_path_buf()
136 } else {
137 let base_path = cwd
138 .map(|p| p.to_path_buf())
139 .unwrap_or_else(|| env::current_dir().unwrap_or_else(|_| PathBuf::from("/")));
140 base_path.join(path)
141 }
142 }
143}
144
145#[cfg(test)]
146mod tests {
147 use super::*;
148
149 #[test]
150 fn test_command_result_success() {
151 let result = CommandResult::success("hello");
152 assert!(result.is_success());
153 assert_eq!(result.stdout, "hello");
154 assert_eq!(result.stderr, "");
155 assert_eq!(result.code, 0);
156 }
157
158 #[test]
159 fn test_command_result_error() {
160 let result = CommandResult::error("something went wrong");
161 assert!(!result.is_success());
162 assert_eq!(result.stdout, "");
163 assert_eq!(result.stderr, "something went wrong");
164 assert_eq!(result.code, 1);
165 }
166
167 #[test]
168 fn test_command_result_error_with_code() {
169 let result = CommandResult::error_with_code("permission denied", 126);
170 assert!(!result.is_success());
171 assert_eq!(result.code, 126);
172 }
173
174 #[test]
175 fn test_resolve_path_absolute() {
176 let absolute_path = if cfg!(windows) {
177 PathBuf::from(r"C:\absolute\path")
178 } else {
179 PathBuf::from("/absolute/path")
180 };
181 let path = VirtualUtils::resolve_path(absolute_path.to_str().unwrap(), None);
182 assert_eq!(path, absolute_path);
183 }
184
185 #[test]
186 fn test_resolve_path_relative() {
187 let cwd = PathBuf::from("/home/user");
188 let path = VirtualUtils::resolve_path("relative/path", Some(&cwd));
189 assert_eq!(path, PathBuf::from("/home/user/relative/path"));
190 }
191
192 #[test]
193 fn test_validate_args_success() {
194 let args = vec!["arg1".to_string()];
195 assert!(VirtualUtils::validate_args(&args, 1, "cmd").is_none());
196 }
197
198 #[test]
199 fn test_validate_args_missing() {
200 let args = vec!["arg1".to_string()];
201 let result = VirtualUtils::validate_args(&args, 2, "cmd");
202 assert!(result.is_some());
203 }
204
205 #[test]
206 fn test_missing_operand_error() {
207 let result = VirtualUtils::missing_operand_error("cat");
208 assert!(!result.is_success());
209 assert!(result.stderr.contains("missing operand"));
210 }
211
212 #[test]
213 fn test_invalid_argument_error() {
214 let result = VirtualUtils::invalid_argument_error("ls", "invalid option");
215 assert!(!result.is_success());
216 assert!(result.stderr.contains("invalid option"));
217 }
218
219 #[test]
223 fn test_reexported_quote() {
224 assert_eq!(quote("hello"), "hello");
225 assert_eq!(quote("hello world"), "'hello world'");
226 }
227
228 #[test]
229 fn test_reexported_ansi_utils() {
230 let text = "\x1b[31mRed text\x1b[0m";
231 assert_eq!(AnsiUtils::strip_ansi(text), "Red text");
232 }
233
234 #[test]
235 fn test_reexported_ansi_config() {
236 let config = AnsiConfig::default();
237 assert!(config.preserve_ansi);
238 assert!(config.preserve_control_chars);
239 }
240}