foundry_common/
fs.rs
1use crate::errors::FsPathError;
4use serde::{de::DeserializeOwned, Serialize};
5use std::{
6 fs::{self, File},
7 io::{BufWriter, Write},
8 path::{Component, Path, PathBuf},
9};
10
11pub type Result<T> = std::result::Result<T, FsPathError>;
13
14pub fn create_file(path: impl AsRef<Path>) -> Result<fs::File> {
16 let path = path.as_ref();
17 File::create(path).map_err(|err| FsPathError::create_file(err, path))
18}
19
20pub fn remove_file(path: impl AsRef<Path>) -> Result<()> {
22 let path = path.as_ref();
23 fs::remove_file(path).map_err(|err| FsPathError::remove_file(err, path))
24}
25
26pub fn read(path: impl AsRef<Path>) -> Result<Vec<u8>> {
28 let path = path.as_ref();
29 fs::read(path).map_err(|err| FsPathError::read(err, path))
30}
31
32pub fn read_link(path: impl AsRef<Path>) -> Result<PathBuf> {
34 let path = path.as_ref();
35 fs::read_link(path).map_err(|err| FsPathError::read_link(err, path))
36}
37
38pub fn read_to_string(path: impl AsRef<Path>) -> Result<String> {
40 let path = path.as_ref();
41 fs::read_to_string(path).map_err(|err| FsPathError::read(err, path))
42}
43
44pub fn read_json_file<T: DeserializeOwned>(path: &Path) -> Result<T> {
46 let s = read_to_string(path)?;
49 serde_json::from_str(&s).map_err(|source| FsPathError::ReadJson { source, path: path.into() })
50}
51
52pub fn write_json_file<T: Serialize>(path: &Path, obj: &T) -> Result<()> {
54 let file = create_file(path)?;
55 let mut writer = BufWriter::new(file);
56 serde_json::to_writer(&mut writer, obj)
57 .map_err(|source| FsPathError::WriteJson { source, path: path.into() })?;
58 writer.flush().map_err(|e| FsPathError::write(e, path))
59}
60
61pub fn write_pretty_json_file<T: Serialize>(path: &Path, obj: &T) -> Result<()> {
63 let file = create_file(path)?;
64 let mut writer = BufWriter::new(file);
65 serde_json::to_writer_pretty(&mut writer, obj)
66 .map_err(|source| FsPathError::WriteJson { source, path: path.into() })?;
67 writer.flush().map_err(|e| FsPathError::write(e, path))
68}
69
70pub fn write(path: impl AsRef<Path>, contents: impl AsRef<[u8]>) -> Result<()> {
72 let path = path.as_ref();
73 fs::write(path, contents).map_err(|err| FsPathError::write(err, path))
74}
75
76pub fn copy(from: impl AsRef<Path>, to: impl AsRef<Path>) -> Result<u64> {
78 let from = from.as_ref();
79 let to = to.as_ref();
80 fs::copy(from, to).map_err(|err| FsPathError::copy(err, from, to))
81}
82
83pub fn create_dir(path: impl AsRef<Path>) -> Result<()> {
85 let path = path.as_ref();
86 fs::create_dir(path).map_err(|err| FsPathError::create_dir(err, path))
87}
88
89pub fn create_dir_all(path: impl AsRef<Path>) -> Result<()> {
91 let path = path.as_ref();
92 fs::create_dir_all(path).map_err(|err| FsPathError::create_dir(err, path))
93}
94
95pub fn remove_dir(path: impl AsRef<Path>) -> Result<()> {
97 let path = path.as_ref();
98 fs::remove_dir(path).map_err(|err| FsPathError::remove_dir(err, path))
99}
100
101pub fn remove_dir_all(path: impl AsRef<Path>) -> Result<()> {
103 let path = path.as_ref();
104 fs::remove_dir_all(path).map_err(|err| FsPathError::remove_dir(err, path))
105}
106
107pub fn open(path: impl AsRef<Path>) -> Result<fs::File> {
109 let path = path.as_ref();
110 fs::File::open(path).map_err(|err| FsPathError::open(err, path))
111}
112
113pub fn normalize_path(path: &Path) -> PathBuf {
120 let mut components = path.components().peekable();
121 let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
122 components.next();
123 PathBuf::from(c.as_os_str())
124 } else {
125 PathBuf::new()
126 };
127
128 for component in components {
129 match component {
130 Component::Prefix(..) => unreachable!(),
131 Component::RootDir => {
132 ret.push(component.as_os_str());
133 }
134 Component::CurDir => {}
135 Component::ParentDir => {
136 ret.pop();
137 }
138 Component::Normal(c) => {
139 ret.push(c);
140 }
141 }
142 }
143 ret
144}
145
146pub fn files_with_ext<'a>(root: &Path, ext: &'a str) -> impl Iterator<Item = PathBuf> + 'a {
148 walkdir::WalkDir::new(root)
149 .sort_by_file_name()
150 .into_iter()
151 .filter_map(walkdir::Result::ok)
152 .filter(|e| e.file_type().is_file() && e.path().extension() == Some(ext.as_ref()))
153 .map(walkdir::DirEntry::into_path)
154}
155
156pub fn json_files(root: &Path) -> impl Iterator<Item = PathBuf> {
158 files_with_ext(root, "json")
159}
160
161pub fn canonicalize_path(path: impl AsRef<Path>) -> std::io::Result<PathBuf> {
166 dunce::canonicalize(path)
167}
168
169#[cfg(test)]
170mod tests {
171 use super::*;
172
173 #[test]
174 fn test_normalize_path() {
175 let p = Path::new("/a/../file.txt");
176 let normalized = normalize_path(p);
177 assert_eq!(normalized, PathBuf::from("/file.txt"));
178 }
179}