@@ -1412,6 +1412,99 @@ impl PathBuf {
1412
1412
}
1413
1413
}
1414
1414
1415
+ /// Sets whether the path has a trailing [separator](MAIN_SEPARATOR).
1416
+ ///
1417
+ /// The value returned by [`has_trailing_sep`](Path::has_trailing_sep) will be equivalent to
1418
+ /// the provided value if possible.
1419
+ ///
1420
+ /// # Examples
1421
+ ///
1422
+ /// ```
1423
+ /// #![feature(path_trailing_sep)]
1424
+ /// use std::path::PathBuf;
1425
+ ///
1426
+ /// let mut p = PathBuf::from("dir");
1427
+ ///
1428
+ /// assert!(!p.has_trailing_sep());
1429
+ /// p.set_trailing_sep(false);
1430
+ /// assert!(!p.has_trailing_sep());
1431
+ /// p.set_trailing_sep(true);
1432
+ /// assert!(p.has_trailing_sep());
1433
+ /// p.set_trailing_sep(false);
1434
+ /// assert!(!p.has_trailing_sep());
1435
+ ///
1436
+ /// p = PathBuf::from("/");
1437
+ /// assert!(p.has_trailing_sep());
1438
+ /// p.set_trailing_sep(false);
1439
+ /// assert!(p.has_trailing_sep());
1440
+ /// ```
1441
+ #[ unstable( feature = "path_trailing_sep" , issue = "142503" ) ]
1442
+ pub fn set_trailing_sep ( & mut self , trailing_sep : bool ) {
1443
+ if trailing_sep { self . push_trailing_sep ( ) } else { self . pop_trailing_sep ( ) }
1444
+ }
1445
+
1446
+ /// Adds a trailing [separator](MAIN_SEPARATOR) to the path.
1447
+ ///
1448
+ /// This acts similarly to [`Path::with_trailing_sep`], but mutates the underlying `PathBuf`.
1449
+ ///
1450
+ /// # Examples
1451
+ ///
1452
+ /// ```
1453
+ /// #![feature(path_trailing_sep)]
1454
+ /// use std::ffi::OsStr;
1455
+ /// use std::path::PathBuf;
1456
+ ///
1457
+ /// let mut p = PathBuf::from("dir");
1458
+ ///
1459
+ /// assert!(!p.has_trailing_sep());
1460
+ /// p.push_trailing_sep();
1461
+ /// assert!(p.has_trailing_sep());
1462
+ /// p.push_trailing_sep();
1463
+ /// assert!(p.has_trailing_sep());
1464
+ ///
1465
+ /// p = PathBuf::from("dir/");
1466
+ /// p.push_trailing_sep();
1467
+ /// assert_eq!(p.as_os_str(), OsStr::new("dir/"));
1468
+ /// ```
1469
+ #[ unstable( feature = "path_trailing_sep" , issue = "142503" ) ]
1470
+ pub fn push_trailing_sep ( & mut self ) {
1471
+ if !self . has_trailing_sep ( ) {
1472
+ self . push ( "" ) ;
1473
+ }
1474
+ }
1475
+
1476
+ /// Removes a trailing [separator](MAIN_SEPARATOR) from the path, if possible.
1477
+ ///
1478
+ /// This acts similarly to [`Path::trim_trailing_sep`], but mutates the underlying `PathBuf`.
1479
+ ///
1480
+ /// # Examples
1481
+ ///
1482
+ /// ```
1483
+ /// #![feature(path_trailing_sep)]
1484
+ /// use std::ffi::OsStr;
1485
+ /// use std::path::PathBuf;
1486
+ ///
1487
+ /// let mut p = PathBuf::from("dir//");
1488
+ ///
1489
+ /// assert!(p.has_trailing_sep());
1490
+ /// assert_eq!(p.as_os_str(), OsStr::new("dir//"));
1491
+ /// p.pop_trailing_sep();
1492
+ /// assert!(!p.has_trailing_sep());
1493
+ /// assert_eq!(p.as_os_str(), OsStr::new("dir"));
1494
+ /// p.pop_trailing_sep();
1495
+ /// assert!(!p.has_trailing_sep());
1496
+ /// assert_eq!(p.as_os_str(), OsStr::new("dir"));
1497
+ ///
1498
+ /// p = PathBuf::from("/");
1499
+ /// assert!(p.has_trailing_sep());
1500
+ /// p.pop_trailing_sep();
1501
+ /// assert!(p.has_trailing_sep());
1502
+ /// ```
1503
+ #[ unstable( feature = "path_trailing_sep" , issue = "142503" ) ]
1504
+ pub fn pop_trailing_sep ( & mut self ) {
1505
+ self . inner . truncate ( self . trim_trailing_sep ( ) . as_os_str ( ) . len ( ) ) ;
1506
+ }
1507
+
1415
1508
/// Updates [`self.file_name`] to `file_name`.
1416
1509
///
1417
1510
/// If [`self.file_name`] was [`None`], this is equivalent to pushing
@@ -1612,7 +1705,7 @@ impl PathBuf {
1612
1705
let new = extension. as_encoded_bytes ( ) ;
1613
1706
if !new. is_empty ( ) {
1614
1707
// truncate until right after the file name
1615
- // this is necessary for trimming the trailing slash
1708
+ // this is necessary for trimming the trailing separator
1616
1709
let end_file_name = file_name[ file_name. len ( ) ..] . as_ptr ( ) . addr ( ) ;
1617
1710
let start = self . inner . as_encoded_bytes ( ) . as_ptr ( ) . addr ( ) ;
1618
1711
self . inner . truncate ( end_file_name. wrapping_sub ( start) ) ;
@@ -2757,6 +2850,94 @@ impl Path {
2757
2850
self . file_name ( ) . map ( rsplit_file_at_dot) . and_then ( |( before, after) | before. and ( after) )
2758
2851
}
2759
2852
2853
+ /// Checks whether the path ends in a trailing [separator](MAIN_SEPARATOR).
2854
+ ///
2855
+ /// This is generally done to ensure that a path is treated as a directory, not a file,
2856
+ /// although it does not actually guarantee that such a path is a directory on the underlying
2857
+ /// file system.
2858
+ ///
2859
+ /// Despite this behavior, two paths are still considered the same in Rust whether they have a
2860
+ /// trailing separator or not.
2861
+ ///
2862
+ /// # Examples
2863
+ ///
2864
+ /// ```
2865
+ /// #![feature(path_trailing_sep)]
2866
+ /// use std::path::Path;
2867
+ ///
2868
+ /// assert!(Path::new("dir/").has_trailing_sep());
2869
+ /// assert!(!Path::new("file.rs").has_trailing_sep());
2870
+ /// ```
2871
+ #[ unstable( feature = "path_trailing_sep" , issue = "142503" ) ]
2872
+ #[ must_use]
2873
+ #[ inline]
2874
+ pub fn has_trailing_sep ( & self ) -> bool {
2875
+ self . as_os_str ( ) . as_encoded_bytes ( ) . last ( ) . copied ( ) . is_some_and ( is_sep_byte)
2876
+ }
2877
+
2878
+ /// Ensures that a path has a trailing [separator](MAIN_SEPARATOR),
2879
+ /// allocating a [`PathBuf`] if necessary.
2880
+ ///
2881
+ /// The resulting path will return true for [`has_trailing_sep`](Self::has_trailing_sep).
2882
+ ///
2883
+ /// # Examples
2884
+ ///
2885
+ /// ```
2886
+ /// #![feature(path_trailing_sep)]
2887
+ /// use std::ffi::OsStr;
2888
+ /// use std::path::Path;
2889
+ ///
2890
+ /// assert_eq!(Path::new("dir//").with_trailing_sep().as_os_str(), OsStr::new("dir//"));
2891
+ /// assert_eq!(Path::new("dir/").with_trailing_sep().as_os_str(), OsStr::new("dir/"));
2892
+ /// assert!(!Path::new("dir").has_trailing_sep());
2893
+ /// assert!(Path::new("dir").with_trailing_sep().has_trailing_sep());
2894
+ /// ```
2895
+ #[ unstable( feature = "path_trailing_sep" , issue = "142503" ) ]
2896
+ #[ must_use]
2897
+ #[ inline]
2898
+ pub fn with_trailing_sep ( & self ) -> Cow < ' _ , Path > {
2899
+ if self . has_trailing_sep ( ) { Cow :: Borrowed ( self ) } else { Cow :: Owned ( self . join ( "" ) ) }
2900
+ }
2901
+
2902
+ /// Trims a trailing [separator](MAIN_SEPARATOR) from a path, if possible.
2903
+ ///
2904
+ /// The resulting path will return false for [`has_trailing_sep`](Self::has_trailing_sep) for
2905
+ /// most paths.
2906
+ ///
2907
+ /// Some paths, like `/`, cannot be trimmed in this way.
2908
+ ///
2909
+ /// # Examples
2910
+ ///
2911
+ /// ```
2912
+ /// #![feature(path_trailing_sep)]
2913
+ /// use std::ffi::OsStr;
2914
+ /// use std::path::Path;
2915
+ ///
2916
+ /// assert_eq!(Path::new("dir//").trim_trailing_sep().as_os_str(), OsStr::new("dir"));
2917
+ /// assert_eq!(Path::new("dir/").trim_trailing_sep().as_os_str(), OsStr::new("dir"));
2918
+ /// assert_eq!(Path::new("dir").trim_trailing_sep().as_os_str(), OsStr::new("dir"));
2919
+ /// assert_eq!(Path::new("/").trim_trailing_sep().as_os_str(), OsStr::new("/"));
2920
+ /// assert_eq!(Path::new("//").trim_trailing_sep().as_os_str(), OsStr::new("//"));
2921
+ /// ```
2922
+ #[ unstable( feature = "path_trailing_sep" , issue = "142503" ) ]
2923
+ #[ must_use]
2924
+ #[ inline]
2925
+ pub fn trim_trailing_sep ( & self ) -> & Path {
2926
+ if self . has_trailing_sep ( ) && ( !self . has_root ( ) || self . parent ( ) . is_some ( ) ) {
2927
+ let mut bytes = self . inner . as_encoded_bytes ( ) ;
2928
+ while let Some ( ( last, init) ) = bytes. split_last ( )
2929
+ && is_sep_byte ( * last)
2930
+ {
2931
+ bytes = init;
2932
+ }
2933
+
2934
+ // SAFETY: Trimming trailing ASCII bytes will retain the validity of the string.
2935
+ Path :: new ( unsafe { OsStr :: from_encoded_bytes_unchecked ( bytes) } )
2936
+ } else {
2937
+ self
2938
+ }
2939
+ }
2940
+
2760
2941
/// Creates an owned [`PathBuf`] with `path` adjoined to `self`.
2761
2942
///
2762
2943
/// If `path` is absolute, it replaces the current path.
@@ -2911,7 +3092,7 @@ impl Path {
2911
3092
/// `a/b` all have `a` and `b` as components, but `./a/b` starts with
2912
3093
/// an additional [`CurDir`] component.
2913
3094
///
2914
- /// * A trailing slash is normalized away, `/a/b` and `/a/b/` are equivalent.
3095
+ /// * Trailing separators are normalized away, so `/a/b` and `/a/b/` are equivalent.
2915
3096
///
2916
3097
/// Note that no other normalization takes place; in particular, `a/c`
2917
3098
/// and `a/b/../c` are distinct, to account for the possibility that `b`
@@ -3714,7 +3895,7 @@ impl Error for NormalizeError {}
3714
3895
///
3715
3896
/// On POSIX platforms, the path is resolved using [POSIX semantics][posix-semantics],
3716
3897
/// except that it stops short of resolving symlinks. This means it will keep `..`
3717
- /// components and trailing slashes .
3898
+ /// components and trailing separators .
3718
3899
///
3719
3900
/// On Windows, for verbatim paths, this will simply return the path as given. For other
3720
3901
/// paths, this is currently equivalent to calling
0 commit comments