@@ -1403,6 +1403,88 @@ impl PathBuf {
1403
1403
}
1404
1404
}
1405
1405
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::ffi::OsStr;
1416
+ /// use std::path::{PathBuf, Path};
1417
+ ///
1418
+ /// let mut p = PathBuf::from("dir");
1419
+ ///
1420
+ /// assert!(!p.has_trailing_sep());
1421
+ /// p.set_trailing_sep(false);
1422
+ /// assert!(!p.has_trailing_sep());
1423
+ /// p.set_trailing_sep(true);
1424
+ /// assert!(p.has_trailing_sep());
1425
+ /// assert_eq!(p.file_name(), None);
1426
+ /// p.set_trailing_sep(false);
1427
+ /// assert!(!p.has_trailing_sep());
1428
+ /// assert_eq!(p.file_name(), Some(OsStr::new("dir")));
1429
+ /// assert_eq!(p, Path::new("dir"));
1430
+ /// ```
1431
+ #[ unstable( feature = "path_trailing_sep" , issue = "142503" ) ]
1432
+ pub fn set_trailing_sep ( & mut self , trailing_sep : bool ) {
1433
+ if trailing_sep { self . push_trailing_sep ( ) } else { self . pop_trailing_sep ( ) }
1434
+ }
1435
+
1436
+ /// Adds a trailing [separator](MAIN_SEPARATOR) to the path.
1437
+ ///
1438
+ /// This acts similarly to [`Path::with_trailing_sep`], but mutates the underlying `PathBuf`.
1439
+ ///
1440
+ /// # Examples
1441
+ ///
1442
+ /// ```
1443
+ /// #![feature(path_trailing_sep)]
1444
+ /// use std::ffi::OsStr;
1445
+ /// use std::path::{Path, PathBuf};
1446
+ ///
1447
+ /// let mut p = PathBuf::from("dir");
1448
+ ///
1449
+ /// assert_eq!(p.file_name(), Some(OsStr::new("dir")));
1450
+ /// p.push_trailing_sep();
1451
+ /// assert_eq!(p.file_name(), None);
1452
+ /// p.push_trailing_sep();
1453
+ /// assert_eq!(p.file_name(), None);
1454
+ /// assert_eq!(p, Path::new("dir/"));
1455
+ /// ```
1456
+ #[ unstable( feature = "path_trailing_sep" , issue = "142503" ) ]
1457
+ pub fn push_trailing_sep ( & mut self ) {
1458
+ if !self . has_trailing_sep ( ) {
1459
+ self . push ( "" ) ;
1460
+ }
1461
+ }
1462
+
1463
+ /// Removes a trailing [separator](MAIN_SEPARATOR) from the path, if possible.
1464
+ ///
1465
+ /// This acts similarly to [`Path::trim_trailing_sep`], but mutates the underlying `PathBuf`.
1466
+ ///
1467
+ /// # Examples
1468
+ ///
1469
+ /// ```
1470
+ /// #![feature(path_trailing_sep)]
1471
+ /// use std::ffi::OsStr;
1472
+ /// use std::path::{Path, PathBuf};
1473
+ ///
1474
+ /// let mut p = PathBuf::from("dir/");
1475
+ ///
1476
+ /// assert_eq!(p.file_name(), None);
1477
+ /// p.pop_trailing_sep();
1478
+ /// assert_eq!(p.file_name(), Some(OsStr::new("dir")));
1479
+ /// p.pop_trailing_sep();
1480
+ /// assert_eq!(p.file_name(), Some(OsStr::new("dir")));
1481
+ /// assert_eq!(p, Path::new("/dir"));
1482
+ /// ```
1483
+ #[ unstable( feature = "path_trailing_sep" , issue = "142503" ) ]
1484
+ pub fn pop_trailing_sep ( & mut self ) {
1485
+ self . inner . truncate ( self . trim_trailing_sep ( ) . as_os_str ( ) . len ( ) ) ;
1486
+ }
1487
+
1406
1488
/// Updates [`self.file_name`] to `file_name`.
1407
1489
///
1408
1490
/// If [`self.file_name`] was [`None`], this is equivalent to pushing
@@ -1603,7 +1685,7 @@ impl PathBuf {
1603
1685
let new = extension. as_encoded_bytes ( ) ;
1604
1686
if !new. is_empty ( ) {
1605
1687
// truncate until right after the file name
1606
- // this is necessary for trimming the trailing slash
1688
+ // this is necessary for trimming the trailing separator
1607
1689
let end_file_name = file_name[ file_name. len ( ) ..] . as_ptr ( ) . addr ( ) ;
1608
1690
let start = self . inner . as_encoded_bytes ( ) . as_ptr ( ) . addr ( ) ;
1609
1691
self . inner . truncate ( end_file_name. wrapping_sub ( start) ) ;
@@ -2686,6 +2768,90 @@ impl Path {
2686
2768
self . file_name ( ) . map ( rsplit_file_at_dot) . and_then ( |( before, after) | before. and ( after) )
2687
2769
}
2688
2770
2771
+ /// Checks whether the path ends in a trailing [separator](MAIN_SEPARATOR).
2772
+ ///
2773
+ /// This is generally done to ensure that a path is treated as a directory, not a file,
2774
+ /// although it does not actually guarantee that such a path is a directory on the underlying
2775
+ /// file system.
2776
+ ///
2777
+ /// Despite this behavior, two paths are still considered the same in Rust whether they have a
2778
+ /// trailing separator or not.
2779
+ ///
2780
+ /// # Examples
2781
+ ///
2782
+ /// ```
2783
+ /// #![feature(path_trailing_sep)]
2784
+ /// use std::path::Path;
2785
+ ///
2786
+ /// assert!(Path::new("dir/").has_trailing_sep());
2787
+ /// assert!(!Path::new("file.rs").has_trailing_sep());
2788
+ /// ```
2789
+ #[ unstable( feature = "path_trailing_sep" , issue = "142503" ) ]
2790
+ #[ must_use]
2791
+ #[ inline]
2792
+ pub fn has_trailing_sep ( & self ) -> bool {
2793
+ self . as_os_str ( ) . as_encoded_bytes ( ) . last ( ) . copied ( ) . is_some_and ( is_sep_byte)
2794
+ }
2795
+
2796
+ /// Ensures that a path has a trailing [separator](MAIN_SEPARATOR),
2797
+ /// allocating a [`PathBuf`] if necessary.
2798
+ ///
2799
+ /// The resulting path will return true for [`has_trailing_sep`](Self::has_trailing_sep) and
2800
+ /// `None` for [`file_name`](Self::file_name).
2801
+ ///
2802
+ /// # Examples
2803
+ ///
2804
+ /// ```
2805
+ /// #![feature(path_trailing_sep)]
2806
+ /// use std::ffi::OsStr;
2807
+ /// use std::path::Path;
2808
+ ///
2809
+ /// assert_eq!(Path::new("dir//").with_trailing_sep().file_name(), None);
2810
+ /// assert_eq!(Path::new("dir/").with_trailing_sep().file_name(), None);
2811
+ /// assert_eq!(Path::new("dir").with_trailing_sep().file_name(), None);
2812
+ /// assert_eq!(Path::new("dir").file_name(), Some(OsStr::new("dir")));
2813
+ /// ```
2814
+ #[ unstable( feature = "path_trailing_sep" , issue = "142503" ) ]
2815
+ #[ must_use]
2816
+ #[ inline]
2817
+ pub fn with_trailing_sep ( & self ) -> Cow < ' _ , Path > {
2818
+ if self . has_trailing_sep ( ) { Cow :: Borrowed ( self ) } else { Cow :: Owned ( self . join ( "" ) ) }
2819
+ }
2820
+
2821
+ /// Trims a trailing [separator](MAIN_SEPARATOR) from a path, if possible.
2822
+ ///
2823
+ /// The resulting path will return false for [`has_trailing_sep`](Self::has_trailing_sep).
2824
+ ///
2825
+ /// # Examples
2826
+ ///
2827
+ /// ```
2828
+ /// #![feature(path_trailing_sep)]
2829
+ /// use std::ffi::OsStr;
2830
+ /// use std::path::Path;
2831
+ ///
2832
+ /// assert_eq!(Path::new("dir//").trim_trailing_sep().file_name(), Some(OsStr::new("dir")));
2833
+ /// assert_eq!(Path::new("dir/").trim_trailing_sep().file_name(), Some(OsStr::new("dir")));
2834
+ /// assert_eq!(Path::new("dir").trim_trailing_sep().file_name(), Some(OsStr::new("dir")));
2835
+ /// ```
2836
+ #[ unstable( feature = "path_trailing_sep" , issue = "142503" ) ]
2837
+ #[ must_use]
2838
+ #[ inline]
2839
+ pub fn trim_trailing_sep ( & self ) -> & Path {
2840
+ if self . has_trailing_sep ( ) && ( !self . has_root ( ) || self . parent ( ) . is_some ( ) ) {
2841
+ let mut bytes = self . inner . as_encoded_bytes ( ) ;
2842
+ while let Some ( ( last, init) ) = bytes. split_last ( )
2843
+ && is_sep_byte ( * last)
2844
+ {
2845
+ bytes = init;
2846
+ }
2847
+
2848
+ // SAFETY: Trimming trailing ASCII bytes will retain the validity of the string.
2849
+ Path :: new ( unsafe { OsStr :: from_encoded_bytes_unchecked ( bytes) } )
2850
+ } else {
2851
+ self
2852
+ }
2853
+ }
2854
+
2689
2855
/// Creates an owned [`PathBuf`] with `path` adjoined to `self`.
2690
2856
///
2691
2857
/// If `path` is absolute, it replaces the current path.
@@ -2840,7 +3006,7 @@ impl Path {
2840
3006
/// `a/b` all have `a` and `b` as components, but `./a/b` starts with
2841
3007
/// an additional [`CurDir`] component.
2842
3008
///
2843
- /// * A trailing slash is normalized away, `/a/b` and `/a/b/` are equivalent.
3009
+ /// * Trailing separators are normalized away, so `/a/b` and `/a/b/` are equivalent.
2844
3010
///
2845
3011
/// Note that no other normalization takes place; in particular, `a/c`
2846
3012
/// and `a/b/../c` are distinct, to account for the possibility that `b`
@@ -3610,7 +3776,7 @@ impl Error for NormalizeError {}
3610
3776
///
3611
3777
/// On POSIX platforms, the path is resolved using [POSIX semantics][posix-semantics],
3612
3778
/// except that it stops short of resolving symlinks. This means it will keep `..`
3613
- /// components and trailing slashes .
3779
+ /// components and trailing separators .
3614
3780
///
3615
3781
/// On Windows, for verbatim paths, this will simply return the path as given. For other
3616
3782
/// paths, this is currently equivalent to calling
0 commit comments