command_stream/commands/
cat.rs1use crate::commands::CommandContext;
4use crate::utils::{trace_lazy, CommandResult, VirtualUtils};
5use std::fs;
6
7pub async fn cat(ctx: CommandContext) -> CommandResult {
11 if ctx.args.is_empty() {
12 if let Some(ref stdin) = ctx.stdin {
14 if !stdin.is_empty() {
15 return CommandResult::success(stdin.clone());
16 }
17 }
18 return CommandResult::success_empty();
19 }
20
21 let cwd = ctx.get_cwd();
22 let mut outputs = Vec::new();
23
24 for file in &ctx.args {
25 if ctx.is_cancelled() {
27 trace_lazy("VirtualCommand", || {
28 "cat: cancelled while processing files".to_string()
29 });
30 return CommandResult::error_with_code("", 130); }
32
33 trace_lazy("VirtualCommand", || format!("cat: reading file {:?}", file));
34
35 let resolved_path = VirtualUtils::resolve_path(file, Some(&cwd));
36
37 match fs::read_to_string(&resolved_path) {
38 Ok(content) => {
39 outputs.push(content);
40 }
41 Err(e) => {
42 let error_msg = if e.kind() == std::io::ErrorKind::NotFound {
43 format!("cat: {}: No such file or directory\n", file)
44 } else if e.kind() == std::io::ErrorKind::IsADirectory
45 || (e.kind() == std::io::ErrorKind::Other
46 && e.to_string().contains("directory"))
47 {
48 format!("cat: {}: Is a directory\n", file)
49 } else {
50 format!("cat: {}: {}\n", file, e)
51 };
52 return CommandResult::error(error_msg);
53 }
54 }
55 }
56
57 let output = outputs.join("");
58 trace_lazy("VirtualCommand", || {
59 format!("cat: success, bytes read: {}", output.len())
60 });
61
62 CommandResult::success(output)
63}
64
65#[cfg(test)]
66mod tests {
67 use super::*;
68 use std::io::Write;
69 use tempfile::NamedTempFile;
70
71 #[tokio::test]
72 async fn test_cat_file() {
73 let mut temp = NamedTempFile::new().unwrap();
74 writeln!(temp, "Hello, World!").unwrap();
75
76 let ctx = CommandContext::new(vec![temp.path().to_string_lossy().to_string()]);
77 let result = cat(ctx).await;
78
79 assert!(result.is_success());
80 assert_eq!(result.stdout, "Hello, World!\n");
81 }
82
83 #[tokio::test]
84 async fn test_cat_stdin() {
85 let mut ctx = CommandContext::new(vec![]);
86 ctx.stdin = Some("stdin content".to_string());
87
88 let result = cat(ctx).await;
89 assert!(result.is_success());
90 assert_eq!(result.stdout, "stdin content");
91 }
92
93 #[tokio::test]
94 async fn test_cat_nonexistent() {
95 let ctx = CommandContext::new(vec!["/nonexistent/file/12345".to_string()]);
96 let result = cat(ctx).await;
97
98 assert!(!result.is_success());
99 assert!(result.stderr.contains("No such file or directory"));
100 }
101
102 #[tokio::test]
103 async fn test_cat_multiple_files() {
104 let mut temp1 = NamedTempFile::new().unwrap();
105 let mut temp2 = NamedTempFile::new().unwrap();
106 write!(temp1, "file1").unwrap();
107 write!(temp2, "file2").unwrap();
108
109 let ctx = CommandContext::new(vec![
110 temp1.path().to_string_lossy().to_string(),
111 temp2.path().to_string_lossy().to_string(),
112 ]);
113 let result = cat(ctx).await;
114
115 assert!(result.is_success());
116 assert_eq!(result.stdout, "file1file2");
117 }
118}