@@ -315,7 +315,7 @@ fn has_physical_root(s: &[u8], prefix: Option<Prefix<'_>>) -> bool {
315
315
}
316
316
317
317
// basic workhorse for splitting stem and extension
318
- fn split_file_at_dot ( file : & OsStr ) -> ( Option < & OsStr > , Option < & OsStr > ) {
318
+ fn rsplit_file_at_dot ( file : & OsStr ) -> ( Option < & OsStr > , Option < & OsStr > ) {
319
319
if os_str_as_u8_slice ( file) == b".." {
320
320
return ( Some ( file) , None ) ;
321
321
}
@@ -334,6 +334,29 @@ fn split_file_at_dot(file: &OsStr) -> (Option<&OsStr>, Option<&OsStr>) {
334
334
}
335
335
}
336
336
337
+ fn split_file_at_dot ( file : & OsStr ) -> ( Option < & OsStr > , Option < & OsStr > ) {
338
+ let slice = os_str_as_u8_slice ( file) ;
339
+ if slice == b".." {
340
+ return ( Some ( file) , None ) ;
341
+ }
342
+
343
+ // The unsafety here stems from converting between &OsStr and &[u8]
344
+ // and back. This is safe to do because (1) we only look at ASCII
345
+ // contents of the encoding and (2) new &OsStr values are produced
346
+ // only from ASCII-bounded slices of existing &OsStr values.
347
+ let i = match slice[ 1 ..] . iter ( ) . position ( |b| * b == b'.' ) {
348
+ Some ( i) => i + 1 ,
349
+ None => slice. len ( ) ,
350
+ } ;
351
+ if i == slice. len ( ) {
352
+ ( Some ( file) , None )
353
+ } else {
354
+ let before = Some ( & slice[ ..i] ) ;
355
+ let after = Some ( & slice[ i + 1 ..] ) ;
356
+ unsafe { ( before. map ( |s| u8_slice_as_os_str ( s) ) , after. map ( |s| u8_slice_as_os_str ( s) ) ) }
357
+ }
358
+ }
359
+
337
360
////////////////////////////////////////////////////////////////////////////////
338
361
// The core iterators
339
362
////////////////////////////////////////////////////////////////////////////////
@@ -2158,6 +2181,32 @@ impl Path {
2158
2181
/// ```
2159
2182
#[ stable( feature = "rust1" , since = "1.0.0" ) ]
2160
2183
pub fn file_stem ( & self ) -> Option < & OsStr > {
2184
+ self . file_name ( ) . map ( rsplit_file_at_dot) . and_then ( |( before, after) | before. or ( after) )
2185
+ }
2186
+
2187
+ /// Extracts the prefix (non-extension(s)) portion of [`self.file_name`]. This is a "left"
2188
+ /// variant of `file_stem` - meaning it takes the portion of the file name before the *first* `.`
2189
+ ///
2190
+ /// [`self.file_name`]: Path::file_name
2191
+ ///
2192
+ /// The prefix is:
2193
+ ///
2194
+ /// * [`None`], if there is no file name;
2195
+ /// * The entire file name if there is no embedded `.`;
2196
+ /// * The entire file name if the file name begins with `.` and has no other `.`s within;
2197
+ /// * Otherwise, the portion of the file name before the first `.`
2198
+ ///
2199
+ /// # Examples
2200
+ ///
2201
+ /// ```
2202
+ /// # #![feature(path_file_prefix)]
2203
+ /// use std::path::Path;
2204
+ ///
2205
+ /// assert_eq!("foo", Path::new("foo.rs").file_prefix().unwrap());
2206
+ /// assert_eq!("foo", Path::new("foo.tar.gz").file_prefix().unwrap());
2207
+ /// ```
2208
+ #[ unstable( feature = "path_file_prefix" , issue = "none" ) ]
2209
+ pub fn file_prefix ( & self ) -> Option < & OsStr > {
2161
2210
self . file_name ( ) . map ( split_file_at_dot) . and_then ( |( before, after) | before. or ( after) )
2162
2211
}
2163
2212
@@ -2182,7 +2231,7 @@ impl Path {
2182
2231
/// ```
2183
2232
#[ stable( feature = "rust1" , since = "1.0.0" ) ]
2184
2233
pub fn extension ( & self ) -> Option < & OsStr > {
2185
- self . file_name ( ) . map ( split_file_at_dot ) . and_then ( |( before, after) | before. and ( after) )
2234
+ self . file_name ( ) . map ( rsplit_file_at_dot ) . and_then ( |( before, after) | before. and ( after) )
2186
2235
}
2187
2236
2188
2237
/// Creates an owned [`PathBuf`] with `path` adjoined to `self`.
0 commit comments