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}