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, Read, Seek, SeekFrom, 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 locked_read_to_string(path: impl AsRef<Path>) -> Result<String> {
64 let path = path.as_ref();
65 let mut file =
66 fs::OpenOptions::new().read(true).open(path).map_err(|err| FsPathError::open(err, path))?;
67 file.lock_shared().map_err(|err| FsPathError::lock(err, path))?;
68 let contents = read_inner(path, &mut file)?;
69 file.unlock().map_err(|err| FsPathError::unlock(err, path))?;
70 String::from_utf8(contents).map_err(|err| FsPathError::read(std::io::Error::other(err), path))
71}
72
73pub fn locked_read(path: impl AsRef<Path>) -> Result<Vec<u8>> {
75 let path = path.as_ref();
76 let mut file =
77 fs::OpenOptions::new().read(true).open(path).map_err(|err| FsPathError::open(err, path))?;
78 file.lock_shared().map_err(|err| FsPathError::lock(err, path))?;
79 let contents = read_inner(path, &mut file)?;
80 file.unlock().map_err(|err| FsPathError::unlock(err, path))?;
81 Ok(contents)
82}
83
84fn read_inner(path: &Path, file: &mut File) -> Result<Vec<u8>> {
85 let file_len = file.metadata().map_err(|err| FsPathError::open(err, path))?.len() as usize;
86 let mut buffer = Vec::with_capacity(file_len);
87 file.read_to_end(&mut buffer).map_err(|err| FsPathError::read(err, path))?;
88 Ok(buffer)
89}
90
91pub fn write_json_file<T: Serialize>(path: &Path, obj: &T) -> Result<()> {
93 let file = create_file(path)?;
94 let mut writer = BufWriter::new(file);
95 serde_json::to_writer(&mut writer, obj)
96 .map_err(|source| FsPathError::WriteJson { source, path: path.into() })?;
97 writer.flush().map_err(|e| FsPathError::write(e, path))
98}
99
100pub fn write_pretty_json_file<T: Serialize>(path: &Path, obj: &T) -> Result<()> {
102 let file = create_file(path)?;
103 let mut writer = BufWriter::new(file);
104 serde_json::to_writer_pretty(&mut writer, obj)
105 .map_err(|source| FsPathError::WriteJson { source, path: path.into() })?;
106 writer.flush().map_err(|e| FsPathError::write(e, path))
107}
108
109pub fn write_json_gzip_file<T: Serialize>(path: &Path, obj: &T) -> Result<()> {
111 let file = create_file(path)?;
112 let writer = BufWriter::new(file);
113 let mut encoder = GzEncoder::new(writer, Compression::default());
114 serde_json::to_writer(&mut encoder, obj)
115 .map_err(|source| FsPathError::WriteJson { source, path: path.into() })?;
116 let mut inner_writer = encoder.finish().map_err(|e| FsPathError::write(e, path))?;
118 inner_writer.flush().map_err(|e| FsPathError::write(e, path))?;
119 Ok(())
120}
121
122pub fn write(path: impl AsRef<Path>, contents: impl AsRef<[u8]>) -> Result<()> {
124 let path = path.as_ref();
125 fs::write(path, contents).map_err(|err| FsPathError::write(err, path))
126}
127
128pub fn locked_write(path: impl AsRef<Path>, contents: impl AsRef<[u8]>) -> Result<()> {
130 let path = path.as_ref();
131 let mut file = fs::OpenOptions::new()
132 .write(true)
133 .create(true)
134 .truncate(true)
135 .open(path)
136 .map_err(|err| FsPathError::open(err, path))?;
137 file.lock().map_err(|err| FsPathError::lock(err, path))?;
138 file.write_all(contents.as_ref()).map_err(|err| FsPathError::write(err, path))?;
139 file.unlock().map_err(|err| FsPathError::unlock(err, path))
140}
141
142pub fn locked_write_line(path: impl AsRef<Path>, line: &str) -> Result<()> {
144 let path = path.as_ref();
145 if cfg!(windows) {
146 return locked_write_line_windows(path, line);
147 }
148
149 let mut file = std::fs::OpenOptions::new()
150 .append(true)
151 .create(true)
152 .open(path)
153 .map_err(|err| FsPathError::open(err, path))?;
154
155 file.lock().map_err(|err| FsPathError::lock(err, path))?;
156 writeln!(file, "{line}").map_err(|err| FsPathError::write(err, path))?;
157 file.unlock().map_err(|err| FsPathError::unlock(err, path))
158}
159
160fn locked_write_line_windows(path: &Path, line: &str) -> Result<()> {
162 let mut file = std::fs::OpenOptions::new()
163 .write(true)
164 .truncate(false)
165 .create(true)
166 .open(path)
167 .map_err(|err| FsPathError::open(err, path))?;
168 file.lock().map_err(|err| FsPathError::lock(err, path))?;
169
170 file.seek(SeekFrom::End(0)).map_err(|err| FsPathError::write(err, path))?;
171 writeln!(file, "{line}").map_err(|err| FsPathError::write(err, path))?;
172
173 file.unlock().map_err(|err| FsPathError::unlock(err, path))
174}
175
176pub fn copy(from: impl AsRef<Path>, to: impl AsRef<Path>) -> Result<u64> {
178 let from = from.as_ref();
179 let to = to.as_ref();
180 fs::copy(from, to).map_err(|err| FsPathError::copy(err, from, to))
181}
182
183pub fn create_dir(path: impl AsRef<Path>) -> Result<()> {
185 let path = path.as_ref();
186 fs::create_dir(path).map_err(|err| FsPathError::create_dir(err, path))
187}
188
189pub fn create_dir_all(path: impl AsRef<Path>) -> Result<()> {
191 let path = path.as_ref();
192 fs::create_dir_all(path).map_err(|err| FsPathError::create_dir(err, path))
193}
194
195pub fn remove_dir(path: impl AsRef<Path>) -> Result<()> {
197 let path = path.as_ref();
198 fs::remove_dir(path).map_err(|err| FsPathError::remove_dir(err, path))
199}
200
201pub fn remove_dir_all(path: impl AsRef<Path>) -> Result<()> {
203 let path = path.as_ref();
204 fs::remove_dir_all(path).map_err(|err| FsPathError::remove_dir(err, path))
205}
206
207pub fn open(path: impl AsRef<Path>) -> Result<fs::File> {
209 let path = path.as_ref();
210 fs::File::open(path).map_err(|err| FsPathError::open(err, path))
211}
212
213pub fn normalize_path(path: &Path) -> PathBuf {
220 let mut components = path.components().peekable();
221 let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().copied() {
222 components.next();
223 PathBuf::from(c.as_os_str())
224 } else {
225 PathBuf::new()
226 };
227
228 for component in components {
229 match component {
230 Component::Prefix(..) => unreachable!(),
231 Component::RootDir => {
232 ret.push(component.as_os_str());
233 }
234 Component::CurDir => {}
235 Component::ParentDir => {
236 ret.pop();
237 }
238 Component::Normal(c) => {
239 ret.push(c);
240 }
241 }
242 }
243 ret
244}
245
246pub fn files_with_ext<'a>(root: &Path, ext: &'a str) -> impl Iterator<Item = PathBuf> + 'a {
248 walkdir::WalkDir::new(root)
249 .sort_by_file_name()
250 .into_iter()
251 .filter_map(walkdir::Result::ok)
252 .filter(|e| e.file_type().is_file() && e.path().extension() == Some(ext.as_ref()))
253 .map(walkdir::DirEntry::into_path)
254}
255
256pub fn json_files(root: &Path) -> impl Iterator<Item = PathBuf> {
258 files_with_ext(root, "json")
259}
260
261pub fn canonicalize_path(path: impl AsRef<Path>) -> std::io::Result<PathBuf> {
266 dunce::canonicalize(path)
267}
268
269#[cfg(test)]
270mod tests {
271 use super::*;
272
273 #[test]
274 fn test_normalize_path() {
275 let p = Path::new("/a/../file.txt");
276 let normalized = normalize_path(p);
277 assert_eq!(normalized, PathBuf::from("/file.txt"));
278 }
279}