oli_tui/tools/fs/
file_ops.rs1use 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 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 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 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}