@@ -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