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 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 #[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}