@@ -1403,6 +1403,85 @@ 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+ /// #![feature(path_trailing_sep)]
1415+ /// use std::path::{PathBuf, Path};
1416+ ///
1417+ /// let mut p = PathBuf::from("dir");
1418+ ///
1419+ /// assert!(!p.has_trailing_sep());
1420+ /// p.set_trailing_sep(false);
1421+ /// assert!(!p.has_trailing_sep());
1422+ /// p.set_trailing_sep(true);
1423+ /// assert!(p.has_trailing_sep());
1424+ /// assert_eq!(p.file_name(), None);
1425+ /// p.set_trailing_sep(false);
1426+ /// assert!(!p.has_trailing_sep());
1427+ /// assert_eq!(p.file_name(), Some("dir"));
1428+ /// assert_eq!(p, Path::new("dir"));
1429+ /// ```
1430+ #[ unstable( feature = "path_trailing_sep" , issue = "142503" ) ]
1431+ pub fn set_trailing_sep ( & mut self , trailing_sep : bool ) {
1432+ if trailing_sep { self . push_trailing_sep ( ) } else { self . pop_trailing_sep ( ) }
1433+ }
1434+
1435+ /// Adds a trailing [separator](MAIN_SEPARATOR) to the path.
1436+ ///
1437+ /// This acts similarly to [`Path::with_trailing_sep`], but mutates the underlying `PathBuf`.
1438+ ///
1439+ /// # Examples
1440+ ///
1441+ /// ```
1442+ /// #![feature(path_trailing_sep)]
1443+ /// use std::path::{Path, PathBuf};
1444+ ///
1445+ /// let mut p = PathBuf::from("dir");
1446+ ///
1447+ /// assert_eq!(p.file_name(), Some(Path::new("dir")));
1448+ /// p.push_trailing_sep();
1449+ /// assert_eq!(p.file_name(), None);
1450+ /// p.push_trailing_sep();
1451+ /// assert_eq!(p.file_name(), None);
1452+ /// assert_eq!(p, Path::new("dir/"));
1453+ /// ```
1454+ #[ unstable( feature = "path_trailing_sep" , issue = "142503" ) ]
1455+ pub fn push_trailing_sep ( & mut self ) {
1456+ if !self . has_trailing_sep ( ) {
1457+ self . push ( "" ) ;
1458+ }
1459+ }
1460+
1461+ /// Removes a trailing [separator](MAIN_SEPARATOR) from the path, if possible.
1462+ ///
1463+ /// This acts similarly to [`Path::trim_trailing_sep`], but mutates the underlying `PathBuf`.
1464+ ///
1465+ /// # Examples
1466+ ///
1467+ /// ```
1468+ /// #![feature(path_trailing_sep)]
1469+ /// use std::path::{Path, PathBuf};
1470+ ///
1471+ /// let mut p = PathBuf::from("dir/");
1472+ ///
1473+ /// assert_eq!(p.file_name(), None);
1474+ /// p.pop_trailing_sep();
1475+ /// assert_eq!(p.file_name(), Some("dir"));
1476+ /// p.pop_trailing_sep();
1477+ /// assert_eq!(p.file_name(), Some("dir"));
1478+ /// assert_eq!(p, Path::new("/dir"));
1479+ /// ```
1480+ #[ unstable( feature = "path_trailing_sep" , issue = "142503" ) ]
1481+ pub fn pop_trailing_sep ( & mut self ) {
1482+ self . inner . truncate ( self . trim_trailing_sep ( ) . as_os_str ( ) . len ( ) ) ;
1483+ }
1484+
14061485 /// Updates [`self.file_name`] to `file_name`.
14071486 ///
14081487 /// If [`self.file_name`] was [`None`], this is equivalent to pushing
@@ -2686,6 +2765,88 @@ impl Path {
26862765 self . file_name ( ) . map ( rsplit_file_at_dot) . and_then ( |( before, after) | before. and ( after) )
26872766 }
26882767
2768+ /// Checks whether the path ends in a trailing [separator](MAIN_SEPARATOR).
2769+ ///
2770+ /// This is generally done to ensure that a path is treated as a directory, not a file,
2771+ /// although it does not actually guarantee that such a path is a directory on the underlying
2772+ /// file system.
2773+ ///
2774+ /// Despite this behavior, two paths are still considered the same in Rust whether they have a
2775+ /// trailing separator or not.
2776+ ///
2777+ /// # Examples
2778+ ///
2779+ /// ```
2780+ /// #![feature(path_trailing_sep)]
2781+ /// use std::path::Path;
2782+ ///
2783+ /// assert!(Path::new("dir/").has_trailing_sep());
2784+ /// assert!(!Path::new("file.rs").has_trailing_sep());
2785+ /// ```
2786+ #[ unstable( feature = "path_trailing_sep" , issue = "142503" ) ]
2787+ #[ must_use]
2788+ #[ inline]
2789+ pub fn has_trailing_sep ( & self ) -> bool {
2790+ self . as_os_str ( ) . as_encoded_bytes ( ) . last ( ) . copied ( ) . is_some_and ( is_sep_byte)
2791+ }
2792+
2793+ /// Ensures that a path has a trailing [separator](MAIN_SEPARATOR),
2794+ /// allocating a [`PathBuf`] if necessary.
2795+ ///
2796+ /// The resulting path will return true for [`has_trailing_sep`](Self::has_trailing_sep) and
2797+ /// `None` for [`file_name`](Self::file_name).
2798+ ///
2799+ /// # Examples
2800+ ///
2801+ /// ```
2802+ /// #![feature(path_trailing_sep)]
2803+ /// use std::path::Path;
2804+ ///
2805+ /// assert_eq!(Path::new("dir//").with_trailing_sep().file_name(), None);
2806+ /// assert_eq!(Path::new("dir/").with_trailing_sep().file_name(), None);
2807+ /// assert_eq!(Path::new("dir").with_trailing_sep().file_name(), None);
2808+ /// assert_eq!(Path::new("dir").file_name(), Some("dir"));
2809+ /// ```
2810+ #[ unstable( feature = "path_trailing_sep" , issue = "142503" ) ]
2811+ #[ must_use]
2812+ #[ inline]
2813+ pub fn with_trailing_sep ( & self ) -> Cow < ' _ , Path > {
2814+ if self . has_trailing_sep ( ) { Cow :: Borrowed ( self ) } else { Cow :: Owned ( self . join ( "" ) ) }
2815+ }
2816+
2817+ /// Trims a trailing [separator](MAIN_SEPARATOR) from a path, if possible.
2818+ ///
2819+ /// The resulting path will return false for [`has_trailing_sep`](Self::has_trailing_sep).
2820+ ///
2821+ /// # Examples
2822+ ///
2823+ /// ```
2824+ /// #![feature(path_trailing_sep)]
2825+ /// use std::path::Path;
2826+ ///
2827+ /// assert_eq!(Path::new("dir//").trim_trailing_sep().file_name(), Some("dir"));
2828+ /// assert_eq!(Path::new("dir/").trim_trailing_sep().file_name(), Some("dir"));
2829+ /// assert_eq!(Path::new("dir").trim_trailing_sep().file_name(), Some("dir"));
2830+ /// ```
2831+ #[ unstable( feature = "path_trailing_sep" , issue = "142503" ) ]
2832+ #[ must_use]
2833+ #[ inline]
2834+ pub fn trim_trailing_sep ( & self ) -> & Path {
2835+ if self . has_trailing_sep ( ) && ( !self . has_root ( ) || self . parent ( ) . is_some ( ) ) {
2836+ let mut bytes = self . inner . as_encoded_bytes ( ) ;
2837+ while let Some ( ( last, init) ) = bytes. split_last ( )
2838+ && is_sep_byte ( * last)
2839+ {
2840+ bytes = init;
2841+ }
2842+
2843+ // SAFETY: Trimming trailing ASCII bytes will retain the validity of the string.
2844+ Path :: new ( unsafe { OsStr :: from_encoded_bytes_unchecked ( bytes) } )
2845+ } else {
2846+ self
2847+ }
2848+ }
2849+
26892850 /// Creates an owned [`PathBuf`] with `path` adjoined to `self`.
26902851 ///
26912852 /// If `path` is absolute, it replaces the current path.
0 commit comments