1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
#![doc = include_str!("../README.md")]
use core::fmt::Debug;
use std::fs::{File, Metadata, OpenOptions, ReadDir};
use std::io::Result;
use std::ops::Div;
use std::{
fs,
path::{Path, PathBuf},
};
pub use path_no_alloc::with_paths;
fn create_parents<P: AsRef<Path>>(path: P) -> Result<()> {
let path = path.as_ref();
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)
} else {
Ok(())
}
}
impl <P> Dir<P> where P: AsRef<Path>
{
#[inline]
/// Join this working dir with some path
pub fn join<P2: AsRef<Path>>(&self, path: P2) -> PathBuf {
let rhs = path.as_ref();
if rhs.is_absolute() {
return rhs.to_owned();
} else {
let lhs = self.0.as_ref();
let mut result = PathBuf::new();
result.reserve(lhs.as_os_str().len() + rhs.as_os_str().len() + 1);
result.push(lhs);
result.push(rhs);
result
}
}
/// Opens a file with the given [`OpenOptions`]
pub fn open<P2: AsRef<Path>>(&self, path: P2, opts: &OpenOptions) -> Result<File> {
with_paths! { path = self / path => opts.open(path) }
}
/// Opens a file in read-only mode
///
/// See: [`std::fs::File::open`]
pub fn open_readonly<P2: AsRef<Path>>(&self, path: P2) -> Result<File> {
with_paths! { path = self / path => File::open(path) }
}
/// Creates any parent directories for a given path. Does nothing
/// if the path has no parents.
///
/// # Errors
/// This function returns an error if the creation of the parent
/// directories fails
pub fn create_parents<P2: AsRef<Path>>(&self, path: P2) -> Result<()> {
with_paths! {
path = self / path => create_parents(path)
}
}
/// Returns true if the path points at an existing entity.
///
/// Warning: this method may be error-prone, consider using try_exists()
/// instead! It also has a risk of introducing time-of-check to
/// time-of-use (TOCTOU) bugs.
///
/// This function will traverse symbolic links to query information
/// about the destination file.
///
/// If you cannot access the metadata of the file, e.g. because of
/// a permission error or broken symbolic links, this will return false.
///
/// See: [`std::path::Path::exists`]
pub fn exists<P2: AsRef<Path>>(&self, path: P2) -> bool {
with_paths! { path = self / path => path.exists() }
}
#[inline]
/// Checks if this directory contains a given path. Alias for [`Dir::exists`]
pub fn contains<P2: AsRef<Path>>(&self, path: P2) -> bool {
self.exists(path)
}
/// Returns `Ok(true)` if the path points at an existing entity.
///
/// This function will traverse symbolic links to query information
/// about the destination file. In case of broken symbolic links this
/// will return `Ok(false)`.
///
/// As opposed to the `exists()` method, this one doesn’t silently
/// ignore errors unrelated to the path not existing. (E.g. it will
/// return `Err(_)` in case of permission denied on some of the parent
/// directories.)
///
/// Note that while this avoids some pitfalls of the `exists()` method,
/// it still can not prevent time-of-check to time-of-use (TOCTOU) bugs.
/// You should only use it in scenarios where those bugs are not an issue.
///
/// See: [`std::path::Path::try_exists`]
pub fn try_exists<P2: AsRef<Path>>(&self, path: P2) -> Result<bool> {
with_paths! { path = self / path => path.try_exists() }
}
#[inline]
/// Checks if this directory contains a given path. Alias for [`Dir::try_exists`]
pub fn try_contains<P2: AsRef<Path>>(&self, path: P2) -> Result<bool> {
self.try_exists(path)
}
/// Moves a path from this working directory, to another working directory.
///
/// Suppose we have some path `path/to/thing`, corresponding to `<self>/path/to/thing`
/// in the current working directory.
///
/// This function will move it to `<B>/path/to/thing`, in working directory B, creating
/// any parent dirs as necessary
///
/// # Errors
///
/// This function will return an error in the following cases:
///
/// - `<working dir>/<path>` does not exist (in which case, there is nothing to rename)
/// - The user lacks permission to view the contents of the path.
/// - The destination is on a separate filesystem
pub fn move_to<P2: AsRef<Path>, P3: AsRef<Path>>(&self, new_root: P2, path: P3) -> Result<()> {
let path = path.as_ref();
with_paths! {
old_path = self / path,
new_path = new_root / path
}
create_parents(new_path)?;
fs::rename(old_path, new_path)
}
/// Returns the canonical, absolute form of a path relative to the current working directory,
/// with all intermediate components normalized and symbolic links resolved.
///
/// See: [`std::fs::canonicalize`]
pub fn canonicalize<P2: AsRef<Path>>(&self, path: P2) -> Result<PathBuf> {
with_paths! { path = self / path => fs::canonicalize(path) }
}
/// Copies the contents of one file to another. This function
/// will also copy the permission bits of the original file to the
/// destination file.
///
/// This function will **overwrite** the contents of to.
///
/// Note that if from and to both point to the same file, then
/// the file will likely get truncated by this operation.
///
/// On success, the total number of bytes copied is returned and it
/// is equal to the length of the to file as reported by metadata.
///
/// If you’re wanting to copy the contents of one file to another
/// and you’re working with Files, see the io::copy() function.
///
/// See: [`std::fs::create_dir`]
pub fn copy<P2: AsRef<Path>, P3: AsRef<Path>>(&self, from: P2, to: P3) -> Result<u64> {
with_paths! {
from = self / from,
to = self / to
}
fs::copy(from, to)
}
/// Creates a new, empty directory at the provided path
///
/// See: [`std::fs::create_dir`]
pub fn create_dir<P2: AsRef<Path>>(&self, path: P2) -> Result<()> {
with_paths! { path = self / path => fs::create_dir(path) }
}
/// Recursively create a directory and all of its parent components if they are missing.
///
/// See: [`std::fs::create_dir_all`]
pub fn create_dir_all<P2: AsRef<Path>>(&self, path: P2) -> Result<()> {
with_paths! { path = self / path => fs::create_dir_all(path) }
}
/// Creates a new hard link on the filesystem.
///
/// The link path will be a link pointing to the original path.
/// Note that systems often require these two paths to both be
/// located on the same filesystem.
///
/// If original names a symbolic link, it is platform-specific
/// whether the symbolic link is followed. On platforms where
/// it’s possible to not follow it, it is not followed, and the
/// created hard link points to the symbolic link itself.
///
/// See: [`std::fs::hard_link`]
pub fn hard_link<P2: AsRef<Path>, P3: AsRef<Path>>(&self, original: P2, link: P3) -> Result<()> {
with_paths! {
original = self / original,
link = self / link
}
fs::hard_link(original, link)
}
/// Given a path, query the file system to get information about
/// a file, directory, etc.
///
/// This function will traverse symbolic links to query
/// information about the destination file.
///
/// See: [`std::fs::metadata`]
pub fn metadata<P2: AsRef<Path>>(&self, path: P2) -> Result<Metadata> {
with_paths! { path = self / path => fs::metadata(path) }
}
/// Read the entire contents of a file into a bytes vector.
///
/// This is a convenience function for using `File::open` and
/// `read_to_end` with fewer imports and without an intermediate variable.
///
/// See: [`std::fs::read`]
pub fn read<P2: AsRef<Path>>(&self, path: P2) -> Result<Vec<u8>> {
with_paths! { path = self / path => fs::read(path) }
}
/// Returns an iterator over the entries within a directory.
///
/// The iterator will yield instances of `io::Result<DirEntry>`.
/// New errors may be encountered after an iterator is initially
/// constructed. Entries for the current and parent directories
/// (typically `.` and `..`) are skipped.
///
/// See: [`std::fs::read_dir`]
pub fn read_dir<P2: AsRef<Path>>(&self, path: P2) -> Result<ReadDir> {
with_paths! { path = self / path => fs::read_dir(path) }
}
/// Reads a symbolic link, returning the file that the link points to.
///
/// See: [`std::fs::read_link`]
pub fn read_link<P2: AsRef<Path>>(&self, path: P2) -> Result<PathBuf> {
with_paths! { path = self / path => fs::read_link(path) }
}
/// Read the entire contents of a file into a string.
///
/// This is a convenience function for using File::open
/// and read_to_string with fewer imports and without an
/// intermediate variable.
///
/// See: [`std::fs::read_to_string`]
pub fn read_to_string<P2: AsRef<Path>>(&self, path: P2) -> Result<String> {
with_paths! { path = self / path => fs::read_to_string(path) }
}
/// Removes an empty directory.
///
/// See: [`std::fs::remove_dir`]
pub fn remove_dir<P2: AsRef<Path>>(&self, path: P2) -> Result<()> {
with_paths! { path = self / path => fs::remove_dir(path) }
}
/// Removes a directory at this path, after removing all
/// its contents. Use carefully!
///
/// This function does **not** follow symbolic links and it will
/// simply remove the symbolic link itself.
///
/// See: [`std::fs::remove_dir_all`]
pub fn remove_dir_all<P2: AsRef<Path>>(&self, path: P2) -> Result<()> {
with_paths! { path = self / path => fs::remove_dir_all(path) }
}
/// Removes a file from the filesystem.
///
/// Note that there is no guarantee that the file is immediately
/// deleted (e.g., depending on platform, other open file
/// descriptors may prevent immediate removal).
///
/// See: [`std::fs::remove_file`]
pub fn remove_file<P2: AsRef<Path>>(&self, path: P2) -> Result<()> {
with_paths! { path = self / path => fs::remove_file(path) }
}
/// Rename a file or directory to a new name, replacing
/// the original file if to already exists.
///
/// This will not work if the new name is on a different mount point.
///
/// See: [`std::fs::rename`]
pub fn rename<P2: AsRef<Path>, P3: AsRef<Path>>(&self, from: P2, to: P3) -> Result<()> {
with_paths! {
from = self / from,
to = self / to
}
fs::rename(from, to)
}
/// Query the metadata about a file without following symlinks.
///
/// See: [`std::fs::symlink_metadata`]
pub fn symlink_metadata<P2: AsRef<Path>>(&self, path: P2) -> Result<Metadata> {
with_paths! { path = self / path => fs::symlink_metadata(path) }
}
/// Write a slice as the entire contents of a file.
///
/// This function will create a file if it does not exist, and will
/// entirely replace its contents if it does.
///
/// Depending on the platform, this function may fail if the full
/// directory path does not exist.
///
/// This is a convenience function for using File::create and
/// write_all with fewer imports.
///
/// See: [`std::fs::write`]
pub fn write<P2: AsRef<Path>, C: AsRef<[u8]>>(&self, path: P2, contents: C) -> Result<()> {
with_paths! {
path = self / path => fs::write(path, contents)
}
}
}
#[repr(transparent)]
#[derive(PartialEq, PartialOrd, Eq, Ord)]
/// Acts as a Working Directory. Provides a variety of functions
/// to manipulate and query directories and files in the context of that directory.
pub struct Dir<P>(pub P)
where
P: AsRef<Path>;
impl<P> Debug for Dir<P>
where
P: AsRef<Path>,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let path = self.0.as_ref();
if let Some(path) = path.to_str() {
if path.ends_with("/") {
write!(f, "Dir(\"{path}\")")
} else {
write!(f, "Dir(\"{path}/\")")
}
} else {
let path = path.to_string_lossy();
if path.ends_with("/") {
write!(f, "Dir(\"{path}\")")
} else {
write!(f, "Dir(\"{path}/\")")
}
}
}
}
impl<P> From<P> for Dir<P>
where
P: AsRef<Path>,
{
#[inline]
fn from(value: P) -> Self {
Dir(value)
}
}
impl<P> Dir<P>
where
P: AsRef<Path>,
{
/// Creates a Dir from the given path
#[inline]
pub fn new(path: P) -> Dir<P> {
Dir(path)
}
}
impl<P> AsRef<Path> for Dir<P>
where
P: AsRef<Path>,
{
#[inline]
fn as_ref(&self) -> &Path {
self.0.as_ref()
}
}
impl <P, Q> Div<Q> for &Dir<P> where P: AsRef<Path>, Q: AsRef<Path> {
type Output = PathBuf;
#[inline]
fn div(self, rhs: Q) -> Self::Output {
self.join(rhs)
}
}
impl <P, Q> Div<Q> for Dir<P> where P: AsRef<Path>, Q: AsRef<Path> {
type Output = PathBuf;
#[inline]
fn div(self, rhs: Q) -> Self::Output {
self.join(rhs)
}
}
#[cfg(test)]
mod tests;