Skip to main content

oli_tui/tools/fs/
file_ops.rs

1use anyhow::{Context, Result};
2use std::fs::{self, File};
3use std::io::{Read, Write};
4use std::path::{Path, PathBuf};
5
6pub struct FileOps;
7
8impl FileOps {
9    pub fn read_file(path: &Path) -> Result<String> {
10        let mut file =
11            File::open(path).with_context(|| format!("Failed to open file: {}", path.display()))?;
12        let mut content = String::new();
13        file.read_to_string(&mut content)
14            .with_context(|| format!("Failed to read file: {}", path.display()))?;
15        Ok(content)
16    }
17
18    pub fn read_file_with_line_numbers(path: &Path) -> Result<String> {
19        let content = Self::read_file(path)?;
20        let numbered_content = content
21            .lines()
22            .enumerate()
23            .map(|(i, line)| format!("{:4} | {}", i + 1, line))
24            .collect::<Vec<_>>()
25            .join("\n");
26        Ok(numbered_content)
27    }
28
29    pub fn read_file_lines(path: &Path, offset: usize, limit: Option<usize>) -> Result<String> {
30        let content = Self::read_file(path)?;
31        let lines: Vec<&str> = content.lines().collect();
32        let start = offset.min(lines.len());
33        let end = match limit {
34            Some(limit) => (start + limit).min(lines.len()),
35            None => lines.len(),
36        };
37
38        let numbered_content = lines[start..end]
39            .iter()
40            .enumerate()
41            .map(|(i, line)| format!("{:4} | {}", i + start + 1, line))
42            .collect::<Vec<_>>()
43            .join("\n");
44        Ok(numbered_content)
45    }
46
47    pub fn write_file(path: &Path, content: &str) -> Result<()> {
48        // Ensure parent directory exists
49        if let Some(parent) = path.parent() {
50            fs::create_dir_all(parent)
51                .with_context(|| format!("Failed to create directory: {}", parent.display()))?;
52        }
53
54        let mut file = File::create(path)
55            .with_context(|| format!("Failed to create file: {}", path.display()))?;
56        file.write_all(content.as_bytes())
57            .with_context(|| format!("Failed to write to file: {}", path.display()))?;
58        Ok(())
59    }
60
61    pub fn edit_file(path: &Path, old_string: &str, new_string: &str) -> Result<()> {
62        let content = Self::read_file(path)?;
63
64        // Count occurrences to ensure we're replacing a unique string
65        let occurrences = content.matches(old_string).count();
66        if occurrences == 0 {
67            anyhow::bail!("The string to replace was not found in the file");
68        }
69        if occurrences > 1 {
70            anyhow::bail!("The string to replace appears multiple times in the file ({}). Please provide more context to ensure a unique match.", occurrences);
71        }
72
73        let new_content = content.replace(old_string, new_string);
74        Self::write_file(path, &new_content)
75    }
76
77    pub fn list_directory(path: &Path) -> Result<Vec<PathBuf>> {
78        let entries = fs::read_dir(path)
79            .with_context(|| format!("Failed to read directory: {}", path.display()))?;
80
81        let mut paths = Vec::new();
82        for entry in entries {
83            let entry = entry.context("Failed to read directory entry")?;
84            paths.push(entry.path());
85        }
86
87        // Sort by name
88        paths.sort();
89
90        Ok(paths)
91    }
92
93    #[allow(dead_code)]
94    pub fn create_directory(path: &Path) -> Result<()> {
95        fs::create_dir_all(path)
96            .with_context(|| format!("Failed to create directory: {}", path.display()))?;
97        Ok(())
98    }
99
100    #[allow(dead_code)]
101    pub fn get_file_info(path: &Path) -> Result<String> {
102        let metadata = fs::metadata(path)
103            .with_context(|| format!("Failed to get metadata for: {}", path.display()))?;
104
105        let file_type = if metadata.is_dir() {
106            "Directory"
107        } else if metadata.is_file() {
108            "File"
109        } else {
110            "Unknown"
111        };
112
113        let size = metadata.len();
114        let modified = metadata
115            .modified()
116            .context("Failed to get modification time")?;
117
118        let info = format!(
119            "Path: {}\nType: {}\nSize: {} bytes\nModified: {:?}",
120            path.display(),
121            file_type,
122            size,
123            modified
124        );
125
126        Ok(info)
127    }
128}