Skip to main content

deepseek_rust_cli/tools/file_io/
ops.rs

1use 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}