foundry_common/
fs.rs
1use crate::errors::FsPathError;
4use flate2::{Compression, read::GzDecoder, write::GzEncoder};
5use serde::{Serialize, de::DeserializeOwned};
6use std::{
7 fs::{self, File},
8 io::{BufReader, BufWriter, Write},
9 path::{Component, Path, PathBuf},
10};
11
12pub type Result<T> = std::result::Result<T, FsPathError>;
14
15pub fn create_file(path: impl AsRef<Path>) -> Result<fs::File> {
17 let path = path.as_ref();
18 File::create(path).map_err(|err| FsPathError::create_file(err, path))
19}
20
21pub fn remove_file(path: impl AsRef<Path>) -> Result<()> {
23 let path = path.as_ref();
24 fs::remove_file(path).map_err(|err| FsPathError::remove_file(err, path))
25}
26
27pub fn read(path: impl AsRef<Path>) -> Result<Vec<u8>> {
29 let path = path.as_ref();
30 fs::read(path).map_err(|err| FsPathError::read(err, path))
31}
32
33pub fn read_link(path: impl AsRef<Path>) -> Result<PathBuf> {
35 let path = path.as_ref();
36 fs::read_link(path).map_err(|err| FsPathError::read_link(err, path))
37}
38
39pub fn read_to_string(path: impl AsRef<Path>) -> Result<String> {
41 let path = path.as_ref();
42 fs::read_to_string(path).map_err(|err| FsPathError::read(err, path))
43}
44
45pub fn read_json_file<T: DeserializeOwned>(path: &Path) -> Result<T> {
47 let s = read_to_string(path)?;
50 serde_json::from_str(&s).map_err(|source| FsPathError::ReadJson { source, path: path.into() })
51}
52
53pub fn read_json_gzip_file<T: DeserializeOwned>(path: &Path) -> Result<T> {
55 let file = open(path)?;
56 let reader = BufReader::new(file);
57 let decoder = GzDecoder::new(reader);
58 serde_json::from_reader(decoder)
59 .map_err(|source| FsPathError::ReadJson { source, path: path.into() })
60}
61
62pub fn write_json_file<T: Serialize>(path: &Path, obj: &T) -> Result<()> {
64 let file = create_file(path)?;
65 let mut writer = BufWriter::new(file);
66 serde_json::to_writer(&mut writer, obj)
67 .map_err(|source| FsPathError::WriteJson { source, path: path.into() })?;
68 writer.flush().map_err(|e| FsPathError::write(e, path))
69}
70
71pub fn write_pretty_json_file<T: Serialize>(path: &Path, obj: &T) -> Result<()> {
73 let file = create_file(path)?;
74 let mut writer = BufWriter::new(file);
75 serde_json::to_writer_pretty(&mut writer, obj)
76 .map_err(|source| FsPathError::WriteJson { source, path: path.into() })?;
77 writer.flush().map_err(|e| FsPathError::write(e, path))
78}
79
80pub fn write_json_gzip_file<T: Serialize>(path: &Path, obj: &T) -> Result<()> {
82 let file = create_file(path)?;
83 let writer = BufWriter::new(file);
84 let mut encoder = GzEncoder::new(writer, Compression::default());
85 serde_json::to_writer(&mut encoder, obj)
86 .map_err(|source| FsPathError::WriteJson { source, path: path.into() })?;
87 encoder
88 .finish()
89 .map_err(serde_json::Error::io)
90 .map_err(|source| FsPathError::WriteJson { source, path: path.into() })?;
91 Ok(())
92}
93
94pub fn write(path: impl AsRef<Path>, contents: impl AsRef<[u8]>) -> Result<()> {
96 let path = path.as_ref();
97 fs::write(path, contents).map_err(|err| FsPathError::write(err, path))
98}
99
100pub fn copy(from: impl AsRef<Path>, to: impl AsRef<Path>) -> Result<u64> {
102 let from = from.as_ref();
103 let to = to.as_ref();
104 fs::copy(from, to).map_err(|err| FsPathError::copy(err, from, to))
105}
106
107pub fn create_dir(path: impl AsRef<Path>) -> Result<()> {
109 let path = path.as_ref();
110 fs::create_dir(path).map_err(|err| FsPathError::create_dir(err, path))
111}
112
113pub fn create_dir_all(path: impl AsRef<Path>) -> Result<()> {
115 let path = path.as_ref();
116 fs::create_dir_all(path).map_err(|err| FsPathError::create_dir(err, path))
117}
118
119pub fn remove_dir(path: impl AsRef<Path>) -> Result<()> {
121 let path = path.as_ref();
122 fs::remove_dir(path).map_err(|err| FsPathError::remove_dir(err, path))
123}
124
125pub fn remove_dir_all(path: impl AsRef<Path>) -> Result<()> {
127 let path = path.as_ref();
128 fs::remove_dir_all(path).map_err(|err| FsPathError::remove_dir(err, path))
129}
130
131pub fn open(path: impl AsRef<Path>) -> Result<fs::File> {
133 let path = path.as_ref();
134 fs::File::open(path).map_err(|err| FsPathError::open(err, path))
135}
136
137pub fn normalize_path(path: &Path) -> PathBuf {
144 let mut components = path.components().peekable();
145 let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().copied() {
146 components.next();
147 PathBuf::from(c.as_os_str())
148 } else {
149 PathBuf::new()
150 };
151
152 for component in components {
153 match component {
154 Component::Prefix(..) => unreachable!(),
155 Component::RootDir => {
156 ret.push(component.as_os_str());
157 }
158 Component::CurDir => {}
159 Component::ParentDir => {
160 ret.pop();
161 }
162 Component::Normal(c) => {
163 ret.push(c);
164 }
165 }
166 }
167 ret
168}
169
170pub fn files_with_ext<'a>(root: &Path, ext: &'a str) -> impl Iterator<Item = PathBuf> + 'a {
172 walkdir::WalkDir::new(root)
173 .sort_by_file_name()
174 .into_iter()
175 .filter_map(walkdir::Result::ok)
176 .filter(|e| e.file_type().is_file() && e.path().extension() == Some(ext.as_ref()))
177 .map(walkdir::DirEntry::into_path)
178}
179
180pub fn json_files(root: &Path) -> impl Iterator<Item = PathBuf> {
182 files_with_ext(root, "json")
183}
184
185pub fn canonicalize_path(path: impl AsRef<Path>) -> std::io::Result<PathBuf> {
190 dunce::canonicalize(path)
191}
192
193#[cfg(test)]
194mod tests {
195 use super::*;
196
197 #[test]
198 fn test_normalize_path() {
199 let p = Path::new("/a/../file.txt");
200 let normalized = normalize_path(p);
201 assert_eq!(normalized, PathBuf::from("/file.txt"));
202 }
203}