diff --git a/build.rs b/build.rs index 23e300d1c..ea64c1486 100644 --- a/build.rs +++ b/build.rs @@ -19,6 +19,7 @@ fn main() { println!("cargo:rustc-check-cfg=cfg(shrink_to)"); println!("cargo:rustc-check-cfg=cfg(try_reserve_2)"); println!("cargo:rustc-check-cfg=cfg(os_str_bytes)"); + println!("cargo:rustc-check-cfg=cfg(absolute_path)"); let compiler = match rustc_version() { Some(compiler) => compiler, @@ -50,11 +51,16 @@ fn main() { { println!("cargo:rustc-cfg=path_buf_deref_mut"); } - // os_str_bytes was added in a 1.74 stable. + // os_str_bytes was added in 1.74. if (compiler.minor >= 74 && compiler.channel == ReleaseChannel::Stable) || compiler.minor >= 75 { println!("cargo:rustc-cfg=os_str_bytes"); } + // absolute_path was added in 1.79. + if (compiler.minor >= 79 && compiler.channel == ReleaseChannel::Stable) || compiler.minor >= 80 + { + println!("cargo:rustc-cfg=absolute_path"); + } // Catch situations where the actual features aren't enabled. Currently, they're only shown with // `-vv` output, but maybe that will be noticed. diff --git a/src/lib.rs b/src/lib.rs index 7d510bdf7..2b71e18f0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3064,6 +3064,91 @@ impl_cmp_os_str!(&'a Utf8Path, Cow<'b, OsStr>); impl_cmp_os_str!(&'a Utf8Path, OsString); // NOTE: impls for Cow<'a, Utf8Path> cannot be defined because of the orphan rule (E0117) +/// Makes the path absolute without accessing the filesystem, converting it to a [`Utf8PathBuf`]. +/// +/// If the path is relative, the current directory is used as the base directory. All intermediate +/// components will be resolved according to platform-specific rules, but unlike +/// [`canonicalize`][Utf8Path::canonicalize] or [`canonicalize_utf8`](Utf8Path::canonicalize_utf8), +/// this does not resolve symlinks and may succeed even if the path does not exist. +/// +/// *Requires Rust 1.79 or newer.* +/// +/// # Errors +/// +/// Errors if: +/// +/// * The path is empty. +/// * The [current directory][std::env::current_dir] cannot be determined. +/// * The path is not valid UTF-8. +/// +/// # Examples +/// +/// ## POSIX paths +/// +/// ``` +/// # #[cfg(unix)] +/// fn main() -> std::io::Result<()> { +/// use camino::Utf8Path; +/// +/// // Relative to absolute +/// let absolute = camino::absolute_utf8("foo/./bar")?; +/// assert!(absolute.ends_with("foo/bar")); +/// +/// // Absolute to absolute +/// let absolute = camino::absolute_utf8("/foo//test/.././bar.rs")?; +/// assert_eq!(absolute, Utf8Path::new("/foo/test/../bar.rs")); +/// Ok(()) +/// } +/// # #[cfg(not(unix))] +/// # fn main() {} +/// ``` +/// +/// The path is resolved using [POSIX semantics][posix-semantics] except that it stops short of +/// resolving symlinks. This means it will keep `..` components and trailing slashes. +/// +/// ## Windows paths +/// +/// ``` +/// # #[cfg(windows)] +/// fn main() -> std::io::Result<()> { +/// use camino::Utf8Path; +/// +/// // Relative to absolute +/// let absolute = camino::absolute_utf8("foo/./bar")?; +/// assert!(absolute.ends_with(r"foo\bar")); +/// +/// // Absolute to absolute +/// let absolute = camino::absolute_utf8(r"C:\foo//test\..\./bar.rs")?; +/// +/// assert_eq!(absolute, Utf8Path::new(r"C:\foo\bar.rs")); +/// Ok(()) +/// } +/// # #[cfg(not(windows))] +/// # fn main() {} +/// ``` +/// +/// For verbatim paths this will simply return the path as given. For other paths this is currently +/// equivalent to calling [`GetFullPathNameW`][windows-path]. +/// +/// Note that this [may change in the future][changes]. +/// +/// [changes]: io#platform-specific-behavior +/// [posix-semantics]: +/// https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_13 +/// [windows-path]: +/// https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfullpathnamew +#[cfg(absolute_path)] +pub fn absolute_utf8>(path: P) -> io::Result { + // Note that even if the passed in path is valid UTF-8, it is not guaranteed that the absolute + // path will be valid UTF-8. For example, the current directory may not be valid UTF-8. + // + // That's why we take `AsRef` instead of `AsRef` here -- we have to pay the cost + // of checking for valid UTF-8 anyway. + let path = path.as_ref(); + #[allow(clippy::incompatible_msrv)] + Utf8PathBuf::try_from(std::path::absolute(path)?).map_err(|error| error.into_io_error()) +} + // invariant: OsStr must be guaranteed to be utf8 data #[inline] unsafe fn str_assume_utf8(string: &OsStr) -> &str {