Skip to main content

sage_runtime/stdlib/
io.rs

1//! I/O helper functions for the Sage standard library.
2
3use std::fs::{self, OpenOptions};
4use std::io::{self, BufRead, Read as StdRead, Write as StdWrite};
5use std::path::Path;
6
7/// Read the entire contents of a file as a string.
8pub fn read_file(path: &str) -> Result<String, String> {
9    fs::read_to_string(path).map_err(|e| format!("failed to read file '{}': {}", path, e))
10}
11
12/// Write a string to a file, creating it if it doesn't exist or truncating it.
13pub fn write_file(path: &str, contents: &str) -> Result<(), String> {
14    fs::write(path, contents).map_err(|e| format!("failed to write file '{}': {}", path, e))
15}
16
17/// Append a string to a file, creating it if it doesn't exist.
18pub fn append_file(path: &str, contents: &str) -> Result<(), String> {
19    let mut file = OpenOptions::new()
20        .create(true)
21        .append(true)
22        .open(path)
23        .map_err(|e| format!("failed to open file '{}' for append: {}", path, e))?;
24    file.write_all(contents.as_bytes())
25        .map_err(|e| format!("failed to append to file '{}': {}", path, e))
26}
27
28/// Check if a file or directory exists.
29#[must_use]
30pub fn file_exists(path: &str) -> bool {
31    Path::new(path).exists()
32}
33
34/// Delete a file.
35pub fn delete_file(path: &str) -> Result<(), String> {
36    fs::remove_file(path).map_err(|e| format!("failed to delete file '{}': {}", path, e))
37}
38
39/// List the contents of a directory.
40pub fn list_dir(path: &str) -> Result<Vec<String>, String> {
41    let entries =
42        fs::read_dir(path).map_err(|e| format!("failed to read directory '{}': {}", path, e))?;
43    let mut result = Vec::new();
44    for entry in entries {
45        let entry = entry.map_err(|e| format!("failed to read directory entry: {}", e))?;
46        result.push(entry.file_name().to_string_lossy().into_owned());
47    }
48    Ok(result)
49}
50
51/// Create a directory and all parent directories.
52pub fn make_dir(path: &str) -> Result<(), String> {
53    fs::create_dir_all(path).map_err(|e| format!("failed to create directory '{}': {}", path, e))
54}
55
56/// Read a single line from stdin.
57pub fn read_line() -> Result<String, String> {
58    let stdin = io::stdin();
59    let mut line = String::new();
60    stdin
61        .lock()
62        .read_line(&mut line)
63        .map_err(|e| format!("failed to read line from stdin: {}", e))?;
64    // Remove trailing newline
65    if line.ends_with('\n') {
66        line.pop();
67        if line.ends_with('\r') {
68            line.pop();
69        }
70    }
71    Ok(line)
72}
73
74/// Read all content from stdin until EOF.
75pub fn read_all() -> Result<String, String> {
76    let mut contents = String::new();
77    io::stdin()
78        .read_to_string(&mut contents)
79        .map_err(|e| format!("failed to read from stdin: {}", e))?;
80    Ok(contents)
81}
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86    use std::fs;
87    use tempfile::TempDir;
88
89    #[test]
90    fn test_read_write_file() {
91        let dir = TempDir::new().unwrap();
92        let path = dir.path().join("test.txt");
93        let path_str = path.to_str().unwrap();
94
95        write_file(path_str, "hello world").unwrap();
96        assert_eq!(read_file(path_str).unwrap(), "hello world");
97    }
98
99    #[test]
100    fn test_append_file() {
101        let dir = TempDir::new().unwrap();
102        let path = dir.path().join("test.txt");
103        let path_str = path.to_str().unwrap();
104
105        write_file(path_str, "hello").unwrap();
106        append_file(path_str, " world").unwrap();
107        assert_eq!(read_file(path_str).unwrap(), "hello world");
108    }
109
110    #[test]
111    fn test_file_exists() {
112        let dir = TempDir::new().unwrap();
113        let path = dir.path().join("test.txt");
114        let path_str = path.to_str().unwrap();
115
116        assert!(!file_exists(path_str));
117        fs::write(&path, "test").unwrap();
118        assert!(file_exists(path_str));
119    }
120
121    #[test]
122    fn test_delete_file() {
123        let dir = TempDir::new().unwrap();
124        let path = dir.path().join("test.txt");
125        let path_str = path.to_str().unwrap();
126
127        fs::write(&path, "test").unwrap();
128        assert!(file_exists(path_str));
129        delete_file(path_str).unwrap();
130        assert!(!file_exists(path_str));
131    }
132
133    #[test]
134    fn test_list_dir() {
135        let dir = TempDir::new().unwrap();
136        fs::write(dir.path().join("a.txt"), "").unwrap();
137        fs::write(dir.path().join("b.txt"), "").unwrap();
138
139        let mut entries = list_dir(dir.path().to_str().unwrap()).unwrap();
140        entries.sort();
141        assert_eq!(entries, vec!["a.txt", "b.txt"]);
142    }
143
144    #[test]
145    fn test_make_dir() {
146        let dir = TempDir::new().unwrap();
147        let nested = dir.path().join("a").join("b").join("c");
148        let nested_str = nested.to_str().unwrap();
149
150        assert!(!file_exists(nested_str));
151        make_dir(nested_str).unwrap();
152        assert!(file_exists(nested_str));
153    }
154}