From aa13ba758ba17577f347a6da721f261b4e854409 Mon Sep 17 00:00:00 2001 From: Stjepan Glavina Date: Tue, 15 Oct 2019 02:05:23 +0200 Subject: [PATCH] Refactor --- src/path/ancestors.rs | 39 ++ src/path/mod.rs | 4 +- src/path/path.rs | 822 ++++++++++++++++++++---------------------- src/path/pathbuf.rs | 128 +++---- 4 files changed, 494 insertions(+), 499 deletions(-) create mode 100644 src/path/ancestors.rs diff --git a/src/path/ancestors.rs b/src/path/ancestors.rs new file mode 100644 index 0000000..c7237ff --- /dev/null +++ b/src/path/ancestors.rs @@ -0,0 +1,39 @@ +use std::iter::FusedIterator; + +use crate::path::Path; + +/// An iterator over [`Path`] and its ancestors. +/// +/// This `struct` is created by the [`ancestors`] method on [`Path`]. +/// See its documentation for more. +/// +/// # Examples +/// +/// ``` +/// use async_std::path::Path; +/// +/// let path = Path::new("/foo/bar"); +/// +/// for ancestor in path.ancestors() { +/// println!("{}", ancestor.display()); +/// } +/// ``` +/// +/// [`ancestors`]: struct.Path.html#method.ancestors +/// [`Path`]: struct.Path.html +#[derive(Copy, Clone, Debug)] +pub struct Ancestors<'a> { + pub(crate) next: Option<&'a Path>, +} + +impl<'a> Iterator for Ancestors<'a> { + type Item = &'a Path; + + fn next(&mut self) -> Option { + let next = self.next; + self.next = next.and_then(Path::parent); + next + } +} + +impl FusedIterator for Ancestors<'_> {} diff --git a/src/path/mod.rs b/src/path/mod.rs index 9b8cdc9..36b9f2f 100644 --- a/src/path/mod.rs +++ b/src/path/mod.rs @@ -4,6 +4,7 @@ //! //! [`std::path`]: https://doc.rust-lang.org/std/path/index.html +mod ancestors; mod path; mod pathbuf; @@ -23,5 +24,6 @@ pub use std::path::MAIN_SEPARATOR; #[doc(inline)] pub use std::path::is_separator; -pub use path::{Ancestors, Path}; +use ancestors::Ancestors; +pub use path::Path; pub use pathbuf::PathBuf; diff --git a/src/path/path.rs b/src/path/path.rs index 63c8659..aa15ab2 100644 --- a/src/path/path.rs +++ b/src/path/path.rs @@ -1,7 +1,6 @@ use std::ffi::OsStr; -use std::iter::FusedIterator; -use crate::path::{Components, Display, Iter, PathBuf, StripPrefixError}; +use crate::path::{Ancestors, Components, Display, Iter, PathBuf, StripPrefixError}; use crate::{fs, io}; /// This struct is an async version of [`std::path::Path`]. @@ -13,30 +12,30 @@ pub struct Path { } impl Path { - /// Produces an iterator over `Path` and its ancestors. + /// Directly wraps a string slice as a `Path` slice. /// - /// The iterator will yield the `Path` that is returned if the [`parent`] method is used zero - /// or more times. That means, the iterator will yield `&self`, `&self.parent().unwrap()`, - /// `&self.parent().unwrap().parent().unwrap()` and so on. If the [`parent`] method returns - /// [`None`], the iterator will do likewise. The iterator will always yield at least one value, - /// namely `&self`. + /// This is a cost-free conversion. /// /// # Examples /// /// ``` /// use async_std::path::Path; /// - /// let mut ancestors = Path::new("/foo/bar").ancestors(); - /// assert_eq!(ancestors.next(), Some(Path::new("/foo/bar").into())); - /// assert_eq!(ancestors.next(), Some(Path::new("/foo").into())); - /// assert_eq!(ancestors.next(), Some(Path::new("/").into())); - /// assert_eq!(ancestors.next(), None); + /// Path::new("foo.txt"); /// ``` /// - /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html - /// [`parent`]: struct.Path.html#method.parent - pub fn ancestors(&self) -> Ancestors<'_> { - Ancestors { next: Some(&self) } + /// You can create `Path`s from `String`s, or even other `Path`s: + /// + /// ``` + /// use async_std::path::Path; + /// + /// let string = String::from("foo.txt"); + /// let from_string = Path::new(&string); + /// let from_path = Path::new(&from_string); + /// assert_eq!(from_string, from_path); + /// ``` + pub fn new + ?Sized>(s: &S) -> &Path { + unsafe { &*(std::path::Path::new(s) as *const std::path::Path as *const Path) } } /// Yields the underlying [`OsStr`] slice. @@ -46,144 +45,130 @@ impl Path { self.inner.as_os_str() } - /// Returns the canonical, absolute form of the path with all intermediate - /// components normalized and symbolic links resolved. + /// Yields a [`&str`] slice if the `Path` is valid unicode. /// - /// This is an alias to [`fs::canonicalize`]. + /// This conversion may entail doing a check for UTF-8 validity. + /// Note that validation is performed because non-UTF-8 strings are + /// perfectly valid for some OS. /// - /// [`fs::canonicalize`]: ../fs/fn.canonicalize.html + /// [`&str`]: https://doc.rust-lang.org/std/primitive.str.html /// /// # Examples /// - /// ```no_run - /// # fn main() -> std::io::Result<()> { async_std::task::block_on(async { - /// # - /// use async_std::path::{Path, PathBuf}; + /// ``` + /// use async_std::path::Path; /// - /// let path = Path::new("/foo/test/../test/bar.rs"); - /// assert_eq!(path.canonicalize().await.unwrap(), PathBuf::from("/foo/test/bar.rs")); - /// # - /// # Ok(()) }) } + /// let path = Path::new("foo.txt"); + /// assert_eq!(path.to_str(), Some("foo.txt")); /// ``` - pub async fn canonicalize(&self) -> io::Result { - fs::canonicalize(self).await + pub fn to_str(&self) -> Option<&str> { + self.inner.to_str() } - /// Produces an iterator over the [`Component`]s of the path. - /// - /// When parsing the path, there is a small amount of normalization: + /// Converts a `Path` to a [`Cow`]. /// - /// * Repeated separators are ignored, so `a/b` and `a//b` both have - /// `a` and `b` as components. + /// Any non-Unicode sequences are replaced with + /// [`U+FFFD REPLACEMENT CHARACTER`][U+FFFD]. /// - /// * Occurrences of `.` are normalized away, except if they are at the - /// beginning of the path. For example, `a/./b`, `a/b/`, `a/b/.` and - /// `a/b` all have `a` and `b` as components, but `./a/b` starts with - /// an additional [`CurDir`] component. + /// [`Cow`]: https://doc.rust-lang.org/std/borrow/enum.Cow.html + /// [U+FFFD]: https://doc.rust-lang.org/std/char/constant.REPLACEMENT_CHARACTER.html /// - /// * A trailing slash is normalized away, `/a/b` and `/a/b/` are equivalent. + /// # Examples /// - /// Note that no other normalization takes place; in particular, `a/c` - /// and `a/b/../c` are distinct, to account for the possibility that `b` - /// is a symbolic link (so its parent isn't `a`). + /// Calling `to_string_lossy` on a `Path` with valid unicode: /// - /// # Examples + /// ``` + /// use async_std::path::Path; /// + /// let path = Path::new("foo.txt"); + /// assert_eq!(path.to_string_lossy(), "foo.txt"); /// ``` - /// use async_std::path::{Path, Component}; - /// use std::ffi::OsStr; /// - /// let mut components = Path::new("/tmp/foo.txt").components(); + /// Had `path` contained invalid unicode, the `to_string_lossy` call might + /// have returned `"fo�.txt"`. + pub fn to_string_lossy(&self) -> std::borrow::Cow<'_, str> { + self.inner.to_string_lossy() + } + + /// Converts a `Path` to an owned [`PathBuf`]. + /// + /// [`PathBuf`]: struct.PathBuf.html + /// + /// # Examples /// - /// assert_eq!(components.next(), Some(Component::RootDir)); - /// assert_eq!(components.next(), Some(Component::Normal(OsStr::new("tmp")))); - /// assert_eq!(components.next(), Some(Component::Normal(OsStr::new("foo.txt")))); - /// assert_eq!(components.next(), None) /// ``` + /// use async_std::path::{Path, PathBuf}; /// - /// [`Component`]: enum.Component.html - /// [`CurDir`]: enum.Component.html#variant.CurDir - pub fn components(&self) -> Components<'_> { - self.inner.components() + /// let path_buf = Path::new("foo.txt").to_path_buf(); + /// assert_eq!(path_buf, PathBuf::from("foo.txt")); + /// ``` + pub fn to_path_buf(&self) -> PathBuf { + PathBuf::from(self.inner.to_path_buf()) } - /// Returns an object that implements [`Display`] for safely printing paths - /// that may contain non-Unicode data. + /// Returns `true` if the `Path` is absolute, i.e., if it is independent of + /// the current directory. /// - /// [`Display`]: https://doc.rust-lang.org/std/fmt/trait.Display.html + /// * On Unix, a path is absolute if it starts with the root, so + /// `is_absolute` and [`has_root`] are equivalent. + /// + /// * On Windows, a path is absolute if it has a prefix and starts with the + /// root: `c:\windows` is absolute, while `c:temp` and `\temp` are not. /// /// # Examples /// /// ``` /// use async_std::path::Path; /// - /// let path = Path::new("/tmp/foo.rs"); - /// - /// println!("{}", path.display()); + /// assert!(!Path::new("foo.txt").is_absolute()); /// ``` - pub fn display(&self) -> Display<'_> { - self.inner.display() + /// + /// [`has_root`]: #method.has_root + pub fn is_absolute(&self) -> bool { + self.inner.is_absolute() } - /// Determines whether `child` is a suffix of `self`. + /// Returns `true` if the `Path` is relative, i.e., not absolute. /// - /// Only considers whole path components to match. + /// See [`is_absolute`]'s documentation for more details. /// /// # Examples /// /// ``` /// use async_std::path::Path; /// - /// let path = Path::new("/etc/passwd"); - /// - /// assert!(path.ends_with("passwd")); + /// assert!(Path::new("foo.txt").is_relative()); /// ``` - pub fn ends_with>(&self, child: P) -> bool - where - P: std::convert::AsRef, - { - self.inner.ends_with(child) + /// + /// [`is_absolute`]: #method.is_absolute + pub fn is_relative(&self) -> bool { + self.inner.is_relative() } - /// Returns `true` if the path points at an existing entity. + /// Returns `true` if the `Path` has a root. /// - /// This function will traverse symbolic links to query information about the - /// destination file. In case of broken symbolic links this will return `false`. + /// * On Unix, a path has a root if it begins with `/`. /// - /// If you cannot access the directory containing the file, e.g., because of a - /// permission error, this will return `false`. + /// * On Windows, a path has a root if it: + /// * has no prefix and begins with a separator, e.g., `\windows` + /// * has a prefix followed by a separator, e.g., `c:\windows` but not `c:windows` + /// * has any non-disk prefix, e.g., `\\server\share` /// /// # Examples /// - /// ```no_run - /// # fn main() -> std::io::Result<()> { async_std::task::block_on(async { - /// # - /// use async_std::path::Path; - /// assert_eq!(Path::new("does_not_exist.txt").exists().await, false); - /// # - /// # Ok(()) }) } /// ``` + /// use async_std::path::Path; /// - /// # See Also - /// - /// This is a convenience function that coerces errors to false. If you want to - /// check errors, call [fs::metadata]. - /// - /// [fs::metadata]: ../fs/fn.metadata.html - pub async fn exists(&self) -> bool { - fs::metadata(self).await.is_ok() + /// assert!(Path::new("/etc/passwd").has_root()); + /// ``` + pub fn has_root(&self) -> bool { + self.inner.has_root() } - /// Extracts the extension of [`self.file_name`], if possible. - /// - /// The extension is: + /// Returns the `Path` without its final component, if there is one. /// - /// * [`None`], if there is no file name; - /// * [`None`], if there is no embedded `.`; - /// * [`None`], if the file name begins with `.` and has no other `.`s within; - /// * Otherwise, the portion of the file name after the final `.` + /// Returns [`None`] if the path terminates in a root or prefix. /// - /// [`self.file_name`]: struct.Path.html#method.file_name /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None /// /// # Examples @@ -191,12 +176,42 @@ impl Path { /// ``` /// use async_std::path::Path; /// - /// let path = Path::new("foo.rs"); + /// let path = Path::new("/foo/bar"); + /// let parent = path.parent().unwrap(); + /// assert_eq!(parent, Path::new("/foo")); /// - /// assert_eq!("rs", path.extension().unwrap()); + /// let grand_parent = parent.parent().unwrap(); + /// assert_eq!(grand_parent, Path::new("/")); + /// assert_eq!(grand_parent.parent(), None); /// ``` - pub fn extension(&self) -> Option<&OsStr> { - self.inner.extension() + pub fn parent(&self) -> Option<&Path> { + self.inner.parent().map(|p| p.into()) + } + + /// Produces an iterator over `Path` and its ancestors. + /// + /// The iterator will yield the `Path` that is returned if the [`parent`] method is used zero + /// or more times. That means, the iterator will yield `&self`, `&self.parent().unwrap()`, + /// `&self.parent().unwrap().parent().unwrap()` and so on. If the [`parent`] method returns + /// [`None`], the iterator will do likewise. The iterator will always yield at least one value, + /// namely `&self`. + /// + /// # Examples + /// + /// ``` + /// use async_std::path::Path; + /// + /// let mut ancestors = Path::new("/foo/bar").ancestors(); + /// assert_eq!(ancestors.next(), Some(Path::new("/foo/bar").into())); + /// assert_eq!(ancestors.next(), Some(Path::new("/foo").into())); + /// assert_eq!(ancestors.next(), Some(Path::new("/").into())); + /// assert_eq!(ancestors.next(), None); + /// ``` + /// + /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html + /// [`parent`]: struct.Path.html#method.parent + pub fn ancestors(&self) -> Ancestors<'_> { + Ancestors { next: Some(&self) } } /// Returns the final component of the `Path`, if there is one. @@ -225,170 +240,226 @@ impl Path { self.inner.file_name() } - /// Extracts the stem (non-extension) portion of [`self.file_name`]. - /// - /// [`self.file_name`]: struct.Path.html#method.file_name + /// Returns a path that, when joined onto `base`, yields `self`. /// - /// The stem is: + /// # Errors /// - /// * [`None`], if there is no file name; - /// * The entire file name if there is no embedded `.`; - /// * The entire file name if the file name begins with `.` and has no other `.`s within; - /// * Otherwise, the portion of the file name before the final `.` + /// If `base` is not a prefix of `self` (i.e., [`starts_with`] + /// returns `false`), returns [`Err`]. /// - /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None + /// [`starts_with`]: #method.starts_with + /// [`Err`]: https://doc.rust-lang.org/std/result/enum.Result.html#variant.Err /// /// # Examples /// /// ``` - /// use async_std::path::Path; + /// use async_std::path::{Path, PathBuf}; /// - /// let path = Path::new("foo.rs"); + /// let path = Path::new("/test/haha/foo.txt"); /// - /// assert_eq!("foo", path.file_stem().unwrap()); + /// assert_eq!(path.strip_prefix("/"), Ok(Path::new("test/haha/foo.txt"))); + /// assert_eq!(path.strip_prefix("/test"), Ok(Path::new("haha/foo.txt"))); + /// assert_eq!(path.strip_prefix("/test/"), Ok(Path::new("haha/foo.txt"))); + /// assert_eq!(path.strip_prefix("/test/haha/foo.txt"), Ok(Path::new(""))); + /// assert_eq!(path.strip_prefix("/test/haha/foo.txt/"), Ok(Path::new(""))); + /// assert_eq!(path.strip_prefix("test").is_ok(), false); + /// assert_eq!(path.strip_prefix("/haha").is_ok(), false); + /// + /// let prefix = PathBuf::from("/test/"); + /// assert_eq!(path.strip_prefix(prefix), Ok(Path::new("haha/foo.txt"))); /// ``` - pub fn file_stem(&self) -> Option<&OsStr> { - self.inner.file_stem() + pub fn strip_prefix

(&self, base: P) -> Result<&Path, StripPrefixError> + where + P: AsRef, + { + Ok(self.inner.strip_prefix(base.as_ref())?.into()) } - /// Returns `true` if the `Path` has a root. - /// - /// * On Unix, a path has a root if it begins with `/`. + /// Determines whether `base` is a prefix of `self`. /// - /// * On Windows, a path has a root if it: - /// * has no prefix and begins with a separator, e.g., `\windows` - /// * has a prefix followed by a separator, e.g., `c:\windows` but not `c:windows` - /// * has any non-disk prefix, e.g., `\\server\share` + /// Only considers whole path components to match. /// /// # Examples /// /// ``` /// use async_std::path::Path; /// - /// assert!(Path::new("/etc/passwd").has_root()); + /// let path = Path::new("/etc/passwd"); + /// + /// assert!(path.starts_with("/etc")); + /// assert!(path.starts_with("/etc/")); + /// assert!(path.starts_with("/etc/passwd")); + /// assert!(path.starts_with("/etc/passwd/")); + /// + /// assert!(!path.starts_with("/e")); /// ``` - pub fn has_root(&self) -> bool { - self.inner.has_root() + pub fn starts_with>(&self, base: P) -> bool { + self.inner.starts_with(base.as_ref()) } - /// Converts a [`Box`][`Box`] into a [`PathBuf`] without copying or - /// allocating. + /// Determines whether `child` is a suffix of `self`. /// - /// [`Box`]: https://doc.rust-lang.org/std/boxed/struct.Box.html - /// [`PathBuf`]: struct.PathBuf.html - pub fn into_path_buf(self: Box) -> PathBuf { - let rw = Box::into_raw(self) as *mut std::path::Path; - let inner = unsafe { Box::from_raw(rw) }; - inner.into_path_buf().into() + /// Only considers whole path components to match. + /// + /// # Examples + /// + /// ``` + /// use async_std::path::Path; + /// + /// let path = Path::new("/etc/passwd"); + /// + /// assert!(path.ends_with("passwd")); + /// ``` + pub fn ends_with>(&self, child: P) -> bool { + self.inner.ends_with(child.as_ref()) } - /// Returns `true` if the `Path` is absolute, i.e., if it is independent of - /// the current directory. + /// Extracts the stem (non-extension) portion of [`self.file_name`]. /// - /// * On Unix, a path is absolute if it starts with the root, so - /// `is_absolute` and [`has_root`] are equivalent. + /// [`self.file_name`]: struct.Path.html#method.file_name /// - /// * On Windows, a path is absolute if it has a prefix and starts with the - /// root: `c:\windows` is absolute, while `c:temp` and `\temp` are not. + /// The stem is: + /// + /// * [`None`], if there is no file name; + /// * The entire file name if there is no embedded `.`; + /// * The entire file name if the file name begins with `.` and has no other `.`s within; + /// * Otherwise, the portion of the file name before the final `.` + /// + /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None /// /// # Examples /// /// ``` /// use async_std::path::Path; /// - /// assert!(!Path::new("foo.txt").is_absolute()); - /// ``` + /// let path = Path::new("foo.rs"); /// - /// [`has_root`]: #method.has_root - pub fn is_absolute(&self) -> bool { - self.inner.is_absolute() + /// assert_eq!("foo", path.file_stem().unwrap()); + /// ``` + pub fn file_stem(&self) -> Option<&OsStr> { + self.inner.file_stem() } - /// Returns `true` if the path exists on disk and is pointing at a directory. + /// Extracts the extension of [`self.file_name`], if possible. /// - /// This function will traverse symbolic links to query information about the - /// destination file. In case of broken symbolic links this will return `false`. + /// The extension is: /// - /// If you cannot access the directory containing the file, e.g., because of a - /// permission error, this will return `false`. + /// * [`None`], if there is no file name; + /// * [`None`], if there is no embedded `.`; + /// * [`None`], if the file name begins with `.` and has no other `.`s within; + /// * Otherwise, the portion of the file name after the final `.` + /// + /// [`self.file_name`]: struct.Path.html#method.file_name + /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None /// /// # Examples /// - /// ```no_run - /// # fn main() -> std::io::Result<()> { async_std::task::block_on(async { - /// # + /// ``` /// use async_std::path::Path; - /// assert_eq!(Path::new("./is_a_directory/").is_dir().await, true); - /// assert_eq!(Path::new("a_file.txt").is_dir().await, false); - /// # - /// # Ok(()) }) } + /// + /// let path = Path::new("foo.rs"); + /// + /// assert_eq!("rs", path.extension().unwrap()); /// ``` + pub fn extension(&self) -> Option<&OsStr> { + self.inner.extension() + } + + /// Creates an owned [`PathBuf`] with `path` adjoined to `self`. /// - /// # See Also + /// See [`PathBuf::push`] for more details on what it means to adjoin a path. /// - /// This is a convenience function that coerces errors to false. If you want to - /// check errors, call [fs::metadata] and handle its Result. Then call - /// [fs::Metadata::is_dir] if it was Ok. + /// [`PathBuf`]: struct.PathBuf.html + /// [`PathBuf::push`]: struct.PathBuf.html#method.push /// - /// [fs::metadata]: ../fs/fn.metadata.html - /// [fs::Metadata::is_dir]: ../fs/struct.Metadata.html#method.is_dir - pub async fn is_dir(&self) -> bool { - fs::metadata(self) - .await - .map(|m| m.is_dir()) - .unwrap_or(false) + /// # Examples + /// + /// ``` + /// use async_std::path::{Path, PathBuf}; + /// + /// assert_eq!(Path::new("/etc").join("passwd"), PathBuf::from("/etc/passwd")); + /// ``` + pub fn join>(&self, path: P) -> PathBuf { + self.inner.join(path.as_ref()).into() } - /// Returns `true` if the path exists on disk and is pointing at a regular file. + /// Creates an owned [`PathBuf`] like `self` but with the given file name. /// - /// This function will traverse symbolic links to query information about the - /// destination file. In case of broken symbolic links this will return `false`. + /// See [`PathBuf::set_file_name`] for more details. /// - /// If you cannot access the directory containing the file, e.g., because of a - /// permission error, this will return `false`. + /// [`PathBuf`]: struct.PathBuf.html + /// [`PathBuf::set_file_name`]: struct.PathBuf.html#method.set_file_name /// /// # Examples /// - /// ```no_run - /// # fn main() -> std::io::Result<()> { async_std::task::block_on(async { - /// # - /// use async_std::path::Path; - /// assert_eq!(Path::new("./is_a_directory/").is_file().await, false); - /// assert_eq!(Path::new("a_file.txt").is_file().await, true); - /// # - /// # Ok(()) }) } /// ``` + /// use async_std::path::{Path, PathBuf}; /// - /// # See Also + /// let path = Path::new("/tmp/foo.txt"); + /// assert_eq!(path.with_file_name("bar.txt"), PathBuf::from("/tmp/bar.txt")); /// - /// This is a convenience function that coerces errors to false. If you want to - /// check errors, call [fs::metadata] and handle its Result. Then call - /// [fs::Metadata::is_file] if it was Ok. + /// let path = Path::new("/tmp"); + /// assert_eq!(path.with_file_name("var"), PathBuf::from("/var")); + /// ``` + pub fn with_file_name>(&self, file_name: S) -> PathBuf { + self.inner.with_file_name(file_name).into() + } + + /// Creates an owned [`PathBuf`] like `self` but with the given extension. /// - /// [fs::metadata]: ../fs/fn.metadata.html - /// [fs::Metadata::is_file]: ../fs/struct.Metadata.html#method.is_file - pub async fn is_file(&self) -> bool { - fs::metadata(self) - .await - .map(|m| m.is_file()) - .unwrap_or(false) + /// See [`PathBuf::set_extension`] for more details. + /// + /// [`PathBuf`]: struct.PathBuf.html + /// [`PathBuf::set_extension`]: struct.PathBuf.html#method.set_extension + /// + /// # Examples + /// + /// ``` + /// use async_std::path::{Path, PathBuf}; + /// + /// let path = Path::new("foo.rs"); + /// assert_eq!(path.with_extension("txt"), PathBuf::from("foo.txt")); + /// ``` + pub fn with_extension>(&self, extension: S) -> PathBuf { + self.inner.with_extension(extension).into() } - /// Returns `true` if the `Path` is relative, i.e., not absolute. + /// Produces an iterator over the [`Component`]s of the path. /// - /// See [`is_absolute`]'s documentation for more details. + /// When parsing the path, there is a small amount of normalization: + /// + /// * Repeated separators are ignored, so `a/b` and `a//b` both have + /// `a` and `b` as components. + /// + /// * Occurrences of `.` are normalized away, except if they are at the + /// beginning of the path. For example, `a/./b`, `a/b/`, `a/b/.` and + /// `a/b` all have `a` and `b` as components, but `./a/b` starts with + /// an additional [`CurDir`] component. + /// + /// * A trailing slash is normalized away, `/a/b` and `/a/b/` are equivalent. + /// + /// Note that no other normalization takes place; in particular, `a/c` + /// and `a/b/../c` are distinct, to account for the possibility that `b` + /// is a symbolic link (so its parent isn't `a`). /// /// # Examples /// /// ``` - /// use async_std::path::Path; + /// use async_std::path::{Path, Component}; + /// use std::ffi::OsStr; /// - /// assert!(Path::new("foo.txt").is_relative()); + /// let mut components = Path::new("/tmp/foo.txt").components(); + /// + /// assert_eq!(components.next(), Some(Component::RootDir)); + /// assert_eq!(components.next(), Some(Component::Normal(OsStr::new("tmp")))); + /// assert_eq!(components.next(), Some(Component::Normal(OsStr::new("foo.txt")))); + /// assert_eq!(components.next(), None) /// ``` /// - /// [`is_absolute`]: #method.is_absolute - pub fn is_relative(&self) -> bool { - self.inner.is_relative() + /// [`Component`]: enum.Component.html + /// [`CurDir`]: enum.Component.html#variant.CurDir + pub fn components(&self) -> Components<'_> { + self.inner.components() } /// Produces an iterator over the path's components viewed as [`OsStr`] @@ -416,25 +487,22 @@ impl Path { self.inner.iter() } - /// Creates an owned [`PathBuf`] with `path` adjoined to `self`. - /// - /// See [`PathBuf::push`] for more details on what it means to adjoin a path. + /// Returns an object that implements [`Display`] for safely printing paths + /// that may contain non-Unicode data. /// - /// [`PathBuf`]: struct.PathBuf.html - /// [`PathBuf::push`]: struct.PathBuf.html#method.push + /// [`Display`]: https://doc.rust-lang.org/std/fmt/trait.Display.html /// /// # Examples /// /// ``` - /// use async_std::path::{Path, PathBuf}; + /// use async_std::path::Path; /// - /// assert_eq!(Path::new("/etc").join("passwd"), PathBuf::from("/etc/passwd")); + /// let path = Path::new("/tmp/foo.rs"); + /// + /// println!("{}", path.display()); /// ``` - pub fn join>(&self, path: P) -> PathBuf - where - P: std::convert::AsRef, - { - self.inner.join(path).into() + pub fn display(&self) -> Display<'_> { + self.inner.display() } /// Queries the file system to get information about a file, directory, etc. @@ -463,53 +531,72 @@ impl Path { fs::metadata(self).await } - /// Directly wraps a string slice as a `Path` slice. + /// Queries the metadata about a file without following symlinks. /// - /// This is a cost-free conversion. + /// This is an alias to [`fs::symlink_metadata`]. + /// + /// [`fs::symlink_metadata`]: ../fs/fn.symlink_metadata.html /// /// # Examples /// - /// ``` + /// ```no_run + /// # fn main() -> std::io::Result<()> { async_std::task::block_on(async { + /// # /// use async_std::path::Path; /// - /// Path::new("foo.txt"); + /// let path = Path::new("/Minas/tirith"); + /// let metadata = path.symlink_metadata().await.expect("symlink_metadata call failed"); + /// println!("{:?}", metadata.file_type()); + /// # + /// # Ok(()) }) } /// ``` + pub async fn symlink_metadata(&self) -> io::Result { + fs::symlink_metadata(self).await + } + + /// Returns the canonical, absolute form of the path with all intermediate + /// components normalized and symbolic links resolved. /// - /// You can create `Path`s from `String`s, or even other `Path`s: + /// This is an alias to [`fs::canonicalize`]. /// - /// ``` - /// use async_std::path::Path; + /// [`fs::canonicalize`]: ../fs/fn.canonicalize.html /// - /// let string = String::from("foo.txt"); - /// let from_string = Path::new(&string); - /// let from_path = Path::new(&from_string); - /// assert_eq!(from_string, from_path); + /// # Examples + /// + /// ```no_run + /// # fn main() -> std::io::Result<()> { async_std::task::block_on(async { + /// # + /// use async_std::path::{Path, PathBuf}; + /// + /// let path = Path::new("/foo/test/../test/bar.rs"); + /// assert_eq!(path.canonicalize().await.unwrap(), PathBuf::from("/foo/test/bar.rs")); + /// # + /// # Ok(()) }) } /// ``` - pub fn new + ?Sized>(s: &S) -> &Path { - unsafe { &*(std::path::Path::new(s) as *const std::path::Path as *const Path) } + pub async fn canonicalize(&self) -> io::Result { + fs::canonicalize(self).await } - /// Returns the `Path` without its final component, if there is one. + /// Reads a symbolic link, returning the file that the link points to. /// - /// Returns [`None`] if the path terminates in a root or prefix. + /// This is an alias to [`fs::read_link`]. /// - /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None + /// [`fs::read_link`]: ../fs/fn.read_link.html /// /// # Examples /// - /// ``` + /// ```no_run + /// # fn main() -> std::io::Result<()> { async_std::task::block_on(async { + /// # /// use async_std::path::Path; /// - /// let path = Path::new("/foo/bar"); - /// let parent = path.parent().unwrap(); - /// assert_eq!(parent, Path::new("/foo")); - /// - /// let grand_parent = parent.parent().unwrap(); - /// assert_eq!(grand_parent, Path::new("/")); - /// assert_eq!(grand_parent.parent(), None); + /// let path = Path::new("/laputa/sky_castle.rs"); + /// let path_link = path.read_link().await.expect("read_link call failed"); + /// # + /// # Ok(()) }) } /// ``` - pub fn parent(&self) -> Option<&Path> { - self.inner.parent().map(|p| p.into()) + pub async fn read_link(&self) -> io::Result { + fs::read_link(self).await } /// Returns an iterator over the entries within a directory. @@ -545,11 +632,13 @@ impl Path { fs::read_dir(self).await } - /// Reads a symbolic link, returning the file that the link points to. + /// Returns `true` if the path points at an existing entity. /// - /// This is an alias to [`fs::read_link`]. + /// This function will traverse symbolic links to query information about the + /// destination file. In case of broken symbolic links this will return `false`. /// - /// [`fs::read_link`]: ../fs/fn.read_link.html + /// If you cannot access the directory containing the file, e.g., because of a + /// permission error, this will return `false`. /// /// # Examples /// @@ -557,81 +646,28 @@ impl Path { /// # fn main() -> std::io::Result<()> { async_std::task::block_on(async { /// # /// use async_std::path::Path; - /// - /// let path = Path::new("/laputa/sky_castle.rs"); - /// let path_link = path.read_link().await.expect("read_link call failed"); + /// assert_eq!(Path::new("does_not_exist.txt").exists().await, false); /// # /// # Ok(()) }) } /// ``` - pub async fn read_link(&self) -> io::Result { - fs::read_link(self).await - } - - /// Determines whether `base` is a prefix of `self`. - /// - /// Only considers whole path components to match. - /// - /// # Examples - /// - /// ``` - /// use async_std::path::Path; - /// - /// let path = Path::new("/etc/passwd"); /// - /// assert!(path.starts_with("/etc")); - /// assert!(path.starts_with("/etc/")); - /// assert!(path.starts_with("/etc/passwd")); - /// assert!(path.starts_with("/etc/passwd/")); - /// - /// assert!(!path.starts_with("/e")); - /// ``` - pub fn starts_with>(&self, base: P) -> bool - where - P: std::convert::AsRef, - { - self.inner.starts_with(base) - } - - /// Returns a path that, when joined onto `base`, yields `self`. - /// - /// # Errors - /// - /// If `base` is not a prefix of `self` (i.e., [`starts_with`] - /// returns `false`), returns [`Err`]. - /// - /// [`starts_with`]: #method.starts_with - /// [`Err`]: https://doc.rust-lang.org/std/result/enum.Result.html#variant.Err - /// - /// # Examples - /// - /// ``` - /// use async_std::path::{Path, PathBuf}; - /// - /// let path = Path::new("/test/haha/foo.txt"); + /// # See Also /// - /// assert_eq!(path.strip_prefix("/"), Ok(Path::new("test/haha/foo.txt"))); - /// assert_eq!(path.strip_prefix("/test"), Ok(Path::new("haha/foo.txt"))); - /// assert_eq!(path.strip_prefix("/test/"), Ok(Path::new("haha/foo.txt"))); - /// assert_eq!(path.strip_prefix("/test/haha/foo.txt"), Ok(Path::new(""))); - /// assert_eq!(path.strip_prefix("/test/haha/foo.txt/"), Ok(Path::new(""))); - /// assert_eq!(path.strip_prefix("test").is_ok(), false); - /// assert_eq!(path.strip_prefix("/haha").is_ok(), false); + /// This is a convenience function that coerces errors to false. If you want to + /// check errors, call [fs::metadata]. /// - /// let prefix = PathBuf::from("/test/"); - /// assert_eq!(path.strip_prefix(prefix), Ok(Path::new("haha/foo.txt"))); - /// ``` - pub fn strip_prefix

(&self, base: P) -> Result<&Path, StripPrefixError> - where - P: AsRef, - { - Ok(self.inner.strip_prefix(base)?.into()) + /// [fs::metadata]: ../fs/fn.metadata.html + pub async fn exists(&self) -> bool { + fs::metadata(self).await.is_ok() } - /// Queries the metadata about a file without following symlinks. + /// Returns `true` if the path exists on disk and is pointing at a regular file. /// - /// This is an alias to [`fs::symlink_metadata`]. + /// This function will traverse symbolic links to query information about the + /// destination file. In case of broken symbolic links this will return `false`. /// - /// [`fs::symlink_metadata`]: ../fs/fn.symlink_metadata.html + /// If you cannot access the directory containing the file, e.g., because of a + /// permission error, this will return `false`. /// /// # Examples /// @@ -639,156 +675,74 @@ impl Path { /// # fn main() -> std::io::Result<()> { async_std::task::block_on(async { /// # /// use async_std::path::Path; - /// - /// let path = Path::new("/Minas/tirith"); - /// let metadata = path.symlink_metadata().await.expect("symlink_metadata call failed"); - /// println!("{:?}", metadata.file_type()); + /// assert_eq!(Path::new("./is_a_directory/").is_file().await, false); + /// assert_eq!(Path::new("a_file.txt").is_file().await, true); /// # /// # Ok(()) }) } /// ``` - pub async fn symlink_metadata(&self) -> io::Result { - fs::symlink_metadata(self).await - } - - /// Converts a `Path` to an owned [`PathBuf`]. - /// - /// [`PathBuf`]: struct.PathBuf.html /// - /// # Examples - /// - /// ``` - /// use async_std::path::{Path, PathBuf}; - /// - /// let path_buf = Path::new("foo.txt").to_path_buf(); - /// assert_eq!(path_buf, PathBuf::from("foo.txt")); - /// ``` - pub fn to_path_buf(&self) -> PathBuf { - PathBuf::from(self.inner.to_path_buf()) - } - - /// Yields a [`&str`] slice if the `Path` is valid unicode. - /// - /// This conversion may entail doing a check for UTF-8 validity. - /// Note that validation is performed because non-UTF-8 strings are - /// perfectly valid for some OS. - /// - /// [`&str`]: https://doc.rust-lang.org/std/primitive.str.html - /// - /// # Examples + /// # See Also /// - /// ``` - /// use async_std::path::Path; + /// This is a convenience function that coerces errors to false. If you want to + /// check errors, call [fs::metadata] and handle its Result. Then call + /// [fs::Metadata::is_file] if it was Ok. /// - /// let path = Path::new("foo.txt"); - /// assert_eq!(path.to_str(), Some("foo.txt")); - /// ``` - pub fn to_str(&self) -> Option<&str> { - self.inner.to_str() + /// [fs::metadata]: ../fs/fn.metadata.html + /// [fs::Metadata::is_file]: ../fs/struct.Metadata.html#method.is_file + pub async fn is_file(&self) -> bool { + fs::metadata(self) + .await + .map(|m| m.is_file()) + .unwrap_or(false) } - /// Converts a `Path` to a [`Cow`]. + /// Returns `true` if the path exists on disk and is pointing at a directory. /// - /// Any non-Unicode sequences are replaced with - /// [`U+FFFD REPLACEMENT CHARACTER`][U+FFFD]. + /// This function will traverse symbolic links to query information about the + /// destination file. In case of broken symbolic links this will return `false`. /// - /// [`Cow`]: https://doc.rust-lang.org/std/borrow/enum.Cow.html - /// [U+FFFD]: https://doc.rust-lang.org/std/char/constant.REPLACEMENT_CHARACTER.html + /// If you cannot access the directory containing the file, e.g., because of a + /// permission error, this will return `false`. /// /// # Examples /// - /// Calling `to_string_lossy` on a `Path` with valid unicode: - /// - /// ``` + /// ```no_run + /// # fn main() -> std::io::Result<()> { async_std::task::block_on(async { + /// # /// use async_std::path::Path; - /// - /// let path = Path::new("foo.txt"); - /// assert_eq!(path.to_string_lossy(), "foo.txt"); + /// assert_eq!(Path::new("./is_a_directory/").is_dir().await, true); + /// assert_eq!(Path::new("a_file.txt").is_dir().await, false); + /// # + /// # Ok(()) }) } /// ``` /// - /// Had `path` contained invalid unicode, the `to_string_lossy` call might - /// have returned `"fo�.txt"`. - pub fn to_string_lossy(&self) -> std::borrow::Cow<'_, str> { - self.inner.to_string_lossy() - } - - /// Creates an owned [`PathBuf`] like `self` but with the given extension. - /// - /// See [`PathBuf::set_extension`] for more details. - /// - /// [`PathBuf`]: struct.PathBuf.html - /// [`PathBuf::set_extension`]: struct.PathBuf.html#method.set_extension - /// - /// # Examples + /// # See Also /// - /// ``` - /// use async_std::path::{Path, PathBuf}; + /// This is a convenience function that coerces errors to false. If you want to + /// check errors, call [fs::metadata] and handle its Result. Then call + /// [fs::Metadata::is_dir] if it was Ok. /// - /// let path = Path::new("foo.rs"); - /// assert_eq!(path.with_extension("txt"), PathBuf::from("foo.txt")); - /// ``` - pub fn with_extension>(&self, extension: S) -> PathBuf { - self.inner.with_extension(extension).into() + /// [fs::metadata]: ../fs/fn.metadata.html + /// [fs::Metadata::is_dir]: ../fs/struct.Metadata.html#method.is_dir + pub async fn is_dir(&self) -> bool { + fs::metadata(self) + .await + .map(|m| m.is_dir()) + .unwrap_or(false) } - /// Creates an owned [`PathBuf`] like `self` but with the given file name. - /// - /// See [`PathBuf::set_file_name`] for more details. + /// Converts a [`Box`][`Box`] into a [`PathBuf`] without copying or + /// allocating. /// + /// [`Box`]: https://doc.rust-lang.org/std/boxed/struct.Box.html /// [`PathBuf`]: struct.PathBuf.html - /// [`PathBuf::set_file_name`]: struct.PathBuf.html#method.set_file_name - /// - /// # Examples - /// - /// ``` - /// use async_std::path::{Path, PathBuf}; - /// - /// let path = Path::new("/tmp/foo.txt"); - /// assert_eq!(path.with_file_name("bar.txt"), PathBuf::from("/tmp/bar.txt")); - /// - /// let path = Path::new("/tmp"); - /// assert_eq!(path.with_file_name("var"), PathBuf::from("/var")); - /// ``` - pub fn with_file_name>(&self, file_name: S) -> PathBuf { - self.inner.with_file_name(file_name).into() - } -} - -/// An iterator over [`Path`] and its ancestors. -/// -/// This `struct` is created by the [`ancestors`] method on [`Path`]. -/// See its documentation for more. -/// -/// # Examples -/// -/// ``` -/// use async_std::path::Path; -/// -/// let path = Path::new("/foo/bar"); -/// -/// for ancestor in path.ancestors() { -/// println!("{}", ancestor.display()); -/// } -/// ``` -/// -/// [`ancestors`]: struct.Path.html#method.ancestors -/// [`Path`]: struct.Path.html -#[derive(Copy, Clone, Debug)] -pub struct Ancestors<'a> { - next: Option<&'a Path>, -} - -impl<'a> Iterator for Ancestors<'a> { - type Item = &'a Path; - - fn next(&mut self) -> Option { - let next = self.next; - self.next = next.and_then(Path::parent); - next + pub fn into_path_buf(self: Box) -> PathBuf { + let rw = Box::into_raw(self) as *mut std::path::Path; + let inner = unsafe { Box::from_raw(rw) }; + inner.into_path_buf().into() } } -impl FusedIterator for Ancestors<'_> {} - impl<'a> From<&'a std::path::Path> for &'a Path { fn from(path: &'a std::path::Path) -> &'a Path { &Path::new(path.as_os_str()) diff --git a/src/path/pathbuf.rs b/src/path/pathbuf.rs index 28062b2..64744e1 100644 --- a/src/path/pathbuf.rs +++ b/src/path/pathbuf.rs @@ -11,6 +11,19 @@ pub struct PathBuf { } impl PathBuf { + /// Allocates an empty `PathBuf`. + /// + /// # Examples + /// + /// ``` + /// use async_std::path::PathBuf; + /// + /// let path = PathBuf::new(); + /// ``` + pub fn new() -> PathBuf { + std::path::PathBuf::new().into() + } + /// Coerces to a [`Path`] slice. /// /// [`Path`]: struct.Path.html @@ -27,42 +40,39 @@ impl PathBuf { self.inner.as_path().into() } - /// Converts this `PathBuf` into a [boxed][`Box`] [`Path`]. + /// Extends `self` with `path`. /// - /// [`Box`]: https://doc.rust-lang.org/std/boxed/struct.Box.html - /// [`Path`]: struct.Path.html - pub fn into_boxed_path(self) -> Box { - let rw = Box::into_raw(self.inner.into_boxed_path()) as *mut Path; - unsafe { Box::from_raw(rw) } - } - - /// Consumes the `PathBuf`, yielding its internal [`OsString`] storage. + /// If `path` is absolute, it replaces the current path. /// - /// [`OsString`]: https://doc.rust-lang.org/std/ffi/struct.OsString.html + /// On Windows: + /// + /// * if `path` has a root but no prefix (e.g., `\windows`), it + /// replaces everything except for the prefix (if any) of `self`. + /// * if `path` has a prefix but no root, it replaces `self`. /// /// # Examples /// + /// Pushing a relative path extends the existing path: + /// /// ``` /// use async_std::path::PathBuf; /// - /// let p = PathBuf::from("/the/head"); - /// let os_str = p.into_os_string(); + /// let mut path = PathBuf::from("/tmp"); + /// path.push("file.bk"); + /// assert_eq!(path, PathBuf::from("/tmp/file.bk")); /// ``` - pub fn into_os_string(self) -> OsString { - self.inner.into_os_string() - } - - /// Allocates an empty `PathBuf`. /// - /// # Examples + /// Pushing an absolute path replaces the existing path: /// /// ``` /// use async_std::path::PathBuf; /// - /// let path = PathBuf::new(); + /// let mut path = PathBuf::from("/tmp"); + /// path.push("/etc"); + /// assert_eq!(path, PathBuf::from("/etc")); /// ``` - pub fn new() -> PathBuf { - std::path::PathBuf::new().into() + pub fn push>(&mut self, path: P) { + self.inner.push(path.as_ref()) } /// Truncates `self` to [`self.parent`]. @@ -89,39 +99,34 @@ impl PathBuf { self.inner.pop() } - /// Extends `self` with `path`. + /// Updates [`self.file_name`] to `file_name`. /// - /// If `path` is absolute, it replaces the current path. + /// If [`self.file_name`] was [`None`], this is equivalent to pushing + /// `file_name`. /// - /// On Windows: + /// Otherwise it is equivalent to calling [`pop`] and then pushing + /// `file_name`. The new path will be a sibling of the original path. + /// (That is, it will have the same parent.) /// - /// * if `path` has a root but no prefix (e.g., `\windows`), it - /// replaces everything except for the prefix (if any) of `self`. - /// * if `path` has a prefix but no root, it replaces `self`. + /// [`self.file_name`]: struct.PathBuf.html#method.file_name + /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None + /// [`pop`]: struct.PathBuf.html#method.pop /// /// # Examples /// - /// Pushing a relative path extends the existing path: - /// - /// ``` - /// use async_std::path::PathBuf; - /// - /// let mut path = PathBuf::from("/tmp"); - /// path.push("file.bk"); - /// assert_eq!(path, PathBuf::from("/tmp/file.bk")); - /// ``` - /// - /// Pushing an absolute path replaces the existing path: - /// /// ``` /// use async_std::path::PathBuf; /// - /// let mut path = PathBuf::from("/tmp"); - /// path.push("/etc"); - /// assert_eq!(path, PathBuf::from("/etc")); + /// let mut buf = PathBuf::from("/"); + /// assert!(buf.file_name() == None); + /// buf.set_file_name("bar"); + /// assert!(buf == PathBuf::from("/bar")); + /// assert!(buf.file_name().is_some()); + /// buf.set_file_name("baz.txt"); + /// assert!(buf == PathBuf::from("/baz.txt")); /// ``` - pub fn push>(&mut self, path: P) { - self.inner.push(path) + pub fn set_file_name>(&mut self, file_name: S) { + self.inner.set_file_name(file_name) } /// Updates [`self.extension`] to `extension`. @@ -153,34 +158,29 @@ impl PathBuf { self.inner.set_extension(extension) } - /// Updates [`self.file_name`] to `file_name`. - /// - /// If [`self.file_name`] was [`None`], this is equivalent to pushing - /// `file_name`. - /// - /// Otherwise it is equivalent to calling [`pop`] and then pushing - /// `file_name`. The new path will be a sibling of the original path. - /// (That is, it will have the same parent.) + /// Consumes the `PathBuf`, yielding its internal [`OsString`] storage. /// - /// [`self.file_name`]: struct.PathBuf.html#method.file_name - /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None - /// [`pop`]: struct.PathBuf.html#method.pop + /// [`OsString`]: https://doc.rust-lang.org/std/ffi/struct.OsString.html /// /// # Examples /// /// ``` /// use async_std::path::PathBuf; /// - /// let mut buf = PathBuf::from("/"); - /// assert!(buf.file_name() == None); - /// buf.set_file_name("bar"); - /// assert!(buf == PathBuf::from("/bar")); - /// assert!(buf.file_name().is_some()); - /// buf.set_file_name("baz.txt"); - /// assert!(buf == PathBuf::from("/baz.txt")); + /// let p = PathBuf::from("/the/head"); + /// let os_str = p.into_os_string(); /// ``` - pub fn set_file_name>(&mut self, file_name: S) { - self.inner.set_file_name(file_name) + pub fn into_os_string(self) -> OsString { + self.inner.into_os_string() + } + + /// Converts this `PathBuf` into a [boxed][`Box`] [`Path`]. + /// + /// [`Box`]: https://doc.rust-lang.org/std/boxed/struct.Box.html + /// [`Path`]: struct.Path.html + pub fn into_boxed_path(self) -> Box { + let rw = Box::into_raw(self.inner.into_boxed_path()) as *mut Path; + unsafe { Box::from_raw(rw) } } }