1use crate::prelude::*;
3use std::{
4 fmt,
5 io::{self, BufWriter, Read, Seek, Write},
6 path::{Path, PathBuf},
7};
8
9#[derive(Debug)]
12pub struct File {
13 inner: BufWriter<std::fs::File>,
14 path: PathBuf,
15}
16
17impl File {
18 pub fn create(path: impl Into<PathBuf>) -> Result<Self> {
24 let path = path.into();
25 create_p_dir(&path);
26 let inner = std::fs::File::create(&path)
27 .with_context(|| format!("failed to create or open file '{}'", path.display()))
28 .map(BufWriter::new)?;
29
30 Ok(Self { path, inner })
31 }
32
33 pub fn append(path: impl Into<PathBuf>) -> Result<Self> {
38 let path = path.into();
39 create_p_dir(&path);
40 let inner = std::fs::File::options()
41 .create(true)
42 .append(true)
43 .open(&path)
44 .with_context(|| format!("failed to create or open file '{}'", path.display()))
45 .map(BufWriter::new)?;
46
47 Ok(Self { path, inner })
48 }
49
50 pub fn open(path: impl Into<PathBuf>) -> Result<Self> {
52 let path = path.into();
53 let inner = std::fs::File::open(&path)
54 .with_context(|| format!("failed to open file '{}'", path.display()))
55 .map(BufWriter::new)?;
56
57 Ok(Self { path, inner })
58 }
59
60 pub fn path(&self) -> &Path {
62 &self.path
63 }
64
65 pub fn exists(path: impl AsRef<Path>) -> bool {
67 path.as_ref().exists()
68 }
69
70 pub fn into_std_file(self) -> Result<std::fs::File> {
72 self.inner.into_inner().map_err(Into::into)
73 }
74
75 pub fn read_to_vec(&mut self) -> Result<Vec<u8>> {
80 let len = self
81 .inner
82 .get_ref()
83 .metadata()
84 .map(|x| x.len())
85 .unwrap_or_default() as usize;
86 let mut buf = Vec::with_capacity(len);
87 self.read_to_end(&mut buf)
88 .with_context(|| format!("failed reading bytes from '{}'", self.path.display()))?;
89 Ok(buf)
90 }
91
92 pub fn read_to_string(&mut self) -> Result<String> {
97 self.read_to_vec().and_then(|x| {
98 String::from_utf8(x).with_context(|| {
99 format!(
100 "failed to encode bytes from '{}' as UTF8",
101 self.path.display()
102 )
103 })
104 })
105 }
106
107 pub fn write(&mut self, contents: impl AsRef<[u8]>) -> Result<()> {
109 self.write_all(contents.as_ref())
110 .with_context(|| format!("failed to write to '{}'", self.path.display()))
111 }
112
113 fn wrap_err(&self, err: io::Error) -> io::Error {
114 let kind = err.kind();
115 io::Error::new(
116 kind,
117 Error {
118 path: self.path.clone(),
119 inner: err,
120 },
121 )
122 }
123}
124
125fn create_p_dir(path: &Path) {
126 if let Some(p) = path.parent() {
127 if let Err(e) = std::fs::create_dir_all(p) {
128 eprintln!("failed to create parent directory '{}': {e}", p.display());
129 }
130 }
131}
132
133#[derive(Debug)]
134struct Error {
135 path: PathBuf,
136 inner: io::Error,
137}
138
139impl std::error::Error for Error {
140 fn cause(&self) -> Option<&dyn std::error::Error> {
141 Some(&self.inner)
142 }
143
144 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
145 Some(&self.inner)
146 }
147}
148
149impl fmt::Display for Error {
150 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
151 write!(
152 f,
153 "io error with file '{}': {}",
154 self.path.display(),
155 self.inner
156 )
157 }
158}
159
160impl Read for File {
161 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
162 self.inner.get_mut().read(buf).map_err(|e| self.wrap_err(e))
163 }
164
165 fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> {
166 self.inner
167 .get_mut()
168 .read_exact(buf)
169 .map_err(|e| self.wrap_err(e))
170 }
171
172 fn read_to_end(&mut self, buf: &mut Vec<u8>) -> io::Result<usize> {
173 self.inner
174 .get_mut()
175 .read_to_end(buf)
176 .map_err(|e| self.wrap_err(e))
177 }
178
179 fn read_vectored(&mut self, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result<usize> {
180 self.inner
181 .get_mut()
182 .read_vectored(bufs)
183 .map_err(|e| self.wrap_err(e))
184 }
185
186 fn read_to_string(&mut self, buf: &mut String) -> io::Result<usize> {
187 self.inner
188 .get_mut()
189 .read_to_string(buf)
190 .map_err(|e| self.wrap_err(e))
191 }
192}
193
194impl Write for File {
195 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
196 self.inner.write(buf).map_err(|e| self.wrap_err(e))
197 }
198
199 fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result<usize> {
200 self.inner
201 .write_vectored(bufs)
202 .map_err(|e| self.wrap_err(e))
203 }
204
205 fn flush(&mut self) -> io::Result<()> {
206 self.inner.flush().map_err(|e| self.wrap_err(e))
207 }
208}
209
210impl Seek for File {
211 fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
212 self.inner.seek(pos).map_err(|e| self.wrap_err(e))
213 }
214
215 fn stream_position(&mut self) -> io::Result<u64> {
216 self.inner.stream_position().map_err(|e| self.wrap_err(e))
217 }
218}
219
220pub fn ls<P, M>(path: P, matching: M) -> Result<Vec<PathBuf>>
238where
239 P: AsRef<Path>,
240 M: AsRef<str>,
241{
242 let pat = matching.as_ref();
243 let glob = globset::Glob::new(pat)
244 .with_context(|| format!("invalid glob pattern: {pat}"))?
245 .compile_matcher();
246
247 let prefix = path.as_ref();
248 let rdr = std::fs::read_dir(prefix).with_context(|| {
249 format!(
250 "failed to read directory: {}",
251 prefix
252 .canonicalize()
253 .unwrap_or_else(|_| prefix.to_path_buf())
254 .display()
255 )
256 })?;
257
258 let mut v = Vec::new();
259 for e in rdr {
260 let e = e.with_context(|| {
261 format!(
262 "failed to read directory: {}",
263 prefix
264 .canonicalize()
265 .unwrap_or_else(|_| prefix.to_path_buf())
266 .display()
267 )
268 })?;
269
270 let path = e.path();
271
272 if glob.is_match(path.strip_prefix(prefix).expect("path prefix matches")) {
273 v.push(path);
274 }
275 }
276
277 v.sort_unstable();
278
279 Ok(v)
280}
281
282#[cfg(test)]
283mod tests {
284 use super::*;
285
286 #[test]
287 fn file_not_found() {
288 let x = File::open("wont-exist.txt").unwrap_err().to_string();
289 assert_eq!(&x, "failed to open file 'wont-exist.txt'");
290 }
291}