From d7d9481e6978ccc114a5d00d7c1aed4f0fd316bf Mon Sep 17 00:00:00 2001 From: rhysd Date: Sun, 3 Jul 2022 15:48:51 +0900 Subject: [PATCH 1/2] preserve trailing slash on Windows --- src/lib.rs | 31 +++++++++++++++++++++++++++++-- src/test.rs | 8 ++++---- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 09fd249..da73d98 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -65,6 +65,33 @@ use std::borrow::Cow; use std::ffi::OsStr; use std::path::{Path, PathBuf, MAIN_SEPARATOR}; +#[cfg(target_os = "windows")] +mod windows { + use super::*; + use std::io::{self, Write}; + + #[derive(Default)] + struct EndsWithMainSep(bool); + + impl io::Write for EndsWithMainSep { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.0 = buf.ends_with(&[MAIN_SEPARATOR as u8]); + Ok(buf.len()) + } + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } + } + + // Workaround for Windows. There is no way to extract raw byte sequence from `OsStr` (in `Path`). + // And `OsStr::to_string_lossy` may cause extra heap allocation. + pub fn ends_with_main_sep(p: &Path) -> bool { + let mut w = EndsWithMainSep::default(); + write!(&mut w, "{}", p.display()).unwrap(); + w.0 + } +} + fn str_to_path(s: &str, sep: char) -> Cow<'_, Path> { let mut buf = String::new(); @@ -155,7 +182,7 @@ impl PathExt for Path { buf.push('/'); } - if buf != "/" && buf.ends_with('/') { + if !windows::ends_with_main_sep(self) && buf != "/" && buf.ends_with('/') { buf.pop(); // Pop last '/' } @@ -204,7 +231,7 @@ impl PathExt for Path { buf.push('/'); } - if buf != "/" && buf.ends_with('/') { + if !windows::ends_with_main_sep(self) && buf != "/" && buf.ends_with('/') { buf.pop(); // Pop last '/' } diff --git a/src/test.rs b/src/test.rs index e2c3ad4..5b24f22 100644 --- a/src/test.rs +++ b/src/test.rs @@ -13,8 +13,8 @@ lazy_static! { ("//", "/"), ("foo", "foo"), ("/foo", "/foo"), - ("foo/", "foo"), - ("/foo/", "/foo"), + ("foo/", "foo/"), + ("/foo/", "/foo/"), ("./foo", "./foo"), ("../foo", "../foo"), ("foo/.", "foo/."), @@ -116,8 +116,8 @@ lazy_static! { "/", "foo", "/foo", - "foo", - "/foo", + "foo/", + "/foo/", "./foo", "../foo", "foo/..", From 91793901aa29f1614bbf503dc32b9828de88b34d Mon Sep 17 00:00:00 2001 From: rhysd Date: Sun, 3 Jul 2022 17:30:44 +0900 Subject: [PATCH 2/2] use `EncodeWide` to check the last character in path on Windows --- src/lib.rs | 21 +++------------------ 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index da73d98..ee79919 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -68,27 +68,12 @@ use std::path::{Path, PathBuf, MAIN_SEPARATOR}; #[cfg(target_os = "windows")] mod windows { use super::*; - use std::io::{self, Write}; - - #[derive(Default)] - struct EndsWithMainSep(bool); - - impl io::Write for EndsWithMainSep { - fn write(&mut self, buf: &[u8]) -> io::Result { - self.0 = buf.ends_with(&[MAIN_SEPARATOR as u8]); - Ok(buf.len()) - } - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } - } + use std::os::windows::ffi::OsStrExt; // Workaround for Windows. There is no way to extract raw byte sequence from `OsStr` (in `Path`). // And `OsStr::to_string_lossy` may cause extra heap allocation. - pub fn ends_with_main_sep(p: &Path) -> bool { - let mut w = EndsWithMainSep::default(); - write!(&mut w, "{}", p.display()).unwrap(); - w.0 + pub(crate) fn ends_with_main_sep(p: &Path) -> bool { + p.as_os_str().encode_wide().last() == Some(MAIN_SEPARATOR as u16) } }