command_stream/commands/
rm.rs1use crate::commands::CommandContext;
4use crate::utils::{trace_lazy, CommandResult, VirtualUtils};
5use std::fs;
6
7pub async fn rm(ctx: CommandContext) -> CommandResult {
11 if ctx.args.is_empty() {
12 return VirtualUtils::missing_operand_error("rm");
13 }
14
15 let mut recursive = false;
17 let mut force = false;
18 let mut paths = Vec::new();
19
20 for arg in &ctx.args {
21 if arg == "-r" || arg == "-R" || arg == "--recursive" {
22 recursive = true;
23 } else if arg == "-f" || arg == "--force" {
24 force = true;
25 } else if arg == "-rf" || arg == "-fr" {
26 recursive = true;
27 force = true;
28 } else if arg.starts_with('-') {
29 if arg.contains('r') || arg.contains('R') {
31 recursive = true;
32 }
33 if arg.contains('f') {
34 force = true;
35 }
36 } else {
37 paths.push(arg.clone());
38 }
39 }
40
41 if paths.is_empty() {
42 return VirtualUtils::missing_operand_error("rm");
43 }
44
45 let cwd = ctx.get_cwd();
46
47 for path_str in paths {
48 let resolved_path = VirtualUtils::resolve_path(&path_str, Some(&cwd));
49
50 trace_lazy("VirtualCommand", || {
51 format!(
52 "rm: removing {:?}, recursive: {}, force: {}",
53 resolved_path, recursive, force
54 )
55 });
56
57 if !resolved_path.exists() {
58 if !force {
59 return CommandResult::error(format!(
60 "rm: cannot remove '{}': No such file or directory\n",
61 path_str
62 ));
63 }
64 continue;
65 }
66
67 let result = if resolved_path.is_dir() {
68 if recursive {
69 fs::remove_dir_all(&resolved_path)
70 } else {
71 return CommandResult::error(format!(
72 "rm: cannot remove '{}': Is a directory\n",
73 path_str
74 ));
75 }
76 } else {
77 fs::remove_file(&resolved_path)
78 };
79
80 if let Err(e) = result {
81 if !force {
82 return CommandResult::error(format!("rm: cannot remove '{}': {}\n", path_str, e));
83 }
84 }
85 }
86
87 CommandResult::success_empty()
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93 use std::io::Write;
94 use tempfile::{tempdir, NamedTempFile};
95
96 #[tokio::test]
97 async fn test_rm_file() {
98 let mut temp = NamedTempFile::new().unwrap();
99 writeln!(temp, "test").unwrap();
100 let path = temp.path().to_path_buf();
101
102 let path_str = path.to_string_lossy().to_string();
104 drop(temp);
105
106 fs::write(&path, "test").unwrap();
108
109 let ctx = CommandContext::new(vec![path_str.clone()]);
110 let result = rm(ctx).await;
111
112 assert!(result.is_success());
113 assert!(!path.exists());
114 }
115
116 #[tokio::test]
117 async fn test_rm_directory_recursive() {
118 let temp = tempdir().unwrap();
119 let dir = temp.path().join("subdir");
120 fs::create_dir(&dir).unwrap();
121 fs::write(dir.join("file.txt"), "test").unwrap();
122
123 let ctx = CommandContext::new(vec!["-r".to_string(), dir.to_string_lossy().to_string()]);
124 let result = rm(ctx).await;
125
126 assert!(result.is_success());
127 assert!(!dir.exists());
128 }
129
130 #[tokio::test]
131 async fn test_rm_nonexistent_force() {
132 let ctx = CommandContext::new(vec![
133 "-f".to_string(),
134 "/nonexistent/file/12345".to_string(),
135 ]);
136 let result = rm(ctx).await;
137
138 assert!(result.is_success());
139 }
140
141 #[tokio::test]
142 async fn test_rm_nonexistent_no_force() {
143 let ctx = CommandContext::new(vec!["/nonexistent/file/12345".to_string()]);
144 let result = rm(ctx).await;
145
146 assert!(!result.is_success());
147 }
148}