deepseek_rust_cli/tools/file_io/
ops.rs1use anyhow::Result;
2use regex::Regex;
3use tokio::fs;
4use walkdir::WalkDir;
5
6use crate::tools::base::validate_path;
7
8pub async fn delete_file(path: &str) -> Result<()> {
9 let p = validate_path(path)?;
10 let meta = fs::metadata(&p).await?;
11 if meta.is_dir() {
12 fs::remove_dir_all(p).await?;
13 } else {
14 fs::remove_file(p).await?;
15 }
16 Ok(())
17}
18
19pub async fn rename_file(src: &str, dst: &str) -> Result<()> {
20 let s = validate_path(src)?;
21 let d = validate_path(dst)?;
22 fs::rename(s, d).await?;
23 Ok(())
24}
25
26pub async fn bulk_rename(path: &str, pattern: &str, replacement: &str) -> Result<String> {
27 let p = validate_path(path)?;
28 let re = Regex::new(pattern)?;
29 let mut count = 0;
30 let mut entries = fs::read_dir(p).await?;
31
32 while let Some(entry) = entries.next_entry().await? {
33 let name = entry.file_name().to_string_lossy().to_string();
34 if re.is_match(&name) {
35 let new_name = re.replace_all(&name, replacement).to_string();
36 let src = entry.path();
37 let dst = src.with_file_name(new_name);
38 let validated_dst = validate_path(
39 dst.to_str()
40 .ok_or_else(|| anyhow::anyhow!("Invalid path Unicode"))?,
41 )?;
42 fs::rename(src, validated_dst).await?;
43 count += 1;
44 }
45 }
46 Ok(format!("Successfully renamed {} files.", count))
47}
48
49pub async fn copy_local_file(src: &str, dst: &str) -> Result<()> {
50 let s = validate_path(src)?;
51 let d = validate_path(dst)?;
52 if let Some(parent) = d.parent() {
53 fs::create_dir_all(parent).await?;
54 }
55 fs::copy(s, d).await?;
56 Ok(())
57}
58
59pub async fn copy_directory(src: &str, dst: &str) -> Result<()> {
60 let s = validate_path(src)?;
61 let d = validate_path(dst)?;
62
63 if !s.is_dir() {
64 anyhow::bail!("Source path is not a directory");
65 }
66
67 let src_clone = s.clone();
68 let dst_clone = d.clone();
69
70 tokio::task::spawn_blocking(move || {
71 for entry in WalkDir::new(&src_clone) {
72 let entry = entry?;
73 let path = entry.path();
74 let relative = path.strip_prefix(&src_clone)?;
75 let target = dst_clone.join(relative);
76
77 if entry.file_type().is_dir() {
78 std::fs::create_dir_all(&target)?;
79 } else {
80 if let Some(parent) = target.parent() {
81 std::fs::create_dir_all(parent)?;
82 }
83 std::fs::copy(path, target)?;
84 }
85 }
86 Ok::<(), anyhow::Error>(())
87 })
88 .await??;
89
90 Ok(())
91}
92
93pub async fn create_directory(path: &str) -> Result<()> {
94 let p = validate_path(path)?;
95 fs::create_dir_all(p).await?;
96 Ok(())
97}
98
99pub async fn file_exists(path: &str) -> Result<bool> {
100 let p = validate_path(path)?;
101 Ok(p.exists())
102}
103
104pub async fn get_file_info(path: &str) -> Result<String> {
105 let p = validate_path(path)?;
106 let meta = fs::metadata(&p).await?;
107
108 let file_type = if meta.is_dir() { "Directory" } else { "File" };
109 let size = meta.len();
110 let modified = meta
111 .modified()
112 .ok()
113 .map(|t| {
114 let datetime: chrono::DateTime<chrono::Local> = t.into();
115 datetime.format("%Y-%m-%d %H:%M:%S").to_string()
116 })
117 .unwrap_or_else(|| "Unknown".to_string());
118
119 let permissions = format!("{:?}", meta.permissions());
120
121 Ok(format!(
122 "Path: {}\nType: {}\nSize: {} bytes\nModified: {}\nPermissions: {}",
123 path, file_type, size, modified, permissions
124 ))
125}
126
127pub async fn split_file(path: &str, pattern: &str, output_prefix: &str) -> Result<String> {
128 let p = validate_path(path)?;
129 let content = fs::read_to_string(&p).await?;
130 let re = Regex::new(pattern)?;
131 let mut parts = Vec::new();
132 let mut last_idx = 0;
133
134 for mat in re.find_iter(&content) {
135 if mat.start() > last_idx {
136 parts.push(&content[last_idx..mat.start()]);
137 }
138 last_idx = mat.start();
139 }
140 parts.push(&content[last_idx..]);
141
142 let mut count = 0;
143 for part in parts {
144 if part.trim().is_empty() {
145 continue;
146 }
147 let out_path_raw = format!("{}_{}.txt", output_prefix, count + 1);
148 let out_path = validate_path(&out_path_raw)?;
149 fs::write(out_path, part).await?;
150 count += 1;
151 }
152
153 Ok(format!("File split into {} parts.", count))
154}
155
156#[cfg(test)]
157mod tests {
158 use std::fs;
159
160 use tempfile::TempDir;
161
162 use super::*;
163
164 fn tempdir_in_cwd() -> TempDir {
165 TempDir::new_in(".").expect("Failed to create temp dir in CWD")
166 }
167
168 #[tokio::test]
169 async fn test_file_exists() {
170 let dir = tempdir_in_cwd();
171 let file_path = dir.path().join("exists.txt");
172 fs::write(&file_path, "exist").unwrap();
173 assert!(file_exists(file_path.to_str().unwrap()).await.unwrap());
174 }
175
176 #[tokio::test]
177 async fn test_copy_local_file() {
178 let dir = tempdir_in_cwd();
179 let src_path = dir.path().join("src.txt");
180 let dst_path = dir.path().join("dst.txt");
181 fs::write(&src_path, "copy test content").unwrap();
182
183 let src_str = src_path.to_str().unwrap();
184 let dst_str = dst_path.to_str().unwrap();
185 copy_local_file(src_str, dst_str).await.unwrap();
186
187 let dst_content = fs::read_to_string(&dst_path).unwrap();
188 assert_eq!(dst_content, "copy test content");
189 }
190
191 #[tokio::test]
192 async fn test_copy_directory() {
193 let dir = tempdir_in_cwd();
194 let src_dir = dir.path().join("src_dir");
195 let dst_dir = dir.path().join("dst_dir");
196 fs::create_dir_all(&src_dir).unwrap();
197 fs::write(src_dir.join("a.txt"), "file a").unwrap();
198 fs::write(src_dir.join("b.txt"), "file b").unwrap();
199
200 let src_str = src_dir.to_str().unwrap();
201 let dst_str = dst_dir.to_str().unwrap();
202 copy_directory(src_str, dst_str).await.unwrap();
203
204 assert!(dst_dir.join("a.txt").exists());
205 assert!(dst_dir.join("b.txt").exists());
206 }
207}