command_stream/commands/
touch.rs1use crate::commands::CommandContext;
4use crate::utils::{trace_lazy, CommandResult, VirtualUtils};
5use std::fs::{self, OpenOptions};
6use std::time::SystemTime;
7
8pub async fn touch(ctx: CommandContext) -> CommandResult {
12 if ctx.args.is_empty() {
13 return VirtualUtils::missing_operand_error("touch");
14 }
15
16 let cwd = ctx.get_cwd();
17
18 for file in &ctx.args {
19 if file.starts_with('-') {
20 continue;
22 }
23
24 let resolved_path = VirtualUtils::resolve_path(file, Some(&cwd));
25
26 trace_lazy("VirtualCommand", || {
27 format!("touch: touching {:?}", resolved_path)
28 });
29
30 if resolved_path.exists() {
31 let now = SystemTime::now();
33 if let Err(e) =
34 filetime::set_file_mtime(&resolved_path, filetime::FileTime::from_system_time(now))
35 {
36 if let Err(e2) = OpenOptions::new().write(true).open(&resolved_path) {
38 return CommandResult::error(format!(
39 "touch: cannot touch '{}': {}\n",
40 file, e2
41 ));
42 }
43 }
44 } else {
45 if let Some(parent) = resolved_path.parent() {
47 if !parent.exists() {
48 if let Err(e) = fs::create_dir_all(parent) {
49 return CommandResult::error(format!(
50 "touch: cannot touch '{}': {}\n",
51 file, e
52 ));
53 }
54 }
55 }
56
57 if let Err(e) = fs::File::create(&resolved_path) {
58 return CommandResult::error(format!("touch: cannot touch '{}': {}\n", file, e));
59 }
60 }
61 }
62
63 CommandResult::success_empty()
64}
65
66#[cfg(test)]
67mod tests {
68 use super::*;
69 use tempfile::tempdir;
70
71 #[tokio::test]
72 async fn test_touch_new_file() {
73 let temp = tempdir().unwrap();
74 let new_file = temp.path().join("new_file.txt");
75
76 let ctx = CommandContext::new(vec![new_file.to_string_lossy().to_string()]);
77 let result = touch(ctx).await;
78
79 assert!(result.is_success());
80 assert!(new_file.exists());
81 }
82
83 #[tokio::test]
84 async fn test_touch_existing_file() {
85 let temp = tempdir().unwrap();
86 let file = temp.path().join("existing.txt");
87 fs::write(&file, "test").unwrap();
88
89 let ctx = CommandContext::new(vec![file.to_string_lossy().to_string()]);
90 let result = touch(ctx).await;
91
92 assert!(result.is_success());
93 }
94
95 #[tokio::test]
96 async fn test_touch_missing_operand() {
97 let ctx = CommandContext::new(vec![]);
98 let result = touch(ctx).await;
99
100 assert!(!result.is_success());
101 assert!(result.stderr.contains("missing operand"));
102 }
103}