Skip to content

Commit 39ebcb7

Browse files
committed
Add Path::has_trailing_sep and related methods
1 parent 9822e3d commit 39ebcb7

File tree

1 file changed

+153
-0
lines changed

1 file changed

+153
-0
lines changed

library/std/src/path.rs

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1403,6 +1403,80 @@ impl PathBuf {
14031403
}
14041404
}
14051405

1406+
/// Sets whether the path has a trailing [separator](MAIN_SEPARATOR).
1407+
///
1408+
/// The value returned by [`has_trailing_sep`](Self::has_trailing_sep) will be equivalent to
1409+
/// the provided value.
1410+
///
1411+
/// # Examples
1412+
///
1413+
/// ```
1414+
/// let mut p = PathBuf::from("dir");
1415+
///
1416+
/// assert!(!p.has_trailing_sep());
1417+
/// p.set_trailing_sep(false);
1418+
/// assert!(!p.has_trailing_sep());
1419+
/// p.set_trailing_sep(true);
1420+
/// assert!(p.has_trailing_sep());
1421+
/// assert_eq!(p.file_name(), None);
1422+
/// p.set_trailing_sep(false);
1423+
/// assert!(!p.has_trailing_sep());
1424+
/// assert_eq!(p.file_name(), Some("dir"));
1425+
/// assert_eq!(p, Path::new("dir"));
1426+
/// ```
1427+
#[unstable(feature = "path_trailing_sep", issue = "142503")]
1428+
pub fn set_trailing_sep(&mut self, trailing_sep: bool) {
1429+
if trailing_sep { self.push_trailing_sep() } else { self.pop_trailing_sep() }
1430+
}
1431+
1432+
/// Adds a trailing [separator](MAIN_SEPARATOR) to the path.
1433+
///
1434+
/// This acts similarly to [`Path::with_trailing_sep`], but mutates the underlying `PathBuf`.
1435+
///
1436+
/// # Examples
1437+
///
1438+
/// ```
1439+
/// use std::path::{Path, PathBuf};
1440+
///
1441+
/// let mut p = PathBuf::from("dir");
1442+
///
1443+
/// assert_eq!(p.file_name(), Some(Path::new("dir")));
1444+
/// p.push_trailing_sep();
1445+
/// assert_eq!(p.file_name(), None);
1446+
/// p.push_trailing_sep();
1447+
/// assert_eq!(p.file_name(), None);
1448+
/// assert_eq!(p, Path::new("dir/"));
1449+
/// ```
1450+
#[unstable(feature = "path_trailing_sep", issue = "142503")]
1451+
pub fn push_trailing_sep(&mut self) {
1452+
if !self.has_trailing_sep() {
1453+
self.push("");
1454+
}
1455+
}
1456+
1457+
/// Removes a trailing [separator](MAIN_SEPARATOR) from the path, if possible.
1458+
///
1459+
/// This acts similarly to [`Path::trim_trailing_sep`], but mutates the underlying `PathBuf`.
1460+
///
1461+
/// # Examples
1462+
///
1463+
/// ```
1464+
/// use std::path::{Path, PathBuf};
1465+
///
1466+
/// let mut p = PathBuf::from("dir/");
1467+
///
1468+
/// assert_eq!(p.file_name(), None);
1469+
/// p.pop_trailing_sep();
1470+
/// assert_eq!(p.file_name(), Some("dir"));
1471+
/// p.pop_trailing_sep();
1472+
/// assert_eq!(p.file_name(), Some("dir"));
1473+
/// assert_eq!(p, Path::new("/dir"));
1474+
/// ```
1475+
#[unstable(feature = "path_trailing_sep", issue = "142503")]
1476+
pub fn pop_trailing_sep(&mut self) {
1477+
self.inner.truncate(self.trim_trailing_sep().as_os_str().len());
1478+
}
1479+
14061480
/// Updates [`self.file_name`] to `file_name`.
14071481
///
14081482
/// If [`self.file_name`] was [`None`], this is equivalent to pushing
@@ -2686,6 +2760,85 @@ impl Path {
26862760
self.file_name().map(rsplit_file_at_dot).and_then(|(before, after)| before.and(after))
26872761
}
26882762

2763+
/// Checks whether the path ends in a trailing [separator](MAIN_SEPARATOR).
2764+
///
2765+
/// This is generally done to ensure that a path is treated as a directory, not a file,
2766+
/// although it does not actually guarantee that such a path is a directory on the underlying
2767+
/// file system.
2768+
///
2769+
/// Despite this behavior, two paths are still considered the same in Rust whether they have a
2770+
/// trailing separator or not.
2771+
///
2772+
/// # Examples
2773+
///
2774+
/// ```
2775+
/// use std::path::Path;
2776+
///
2777+
/// assert!(Path::new("dir/").has_trailing_sep());
2778+
/// assert!(!Path::new("file.rs").has_trailing_sep());
2779+
/// ```
2780+
#[unstable(feature = "path_trailing_sep", issue = "142503")]
2781+
#[must_use]
2782+
#[inline]
2783+
pub fn has_trailing_sep(&self) -> bool {
2784+
self.as_os_str().as_encoded_bytes().last().copied().is_some_and(is_sep_byte)
2785+
}
2786+
2787+
/// Ensures that a path has a trailing [separator](MAIN_SEPARATOR),
2788+
/// allocating a [`PathBuf`] if necessary.
2789+
///
2790+
/// The resulting path will return true for [`has_trailing_sep`](Self::has_trailing_sep) and
2791+
/// `None` for [`file_name`](Self::file_name).
2792+
///
2793+
/// # Examples
2794+
///
2795+
/// ```
2796+
/// use std::path::Path;
2797+
///
2798+
/// assert_eq!(Path::new("dir//").with_trailing_sep().file_name(), None);
2799+
/// assert_eq!(Path::new("dir/").with_trailing_sep().file_name(), None);
2800+
/// assert_eq!(Path::new("dir").with_trailing_sep().file_name(), None);
2801+
/// assert_eq!(Path::new("dir").file_name(), Some("dir"));
2802+
/// ```
2803+
#[unstable(feature = "path_trailing_sep", issue = "142503")]
2804+
#[must_use]
2805+
#[inline]
2806+
pub fn with_trailing_sep(&self) -> Cow<'_, Path> {
2807+
if self.has_trailing_sep() { Cow::Borrowed(self) } else { Cow::Owned(self.join("")) }
2808+
}
2809+
2810+
/// Trims a trailing [separator](MAIN_SEPARATOR) from a path, if possible.
2811+
///
2812+
/// The resulting path will return false for [`has_trailing_sep`](Self::has_trailing_sep).
2813+
///
2814+
/// # Examples
2815+
///
2816+
/// ```
2817+
/// use std::path::Path;
2818+
///
2819+
/// assert_eq!(Path::new("dir//").trim_trailing_sep().file_name(), Some("dir"));
2820+
/// assert_eq!(Path::new("dir/").trim_trailing_sep().file_name(), Some("dir"));
2821+
/// assert_eq!(Path::new("dir").trim_trailing_sep().file_name(), Some("dir"));
2822+
/// ```
2823+
#[unstable(feature = "path_trailing_sep", issue = "142503")]
2824+
#[must_use]
2825+
#[inline]
2826+
pub fn trim_trailing_sep(&self) -> &Path {
2827+
if self.has_trailing_sep() && (!self.has_root() || self.parent().is_some()) {
2828+
let mut bytes = self.inner.as_encoded_bytes();
2829+
while let Some((last, init)) = bytes.split_last()
2830+
&& is_sep_byte(*last)
2831+
{
2832+
bytes = init;
2833+
}
2834+
2835+
// SAFETY: Trimming trailing ASCII bytes will retain the validity of the string.
2836+
Path::new(unsafe { OsStr::from_encoded_bytes_unchecked(bytes) })
2837+
} else {
2838+
self
2839+
}
2840+
}
2841+
26892842
/// Creates an owned [`PathBuf`] with `path` adjoined to `self`.
26902843
///
26912844
/// If `path` is absolute, it replaces the current path.

0 commit comments

Comments
 (0)