diff --git a/src/fs/canonicalize.rs b/src/fs/canonicalize.rs index c484aeeb5..a763e4e44 100644 --- a/src/fs/canonicalize.rs +++ b/src/fs/canonicalize.rs @@ -1,6 +1,5 @@ -use std::path::{Path, PathBuf}; - use crate::io; +use crate::path::{Path, PathBuf}; use crate::task::blocking; /// Returns the canonical form of a path. @@ -33,5 +32,5 @@ use crate::task::blocking; /// ``` pub async fn canonicalize>(path: P) -> io::Result { let path = path.as_ref().to_owned(); - blocking::spawn(async move { std::fs::canonicalize(path) }).await + blocking::spawn(async move { std::fs::canonicalize(&path).map(Into::into) }).await } diff --git a/src/fs/copy.rs b/src/fs/copy.rs index fc3fd3e69..c0c6b9e93 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 aa2d5724f..99f4ac45d 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 33176f75d..0dc446e1a 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 3064f7a30..1fb085021 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 66b3cb7c9..3d42f83b1 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 bdf934748..b52bcd07d 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 5f950cde1..2ae2cad44 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/metadata.rs b/src/fs/metadata.rs index 2c9e41ecd..8c9238146 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. diff --git a/src/fs/open_options.rs b/src/fs/open_options.rs index 252873cd2..d7e0454f4 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 6b3560db7..b562ea3cf 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_dir.rs b/src/fs/read_dir.rs index 9b4269df2..fd1a21f47 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}; diff --git a/src/fs/read_link.rs b/src/fs/read_link.rs index aede99bc8..53080ddc4 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. @@ -29,5 +28,5 @@ use crate::task::blocking; /// ``` 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 + blocking::spawn(async move { std::fs::read_link(path).map(Into::into) }).await } diff --git a/src/fs/read_to_string.rs b/src/fs/read_to_string.rs index 345f76efa..a4d175fa7 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 a176edc85..f45712467 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 9db0c31f5..3b12d2642 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 cc0eeb259..216209fec 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 72cd227f9..f517a266a 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 6fa6306fc..68dd8d404 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/symlink_metadata.rs b/src/fs/symlink_metadata.rs index 6f1b9d501..e2bc12dd6 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. diff --git a/src/fs/write.rs b/src/fs/write.rs index b0d7abcc2..4db522120 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/lib.rs b/src/lib.rs index fa4e946f9..83453cd6b 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/os/unix/fs.rs b/src/os/unix/fs.rs index be8932c06..f00aaec66 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 1f971f7f5..61adc09eb 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 eba2fadc3..2d68a6b9e 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 a719a4845..2c79e8c3e 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 ae30b5bf9..8245e638d 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/ancestors.rs b/src/path/ancestors.rs new file mode 100644 index 000000000..c7237ffd8 --- /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 915324391..36b9f2f08 100644 --- a/src/path/mod.rs +++ b/src/path/mod.rs @@ -4,9 +4,13 @@ //! //! [`std::path`]: https://doc.rust-lang.org/std/path/index.html +mod ancestors; +mod path; +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)] @@ -19,3 +23,7 @@ pub use std::path::MAIN_SEPARATOR; // Functions re-export #[doc(inline)] pub use std::path::is_separator; + +use ancestors::Ancestors; +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 000000000..aa15ab225 --- /dev/null +++ b/src/path/path.rs @@ -0,0 +1,812 @@ +use std::ffi::OsStr; + +use crate::path::{Ancestors, Components, Display, Iter, PathBuf, StripPrefixError}; +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, +} + +impl Path { + /// Directly wraps a string slice as a `Path` slice. + /// + /// This is a cost-free conversion. + /// + /// # Examples + /// + /// ``` + /// use async_std::path::Path; + /// + /// Path::new("foo.txt"); + /// ``` + /// + /// 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. + /// + /// [`OsStr`]: https://doc.rust-lang.org/std/ffi/struct.OsStr.html + pub fn as_os_str(&self) -> &OsStr { + self.inner.as_os_str() + } + + /// 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() + } + + /// 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() + } + + /// 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()) + } + + /// 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() + } + + /// 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() + } + + /// 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() + } + + /// 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()) + } + + /// 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. + /// + /// 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() + } + + /// 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.as_ref())?.into()) + } + + /// 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 { + self.inner.starts_with(base.as_ref()) + } + + /// 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 { + self.inner.ends_with(child.as_ref()) + } + + /// 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() + } + + /// 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#variant.None + /// + /// # 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() + } + + /// 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 { + self.inner.join(path.as_ref()).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() + } + + /// 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() + } + + /// 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<'_> { + self.inner.components() + } + + /// 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() + } + + /// 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() + } + + /// 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 + } + + /// 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 + /// # 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 async fn canonicalize(&self) -> io::Result { + fs::canonicalize(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 + } + + /// 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; + /// use futures_util::stream::StreamExt; + /// + /// 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 + } + + /// 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 + /// # 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(()) }) } + /// ``` + /// + /// # 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() + } + + /// 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) + } + + /// 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) + } + + /// 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() + } +} + +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) -> &std::path::Path { + self.into() + } +} + +impl AsRef for std::path::Path { + fn as_ref(&self) -> &Path { + self.into() + } +} + +impl AsRef for Path { + fn as_ref(&self) -> &Path { + self + } +} + +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) + } +} + +impl AsRef for str { + fn as_ref(&self) -> &Path { + Path::new(self) + } +} + +impl AsRef for String { + fn as_ref(&self) -> &Path { + Path::new(self) + } +} + +impl AsRef for std::path::PathBuf { + fn as_ref(&self) -> &Path { + Path::new(self.into()) + } +} + +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 new file mode 100644 index 000000000..64744e140 --- /dev/null +++ b/src/path/pathbuf.rs @@ -0,0 +1,235 @@ +use std::ffi::{OsStr, 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 +#[derive(Debug, PartialEq)] +pub struct PathBuf { + inner: std::path::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 + /// + /// # 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() + } + + /// 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.as_ref()) + } + + /// 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() + } + + /// 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) + } + + /// 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) + } + + /// 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() + } + + /// 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 std::ops::Deref for PathBuf { + type Target = Path; + + fn deref(&self) -> &Path { + self.as_ref() + } +} + +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 } + } +} + +impl Into for PathBuf { + fn into(self) -> std::path::PathBuf { + self.inner.into() + } +} + +impl From for PathBuf { + fn from(path: OsString) -> PathBuf { + std::path::PathBuf::from(path).into() + } +} + +impl From<&str> for PathBuf { + fn from(path: &str) -> PathBuf { + std::path::PathBuf::from(path).into() + } +} + +impl AsRef for PathBuf { + fn as_ref(&self) -> &Path { + Path::new(&self.inner) + } +} + +impl AsRef for PathBuf { + fn as_ref(&self) -> &std::path::Path { + self.inner.as_ref() + } +}