1use std::path::{Path, PathBuf};
7use tokio::fs;
8use tokio::io::{AsyncReadExt, AsyncWriteExt};
9
10pub async fn read_to_string<P: AsRef<Path>>(path: P) -> Result<String, String> {
14 let path = path.as_ref();
15 let mut file = fs::File::open(path)
16 .await
17 .map_err(|e| format!("Failed to open {}: {}", path.display(), e))?;
18 let mut contents = String::new();
19 file.read_to_string(&mut contents)
20 .await
21 .map_err(|e| format!("Failed to read {}: {}", path.display(), e))?;
22 Ok(contents)
23}
24
25pub async fn read_bytes<P: AsRef<Path>>(path: P) -> Result<Vec<u8>, String> {
27 let path = path.as_ref();
28 fs::read(path)
29 .await
30 .map_err(|e| format!("Failed to read {}: {}", path.display(), e))
31}
32
33pub async fn write_string<P: AsRef<Path>>(path: P, content: &str) -> Result<(), String> {
35 let path = path.as_ref();
36 if let Some(parent) = path.parent() {
37 fs::create_dir_all(parent)
38 .await
39 .map_err(|e| format!("Failed to create dir {}: {}", parent.display(), e))?;
40 }
41 let mut file = fs::File::create(path)
42 .await
43 .map_err(|e| format!("Failed to create {}: {}", path.display(), e))?;
44 file.write_all(content.as_bytes())
45 .await
46 .map_err(|e| format!("Failed to write {}: {}", path.display(), e))
47}
48
49pub async fn write_bytes<P: AsRef<Path>>(path: P, data: &[u8]) -> Result<(), String> {
51 let path = path.as_ref();
52 if let Some(parent) = path.parent() {
53 fs::create_dir_all(parent)
54 .await
55 .map_err(|e| format!("Failed to create dir {}: {}", parent.display(), e))?;
56 }
57 fs::write(path, data)
58 .await
59 .map_err(|e| format!("Failed to write {}: {}", path.display(), e))
60}
61
62pub async fn exists<P: AsRef<Path>>(path: P) -> bool {
64 fs::try_exists(path).await.unwrap_or(false)
65}
66
67pub fn resolve(base: &Path, relative: &Path) -> PathBuf {
69 if relative.is_absolute() {
70 relative.to_path_buf()
71 } else {
72 base.join(relative)
73 }
74}
75
76pub fn walk_files(dir: &Path, extensions: &[&str]) -> Vec<PathBuf> {
80 let mut results = Vec::new();
81 if !dir.is_dir() {
82 return results;
83 }
84 walk_dir_recursive(dir, extensions, &mut results);
85 results
86}
87
88fn walk_dir_recursive(dir: &Path, extensions: &[&str], results: &mut Vec<PathBuf>) {
89 if let Ok(entries) = std::fs::read_dir(dir) {
90 for entry in entries.flatten() {
91 let path = entry.path();
92 if path.is_dir() {
93 walk_dir_recursive(&path, extensions, results);
94 } else if let Some(ext) = path.extension() {
95 if extensions.iter().any(|e| ext == *e) {
96 results.push(path);
97 }
98 }
99 }
100 }
101}
102
103#[cfg(test)]
104mod tests {
105 use super::*;
106
107 #[tokio::test]
108 async fn test_write_and_read_string() {
109 let dir = std::env::temp_dir().join("iris-io-test");
110 let path = dir.join("hello.txt");
111 write_string(&path, "Hello Iris!").await.unwrap();
112 let content = read_to_string(&path).await.unwrap();
113 assert_eq!(content, "Hello Iris!");
114 let _ = std::fs::remove_dir_all(&dir);
115 }
116
117 #[test]
118 fn test_resolve() {
119 let base = Path::new("/project/src");
120 let rel = Path::new("../main.rs");
121 let abs = Path::new("/absolute/path.rs");
122 assert!(resolve(base, rel).ends_with("main.rs"));
123 assert_eq!(resolve(base, abs), abs);
124 }
125
126 #[test]
127 fn test_walk_files() {
128 let files = walk_files(Path::new("."), &["rs"]);
129 assert!(!files.is_empty());
130 }
131}