scirs2_core/
io.rs

1//! I/O utilities for ``SciRS2``
2//!
3//! This module provides utilities for input/output operations.
4
5use std::fs::File;
6use std::io::{BufRead, BufReader, BufWriter, Read, Write};
7use std::path::Path;
8
9use crate::error::CoreResult;
10
11/// Opens a file for reading
12///
13/// # Arguments
14///
15/// * `path` - Path to the file
16///
17/// # Returns
18///
19/// * `Ok(BufReader<File>)` if the file was opened successfully
20/// * `Err(CoreError::IoError)` if the file could not be opened
21///
22/// # Errors
23/// Returns `CoreError::IoError` if the file could not be opened.
24#[allow(dead_code)]
25pub fn open_file<P: AsRef<Path>>(path: P) -> CoreResult<BufReader<File>> {
26    let file = File::open(path.as_ref())?;
27    Ok(BufReader::new(file))
28}
29
30/// Opens a file for writing
31///
32/// # Arguments
33///
34/// * `path` - Path to the file
35///
36/// # Returns
37///
38/// * `Ok(BufWriter<File>)` if the file was opened successfully
39/// * `Err(CoreError::IoError)` if the file could not be opened
40///
41/// # Errors
42/// Returns `CoreError::IoError` if the file could not be opened.
43#[allow(dead_code)]
44pub fn create_file<P: AsRef<Path>>(path: P) -> CoreResult<BufWriter<File>> {
45    let file = File::create(path.as_ref())?;
46    Ok(BufWriter::new(file))
47}
48
49/// Reads an entire file into a string
50///
51/// # Arguments
52///
53/// * `path` - Path to the file
54///
55/// # Returns
56///
57/// * `Ok(String)` if the file was read successfully
58/// * `Err(CoreError::IoError)` if the file could not be read
59///
60/// # Errors
61/// Returns `CoreError::IoError` if the file could not be read.
62#[allow(dead_code)]
63pub fn read_to_string<P: AsRef<Path>>(path: P) -> CoreResult<String> {
64    let mut file = open_file(path)?;
65    let mut contents = String::new();
66    file.read_to_string(&mut contents)?;
67    Ok(contents)
68}
69
70/// Reads an entire file into a byte vector
71///
72/// # Arguments
73///
74/// * `path` - Path to the file
75///
76/// # Returns
77///
78/// * `Ok(Vec<u8>)` if the file was read successfully
79/// * `Err(CoreError::IoError)` if the file could not be read
80///
81/// # Errors
82/// Returns `CoreError::IoError` if the file could not be read.
83#[allow(dead_code)]
84pub fn read_to_bytes<P: AsRef<Path>>(path: P) -> CoreResult<Vec<u8>> {
85    let mut file = open_file(path)?;
86    let mut contents = Vec::new();
87    file.read_to_end(&mut contents)?;
88    Ok(contents)
89}
90
91/// Writes a string to a file
92///
93/// # Arguments
94///
95/// * `path` - Path to the file
96/// * `contents` - String to write
97///
98/// # Returns
99///
100/// * `Ok(())` if the file was written successfully
101/// * `Err(CoreError::IoError)` if the file could not be written
102#[allow(dead_code)]
103pub fn write_string<P: AsRef<Path>, S: AsRef<str>>(path: P, contents: S) -> CoreResult<()> {
104    let mut file = create_file(path)?;
105    file.write_all(contents.as_ref().as_bytes())?;
106    file.flush()?;
107    Ok(())
108}
109
110/// Writes bytes to a file
111///
112/// # Arguments
113///
114/// * `path` - Path to the file
115/// * `contents` - Bytes to write
116///
117/// # Returns
118///
119/// * `Ok(())` if the file was written successfully
120/// * `Err(CoreError::IoError)` if the file could not be written
121#[allow(dead_code)]
122pub fn write_bytes<P: AsRef<Path>, B: AsRef<[u8]>>(path: P, contents: B) -> CoreResult<()> {
123    let mut file = create_file(path)?;
124    file.write_all(contents.as_ref())?;
125    file.flush()?;
126    Ok(())
127}
128
129/// Reads a file line by line
130///
131/// # Arguments
132///
133/// * `path` - Path to the file
134/// * `callback` - Function to call for each line
135///
136/// # Returns
137///
138/// * `Ok(())` if the file was read successfully
139/// * `Err(CoreError::IoError)` if the file could not be read
140#[allow(dead_code)]
141pub fn read_lines<P, F>(path: P, mut callback: F) -> CoreResult<()>
142where
143    P: AsRef<Path>,
144    F: FnMut(String) -> CoreResult<()>,
145{
146    let file = open_file(path)?;
147    for line in file.lines() {
148        let line = line?;
149        callback(line)?;
150    }
151    Ok(())
152}
153
154/// Checks if a file exists
155///
156/// # Arguments
157///
158/// * `path` - Path to the file
159///
160/// # Returns
161///
162/// * `true` if the file exists
163/// * `false` if the file does not exist
164#[must_use]
165#[allow(dead_code)]
166pub fn file_exists<P: AsRef<Path>>(path: P) -> bool {
167    path.as_ref().exists() && path.as_ref().is_file()
168}
169
170/// Checks if a directory exists
171///
172/// # Arguments
173///
174/// * `path` - Path to the directory
175///
176/// # Returns
177///
178/// * `true` if the directory exists
179/// * `false` if the directory does not exist
180#[must_use]
181#[allow(dead_code)]
182pub fn directory_exists<P: AsRef<Path>>(path: P) -> bool {
183    path.as_ref().exists() && path.as_ref().is_dir()
184}
185
186/// Creates a directory if it doesn't exist
187///
188/// # Arguments
189///
190/// * `path` - Path to the directory
191///
192/// # Returns
193///
194/// * `Ok(())` if the directory was created successfully or already exists
195/// * `Err(CoreError::IoError)` if the directory could not be created
196#[allow(dead_code)]
197pub fn create_directory<P: AsRef<Path>>(path: P) -> CoreResult<()> {
198    if !directory_exists(&path) {
199        std::fs::create_dir_all(path.as_ref())?;
200    }
201    Ok(())
202}
203
204/// Returns the file size in bytes
205///
206/// # Arguments
207///
208/// * `path` - Path to the file
209///
210/// # Returns
211///
212/// * `Ok(u64)` if the file size was determined successfully
213/// * `Err(CoreError::IoError)` if the file size could not be determined
214///
215/// # Errors
216/// Returns `CoreError::IoError` if the file size could not be determined.
217#[allow(dead_code)]
218pub fn filesize<P: AsRef<Path>>(path: P) -> CoreResult<u64> {
219    let metadata = std::fs::metadata(path.as_ref())?;
220    Ok(metadata.len())
221}
222
223/// Pretty-prints a file size with appropriate units
224///
225/// # Arguments
226///
227/// * `size` - File size in bytes
228///
229/// # Returns
230///
231/// * A formatted string with appropriate units
232#[must_use]
233#[allow(dead_code)]
234pub fn formatsize(size: u64) -> String {
235    const KB: u64 = 1024;
236    const MB: u64 = KB * 1024;
237    const GB: u64 = MB * 1024;
238    const TB: u64 = GB * 1024;
239
240    if size >= TB {
241        format!("{:.2} TB", size as f64 / TB as f64)
242    } else if size >= GB {
243        format!("{:.2} GB", size as f64 / GB as f64)
244    } else if size >= MB {
245        format!("{:.2} MB", size as f64 / MB as f64)
246    } else if size >= KB {
247        format!("{:.2} KB", size as f64 / KB as f64)
248    } else {
249        format!("{size} B")
250    }
251}
252
253#[cfg(test)]
254mod tests {
255    use super::*;
256    use std::io::Write;
257    use tempfile::tempdir;
258
259    #[test]
260    fn test_read_write_string() {
261        let dir = tempdir().unwrap();
262        let filepath = dir.path().join("test.txt");
263
264        let test_str = "Hello, world!";
265        write_string(&filepath, test_str).unwrap();
266
267        let contents = read_to_string(&filepath).unwrap();
268        assert_eq!(contents, test_str);
269    }
270
271    #[test]
272    fn test_read_write_bytes() {
273        let dir = tempdir().unwrap();
274        let filepath = dir.path().join("test.bin");
275
276        let test_bytes = vec![1, 2, 3, 4, 5];
277        write_bytes(&filepath, &test_bytes).unwrap();
278
279        let contents = read_to_bytes(&filepath).unwrap();
280        assert_eq!(contents, test_bytes);
281    }
282
283    #[test]
284    fn test_read_lines() {
285        let dir = tempdir().unwrap();
286        let filepath = dir.path().join("test_lines.txt");
287
288        {
289            let mut file = File::create(&filepath).unwrap();
290            writeln!(file, "Line 1").unwrap();
291            writeln!(file, "Line 2").unwrap();
292            writeln!(file, "Line 3").unwrap();
293        }
294
295        let mut lines = Vec::new();
296        read_lines(&filepath, |line| {
297            lines.push(line);
298            Ok(())
299        })
300        .unwrap();
301
302        assert_eq!(lines, vec!["Line 1", "Line 2", "Line 3"]);
303    }
304
305    #[test]
306    fn test_file_exists() {
307        let dir = tempdir().unwrap();
308        let filepath = dir.path().join("test_exists.txt");
309
310        assert!(!file_exists(&filepath));
311
312        File::create(&filepath).unwrap();
313
314        assert!(file_exists(&filepath));
315    }
316
317    #[test]
318    fn test_directory_exists() {
319        let dir = tempdir().unwrap();
320        let dirpath = dir.path().join("test_dir");
321
322        assert!(!directory_exists(&dirpath));
323
324        std::fs::create_dir(&dirpath).unwrap();
325
326        assert!(directory_exists(&dirpath));
327    }
328
329    #[test]
330    fn test_create_directory() {
331        let dir = tempdir().unwrap();
332        let dirpath = dir.path().join("test_create_dir");
333
334        assert!(!directory_exists(&dirpath));
335
336        create_directory(&dirpath).unwrap();
337
338        assert!(directory_exists(&dirpath));
339    }
340
341    #[test]
342    fn test_filesize() {
343        let dir = tempdir().unwrap();
344        let filepath = dir.path().join("testsize.txt");
345
346        let test_str = "Hello, world!";
347        write_string(&filepath, test_str).unwrap();
348
349        let size = filesize(&filepath).unwrap();
350        assert_eq!(size, test_str.len() as u64);
351    }
352
353    #[test]
354    fn test_formatsize() {
355        assert_eq!(formatsize(500), "500 B");
356        assert_eq!(formatsize(1024), "1.00 KB");
357        assert_eq!(formatsize(1500), "1.46 KB");
358        assert_eq!(formatsize(1024 * 1024), "1.00 MB");
359        assert_eq!(formatsize(1024 * 1024 * 1024), "1.00 GB");
360        assert_eq!(formatsize(1024 * 1024 * 1024 * 1024), "1.00 TB");
361    }
362}