From e27b578c27057478d01bdc3abe5fe48c2ace5cc7 Mon Sep 17 00:00:00 2001 From: Wouter Geraedts Date: Sun, 13 Oct 2019 13:06:39 +0200 Subject: [PATCH 01/42] WIP init Path and PathBuf async stubs --- src/fs/canonicalize.rs | 8 ++-- src/lib.rs | 3 +- src/path/mod.rs | 6 +++ src/path/path.rs | 105 +++++++++++++++++++++++++++++++++++++++++ src/path/pathbuf.rs | 34 +++++++++++++ 5 files changed, 151 insertions(+), 5 deletions(-) create mode 100644 src/path/path.rs create mode 100644 src/path/pathbuf.rs diff --git a/src/fs/canonicalize.rs b/src/fs/canonicalize.rs index c484aee..84278cb 100644 --- a/src/fs/canonicalize.rs +++ b/src/fs/canonicalize.rs @@ -1,4 +1,4 @@ -use std::path::{Path, PathBuf}; +use crate::path::{Path, PathBuf}; use crate::io; use crate::task::blocking; @@ -32,6 +32,8 @@ use crate::task::blocking; /// # Ok(()) }) } /// ``` pub async fn canonicalize>(path: P) -> io::Result { - let path = path.as_ref().to_owned(); - blocking::spawn(async move { std::fs::canonicalize(path) }).await + let path: std::path::PathBuf = path.as_ref().to_path_buf().into(); + Ok(blocking::spawn(async move { std::fs::canonicalize(&path) }) + .await? + .into()) } diff --git a/src/lib.rs b/src/lib.rs index fa4e946..83453cd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -55,6 +55,7 @@ pub mod future; pub mod io; pub mod net; pub mod os; +pub mod path; pub mod prelude; pub mod stream; pub mod sync; @@ -62,8 +63,6 @@ pub mod task; cfg_if! { if #[cfg(any(feature = "unstable", feature = "docs"))] { - #[cfg_attr(feature = "docs", doc(cfg(unstable)))] - pub mod path; #[cfg_attr(feature = "docs", doc(cfg(unstable)))] pub mod pin; diff --git a/src/path/mod.rs b/src/path/mod.rs index 9153243..35a6717 100644 --- a/src/path/mod.rs +++ b/src/path/mod.rs @@ -4,6 +4,9 @@ //! //! [`std::path`]: https://doc.rust-lang.org/std/path/index.html +mod path; +mod pathbuf; + // Structs re-export #[doc(inline)] pub use std::path::{Ancestors, Components, Display, Iter, PrefixComponent, StripPrefixError}; @@ -19,3 +22,6 @@ pub use std::path::MAIN_SEPARATOR; // Functions re-export #[doc(inline)] pub use std::path::is_separator; + +pub use path::Path; +pub use pathbuf::PathBuf; diff --git a/src/path/path.rs b/src/path/path.rs new file mode 100644 index 0000000..11dda64 --- /dev/null +++ b/src/path/path.rs @@ -0,0 +1,105 @@ +use std::ffi::OsStr; + +use crate::path::PathBuf; +use crate::{fs, io}; + +/// This struct is an async version of [`std::path::Path`]. +/// +/// [`std::path::Path`]: https://doc.rust-lang.org/std/path/struct.Path.html +pub struct Path { + inner: OsStr, +} + +impl Path { + /// Yields the underlying [`OsStr`] slice. + /// + /// [`OsStr`]: https://doc.rust-lang.org/std/ffi/struct.OsStr.html + pub fn as_os_str(&self) -> &OsStr { + &self.inner + } + + /// Returns the canonical, absolute form of the path with all intermediate + /// components normalized and symbolic links resolved. + /// + /// This is an alias to [`fs::canonicalize`]. + /// + /// [`fs::canonicalize`]: ../fs/fn.canonicalize.html + /// + /// # Examples + /// + /// ```no_run + /// use crate::path::{Path, PathBuf}; + /// + /// let path = Path::new("/foo/test/../test/bar.rs"); + /// assert_eq!(path.canonicalize().unwrap(), PathBuf::from("/foo/test/bar.rs")); + /// ``` + pub async fn canonicalize(&self) -> io::Result { + fs::canonicalize(self).await + } + + /// Directly wraps a string slice as a `Path` slice. + /// + /// This is a cost-free conversion. + /// + /// # Examples + /// + /// ``` + /// use crate::path::Path; + /// + /// Path::new("foo.txt"); + /// ``` + /// + /// You can create `Path`s from `String`s, or even other `Path`s: + /// + /// ``` + /// use crate::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 { &*(s.as_ref() as *const OsStr as *const Path) } + } + + /// Converts a `Path` to an owned [`PathBuf`]. + /// + /// [`PathBuf`]: struct.PathBuf.html + /// + /// # Examples + /// + /// ``` + /// use crate::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_os_string()) + } +} + +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()) + } +} + +impl<'a> Into<&'a std::path::Path> for &'a Path { + fn into(self) -> &'a std::path::Path { + std::path::Path::new(&self.inner) + } +} + +impl AsRef for Path { + fn as_ref(&self) -> &Path { + self + } +} + +impl std::fmt::Debug for Path { + fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Debug::fmt(&self.inner, formatter) + } +} diff --git a/src/path/pathbuf.rs b/src/path/pathbuf.rs new file mode 100644 index 0000000..8e379ad --- /dev/null +++ b/src/path/pathbuf.rs @@ -0,0 +1,34 @@ +use std::ffi::OsString; + +/// This struct is an async version of [`std::path::PathBuf`]. +/// +/// [`std::path::Path`]: https://doc.rust-lang.org/std/path/struct.PathBuf.html +pub struct PathBuf { + inner: OsString, +} + +impl From for PathBuf { + fn from(path: std::path::PathBuf) -> PathBuf { + PathBuf { + inner: path.into_os_string(), + } + } +} + +impl Into for PathBuf { + fn into(self) -> std::path::PathBuf { + self.inner.into() + } +} + +impl From for PathBuf { + fn from(path: OsString) -> PathBuf { + PathBuf { inner: path } + } +} + +impl std::fmt::Debug for PathBuf { + fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Debug::fmt(&self.inner, formatter) + } +} From 3bd6a9df6d1ec8ad8252483c31568e3ca5eeefdc Mon Sep 17 00:00:00 2001 From: Wouter Geraedts Date: Sun, 13 Oct 2019 13:39:12 +0200 Subject: [PATCH 02/42] Implemented components --- src/path/path.rs | 81 ++++++++++++++++++++++++++++++++++++++++++--- src/path/pathbuf.rs | 8 +++++ 2 files changed, 84 insertions(+), 5 deletions(-) diff --git a/src/path/path.rs b/src/path/path.rs index 11dda64..f16c8c0 100644 --- a/src/path/path.rs +++ b/src/path/path.rs @@ -1,6 +1,6 @@ use std::ffi::OsStr; -use crate::path::PathBuf; +use crate::path::{Components, PathBuf}; use crate::{fs, io}; /// This struct is an async version of [`std::path::Path`]. @@ -28,7 +28,7 @@ impl Path { /// # Examples /// /// ```no_run - /// use crate::path::{Path, PathBuf}; + /// use async_std::path::{Path, PathBuf}; /// /// let path = Path::new("/foo/test/../test/bar.rs"); /// assert_eq!(path.canonicalize().unwrap(), PathBuf::from("/foo/test/bar.rs")); @@ -37,6 +37,45 @@ impl Path { fs::canonicalize(self).await } + /// Produces an iterator over the [`Component`]s of the path. + /// + /// 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, Component}; + /// use std::ffi::OsStr; + /// + /// 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) + /// ``` + /// + /// [`Component`]: enum.Component.html + /// [`CurDir`]: enum.Component.html#variant.CurDir + pub fn components(&self) -> Components<'_> { + let path: &std::path::Path = self.into(); + path.components() + } + /// Directly wraps a string slice as a `Path` slice. /// /// This is a cost-free conversion. @@ -44,7 +83,7 @@ impl Path { /// # Examples /// /// ``` - /// use crate::path::Path; + /// use async_std::path::Path; /// /// Path::new("foo.txt"); /// ``` @@ -52,7 +91,7 @@ impl Path { /// You can create `Path`s from `String`s, or even other `Path`s: /// /// ``` - /// use crate::path::Path; + /// use async_std::path::Path; /// /// let string = String::from("foo.txt"); /// let from_string = Path::new(&string); @@ -70,7 +109,7 @@ impl Path { /// # Examples /// /// ``` - /// use crate::path::{Path, PathBuf}; + /// use async_std::path::{Path, PathBuf}; /// /// let path_buf = Path::new("foo.txt").to_path_buf(); /// assert_eq!(path_buf, PathBuf::from("foo.txt")); @@ -98,8 +137,40 @@ impl AsRef for Path { } } +impl AsRef for OsStr { + fn as_ref(&self) -> &Path { + Path::new(self) + } +} + +impl AsRef for str { + fn as_ref(&self) -> &Path { + Path::new(self) + } +} + impl std::fmt::Debug for Path { fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Debug::fmt(&self.inner, formatter) } } + +impl std::cmp::PartialEq for Path { + fn eq(&self, other: &Path) -> bool { + self.components().eq(other.components()) + } +} + +impl std::cmp::Eq for Path {} + +impl std::cmp::PartialOrd for Path { + fn partial_cmp(&self, other: &Path) -> Option { + self.components().partial_cmp(other.components()) + } +} + +impl std::cmp::Ord for Path { + fn cmp(&self, other: &Path) -> std::cmp::Ordering { + self.components().cmp(other.components()) + } +} \ No newline at end of file diff --git a/src/path/pathbuf.rs b/src/path/pathbuf.rs index 8e379ad..5ec0ed8 100644 --- a/src/path/pathbuf.rs +++ b/src/path/pathbuf.rs @@ -1,5 +1,7 @@ use std::ffi::OsString; +use crate::path::Path; + /// This struct is an async version of [`std::path::PathBuf`]. /// /// [`std::path::Path`]: https://doc.rust-lang.org/std/path/struct.PathBuf.html @@ -27,6 +29,12 @@ impl From for PathBuf { } } +impl AsRef for PathBuf { + fn as_ref(&self) -> &Path { + Path::new(&self.inner) + } +} + impl std::fmt::Debug for PathBuf { fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Debug::fmt(&self.inner, formatter) From 930b81868d690e48d047b86470c254aba25fbae9 Mon Sep 17 00:00:00 2001 From: Wouter Geraedts Date: Sun, 13 Oct 2019 13:46:02 +0200 Subject: [PATCH 03/42] Use std variants of Path and PathBuf internally --- src/path/path.rs | 43 +++++-------------------------------------- src/path/pathbuf.rs | 10 +++++----- 2 files changed, 10 insertions(+), 43 deletions(-) diff --git a/src/path/path.rs b/src/path/path.rs index f16c8c0..0a93ac6 100644 --- a/src/path/path.rs +++ b/src/path/path.rs @@ -7,7 +7,7 @@ use crate::{fs, io}; /// /// [`std::path::Path`]: https://doc.rust-lang.org/std/path/struct.Path.html pub struct Path { - inner: OsStr, + inner: std::path::Path, } impl Path { @@ -15,7 +15,7 @@ impl Path { /// /// [`OsStr`]: https://doc.rust-lang.org/std/ffi/struct.OsStr.html pub fn as_os_str(&self) -> &OsStr { - &self.inner + self.inner.as_os_str() } /// Returns the canonical, absolute form of the path with all intermediate @@ -72,8 +72,7 @@ impl Path { /// [`Component`]: enum.Component.html /// [`CurDir`]: enum.Component.html#variant.CurDir pub fn components(&self) -> Components<'_> { - let path: &std::path::Path = self.into(); - path.components() + self.inner.components() } /// Directly wraps a string slice as a `Path` slice. @@ -99,7 +98,7 @@ impl Path { /// assert_eq!(from_string, from_path); /// ``` pub fn new + ?Sized>(s: &S) -> &Path { - unsafe { &*(s.as_ref() as *const OsStr as *const Path) } + unsafe { &*(std::path::Path::new(s) as *const std::path::Path as *const Path) } } /// Converts a `Path` to an owned [`PathBuf`]. @@ -115,7 +114,7 @@ impl Path { /// assert_eq!(path_buf, PathBuf::from("foo.txt")); /// ``` pub fn to_path_buf(&self) -> PathBuf { - PathBuf::from(self.inner.to_os_string()) + PathBuf::from(self.inner.to_path_buf()) } } @@ -137,40 +136,8 @@ impl AsRef for Path { } } -impl AsRef for OsStr { - fn as_ref(&self) -> &Path { - Path::new(self) - } -} - -impl AsRef for str { - fn as_ref(&self) -> &Path { - Path::new(self) - } -} - impl std::fmt::Debug for Path { fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Debug::fmt(&self.inner, formatter) } } - -impl std::cmp::PartialEq for Path { - fn eq(&self, other: &Path) -> bool { - self.components().eq(other.components()) - } -} - -impl std::cmp::Eq for Path {} - -impl std::cmp::PartialOrd for Path { - fn partial_cmp(&self, other: &Path) -> Option { - self.components().partial_cmp(other.components()) - } -} - -impl std::cmp::Ord for Path { - fn cmp(&self, other: &Path) -> std::cmp::Ordering { - self.components().cmp(other.components()) - } -} \ No newline at end of file diff --git a/src/path/pathbuf.rs b/src/path/pathbuf.rs index 5ec0ed8..c3665e9 100644 --- a/src/path/pathbuf.rs +++ b/src/path/pathbuf.rs @@ -6,14 +6,12 @@ use crate::path::Path; /// /// [`std::path::Path`]: https://doc.rust-lang.org/std/path/struct.PathBuf.html pub struct PathBuf { - inner: OsString, + inner: std::path::PathBuf, } impl From for PathBuf { fn from(path: std::path::PathBuf) -> PathBuf { - PathBuf { - inner: path.into_os_string(), - } + PathBuf { inner: path } } } @@ -25,7 +23,9 @@ impl Into for PathBuf { impl From for PathBuf { fn from(path: OsString) -> PathBuf { - PathBuf { inner: path } + PathBuf { + inner: std::path::PathBuf::from(path), + } } } From e690b55b18f086fa4aa1adf9833bb3603e0e7872 Mon Sep 17 00:00:00 2001 From: Wouter Geraedts Date: Sun, 13 Oct 2019 13:52:51 +0200 Subject: [PATCH 04/42] Implemented fs::metadata and Path::exists --- src/fs/metadata.rs | 5 ++--- src/path/path.rs | 25 +++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/fs/metadata.rs b/src/fs/metadata.rs index 2c9e41e..ef99a56 100644 --- a/src/fs/metadata.rs +++ b/src/fs/metadata.rs @@ -1,8 +1,7 @@ -use std::path::Path; - use cfg_if::cfg_if; use crate::io; +use crate::path::Path; use crate::task::blocking; /// Reads metadata for a path. @@ -36,7 +35,7 @@ use crate::task::blocking; /// # Ok(()) }) } /// ``` pub async fn metadata>(path: P) -> io::Result { - let path = path.as_ref().to_owned(); + let path: std::path::PathBuf = path.as_ref().to_path_buf().into(); blocking::spawn(async move { std::fs::metadata(path) }).await } diff --git a/src/path/path.rs b/src/path/path.rs index 0a93ac6..e7353e9 100644 --- a/src/path/path.rs +++ b/src/path/path.rs @@ -75,6 +75,31 @@ impl Path { self.inner.components() } + /// Returns `true` if the path points at an existing entity. + /// + /// This function will traverse symbolic links to query information about the + /// destination file. In case of broken symbolic links this will return `false`. + /// + /// If you cannot access the directory containing the file, e.g., because of a + /// permission error, this will return `false`. + /// + /// # Examples + /// + /// ```no_run + /// use async_std::path::Path; + /// assert_eq!(Path::new("does_not_exist.txt").exists(), false); + /// ``` + /// + /// # 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() + } + /// Directly wraps a string slice as a `Path` slice. /// /// This is a cost-free conversion. From 6bbfd039b1a69a70f7ecb57456036055120739c6 Mon Sep 17 00:00:00 2001 From: Wouter Geraedts Date: Sun, 13 Oct 2019 14:11:41 +0200 Subject: [PATCH 05/42] Fixed various tests --- src/path/path.rs | 31 ++++++++++++++++++++++++++----- src/path/pathbuf.rs | 17 ++++++++--------- 2 files changed, 34 insertions(+), 14 deletions(-) diff --git a/src/path/path.rs b/src/path/path.rs index e7353e9..196935a 100644 --- a/src/path/path.rs +++ b/src/path/path.rs @@ -6,6 +6,7 @@ use crate::{fs, io}; /// This struct is an async version of [`std::path::Path`]. /// /// [`std::path::Path`]: https://doc.rust-lang.org/std/path/struct.Path.html +#[derive(Debug, PartialEq)] pub struct Path { inner: std::path::Path, } @@ -28,10 +29,14 @@ impl 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().unwrap(), PathBuf::from("/foo/test/bar.rs")); + /// assert_eq!(path.canonicalize().await.unwrap(), PathBuf::from("/foo/test/bar.rs")); + /// # + /// # Ok(()) }) } /// ``` pub async fn canonicalize(&self) -> io::Result { fs::canonicalize(self).await @@ -86,8 +91,12 @@ impl Path { /// # 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(), false); + /// assert_eq!(Path::new("does_not_exist.txt").exists().await, false); + /// # + /// # Ok(()) }) } /// ``` /// /// # See Also @@ -155,14 +164,26 @@ impl<'a> Into<&'a std::path::Path> for &'a Path { } } +impl AsRef for Path { + fn as_ref(&self) -> &OsStr { + self.inner.as_ref() + } +} + impl AsRef for Path { fn as_ref(&self) -> &Path { self } } -impl std::fmt::Debug for Path { - fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - std::fmt::Debug::fmt(&self.inner, formatter) +impl AsRef for OsStr { + fn as_ref(&self) -> &Path { + Path::new(self) } } + +impl AsRef for str { + fn as_ref(&self) -> &Path { + Path::new(self) + } +} \ No newline at end of file diff --git a/src/path/pathbuf.rs b/src/path/pathbuf.rs index c3665e9..308200e 100644 --- a/src/path/pathbuf.rs +++ b/src/path/pathbuf.rs @@ -5,6 +5,7 @@ use crate::path::Path; /// This struct is an async version of [`std::path::PathBuf`]. /// /// [`std::path::Path`]: https://doc.rust-lang.org/std/path/struct.PathBuf.html +#[derive(Debug, PartialEq)] pub struct PathBuf { inner: std::path::PathBuf, } @@ -23,20 +24,18 @@ impl Into for PathBuf { impl From for PathBuf { fn from(path: OsString) -> PathBuf { - PathBuf { - inner: std::path::PathBuf::from(path), - } + std::path::PathBuf::from(path).into() } } -impl AsRef for PathBuf { - fn as_ref(&self) -> &Path { - Path::new(&self.inner) +impl From<&str> for PathBuf { + fn from(path: &str) -> PathBuf { + std::path::PathBuf::from(path).into() } } -impl std::fmt::Debug for PathBuf { - fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - std::fmt::Debug::fmt(&self.inner, formatter) +impl AsRef for PathBuf { + fn as_ref(&self) -> &Path { + Path::new(&self.inner) } } From 6c6106a292556cd34fcce346d4e2237dbf897aa6 Mon Sep 17 00:00:00 2001 From: Wouter Geraedts Date: Sun, 13 Oct 2019 14:17:33 +0200 Subject: [PATCH 06/42] Implemented Path::{metadata, symlink_metadata} --- src/fs/symlink_metadata.rs | 5 ++-- src/path/path.rs | 51 +++++++++++++++++++++++++++++++++++++- 2 files changed, 52 insertions(+), 4 deletions(-) diff --git a/src/fs/symlink_metadata.rs b/src/fs/symlink_metadata.rs index 6f1b9d5..7ccc596 100644 --- a/src/fs/symlink_metadata.rs +++ b/src/fs/symlink_metadata.rs @@ -1,7 +1,6 @@ -use std::path::Path; - use crate::fs::Metadata; use crate::io; +use crate::path::Path; use crate::task::blocking; /// Reads metadata for a path without following symbolic links. @@ -34,6 +33,6 @@ use crate::task::blocking; /// # Ok(()) }) } /// ``` pub async fn symlink_metadata>(path: P) -> io::Result { - let path = path.as_ref().to_owned(); + let path: std::path::PathBuf = path.as_ref().to_path_buf().into(); blocking::spawn(async move { std::fs::symlink_metadata(path) }).await } diff --git a/src/path/path.rs b/src/path/path.rs index 196935a..ffe80d9 100644 --- a/src/path/path.rs +++ b/src/path/path.rs @@ -109,6 +109,55 @@ impl Path { fs::metadata(self).await.is_ok() } + /// Queries the file system to get information about a file, directory, etc. + /// + /// This function will traverse symbolic links to query information about the + /// destination file. + /// + /// This is an alias to [`fs::metadata`]. + /// + /// [`fs::metadata`]: ../fs/fn.metadata.html + /// + /// # Examples + /// + /// ```no_run + /// # 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.metadata().await.expect("metadata call failed"); + /// println!("{:?}", metadata.file_type()); + /// # + /// # Ok(()) }) } + /// ``` + pub async fn metadata(&self) -> io::Result { + fs::metadata(self).await + } + + /// Queries the metadata about a file without following symlinks. + /// + /// 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; + /// + /// 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 + } + /// Directly wraps a string slice as a `Path` slice. /// /// This is a cost-free conversion. @@ -186,4 +235,4 @@ impl AsRef for str { fn as_ref(&self) -> &Path { Path::new(self) } -} \ No newline at end of file +} From a57ba7ece0f99936e011774833470963604b093b Mon Sep 17 00:00:00 2001 From: Wouter Geraedts Date: Sun, 13 Oct 2019 18:49:52 +0200 Subject: [PATCH 07/42] Implemented Path::into_path_buf --- src/path/path.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/path/path.rs b/src/path/path.rs index ffe80d9..32e59b2 100644 --- a/src/path/path.rs +++ b/src/path/path.rs @@ -109,6 +109,17 @@ impl Path { fs::metadata(self).await.is_ok() } + /// 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 + 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() + } + /// Queries the file system to get information about a file, directory, etc. /// /// This function will traverse symbolic links to query information about the From 759e357bea1a90dc662da0c02062c7bbd382c4b3 Mon Sep 17 00:00:00 2001 From: Wouter Geraedts Date: Sun, 13 Oct 2019 18:52:59 +0200 Subject: [PATCH 08/42] Implemented Path::ancestors --- src/path/path.rs | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/path/path.rs b/src/path/path.rs index 32e59b2..6e76028 100644 --- a/src/path/path.rs +++ b/src/path/path.rs @@ -1,6 +1,6 @@ use std::ffi::OsStr; -use crate::path::{Components, PathBuf}; +use crate::path::{Ancestors, Components, PathBuf}; use crate::{fs, io}; /// This struct is an async version of [`std::path::Path`]. @@ -12,6 +12,32 @@ pub struct Path { } impl Path { + /// 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<'_> { + self.inner.ancestors() + } + /// Yields the underlying [`OsStr`] slice. /// /// [`OsStr`]: https://doc.rust-lang.org/std/ffi/struct.OsStr.html From 5235cd58be2852bb496caee919ae6d203a0c6296 Mon Sep 17 00:00:00 2001 From: Wouter Geraedts Date: Sun, 13 Oct 2019 18:56:26 +0200 Subject: [PATCH 09/42] Implemented Path::display --- src/path/path.rs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/path/path.rs b/src/path/path.rs index 6e76028..dcda43a 100644 --- a/src/path/path.rs +++ b/src/path/path.rs @@ -1,6 +1,6 @@ use std::ffi::OsStr; -use crate::path::{Ancestors, Components, PathBuf}; +use crate::path::{Ancestors, Components, Display, PathBuf}; use crate::{fs, io}; /// This struct is an async version of [`std::path::Path`]. @@ -106,6 +106,24 @@ impl Path { self.inner.components() } + /// Returns an object that implements [`Display`] for safely printing paths + /// that may contain non-Unicode data. + /// + /// [`Display`]: https://doc.rust-lang.org/std/fmt/trait.Display.html + /// + /// # Examples + /// + /// ``` + /// use async_std::path::Path; + /// + /// let path = Path::new("/tmp/foo.rs"); + /// + /// println!("{}", path.display()); + /// ``` + pub fn display(&self) -> Display<'_> { + self.inner.display() + } + /// Returns `true` if the path points at an existing entity. /// /// This function will traverse symbolic links to query information about the From 40708334820cf9cfcaf164d3a14cc6746c070bab Mon Sep 17 00:00:00 2001 From: Wouter Geraedts Date: Sun, 13 Oct 2019 18:58:36 +0200 Subject: [PATCH 10/42] Implemented Path::ends_with --- src/path/path.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/path/path.rs b/src/path/path.rs index dcda43a..fffe4c1 100644 --- a/src/path/path.rs +++ b/src/path/path.rs @@ -124,6 +124,26 @@ impl Path { self.inner.display() } + /// Determines whether `child` is a suffix of `self`. + /// + /// 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 + where + P: std::convert::AsRef, + { + self.inner.ends_with(child) + } + /// Returns `true` if the path points at an existing entity. /// /// This function will traverse symbolic links to query information about the From a7eaae91ae2c192774f465c1e2e03b141987c08d Mon Sep 17 00:00:00 2001 From: Wouter Geraedts Date: Sun, 13 Oct 2019 19:00:34 +0200 Subject: [PATCH 11/42] Implemented Path::extension --- src/path/path.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/path/path.rs b/src/path/path.rs index fffe4c1..c1b902b 100644 --- a/src/path/path.rs +++ b/src/path/path.rs @@ -173,6 +173,31 @@ impl Path { fs::metadata(self).await.is_ok() } + /// Extracts the extension of [`self.file_name`], if possible. + /// + /// The extension is: + /// + /// * [`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 + /// + /// # Examples + /// + /// ``` + /// use async_std::path::Path; + /// + /// let path = Path::new("foo.rs"); + /// + /// assert_eq!("rs", path.extension().unwrap()); + /// ``` + pub fn extension(&self) -> Option<&OsStr> { + self.inner.extension() + } + /// Converts a [`Box`][`Box`] into a [`PathBuf`] without copying or /// allocating. /// From a6e1abecfceaf4a5ac26ae8a1f266217745f442e Mon Sep 17 00:00:00 2001 From: Wouter Geraedts Date: Sun, 13 Oct 2019 19:03:33 +0200 Subject: [PATCH 12/42] Implemented Path::file_name --- src/path/path.rs | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/path/path.rs b/src/path/path.rs index c1b902b..83af632 100644 --- a/src/path/path.rs +++ b/src/path/path.rs @@ -183,7 +183,7 @@ impl Path { /// * 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 + /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None /// /// # Examples /// @@ -198,6 +198,32 @@ impl Path { self.inner.extension() } + /// Returns the final component of the `Path`, if there is one. + /// + /// If the path is a normal file, this is the file name. If it's the path of a directory, this + /// is the directory name. + /// + /// Returns [`None`] if the path terminates in `..`. + /// + /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None + /// + /// # Examples + /// + /// ``` + /// use async_std::path::Path; + /// use std::ffi::OsStr; + /// + /// assert_eq!(Some(OsStr::new("bin")), Path::new("/usr/bin/").file_name()); + /// assert_eq!(Some(OsStr::new("foo.txt")), Path::new("tmp/foo.txt").file_name()); + /// assert_eq!(Some(OsStr::new("foo.txt")), Path::new("foo.txt/.").file_name()); + /// assert_eq!(Some(OsStr::new("foo.txt")), Path::new("foo.txt/.//").file_name()); + /// assert_eq!(None, Path::new("foo.txt/..").file_name()); + /// assert_eq!(None, Path::new("/").file_name()); + /// ``` + pub fn file_name(&self) -> Option<&OsStr> { + self.inner.file_name() + } + /// Converts a [`Box`][`Box`] into a [`PathBuf`] without copying or /// allocating. /// From 28e936f6fec1d1a43df00c6452a36b1512052d28 Mon Sep 17 00:00:00 2001 From: Wouter Geraedts Date: Sun, 13 Oct 2019 19:05:18 +0200 Subject: [PATCH 13/42] Implemented Path::file_stem --- src/path/path.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/path/path.rs b/src/path/path.rs index 83af632..2c0f718 100644 --- a/src/path/path.rs +++ b/src/path/path.rs @@ -224,6 +224,32 @@ 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 + /// + /// 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; + /// + /// let path = Path::new("foo.rs"); + /// + /// assert_eq!("foo", path.file_stem().unwrap()); + /// ``` + pub fn file_stem(&self) -> Option<&OsStr> { + self.inner.file_stem() + } + /// Converts a [`Box`][`Box`] into a [`PathBuf`] without copying or /// allocating. /// From 3a9597cd32a4e95b3a4cd2aa06bc241cc2f29e96 Mon Sep 17 00:00:00 2001 From: Wouter Geraedts Date: Sun, 13 Oct 2019 19:07:12 +0200 Subject: [PATCH 14/42] Implemented Path::has_root --- src/path/path.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/path/path.rs b/src/path/path.rs index 2c0f718..be34172 100644 --- a/src/path/path.rs +++ b/src/path/path.rs @@ -250,6 +250,26 @@ impl Path { self.inner.file_stem() } + /// Returns `true` if the `Path` has a root. + /// + /// * On Unix, a path has a root if it begins with `/`. + /// + /// * 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 + /// + /// ``` + /// use async_std::path::Path; + /// + /// assert!(Path::new("/etc/passwd").has_root()); + /// ``` + pub fn has_root(&self) -> bool { + self.inner.has_root() + } + /// Converts a [`Box`][`Box`] into a [`PathBuf`] without copying or /// allocating. /// From 20f58ea1c1427ca19fddcc8fa063f9663863c0e7 Mon Sep 17 00:00:00 2001 From: Wouter Geraedts Date: Sun, 13 Oct 2019 19:10:16 +0200 Subject: [PATCH 15/42] Implemented Path::is_absolute --- src/path/path.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/path/path.rs b/src/path/path.rs index be34172..2f7e1c6 100644 --- a/src/path/path.rs +++ b/src/path/path.rs @@ -281,6 +281,28 @@ impl Path { inner.into_path_buf().into() } + /// Returns `true` if the `Path` is absolute, i.e., if it is independent of + /// the current directory. + /// + /// * 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; + /// + /// assert!(!Path::new("foo.txt").is_absolute()); + /// ``` + /// + /// [`has_root`]: #method.has_root + pub fn is_absolute(&self) -> bool { + self.inner.is_absolute() + } + /// Queries the file system to get information about a file, directory, etc. /// /// This function will traverse symbolic links to query information about the From df9a01f534adef469a3ba809177d96f9256a8e9b Mon Sep 17 00:00:00 2001 From: Wouter Geraedts Date: Sun, 13 Oct 2019 19:12:14 +0200 Subject: [PATCH 16/42] Implemented Path::is_file --- src/path/path.rs | 70 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/src/path/path.rs b/src/path/path.rs index 2f7e1c6..7249f9d 100644 --- a/src/path/path.rs +++ b/src/path/path.rs @@ -303,6 +303,76 @@ impl Path { self.inner.is_absolute() } + /// Returns `true` if the path exists on disk and is pointing at a directory. + /// + /// This function will traverse symbolic links to query information about the + /// destination file. In case of broken symbolic links this will return `false`. + /// + /// If you cannot access the directory containing the file, e.g., because of a + /// permission error, this will return `false`. + /// + /// # 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(()) }) } + /// ``` + /// + /// # See Also + /// + /// 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. + /// + /// [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) + } + + /// Returns `true` if the path exists on disk and is pointing at a regular file. + /// + /// This function will traverse symbolic links to query information about the + /// destination file. In case of broken symbolic links this will return `false`. + /// + /// If you cannot access the directory containing the file, e.g., because of a + /// permission error, this will return `false`. + /// + /// # 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(()) }) } + /// ``` + /// + /// # See Also + /// + /// 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. + /// + /// [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) + } + /// Queries the file system to get information about a file, directory, etc. /// /// This function will traverse symbolic links to query information about the From 5d87006006953f74b2125c5fc75a551805ca2299 Mon Sep 17 00:00:00 2001 From: Wouter Geraedts Date: Sun, 13 Oct 2019 19:16:52 +0200 Subject: [PATCH 17/42] Implemented Path::is_relative --- src/path/path.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/path/path.rs b/src/path/path.rs index 7249f9d..5146bc3 100644 --- a/src/path/path.rs +++ b/src/path/path.rs @@ -373,6 +373,23 @@ impl Path { .unwrap_or(false) } + /// Returns `true` if the `Path` is relative, i.e., not absolute. + /// + /// See [`is_absolute`]'s documentation for more details. + /// + /// # Examples + /// + /// ``` + /// use async_std::path::Path; + /// + /// assert!(Path::new("foo.txt").is_relative()); + /// ``` + /// + /// [`is_absolute`]: #method.is_absolute + pub fn is_relative(&self) -> bool { + self.inner.is_relative() + } + /// Queries the file system to get information about a file, directory, etc. /// /// This function will traverse symbolic links to query information about the From 0c03b923736eab63bf0d019fa855e4081548fa3a Mon Sep 17 00:00:00 2001 From: Wouter Geraedts Date: Sun, 13 Oct 2019 19:31:17 +0200 Subject: [PATCH 18/42] Implemented Path::iter --- src/path/path.rs | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/path/path.rs b/src/path/path.rs index 5146bc3..038fd24 100644 --- a/src/path/path.rs +++ b/src/path/path.rs @@ -1,6 +1,6 @@ use std::ffi::OsStr; -use crate::path::{Ancestors, Components, Display, PathBuf}; +use crate::path::{Ancestors, Components, Display, Iter, PathBuf}; use crate::{fs, io}; /// This struct is an async version of [`std::path::Path`]. @@ -390,6 +390,31 @@ impl Path { self.inner.is_relative() } + /// Produces an iterator over the path's components viewed as [`OsStr`] + /// slices. + /// + /// For more information about the particulars of how the path is separated + /// into components, see [`components`]. + /// + /// [`components`]: #method.components + /// [`OsStr`]: https://doc.rust-lang.org/std/ffi/struct.OsStr.html + /// + /// # Examples + /// + /// ``` + /// use async_std::path::{self, Path}; + /// use std::ffi::OsStr; + /// + /// let mut it = Path::new("/tmp/foo.txt").iter(); + /// assert_eq!(it.next(), Some(OsStr::new(&path::MAIN_SEPARATOR.to_string()))); + /// assert_eq!(it.next(), Some(OsStr::new("tmp"))); + /// assert_eq!(it.next(), Some(OsStr::new("foo.txt"))); + /// assert_eq!(it.next(), None) + /// ``` + pub fn iter(&self) -> Iter<'_> { + self.inner.iter() + } + /// Queries the file system to get information about a file, directory, etc. /// /// This function will traverse symbolic links to query information about the From cc57db02a3a41480592a07b5f8544d7aa0c2c2d7 Mon Sep 17 00:00:00 2001 From: Wouter Geraedts Date: Sun, 13 Oct 2019 19:33:55 +0200 Subject: [PATCH 19/42] Implemented Path::join --- src/path/path.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/path/path.rs b/src/path/path.rs index 038fd24..2c4a97a 100644 --- a/src/path/path.rs +++ b/src/path/path.rs @@ -415,6 +415,27 @@ 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. + /// + /// [`PathBuf`]: struct.PathBuf.html + /// [`PathBuf::push`]: struct.PathBuf.html#method.push + /// + /// # 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 + where + P: std::convert::AsRef, + { + self.inner.join(path).into() + } + /// Queries the file system to get information about a file, directory, etc. /// /// This function will traverse symbolic links to query information about the From 141954d205c4a5e5da4c5fbf551e88ce7a7c2b53 Mon Sep 17 00:00:00 2001 From: Wouter Geraedts Date: Sun, 13 Oct 2019 19:38:33 +0200 Subject: [PATCH 20/42] Implemented Path::parent --- src/path/path.rs | 69 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 46 insertions(+), 23 deletions(-) diff --git a/src/path/path.rs b/src/path/path.rs index 2c4a97a..55a7922 100644 --- a/src/path/path.rs +++ b/src/path/path.rs @@ -462,29 +462,6 @@ impl Path { fs::metadata(self).await } - /// Queries the metadata about a file without following symlinks. - /// - /// 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; - /// - /// 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 - } - /// Directly wraps a string slice as a `Path` slice. /// /// This is a cost-free conversion. @@ -511,6 +488,52 @@ impl Path { unsafe { &*(std::path::Path::new(s) as *const std::path::Path as *const Path) } } + /// Returns the `Path` without its final component, if there is one. + /// + /// Returns [`None`] if the path terminates in a root or prefix. + /// + /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None + /// + /// # Examples + /// + /// ``` + /// 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); + /// ``` + pub fn parent(&self) -> Option<&Path> { + self.inner.parent().map(|p| p.into()) + } + + /// Queries the metadata about a file without following symlinks. + /// + /// 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; + /// + /// 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 + } + /// Converts a `Path` to an owned [`PathBuf`]. /// /// [`PathBuf`]: struct.PathBuf.html From 89f73d3edac31e2dc6284ff39bf7b4fa298c65c9 Mon Sep 17 00:00:00 2001 From: Wouter Geraedts Date: Sun, 13 Oct 2019 19:57:46 +0200 Subject: [PATCH 21/42] Implemented Path::read_dir --- src/fs/read_dir.rs | 4 ++-- src/path/path.rs | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/src/fs/read_dir.rs b/src/fs/read_dir.rs index 9b4269d..fe3ff2e 100644 --- a/src/fs/read_dir.rs +++ b/src/fs/read_dir.rs @@ -1,9 +1,9 @@ -use std::path::Path; use std::pin::Pin; use crate::fs::DirEntry; use crate::future::Future; use crate::io; +use crate::path::Path; use crate::stream::Stream; use crate::task::{blocking, Context, JoinHandle, Poll}; @@ -44,7 +44,7 @@ use crate::task::{blocking, Context, JoinHandle, Poll}; /// # Ok(()) }) } /// ``` pub async fn read_dir>(path: P) -> io::Result { - let path = path.as_ref().to_owned(); + let path: std::path::PathBuf = path.as_ref().to_path_buf().into(); blocking::spawn(async move { std::fs::read_dir(path) }) .await .map(ReadDir::new) diff --git a/src/path/path.rs b/src/path/path.rs index 55a7922..88d7ba8 100644 --- a/src/path/path.rs +++ b/src/path/path.rs @@ -511,6 +511,38 @@ impl Path { self.inner.parent().map(|p| p.into()) } + /// Returns an iterator over the entries within a directory. + /// + /// The iterator will yield instances of [`io::Result`]`<`[`DirEntry`]`>`. New + /// errors may be encountered after an iterator is initially constructed. + /// + /// This is an alias to [`fs::read_dir`]. + /// + /// [`io::Result`]: ../io/type.Result.html + /// [`DirEntry`]: ../fs/struct.DirEntry.html + /// [`fs::read_dir`]: ../fs/fn.read_dir.html + /// + /// # Examples + /// + /// ```no_run + /// # fn main() -> std::io::Result<()> { async_std::task::block_on(async { + /// # + /// use async_std::path::Path; + /// use async_std::fs; + /// + /// let path = Path::new("/laputa"); + /// let mut dir = fs::read_dir(&path).await.expect("read_dir call failed"); + /// while let Some(res) = dir.next().await { + /// let entry = res?; + /// println!("{}", entry.file_name().to_string_lossy()); + /// } + /// # + /// # Ok(()) }) } + /// ``` + pub async fn read_dir(&self) -> io::Result { + fs::read_dir(self).await + } + /// Queries the metadata about a file without following symlinks. /// /// This is an alias to [`fs::symlink_metadata`]. @@ -586,3 +618,9 @@ impl AsRef for str { Path::new(self) } } + +impl AsRef for String { + fn as_ref(&self) -> &Path { + Path::new(self) + } +} From d349333a4377b0cd71ed0c808a572d0f3a0814b5 Mon Sep 17 00:00:00 2001 From: Wouter Geraedts Date: Sun, 13 Oct 2019 20:12:57 +0200 Subject: [PATCH 22/42] Implemented Path::read_link --- src/fs/read_link.rs | 9 +++++---- src/path/path.rs | 23 +++++++++++++++++++++++ 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/fs/read_link.rs b/src/fs/read_link.rs index aede99b..b7ef69a 100644 --- a/src/fs/read_link.rs +++ b/src/fs/read_link.rs @@ -1,6 +1,5 @@ -use std::path::{Path, PathBuf}; - use crate::io; +use crate::path::{Path, PathBuf}; use crate::task::blocking; /// Reads a symbolic link and returns the path it points to. @@ -28,6 +27,8 @@ use crate::task::blocking; /// # Ok(()) }) } /// ``` pub async fn read_link>(path: P) -> io::Result { - let path = path.as_ref().to_owned(); - blocking::spawn(async move { std::fs::read_link(path) }).await + let path: std::path::PathBuf = path.as_ref().to_path_buf().into(); + Ok(blocking::spawn(async move { std::fs::read_link(path) }) + .await? + .into()) } diff --git a/src/path/path.rs b/src/path/path.rs index 88d7ba8..e159ca1 100644 --- a/src/path/path.rs +++ b/src/path/path.rs @@ -529,6 +529,7 @@ impl Path { /// # /// use async_std::path::Path; /// use async_std::fs; + /// use futures_util::stream::StreamExt; /// /// let path = Path::new("/laputa"); /// let mut dir = fs::read_dir(&path).await.expect("read_dir call failed"); @@ -543,6 +544,28 @@ impl Path { fs::read_dir(self).await } + /// Reads a symbolic link, returning the file that the link points to. + /// + /// This is an alias to [`fs::read_link`]. + /// + /// [`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("/laputa/sky_castle.rs"); + /// let path_link = path.read_link().await.expect("read_link call failed"); + /// # + /// # Ok(()) }) } + /// ``` + pub async fn read_link(&self) -> io::Result { + fs::read_link(self).await + } + /// Queries the metadata about a file without following symlinks. /// /// This is an alias to [`fs::symlink_metadata`]. From 942403c52ca16870f3ca11625f61f8c2473fd132 Mon Sep 17 00:00:00 2001 From: Wouter Geraedts Date: Sun, 13 Oct 2019 20:14:07 +0200 Subject: [PATCH 23/42] Implemented Path::starts_with --- src/path/path.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/path/path.rs b/src/path/path.rs index e159ca1..f4cae23 100644 --- a/src/path/path.rs +++ b/src/path/path.rs @@ -566,6 +566,31 @@ impl Path { 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) + } + /// Queries the metadata about a file without following symlinks. /// /// This is an alias to [`fs::symlink_metadata`]. From df53a07fc5492519eb02e0e379d80ee39dbbd7f4 Mon Sep 17 00:00:00 2001 From: Wouter Geraedts Date: Sun, 13 Oct 2019 20:17:56 +0200 Subject: [PATCH 24/42] Implemented Path::strip_prefix --- src/path/path.rs | 37 ++++++++++++++++++++++++++++++++++++- src/path/pathbuf.rs | 6 ++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/src/path/path.rs b/src/path/path.rs index f4cae23..c43ed04 100644 --- a/src/path/path.rs +++ b/src/path/path.rs @@ -1,6 +1,6 @@ use std::ffi::OsStr; -use crate::path::{Ancestors, Components, Display, Iter, PathBuf}; +use crate::path::{Ancestors, Components, Display, Iter, PathBuf, StripPrefixError}; use crate::{fs, io}; /// This struct is an async version of [`std::path::Path`]. @@ -591,6 +591,41 @@ impl Path { 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"); + /// + /// 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 strip_prefix

(&self, base: P) -> Result<&Path, StripPrefixError> + where + P: AsRef, + { + Ok(self.inner.strip_prefix(base)?.into()) + } + /// Queries the metadata about a file without following symlinks. /// /// This is an alias to [`fs::symlink_metadata`]. diff --git a/src/path/pathbuf.rs b/src/path/pathbuf.rs index 308200e..2f33328 100644 --- a/src/path/pathbuf.rs +++ b/src/path/pathbuf.rs @@ -39,3 +39,9 @@ impl AsRef for PathBuf { Path::new(&self.inner) } } + +impl AsRef for PathBuf { + fn as_ref(&self) -> &std::path::Path { + self.inner.as_ref() + } +} From ea43d7fd29f21cec0b2661c164a833a5aac14337 Mon Sep 17 00:00:00 2001 From: Wouter Geraedts Date: Sun, 13 Oct 2019 20:46:51 +0200 Subject: [PATCH 25/42] Implemented Path::to_str --- src/path/path.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/path/path.rs b/src/path/path.rs index c43ed04..4bf53c3 100644 --- a/src/path/path.rs +++ b/src/path/path.rs @@ -664,6 +664,26 @@ impl Path { 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 + /// + /// ``` + /// use async_std::path::Path; + /// + /// 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() + } } impl<'a> From<&'a std::path::Path> for &'a Path { From a17b017e01ec96adce54a34a5aee98997564be27 Mon Sep 17 00:00:00 2001 From: Wouter Geraedts Date: Sun, 13 Oct 2019 20:49:57 +0200 Subject: [PATCH 26/42] Implemented Path::to_string_lossy --- src/path/path.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/path/path.rs b/src/path/path.rs index 4bf53c3..a0ba8ef 100644 --- a/src/path/path.rs +++ b/src/path/path.rs @@ -684,6 +684,31 @@ impl Path { pub fn to_str(&self) -> Option<&str> { self.inner.to_str() } + + /// Converts a `Path` to a [`Cow`]. + /// + /// Any non-Unicode sequences are replaced with + /// [`U+FFFD REPLACEMENT CHARACTER`][U+FFFD]. + /// + /// [`Cow`]: https://doc.rust-lang.org/std/borrow/enum.Cow.html + /// [U+FFFD]: https://doc.rust-lang.org/std/char/constant.REPLACEMENT_CHARACTER.html + /// + /// # Examples + /// + /// Calling `to_string_lossy` on a `Path` with valid unicode: + /// + /// ``` + /// use async_std::path::Path; + /// + /// let path = Path::new("foo.txt"); + /// assert_eq!(path.to_string_lossy(), "foo.txt"); + /// ``` + /// + /// 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() + } } impl<'a> From<&'a std::path::Path> for &'a Path { From 3c24b1891b7895be2fa0714f5c92b210737fbe1d Mon Sep 17 00:00:00 2001 From: Wouter Geraedts Date: Sun, 13 Oct 2019 20:54:08 +0200 Subject: [PATCH 27/42] Implemented Path::with_extension --- src/path/path.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/path/path.rs b/src/path/path.rs index a0ba8ef..2179c00 100644 --- a/src/path/path.rs +++ b/src/path/path.rs @@ -709,6 +709,25 @@ impl Path { 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 + /// + /// ``` + /// 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() + } } impl<'a> From<&'a std::path::Path> for &'a Path { From 409a10a8b5eb2791fa855b3fc913ddabfb8595dd Mon Sep 17 00:00:00 2001 From: Wouter Geraedts Date: Sun, 13 Oct 2019 20:55:48 +0200 Subject: [PATCH 28/42] Implemented Path::with_file_name --- src/path/path.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/path/path.rs b/src/path/path.rs index 2179c00..21ab5e2 100644 --- a/src/path/path.rs +++ b/src/path/path.rs @@ -728,6 +728,28 @@ impl Path { pub fn with_extension>(&self, extension: S) -> PathBuf { self.inner.with_extension(extension).into() } + + /// Creates an owned [`PathBuf`] like `self` but with the given file name. + /// + /// See [`PathBuf::set_file_name`] for more details. + /// + /// [`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() + } } impl<'a> From<&'a std::path::Path> for &'a Path { From 1bd17f11f2809a1132fea2469af5b03437ecafe9 Mon Sep 17 00:00:00 2001 From: Wouter Geraedts Date: Sun, 13 Oct 2019 21:04:47 +0200 Subject: [PATCH 29/42] Implemented PathBuf::as_path --- src/path/pathbuf.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/path/pathbuf.rs b/src/path/pathbuf.rs index 2f33328..59f2746 100644 --- a/src/path/pathbuf.rs +++ b/src/path/pathbuf.rs @@ -10,6 +10,24 @@ pub struct PathBuf { inner: std::path::PathBuf, } +impl PathBuf { + /// Coerces to a [`Path`] slice. + /// + /// [`Path`]: struct.Path.html + /// + /// # Examples + /// + /// ``` + /// use async_std::path::{Path, PathBuf}; + /// + /// let p = PathBuf::from("/test"); + /// assert_eq!(Path::new("/test"), p.as_path()); + /// ``` + pub fn as_path(&self) -> &Path { + self.inner.as_path().into() + } +} + impl From for PathBuf { fn from(path: std::path::PathBuf) -> PathBuf { PathBuf { inner: path } From 80eaa285525c1526df71a1922f5b9f6244477ed5 Mon Sep 17 00:00:00 2001 From: Wouter Geraedts Date: Sun, 13 Oct 2019 21:11:39 +0200 Subject: [PATCH 30/42] Implemented PathBuf::into_boxed_path --- src/path/pathbuf.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/path/pathbuf.rs b/src/path/pathbuf.rs index 59f2746..c41de4a 100644 --- a/src/path/pathbuf.rs +++ b/src/path/pathbuf.rs @@ -26,6 +26,15 @@ impl PathBuf { pub fn as_path(&self) -> &Path { self.inner.as_path().into() } + + /// 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) } + } } impl From for PathBuf { From 47ef222dab80059e5d8054bfcb21e6ed69fb18fb Mon Sep 17 00:00:00 2001 From: Wouter Geraedts Date: Sun, 13 Oct 2019 21:13:01 +0200 Subject: [PATCH 31/42] Implemented PathBuf::into_os_string --- src/path/pathbuf.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/path/pathbuf.rs b/src/path/pathbuf.rs index c41de4a..5403967 100644 --- a/src/path/pathbuf.rs +++ b/src/path/pathbuf.rs @@ -35,6 +35,22 @@ impl PathBuf { 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. + /// + /// [`OsString`]: https://doc.rust-lang.org/std/ffi/struct.OsString.html + /// + /// # Examples + /// + /// ``` + /// use async_std::path::PathBuf; + /// + /// let p = PathBuf::from("/the/head"); + /// let os_str = p.into_os_string(); + /// ``` + pub fn into_os_string(self) -> OsString { + self.inner.into_os_string() + } } impl From for PathBuf { From 71125d5c3b280f9e1aad4b7e2fd527e6e58256e7 Mon Sep 17 00:00:00 2001 From: Wouter Geraedts Date: Sun, 13 Oct 2019 21:19:23 +0200 Subject: [PATCH 32/42] Implemented PathBuf::new --- src/path/pathbuf.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/path/pathbuf.rs b/src/path/pathbuf.rs index 5403967..2d6ba19 100644 --- a/src/path/pathbuf.rs +++ b/src/path/pathbuf.rs @@ -51,6 +51,19 @@ impl PathBuf { pub fn into_os_string(self) -> OsString { self.inner.into_os_string() } + + /// Allocates an empty `PathBuf`. + /// + /// # Examples + /// + /// ``` + /// use async_std::path::PathBuf; + /// + /// let path = PathBuf::new(); + /// ``` + pub fn new() -> PathBuf { + std::path::PathBuf::new().into() + } } impl From for PathBuf { From 07f9e485796c4fcc344d990554bde25751688398 Mon Sep 17 00:00:00 2001 From: Wouter Geraedts Date: Sun, 13 Oct 2019 21:20:43 +0200 Subject: [PATCH 33/42] Implemented PathBuf::pop --- src/path/pathbuf.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/path/pathbuf.rs b/src/path/pathbuf.rs index 2d6ba19..b3a8d0a 100644 --- a/src/path/pathbuf.rs +++ b/src/path/pathbuf.rs @@ -64,6 +64,30 @@ impl PathBuf { pub fn new() -> PathBuf { std::path::PathBuf::new().into() } + + /// Truncates `self` to [`self.parent`]. + /// + /// Returns `false` and does nothing if [`self.parent`] is [`None`]. + /// Otherwise, returns `true`. + /// + /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None + /// [`self.parent`]: struct.PathBuf.html#method.parent + /// + /// # Examples + /// + /// ``` + /// use async_std::path::{Path, PathBuf}; + /// + /// let mut p = PathBuf::from("/test/test.rs"); + /// + /// p.pop(); + /// assert_eq!(Path::new("/test"), p.as_ref()); + /// p.pop(); + /// assert_eq!(Path::new("/"), p.as_ref()); + /// ``` + pub fn pop(&mut self) -> bool { + self.inner.pop() + } } impl From for PathBuf { From cc417cc0015a99d390526722c2066c6f1fc18693 Mon Sep 17 00:00:00 2001 From: Wouter Geraedts Date: Sun, 13 Oct 2019 21:38:47 +0200 Subject: [PATCH 34/42] Implemented PathBuf::push --- src/path/pathbuf.rs | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/path/pathbuf.rs b/src/path/pathbuf.rs index b3a8d0a..9907cd3 100644 --- a/src/path/pathbuf.rs +++ b/src/path/pathbuf.rs @@ -88,6 +88,41 @@ impl PathBuf { pub fn pop(&mut self) -> bool { self.inner.pop() } + + /// Extends `self` with `path`. + /// + /// If `path` is absolute, it replaces the current path. + /// + /// 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 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")); + /// ``` + pub fn push>(&mut self, path: P) { + self.inner.push(path) + } } impl From for PathBuf { From 54c94b717c25a554f2f51f34da6df35b7aea23da Mon Sep 17 00:00:00 2001 From: Wouter Geraedts Date: Sun, 13 Oct 2019 21:41:16 +0200 Subject: [PATCH 35/42] Implemented PathBuf::set_extension --- src/path/pathbuf.rs | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/src/path/pathbuf.rs b/src/path/pathbuf.rs index 9907cd3..b4b781f 100644 --- a/src/path/pathbuf.rs +++ b/src/path/pathbuf.rs @@ -1,4 +1,4 @@ -use std::ffi::OsString; +use std::ffi::{OsStr, OsString}; use crate::path::Path; @@ -123,6 +123,35 @@ impl PathBuf { pub fn push>(&mut self, path: P) { self.inner.push(path) } + + /// Updates [`self.extension`] to `extension`. + /// + /// Returns `false` and does nothing if [`self.file_name`] is [`None`], + /// returns `true` and updates the extension otherwise. + /// + /// If [`self.extension`] is [`None`], the extension is added; otherwise + /// it is replaced. + /// + /// [`self.file_name`]: struct.PathBuf.html#method.file_name + /// [`self.extension`]: struct.PathBuf.html#method.extension + /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None + /// + /// # Examples + /// + /// ``` + /// use async_std::path::{Path, PathBuf}; + /// + /// let mut p = PathBuf::from("/feel/the"); + /// + /// p.set_extension("force"); + /// assert_eq!(Path::new("/feel/the.force"), p.as_path()); + /// + /// p.set_extension("dark_side"); + /// assert_eq!(Path::new("/feel/the.dark_side"), p.as_path()); + /// ``` + pub fn set_extension>(&mut self, extension: S) -> bool { + self.inner.set_extension(extension) + } } impl From for PathBuf { From 8df55dd015acc9baccd3141b47fda4e21f718b57 Mon Sep 17 00:00:00 2001 From: Wouter Geraedts Date: Sun, 13 Oct 2019 21:42:38 +0200 Subject: [PATCH 36/42] Implemented PathBuf::set_file_name --- src/path/pathbuf.rs | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/path/pathbuf.rs b/src/path/pathbuf.rs index b4b781f..928a65a 100644 --- a/src/path/pathbuf.rs +++ b/src/path/pathbuf.rs @@ -152,6 +152,44 @@ impl PathBuf { pub fn set_extension>(&mut self, extension: S) -> bool { 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.) + /// + /// [`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 + /// + /// ``` + /// 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")); + /// ``` + pub fn set_file_name>(&mut self, file_name: S) { + self.inner.set_file_name(file_name) + } +} + +impl std::ops::Deref for PathBuf { + type Target = Path; + + fn deref(&self) -> &Path { + self.as_ref() + } } impl From for PathBuf { From ba87048db559b3b892bf8ebc3901c65ddd352259 Mon Sep 17 00:00:00 2001 From: Wouter Geraedts Date: Mon, 14 Oct 2019 22:00:45 +0200 Subject: [PATCH 37/42] Implemented our own Path::ancestors iterator --- src/path/mod.rs | 4 ++-- src/path/path.rs | 41 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/src/path/mod.rs b/src/path/mod.rs index 35a6717..9b8cdc9 100644 --- a/src/path/mod.rs +++ b/src/path/mod.rs @@ -9,7 +9,7 @@ mod pathbuf; // Structs re-export #[doc(inline)] -pub use std::path::{Ancestors, Components, Display, Iter, PrefixComponent, StripPrefixError}; +pub use std::path::{Components, Display, Iter, PrefixComponent, StripPrefixError}; // Enums re-export #[doc(inline)] @@ -23,5 +23,5 @@ pub use std::path::MAIN_SEPARATOR; #[doc(inline)] pub use std::path::is_separator; -pub use path::Path; +pub use path::{Ancestors, Path}; pub use pathbuf::PathBuf; diff --git a/src/path/path.rs b/src/path/path.rs index 21ab5e2..e66c6c2 100644 --- a/src/path/path.rs +++ b/src/path/path.rs @@ -1,6 +1,7 @@ use std::ffi::OsStr; +use std::iter::FusedIterator; -use crate::path::{Ancestors, Components, Display, Iter, PathBuf, StripPrefixError}; +use crate::path::{Components, Display, Iter, PathBuf, StripPrefixError}; use crate::{fs, io}; /// This struct is an async version of [`std::path::Path`]. @@ -35,7 +36,7 @@ impl Path { /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html /// [`parent`]: struct.Path.html#method.parent pub fn ancestors(&self) -> Ancestors<'_> { - self.inner.ancestors() + Ancestors { next: Some(&self) } } /// Yields the underlying [`OsStr`] slice. @@ -752,6 +753,42 @@ impl 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> { + 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<'_> {} + 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()) From 0adcb50f584b1da7e1bd926ebaef0993392e6dd9 Mon Sep 17 00:00:00 2001 From: Stjepan Glavina Date: Tue, 15 Oct 2019 01:08:12 +0200 Subject: [PATCH 38/42] Add ToOwned and Borrow impls --- src/fs/canonicalize.rs | 2 +- src/fs/metadata.rs | 2 +- src/fs/read_dir.rs | 2 +- src/fs/read_link.rs | 2 +- src/fs/symlink_metadata.rs | 2 +- src/path/path.rs | 8 ++++++++ src/path/pathbuf.rs | 6 ++++++ 7 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/fs/canonicalize.rs b/src/fs/canonicalize.rs index 84278cb..a671aba 100644 --- a/src/fs/canonicalize.rs +++ b/src/fs/canonicalize.rs @@ -32,7 +32,7 @@ use crate::task::blocking; /// # Ok(()) }) } /// ``` pub async fn canonicalize>(path: P) -> io::Result { - let path: std::path::PathBuf = path.as_ref().to_path_buf().into(); + let path: PathBuf = path.as_ref().to_owned(); Ok(blocking::spawn(async move { std::fs::canonicalize(&path) }) .await? .into()) diff --git a/src/fs/metadata.rs b/src/fs/metadata.rs index ef99a56..8c92381 100644 --- a/src/fs/metadata.rs +++ b/src/fs/metadata.rs @@ -35,7 +35,7 @@ use crate::task::blocking; /// # Ok(()) }) } /// ``` pub async fn metadata>(path: P) -> io::Result { - let path: std::path::PathBuf = path.as_ref().to_path_buf().into(); + let path = path.as_ref().to_owned(); blocking::spawn(async move { std::fs::metadata(path) }).await } diff --git a/src/fs/read_dir.rs b/src/fs/read_dir.rs index fe3ff2e..fd1a21f 100644 --- a/src/fs/read_dir.rs +++ b/src/fs/read_dir.rs @@ -44,7 +44,7 @@ use crate::task::{blocking, Context, JoinHandle, Poll}; /// # Ok(()) }) } /// ``` pub async fn read_dir>(path: P) -> io::Result { - let path: std::path::PathBuf = path.as_ref().to_path_buf().into(); + let path = path.as_ref().to_owned(); blocking::spawn(async move { std::fs::read_dir(path) }) .await .map(ReadDir::new) diff --git a/src/fs/read_link.rs b/src/fs/read_link.rs index b7ef69a..37ef2c6 100644 --- a/src/fs/read_link.rs +++ b/src/fs/read_link.rs @@ -27,7 +27,7 @@ use crate::task::blocking; /// # Ok(()) }) } /// ``` pub async fn read_link>(path: P) -> io::Result { - let path: std::path::PathBuf = path.as_ref().to_path_buf().into(); + let path = path.as_ref().to_owned(); Ok(blocking::spawn(async move { std::fs::read_link(path) }) .await? .into()) diff --git a/src/fs/symlink_metadata.rs b/src/fs/symlink_metadata.rs index 7ccc596..e2bc12d 100644 --- a/src/fs/symlink_metadata.rs +++ b/src/fs/symlink_metadata.rs @@ -33,6 +33,6 @@ use crate::task::blocking; /// # Ok(()) }) } /// ``` pub async fn symlink_metadata>(path: P) -> io::Result { - let path: std::path::PathBuf = path.as_ref().to_path_buf().into(); + let path = path.as_ref().to_owned(); blocking::spawn(async move { std::fs::symlink_metadata(path) }).await } diff --git a/src/path/path.rs b/src/path/path.rs index e66c6c2..4cb4083 100644 --- a/src/path/path.rs +++ b/src/path/path.rs @@ -830,3 +830,11 @@ impl AsRef for String { Path::new(self) } } + +impl std::borrow::ToOwned for Path { + type Owned = PathBuf; + + fn to_owned(&self) -> PathBuf { + self.to_path_buf() + } +} diff --git a/src/path/pathbuf.rs b/src/path/pathbuf.rs index 928a65a..28062b2 100644 --- a/src/path/pathbuf.rs +++ b/src/path/pathbuf.rs @@ -192,6 +192,12 @@ impl std::ops::Deref for PathBuf { } } +impl std::borrow::Borrow for PathBuf { + fn borrow(&self) -> &Path { + &**self + } +} + impl From for PathBuf { fn from(path: std::path::PathBuf) -> PathBuf { PathBuf { inner: path } From f9cfee9e2cd1884f68cfa3ce2729c64981fb05a4 Mon Sep 17 00:00:00 2001 From: Stjepan Glavina Date: Tue, 15 Oct 2019 01:11:32 +0200 Subject: [PATCH 39/42] Formatting --- src/fs/canonicalize.rs | 9 +++------ src/fs/read_link.rs | 4 +--- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/fs/canonicalize.rs b/src/fs/canonicalize.rs index a671aba..a763e4e 100644 --- a/src/fs/canonicalize.rs +++ b/src/fs/canonicalize.rs @@ -1,6 +1,5 @@ -use crate::path::{Path, PathBuf}; - use crate::io; +use crate::path::{Path, PathBuf}; use crate::task::blocking; /// Returns the canonical form of a path. @@ -32,8 +31,6 @@ use crate::task::blocking; /// # Ok(()) }) } /// ``` pub async fn canonicalize>(path: P) -> io::Result { - let path: PathBuf = path.as_ref().to_owned(); - Ok(blocking::spawn(async move { std::fs::canonicalize(&path) }) - .await? - .into()) + let path = path.as_ref().to_owned(); + blocking::spawn(async move { std::fs::canonicalize(&path).map(Into::into) }).await } diff --git a/src/fs/read_link.rs b/src/fs/read_link.rs index 37ef2c6..53080dd 100644 --- a/src/fs/read_link.rs +++ b/src/fs/read_link.rs @@ -28,7 +28,5 @@ use crate::task::blocking; /// ``` pub async fn read_link>(path: P) -> io::Result { let path = path.as_ref().to_owned(); - Ok(blocking::spawn(async move { std::fs::read_link(path) }) - .await? - .into()) + blocking::spawn(async move { std::fs::read_link(path).map(Into::into) }).await } From 504f8cb13741a7949a5016ec0f46fb7dbcc93424 Mon Sep 17 00:00:00 2001 From: Stjepan Glavina Date: Tue, 15 Oct 2019 01:25:20 +0200 Subject: [PATCH 40/42] Use crate::path everywhere --- src/fs/copy.rs | 3 +-- src/fs/create_dir.rs | 3 +-- src/fs/create_dir_all.rs | 3 +-- src/fs/dir_builder.rs | 3 +-- src/fs/dir_entry.rs | 4 ++-- src/fs/file.rs | 2 +- src/fs/hard_link.rs | 3 +-- src/fs/open_options.rs | 3 +-- src/fs/read.rs | 3 +-- src/fs/read_to_string.rs | 3 +-- src/fs/remove_dir.rs | 3 +-- src/fs/remove_dir_all.rs | 3 +-- src/fs/remove_file.rs | 3 +-- src/fs/rename.rs | 3 +-- src/fs/set_permissions.rs | 3 +-- src/fs/write.rs | 3 +-- src/os/unix/fs.rs | 3 +-- src/os/unix/net/datagram.rs | 2 +- src/os/unix/net/listener.rs | 2 +- src/os/unix/net/mod.rs | 6 +++--- src/os/unix/net/stream.rs | 2 +- src/path/path.rs | 18 +++++++++++++++--- 22 files changed, 39 insertions(+), 42 deletions(-) diff --git a/src/fs/copy.rs b/src/fs/copy.rs index fc3fd3e..c0c6b9e 100644 --- a/src/fs/copy.rs +++ b/src/fs/copy.rs @@ -1,6 +1,5 @@ -use std::path::Path; - use crate::io; +use crate::path::Path; use crate::task::blocking; /// Copies the contents and permissions of a file to a new location. diff --git a/src/fs/create_dir.rs b/src/fs/create_dir.rs index aa2d572..99f4ac4 100644 --- a/src/fs/create_dir.rs +++ b/src/fs/create_dir.rs @@ -1,6 +1,5 @@ -use std::path::Path; - use crate::io; +use crate::path::Path; use crate::task::blocking; /// Creates a new directory. diff --git a/src/fs/create_dir_all.rs b/src/fs/create_dir_all.rs index 33176f7..0dc446e 100644 --- a/src/fs/create_dir_all.rs +++ b/src/fs/create_dir_all.rs @@ -1,6 +1,5 @@ -use std::path::Path; - use crate::io; +use crate::path::Path; use crate::task::blocking; /// Creates a new directory and all of its parents if they are missing. diff --git a/src/fs/dir_builder.rs b/src/fs/dir_builder.rs index 3064f7a..1fb0850 100644 --- a/src/fs/dir_builder.rs +++ b/src/fs/dir_builder.rs @@ -1,9 +1,8 @@ -use std::path::Path; - use cfg_if::cfg_if; use crate::future::Future; use crate::io; +use crate::path::Path; use crate::task::blocking; /// A builder for creating directories with configurable options. diff --git a/src/fs/dir_entry.rs b/src/fs/dir_entry.rs index 66b3cb7..3d42f83 100644 --- a/src/fs/dir_entry.rs +++ b/src/fs/dir_entry.rs @@ -1,12 +1,12 @@ use std::ffi::OsString; use std::fmt; -use std::path::PathBuf; use std::sync::Arc; use cfg_if::cfg_if; use crate::fs::{FileType, Metadata}; use crate::io; +use crate::path::PathBuf; use crate::task::blocking; /// An entry in a directory. @@ -50,7 +50,7 @@ impl DirEntry { /// # Ok(()) }) } /// ``` pub fn path(&self) -> PathBuf { - self.0.path() + self.0.path().into() } /// Reads the metadata for this entry. diff --git a/src/fs/file.rs b/src/fs/file.rs index bdf9347..b52bcd0 100644 --- a/src/fs/file.rs +++ b/src/fs/file.rs @@ -3,7 +3,6 @@ use std::cmp; use std::fmt; use std::io::{Read as _, Seek as _, Write as _}; use std::ops::{Deref, DerefMut}; -use std::path::Path; use std::pin::Pin; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex}; @@ -13,6 +12,7 @@ use cfg_if::cfg_if; use crate::fs::{Metadata, Permissions}; use crate::future; use crate::io::{self, Read, Seek, SeekFrom, Write}; +use crate::path::Path; use crate::prelude::*; use crate::task::{self, blocking, Context, Poll, Waker}; diff --git a/src/fs/hard_link.rs b/src/fs/hard_link.rs index 5f950cd..2ae2cad 100644 --- a/src/fs/hard_link.rs +++ b/src/fs/hard_link.rs @@ -1,6 +1,5 @@ -use std::path::Path; - use crate::io; +use crate::path::Path; use crate::task::blocking; /// Creates a hard link on the filesystem. diff --git a/src/fs/open_options.rs b/src/fs/open_options.rs index 252873c..d7e0454 100644 --- a/src/fs/open_options.rs +++ b/src/fs/open_options.rs @@ -1,10 +1,9 @@ -use std::path::Path; - use cfg_if::cfg_if; use crate::fs::File; use crate::future::Future; use crate::io; +use crate::path::Path; use crate::task::blocking; /// A builder for opening files with configurable options. diff --git a/src/fs/read.rs b/src/fs/read.rs index 6b3560d..b562ea3 100644 --- a/src/fs/read.rs +++ b/src/fs/read.rs @@ -1,6 +1,5 @@ -use std::path::Path; - use crate::io; +use crate::path::Path; use crate::task::blocking; /// Reads the entire contents of a file as raw bytes. diff --git a/src/fs/read_to_string.rs b/src/fs/read_to_string.rs index 345f76e..a4d175f 100644 --- a/src/fs/read_to_string.rs +++ b/src/fs/read_to_string.rs @@ -1,6 +1,5 @@ -use std::path::Path; - use crate::io; +use crate::path::Path; use crate::task::blocking; /// Reads the entire contents of a file as a string. diff --git a/src/fs/remove_dir.rs b/src/fs/remove_dir.rs index a176edc..f457124 100644 --- a/src/fs/remove_dir.rs +++ b/src/fs/remove_dir.rs @@ -1,6 +1,5 @@ -use std::path::Path; - use crate::io; +use crate::path::Path; use crate::task::blocking; /// Removes an empty directory. diff --git a/src/fs/remove_dir_all.rs b/src/fs/remove_dir_all.rs index 9db0c31..3b12d26 100644 --- a/src/fs/remove_dir_all.rs +++ b/src/fs/remove_dir_all.rs @@ -1,6 +1,5 @@ -use std::path::Path; - use crate::io; +use crate::path::Path; use crate::task::blocking; /// Removes a directory and all of its contents. diff --git a/src/fs/remove_file.rs b/src/fs/remove_file.rs index cc0eeb2..216209f 100644 --- a/src/fs/remove_file.rs +++ b/src/fs/remove_file.rs @@ -1,6 +1,5 @@ -use std::path::Path; - use crate::io; +use crate::path::Path; use crate::task::blocking; /// Removes a file. diff --git a/src/fs/rename.rs b/src/fs/rename.rs index 72cd227..f517a26 100644 --- a/src/fs/rename.rs +++ b/src/fs/rename.rs @@ -1,6 +1,5 @@ -use std::path::Path; - use crate::io; +use crate::path::Path; use crate::task::blocking; /// Renames a file or directory to a new location. diff --git a/src/fs/set_permissions.rs b/src/fs/set_permissions.rs index 6fa6306..68dd8d4 100644 --- a/src/fs/set_permissions.rs +++ b/src/fs/set_permissions.rs @@ -1,7 +1,6 @@ -use std::path::Path; - use crate::fs::Permissions; use crate::io; +use crate::path::Path; use crate::task::blocking; /// Changes the permissions of a file or directory. diff --git a/src/fs/write.rs b/src/fs/write.rs index b0d7abc..4db5221 100644 --- a/src/fs/write.rs +++ b/src/fs/write.rs @@ -1,6 +1,5 @@ -use std::path::Path; - use crate::io; +use crate::path::Path; use crate::task::blocking; /// Writes a slice of bytes as the new contents of a file. diff --git a/src/os/unix/fs.rs b/src/os/unix/fs.rs index be8932c..f00aaec 100644 --- a/src/os/unix/fs.rs +++ b/src/os/unix/fs.rs @@ -1,10 +1,9 @@ //! Unix-specific filesystem extensions. -use std::path::Path; - use cfg_if::cfg_if; use crate::io; +use crate::path::Path; use crate::task::blocking; /// Creates a new symbolic link on the filesystem. diff --git a/src/os/unix/net/datagram.rs b/src/os/unix/net/datagram.rs index 1f971f7..61adc09 100644 --- a/src/os/unix/net/datagram.rs +++ b/src/os/unix/net/datagram.rs @@ -2,7 +2,6 @@ use std::fmt; use std::net::Shutdown; -use std::path::Path; use mio_uds; @@ -11,6 +10,7 @@ use crate::future; use crate::io; use crate::net::driver::Watcher; use crate::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd}; +use crate::path::Path; use crate::task::blocking; /// A Unix datagram socket. diff --git a/src/os/unix/net/listener.rs b/src/os/unix/net/listener.rs index eba2fad..2d68a6b 100644 --- a/src/os/unix/net/listener.rs +++ b/src/os/unix/net/listener.rs @@ -1,7 +1,6 @@ //! Unix-specific networking extensions. use std::fmt; -use std::path::Path; use std::pin::Pin; use mio_uds; @@ -12,6 +11,7 @@ use crate::future::{self, Future}; use crate::io; use crate::net::driver::Watcher; use crate::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd}; +use crate::path::Path; use crate::stream::Stream; use crate::task::{blocking, Context, Poll}; diff --git a/src/os/unix/net/mod.rs b/src/os/unix/net/mod.rs index a719a48..2c79e8c 100644 --- a/src/os/unix/net/mod.rs +++ b/src/os/unix/net/mod.rs @@ -13,7 +13,8 @@ mod stream; cfg_if! { if #[cfg(feature = "docs")] { use std::fmt; - use std::path::Path; + + use crate::path::Path; /// An address associated with a Unix socket. /// @@ -65,9 +66,8 @@ cfg_if! { /// With a pathname: /// /// ```no_run - /// use std::path::Path; - /// /// use async_std::os::unix::net::UnixListener; + /// use async_std::path::Path; /// /// let socket = UnixListener::bind("/tmp/socket").await?; /// let addr = socket.local_addr()?; diff --git a/src/os/unix/net/stream.rs b/src/os/unix/net/stream.rs index ae30b5b..8245e63 100644 --- a/src/os/unix/net/stream.rs +++ b/src/os/unix/net/stream.rs @@ -3,7 +3,6 @@ use std::fmt; use std::io::{Read as _, Write as _}; use std::net::Shutdown; -use std::path::Path; use std::pin::Pin; use mio_uds; @@ -12,6 +11,7 @@ use super::SocketAddr; use crate::io::{self, Read, Write}; use crate::net::driver::Watcher; use crate::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd}; +use crate::path::Path; use crate::task::{blocking, Context, Poll}; /// A Unix stream socket. diff --git a/src/path/path.rs b/src/path/path.rs index 4cb4083..4bc0757 100644 --- a/src/path/path.rs +++ b/src/path/path.rs @@ -801,9 +801,15 @@ impl<'a> Into<&'a std::path::Path> for &'a Path { } } -impl AsRef for Path { - fn as_ref(&self) -> &OsStr { - self.inner.as_ref() +impl AsRef for Path { + fn as_ref(&self) -> &std::path::Path { + self.into() + } +} + +impl AsRef for std::path::Path { + fn as_ref(&self) -> &Path { + self.into() } } @@ -813,6 +819,12 @@ impl AsRef for Path { } } +impl AsRef for Path { + fn as_ref(&self) -> &OsStr { + self.inner.as_ref() + } +} + impl AsRef for OsStr { fn as_ref(&self) -> &Path { Path::new(self) From 5c1e0522b755ea61ccb703561b8b8e8c362551c0 Mon Sep 17 00:00:00 2001 From: Stjepan Glavina Date: Tue, 15 Oct 2019 01:33:36 +0200 Subject: [PATCH 41/42] Fix failing tests --- src/path/path.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/path/path.rs b/src/path/path.rs index 4bc0757..63c8659 100644 --- a/src/path/path.rs +++ b/src/path/path.rs @@ -843,6 +843,12 @@ impl AsRef for String { } } +impl AsRef for std::path::PathBuf { + fn as_ref(&self) -> &Path { + Path::new(self.into()) + } +} + impl std::borrow::ToOwned for Path { type Owned = PathBuf; From aa13ba758ba17577f347a6da721f261b4e854409 Mon Sep 17 00:00:00 2001 From: Stjepan Glavina Date: Tue, 15 Oct 2019 02:05:23 +0200 Subject: [PATCH 42/42] 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) } } }