relative_path_utils/root/mod.rs
1// Some documentation copied from the Rust project under the MIT license.
2//
3// See https://github.com/rust-lang/rust
4
5use alloc::string::String;
6use alloc::vec::Vec;
7
8use std::ffi::OsString;
9use std::fs::File;
10use std::io::{self, Read, Write};
11use std::path::Path;
12
13use crate::Glob;
14use relative_path::RelativePath;
15
16#[cfg_attr(windows, path = "windows.rs")]
17#[cfg_attr(unix, path = "unix.rs")]
18mod imp;
19
20#[cfg(not(any(windows, unix)))]
21compile_error!("root is only supported on cfg(windows) and cfg(unix)");
22
23/// An open root directory from which relative paths can be opened.
24///
25/// In contrast to using APIs such as [`RelativePath::to_path`], this does not
26/// require allocations to open a path.
27///
28/// This is achieved by keeping an open handle to the directory and using
29/// platform-specific APIs to open a relative path, such as [`openat`] on `unix`.
30///
31/// [`openat`]: https://linux.die.net/man/2/openat
32pub struct Root {
33 inner: imp::Root,
34}
35
36impl Root {
37 /// Open the given directory that can be used as a root for opening and
38 /// manipulating relative paths.
39 ///
40 /// # Errors
41 ///
42 /// Errors if the underlying I/O operation fails.
43 ///
44 /// # Examples
45 ///
46 /// ```no_run
47 /// use relative_path_utils::Root;
48 ///
49 /// let root = Root::new(".")?;
50 /// # Ok::<_, std::io::Error>(())
51 /// ```
52 pub fn new<P>(path: P) -> io::Result<Self>
53 where
54 P: AsRef<Path>,
55 {
56 Ok(Self {
57 inner: imp::Root::new(path.as_ref())?,
58 })
59 }
60
61 /// Construct an open options associated with this root.
62 ///
63 /// # Examples
64 ///
65 /// ```no_run
66 /// use relative_path_utils::Root;
67 ///
68 /// let root = Root::new(".")?;
69 ///
70 /// let file = root.open_options().read(true).open("foo.txt");
71 /// # Ok::<_, std::io::Error>(())
72 /// ```
73 pub fn open_options(&self) -> OpenOptions {
74 OpenOptions {
75 root: &self.inner,
76 options: imp::OpenOptions::new(),
77 }
78 }
79
80 /// Opens a file in write-only mode.
81 ///
82 /// This function will create a file if it does not exist, and will truncate
83 /// it if it does.
84 ///
85 /// Depending on the platform, this function may fail if the full directory
86 /// path does not exist. See the [`OpenOptions::open`] function for more
87 /// details.
88 ///
89 /// See also [`Root::write()`] for a simple function to create a file with a
90 /// given data.
91 ///
92 /// # Errors
93 ///
94 /// Errors if the underlying I/O operation fails.
95 ///
96 /// # Examples
97 ///
98 /// ```no_run
99 /// use std::io::Write;
100 ///
101 /// use relative_path_utils::Root;
102 ///
103 /// let root = Root::new(".")?;
104 ///
105 /// let mut f = root.create("foo.txt")?;
106 /// f.write_all(&1234_u32.to_be_bytes())?;
107 /// # Ok::<_, std::io::Error>(())
108 /// ```
109 pub fn create<P>(&self, path: P) -> io::Result<File>
110 where
111 P: AsRef<RelativePath>,
112 {
113 self.open_options().write(true).create(true).open(path)
114 }
115
116 /// Attempts to open a file in read-only mode.
117 ///
118 /// See the [`OpenOptions::open`] method for more details.
119 ///
120 /// If you only need to read the entire file contents, consider
121 /// [`std::fs::read()`][Root::read] or
122 /// [`std::fs::read_to_string()`][Root::read_to_string] instead.
123 ///
124 /// # Errors
125 ///
126 /// This function will return an error if `path` does not already exist.
127 /// Other errors may also be returned according to [`OpenOptions::open`].
128 ///
129 /// # Examples
130 ///
131 /// ```no_run
132 /// use std::io::Read;
133 ///
134 /// use relative_path_utils::Root;
135 ///
136 /// let root = Root::new(".")?;
137 ///
138 /// let mut f = root.open("foo.txt")?;
139 /// let mut data = vec![];
140 /// f.read_to_end(&mut data)?;
141 /// # Ok::<_, std::io::Error>(())
142 /// ```
143 pub fn open<P>(&self, path: P) -> io::Result<File>
144 where
145 P: AsRef<RelativePath>,
146 {
147 self.open_options().read(true).open(path)
148 }
149
150 /// Read the entire contents of a file into a bytes vector.
151 ///
152 /// This is a convenience function for using [`File::open`] and
153 /// [`read_to_end`] with fewer imports and without an intermediate variable.
154 ///
155 /// [`read_to_end`]: Read::read_to_end
156 ///
157 /// # Errors
158 ///
159 /// This function will return an error if `path` does not already exist.
160 /// Other errors may also be returned according to [`OpenOptions::open`].
161 ///
162 /// While reading from the file, this function handles
163 /// [`io::ErrorKind::Interrupted`] with automatic retries. See [`io::Read`]
164 /// documentation for details.
165 ///
166 /// # Examples
167 ///
168 /// ```no_run
169 /// use std::net::SocketAddr;
170 ///
171 /// use relative_path_utils::Root;
172 ///
173 /// let root = Root::new(".")?;
174 /// let foo: SocketAddr = String::from_utf8_lossy(&root.read("address.txt")?).parse()?;
175 /// # Ok::<_, Box<dyn std::error::Error>>(())
176 /// ```
177 pub fn read<P>(&self, path: P) -> io::Result<Vec<u8>>
178 where
179 P: AsRef<RelativePath>,
180 {
181 fn inner(this: &Root, path: &RelativePath) -> io::Result<Vec<u8>> {
182 let mut file = this.open(path)?;
183 let size = file
184 .metadata()
185 .map(|m| usize::try_from(m.len()).unwrap_or(usize::MAX))
186 .ok();
187 let mut bytes = Vec::with_capacity(size.unwrap_or(0));
188 file.read_to_end(&mut bytes)?;
189 Ok(bytes)
190 }
191
192 inner(self, path.as_ref())
193 }
194
195 /// Read the entire contents of a file into a string.
196 ///
197 /// This is a convenience function for using [`File::open`] and
198 /// [`read_to_string`] with fewer imports and without an intermediate
199 /// variable.
200 ///
201 /// [`read_to_string`]: Read::read_to_string
202 ///
203 /// # Errors
204 ///
205 /// This function will return an error if `path` does not already exist.
206 /// Other errors may also be returned according to [`OpenOptions::open`].
207 ///
208 /// If the contents of the file are not valid UTF-8, then an error will also
209 /// be returned.
210 ///
211 /// # Examples
212 ///
213 /// ```no_run
214 /// use std::net::SocketAddr;
215 ///
216 /// use relative_path_utils::Root;
217 ///
218 /// let root = Root::new(".")?;
219 /// let foo: SocketAddr = root.read_to_string("address.txt")?.parse()?;
220 /// # Ok::<_, Box<dyn std::error::Error>>(())
221 /// ```
222 pub fn read_to_string<P>(&self, path: P) -> io::Result<String>
223 where
224 P: AsRef<RelativePath>,
225 {
226 fn inner(this: &Root, path: &RelativePath) -> io::Result<String> {
227 let mut file = this.open(path)?;
228 let size = file
229 .metadata()
230 .map(|m| usize::try_from(m.len()).unwrap_or(usize::MAX))
231 .ok();
232 let mut string = String::with_capacity(size.unwrap_or(0));
233 file.read_to_string(&mut string)?;
234 Ok(string)
235 }
236
237 inner(self, path.as_ref())
238 }
239
240 /// Write a slice as the entire contents of a file.
241 ///
242 /// This function will create a file if it does not exist,
243 /// and will entirely replace its contents if it does.
244 ///
245 /// Depending on the platform, this function may fail if the
246 /// full directory path does not exist.
247 ///
248 /// This is a convenience function for using [`File::create`] and
249 /// [`write_all`] with fewer imports.
250 ///
251 /// [`write_all`]: Write::write_all
252 ///
253 /// # Errors
254 ///
255 /// Fails if an underlying I/O operation fails.
256 ///
257 /// # Examples
258 ///
259 /// ```no_run
260 /// use relative_path_utils::Root;
261 ///
262 /// let root = Root::new(".")?;
263 ///
264 /// root.write("foo.txt", b"Lorem ipsum")?;
265 /// root.write("bar.txt", "dolor sit")?;
266 /// # Ok::<_, std::io::Error>(())
267 /// ```
268 pub fn write<P, C>(&self, path: P, contents: C) -> io::Result<()>
269 where
270 P: AsRef<RelativePath>,
271 C: AsRef<[u8]>,
272 {
273 self.create(path)?.write_all(contents.as_ref())
274 }
275
276 /// Given a path, query the file system to get information about a file,
277 /// directory, etc.
278 ///
279 /// This function will traverse symbolic links to query information about
280 /// the destination file.
281 ///
282 /// # Platform-specific behavior
283 ///
284 /// This function currently corresponds to the `stat` function on Unix and
285 /// the `GetFileInformationByHandle` function on Windows. Note that, this
286 /// [may change in the future][changes].
287 ///
288 /// [changes]: io#platform-specific-behavior
289 ///
290 /// # Errors
291 ///
292 /// This function will return an error in the following situations, but is
293 /// not limited to just these cases:
294 ///
295 /// * The user lacks permissions to perform `metadata` call on `path`.
296 /// * `path` does not exist.
297 ///
298 /// # Examples
299 ///
300 /// ```rust,no_run
301 /// use relative_path_utils::Root;
302 ///
303 /// let root = Root::new(".")?;
304 /// let attr = root.metadata("file/path.txt")?;
305 /// # Ok::<_, std::io::Error>(())
306 /// ```
307 pub fn metadata<P>(&self, path: P) -> io::Result<Metadata>
308 where
309 P: AsRef<RelativePath>,
310 {
311 Ok(Metadata {
312 inner: self.inner.metadata(path.as_ref())?,
313 })
314 }
315
316 /// Returns `true` if the path exists on disk and is pointing at a
317 /// directory.
318 ///
319 /// This function will traverse symbolic links to query information about
320 /// the destination file.
321 ///
322 /// If you cannot access the metadata of the file, e.g. because of a
323 /// permission error or broken symbolic links, this will return `false`.
324 ///
325 /// # Examples
326 ///
327 /// ```no_run
328 /// use relative_path_utils::Root;
329 ///
330 /// let root = Root::new(".")?;
331 ///
332 /// assert_eq!(root.is_dir("./is_a_directory/"), true);
333 /// assert_eq!(root.is_dir("a_file.txt"), false);
334 /// # Ok::<_, std::io::Error>(())
335 /// ```
336 ///
337 /// # See Also
338 ///
339 /// This is a convenience function that coerces errors to false. If you want
340 /// to check errors, call [`Root::metadata`] and handle its [`Result`]. Then
341 /// call [`Metadata::is_dir`] if it was [`Ok`].
342 pub fn is_dir<P>(&self, path: P) -> bool
343 where
344 P: AsRef<RelativePath>,
345 {
346 self.metadata(path).map(|m| m.is_dir()).unwrap_or(false)
347 }
348
349 /// Returns an iterator over the entries within a directory.
350 ///
351 /// The iterator will yield instances of
352 /// <code>[`io::Result`]<[`DirEntry`]></code>. New errors may be encountered
353 /// after an iterator is initially constructed. Entries for the current and
354 /// parent directories (typically `.` and `..`) are skipped.
355 ///
356 /// # Platform-specific behavior
357 ///
358 /// This function currently corresponds to the `opendir` function on Unix
359 /// and the `FindFirstFile` function on Windows. Advancing the iterator
360 /// currently corresponds to `readdir` on Unix and `FindNextFile` on Windows.
361 /// Note that, this [may change in the future][changes].
362 ///
363 /// [changes]: io#platform-specific-behavior
364 ///
365 /// The order in which this iterator returns entries is platform and filesystem
366 /// dependent.
367 ///
368 /// # Errors
369 ///
370 /// This function will return an error in the following situations, but is not
371 /// limited to just these cases:
372 ///
373 /// * The provided `path` doesn't exist.
374 /// * The process lacks permissions to view the contents.
375 /// * The `path` points at a non-directory file.
376 ///
377 /// # Examples
378 ///
379 /// ```
380 /// use std::io;
381 ///
382 /// use relative_path_utils::{Root, DirEntry};
383 /// use relative_path::RelativePath;
384 ///
385 /// // one possible implementation of walking a directory only visiting files
386 /// fn visit_dirs(root: &Root, dir: &RelativePath, cb: &dyn Fn(&DirEntry)) -> io::Result<()> {
387 /// if root.is_dir(dir) {
388 /// for entry in root.read_dir(dir)? {
389 /// let entry = entry?;
390 /// let file_name = entry.file_name();
391 /// let path = dir.join(file_name.to_string_lossy().as_ref());
392 ///
393 /// if root.is_dir(&path) {
394 /// visit_dirs(root, &path, cb)?;
395 /// } else {
396 /// cb(&entry);
397 /// }
398 /// }
399 /// }
400 ///
401 /// Ok(())
402 /// }
403 /// ```
404 pub fn read_dir<P>(&self, path: P) -> io::Result<ReadDir>
405 where
406 P: AsRef<RelativePath>,
407 {
408 self.inner
409 .read_dir(path.as_ref())
410 .map(|inner| ReadDir { inner })
411 }
412
413 /// Parse a glob over the specified path.
414 ///
415 /// To perform the globbing, use [`Glob::matcher`].
416 ///
417 /// # Examples
418 ///
419 /// ```no_run
420 /// use relative_path_utils::Root;
421 ///
422 /// let root = Root::new("src")?;
423 ///
424 /// let glob = root.glob("**/*.rs");
425 ///
426 /// let mut results = Vec::new();
427 ///
428 /// for e in glob.matcher() {
429 /// results.push(e?);
430 /// }
431 ///
432 /// results.sort();
433 /// assert_eq!(results, vec!["lib.rs", "main.rs"]);
434 /// # Ok::<_, Box<dyn std::error::Error>>(())
435 /// ```
436 pub fn glob<'a, P>(&'a self, path: &'a P) -> Glob<'a>
437 where
438 P: ?Sized + AsRef<RelativePath>,
439 {
440 Glob::new(self, path.as_ref())
441 }
442}
443
444/// Options and flags which can be used to configure how a file is opened.
445///
446/// This builder exposes the ability to configure how a [`File`] is opened and
447/// what operations are permitted on the open file. The [`File::open`] and
448/// [`File::create`] methods are aliases for commonly used options using this
449/// builder.
450///
451/// Generally speaking, when using `OpenOptions`, you'll first call
452/// [`Root::open_options`], then chain calls to methods to set each option, then
453/// call [`OpenOptions::open`], passing the path of the file you're trying to
454/// open. This will give you a [`io::Result`] with a [`File`] inside that you
455/// can further operate on.
456///
457/// # Examples
458///
459/// Opening a file to read:
460///
461/// ```no_run
462/// use relative_path_utils::Root;
463///
464/// let root = Root::new(".")?;
465///
466/// let file = root.open_options().read(true).open("foo.txt");
467/// # Ok::<_, std::io::Error>(())
468/// ```
469///
470/// Opening a file for both reading and writing, as well as creating it if it
471/// doesn't exist:
472///
473/// ```no_run
474/// use relative_path_utils::Root;
475///
476/// let root = Root::new(".")?;
477///
478/// let file = root
479/// .open_options()
480/// .read(true)
481/// .write(true)
482/// .create(true)
483/// .open("foo.txt")?;
484/// # Ok::<_, std::io::Error>(())
485/// ```
486#[derive(Clone, Debug)]
487#[must_use]
488pub struct OpenOptions<'a> {
489 root: &'a imp::Root,
490 options: imp::OpenOptions,
491}
492
493impl OpenOptions<'_> {
494 /// Sets the option for read access.
495 ///
496 /// This option, when true, will indicate that the file should be
497 /// `read`-able if opened.
498 ///
499 /// # Examples
500 ///
501 /// ```no_run
502 /// use relative_path_utils::Root;
503 ///
504 /// let root = Root::new(".")?;
505 ///
506 /// let file = root.open_options().read(true).open("foo.txt");
507 /// # Ok::<_, std::io::Error>(())
508 /// ```
509 pub fn read(&mut self, read: bool) -> &mut Self {
510 self.options.read(read);
511 self
512 }
513
514 /// Sets the option for write access.
515 ///
516 /// This option, when true, will indicate that the file should be
517 /// `write`-able if opened.
518 ///
519 /// If the file already exists, any write calls on it will overwrite its
520 /// contents, without truncating it.
521 ///
522 /// # Examples
523 ///
524 /// ```no_run
525 /// use relative_path_utils::Root;
526 ///
527 /// let root = Root::new(".")?;
528 ///
529 /// let file = root.open_options().write(true).open("foo.txt");
530 /// # Ok::<_, std::io::Error>(())
531 /// ```
532 pub fn write(&mut self, write: bool) -> &mut Self {
533 self.options.write(write);
534 self
535 }
536
537 /// Sets the option for the append mode.
538 ///
539 /// This option, when true, means that writes will append to a file instead
540 /// of overwriting previous contents. Note that setting
541 /// `.write(true).append(true)` has the same effect as setting only
542 /// `.append(true)`.
543 ///
544 /// For most filesystems, the operating system guarantees that all writes
545 /// are atomic: no writes get mangled because another process writes at the
546 /// same time.
547 ///
548 /// One maybe obvious note when using append-mode: make sure that all data
549 /// that belongs together is written to the file in one operation. This can
550 /// be done by concatenating strings before passing them to [`write()`], or
551 /// using a buffered writer (with a buffer of adequate size), and calling
552 /// [`flush()`] when the message is complete.
553 ///
554 /// If a file is opened with both read and append access, beware that after
555 /// opening, and after every write, the position for reading may be set at
556 /// the end of the file. So, before writing, save the current position
557 /// (using <code>[`seek`]\([`SeekFrom`]::[`Current`]\(0))</code>), and
558 /// restore it before the next read.
559 ///
560 /// ## Note
561 ///
562 /// This function doesn't create the file if it doesn't exist. Use the
563 /// [`OpenOptions::create`] method to do so.
564 ///
565 /// [`write()`]: Write::write "io::Write::write"
566 /// [`flush()`]: Write::flush "io::Write::flush"
567 /// [`seek`]: std::io::Seek::seek "io::Seek::seek"
568 /// [`SeekFrom`]: std::io::SeekFrom
569 /// [`Current`]: std::io::SeekFrom::Current
570 ///
571 /// # Examples
572 ///
573 /// ```no_run
574 /// use relative_path_utils::Root;
575 ///
576 /// let root = Root::new(".")?;
577 ///
578 /// let file = root.open_options().append(true).open("foo.txt");
579 /// # Ok::<_, std::io::Error>(())
580 /// ```
581 pub fn append(&mut self, append: bool) -> &mut Self {
582 self.options.append(append);
583 self
584 }
585
586 /// Sets the option for truncating a previous file.
587 ///
588 /// If a file is successfully opened with this option set it will truncate
589 /// the file to 0 length if it already exists.
590 ///
591 /// The file must be opened with write access for truncate to work.
592 ///
593 /// # Examples
594 ///
595 /// ```no_run
596 /// use relative_path_utils::Root;
597 ///
598 /// let root = Root::new(".")?;
599 ///
600 /// let file = root.open_options().write(true).truncate(true).open("foo.txt");
601 /// # Ok::<_, std::io::Error>(())
602 /// ```
603 pub fn truncate(&mut self, truncate: bool) -> &mut Self {
604 self.options.truncate(truncate);
605 self
606 }
607
608 /// Sets the option to create a new file, or open it if it already exists.
609 ///
610 /// In order for the file to be created, [`OpenOptions::write`] or
611 /// [`OpenOptions::append`] access must be used.
612 ///
613 /// See also [`Root::write()`] for a simple function to create a file with a
614 /// given data.
615 ///
616 /// # Examples
617 ///
618 /// ```no_run
619 /// use relative_path_utils::Root;
620 ///
621 /// let root = Root::new(".")?;
622 ///
623 /// let file = root.open_options().write(true).create(true).open("foo.txt");
624 /// # Ok::<_, std::io::Error>(())
625 /// ```
626 pub fn create(&mut self, create: bool) -> &mut Self {
627 self.options.create(create);
628 self
629 }
630
631 /// Sets the option to create a new file, failing if it already exists.
632 ///
633 /// No file is allowed to exist at the target location, also no (dangling) symlink. In this
634 /// way, if the call succeeds, the file returned is guaranteed to be new.
635 ///
636 /// This option is useful because it is atomic. Otherwise between checking
637 /// whether a file exists and creating a new one, the file may have been
638 /// created by another process (a TOCTOU race condition / attack).
639 ///
640 /// If `.create_new(true)` is set, [`.create()`] and [`.truncate()`] are
641 /// ignored.
642 ///
643 /// The file must be opened with write or append access in order to create
644 /// a new file.
645 ///
646 /// [`.create()`]: OpenOptions::create
647 /// [`.truncate()`]: OpenOptions::truncate
648 ///
649 /// # Examples
650 ///
651 /// ```no_run
652 /// use relative_path_utils::Root;
653 ///
654 /// let root = Root::new(".")?;
655 ///
656 /// let file = root.open_options().write(true).create_new(true).open("foo.txt");
657 /// # Ok::<_, std::io::Error>(())
658 /// ```
659 pub fn create_new(&mut self, create_new: bool) -> &mut Self {
660 self.options.create_new(create_new);
661 self
662 }
663
664 /// Opens a file at `path` with the options specified by `self`.
665 ///
666 /// # Errors
667 ///
668 /// This function will return an error under a number of different
669 /// circumstances. Some of these error conditions are listed here, together
670 /// with their [`io::ErrorKind`]. The mapping to [`io::ErrorKind`]s is not
671 /// part of the compatibility contract of the function.
672 ///
673 /// * [`NotFound`]: The specified file does not exist and neither `create`
674 /// or `create_new` is set.
675 /// * [`NotFound`]: One of the directory components of the file path does
676 /// not exist.
677 /// * [`PermissionDenied`]: The user lacks permission to get the specified
678 /// access rights for the file.
679 /// * [`PermissionDenied`]: The user lacks permission to open one of the
680 /// directory components of the specified path.
681 /// * [`AlreadyExists`]: `create_new` was specified and the file already
682 /// exists.
683 /// * [`InvalidInput`]: Invalid combinations of open options (truncate
684 /// without write access, no access mode set, etc.).
685 ///
686 /// The following errors don't match any existing [`io::ErrorKind`] at the moment:
687 /// * One of the directory components of the specified file path
688 /// was not, in fact, a directory.
689 /// * Filesystem-level errors: full disk, write permission
690 /// requested on a read-only file system, exceeded disk quota, too many
691 /// open files, too long filename, too many symbolic links in the
692 /// specified path (Unix-like systems only), etc.
693 ///
694 /// # Examples
695 ///
696 /// ```no_run
697 /// use relative_path_utils::Root;
698 ///
699 /// let root = Root::new(".")?;
700 ///
701 /// let file = root.open_options().read(true).open("foo.txt");
702 /// # Ok::<_, std::io::Error>(())
703 /// ```
704 ///
705 /// [`AlreadyExists`]: io::ErrorKind::AlreadyExists
706 /// [`InvalidInput`]: io::ErrorKind::InvalidInput
707 /// [`NotFound`]: io::ErrorKind::NotFound
708 /// [`PermissionDenied`]: io::ErrorKind::PermissionDenied
709 pub fn open<P>(&self, path: P) -> io::Result<File>
710 where
711 P: AsRef<RelativePath>,
712 {
713 self.root.open_at(path.as_ref(), &self.options)
714 }
715}
716
717/// Iterator over the entries in a directory.
718///
719/// This iterator is returned from the [`Root::read_dir`] function and will
720/// yield instances of <code>[`io::Result`]<[`DirEntry`]></code>. Through a
721/// [`DirEntry`] information like the entry's path and possibly other metadata
722/// can be learned.
723///
724/// The order in which this iterator returns entries is platform and filesystem
725/// dependent.
726///
727/// # Errors
728///
729/// This [`io::Result`] will be an [`Err`] if there's some sort of intermittent
730/// IO error during iteration.
731pub struct ReadDir {
732 inner: imp::ReadDir,
733}
734
735impl Iterator for ReadDir {
736 type Item = io::Result<DirEntry>;
737
738 #[inline]
739 fn next(&mut self) -> Option<Self::Item> {
740 let inner = self.inner.next()?;
741 Some(inner.map(|inner| DirEntry { inner }))
742 }
743}
744
745/// Entries returned by the [`ReadDir`] iterator.
746///
747/// An instance of `DirEntry` represents an entry inside of a directory on the
748/// filesystem. Each entry can be inspected via methods to learn about the full
749/// path or possibly other metadata through per-platform extension traits.
750///
751/// # Platform-specific behavior
752///
753/// On Unix, the `DirEntry` struct contains an internal reference to the open
754/// directory. Holding `DirEntry` objects will consume a file handle even after
755/// the `ReadDir` iterator is dropped.
756pub struct DirEntry {
757 inner: imp::DirEntry,
758}
759
760impl DirEntry {
761 /// Returns the file name of this directory entry without any
762 /// leading path component(s).
763 ///
764 /// As an example,
765 /// the output of the function will result in "foo" for all the following paths:
766 /// - "./foo"
767 /// - "/the/foo"
768 /// - "../../foo"
769 ///
770 /// # Examples
771 ///
772 /// ```no_run
773 /// use relative_path_utils::Root;
774 ///
775 /// let mut root = Root::new(".")?;
776 ///
777 /// for entry in root.read_dir("src")? {
778 /// let entry = entry?;
779 /// println!("{:?}", entry.file_name());
780 /// }
781 /// # Ok::<_, std::io::Error>(())
782 /// ```
783 #[must_use]
784 pub fn file_name(&self) -> OsString {
785 self.inner.file_name()
786 }
787}
788
789/// Metadata information about a file.
790///
791/// This structure is returned from the [`metadata`] function and represents
792/// known metadata about a file such as its permissions, size, modification
793/// times, etc.
794///
795/// [`metadata`]: Root::metadata
796#[derive(Clone)]
797pub struct Metadata {
798 inner: imp::Metadata,
799}
800
801impl Metadata {
802 /// Returns `true` if this metadata is for a directory. The result is
803 /// mutually exclusive to the result of [`Metadata::is_file`].
804 ///
805 /// # Examples
806 ///
807 /// ```no_run
808 /// use relative_path_utils::Root;
809 ///
810 /// let root = Root::new(".")?;
811 ///
812 /// let metadata = root.metadata("foo.txt")?;
813 /// assert!(!metadata.is_dir());
814 /// # Ok::<_, std::io::Error>(())
815 /// ```
816 #[must_use]
817 #[inline]
818 pub fn is_dir(&self) -> bool {
819 self.inner.is_dir()
820 }
821
822 /// Returns `true` if this metadata is for a regular file. The result is
823 /// mutually exclusive to the result of [`Metadata::is_dir`].
824 ///
825 /// When the goal is simply to read from (or write to) the source, the most
826 /// reliable way to test the source can be read (or written to) is to open
827 /// it. Only using `is_file` can break workflows like `diff <( prog_a )` on
828 /// a Unix-like system for example. See [`Root::open`] or
829 /// [`OpenOptions::open`] for more information.
830 ///
831 /// # Examples
832 ///
833 /// ```no_run
834 /// use relative_path_utils::Root;
835 ///
836 /// let root = Root::new(".")?;
837 ///
838 /// let metadata = root.metadata("foo.txt")?;
839 /// assert!(metadata.is_file());
840 /// # Ok::<_, std::io::Error>(())
841 /// ```
842 #[must_use]
843 #[inline]
844 pub fn is_file(&self) -> bool {
845 self.inner.is_file()
846 }
847
848 /// Returns `true` if this metadata is for a symbolic link.
849 ///
850 /// # Examples
851 ///
852 /// ```no_run
853 /// use relative_path_utils::Root;
854 ///
855 /// let root = Root::new(".")?;
856 ///
857 /// let metadata = root.metadata("foo.txt")?;
858 /// assert!(metadata.is_symlink());
859 /// # Ok::<_, std::io::Error>(())
860 /// ```
861 #[must_use]
862 pub fn is_symlink(&self) -> bool {
863 self.inner.is_symlink()
864 }
865}