@@ -315,7 +315,7 @@ fn has_physical_root(s: &[u8], prefix: Option<Prefix<'_>>) -> bool {
315315}
316316
317317// 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 > ) {
319319 if os_str_as_u8_slice ( file) == b".." {
320320 return ( Some ( file) , None ) ;
321321 }
@@ -334,6 +334,29 @@ fn split_file_at_dot(file: &OsStr) -> (Option<&OsStr>, Option<&OsStr>) {
334334 }
335335}
336336
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+
337360////////////////////////////////////////////////////////////////////////////////
338361// The core iterators
339362////////////////////////////////////////////////////////////////////////////////
@@ -2158,6 +2181,32 @@ impl Path {
21582181 /// ```
21592182 #[ stable( feature = "rust1" , since = "1.0.0" ) ]
21602183 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 > {
21612210 self . file_name ( ) . map ( split_file_at_dot) . and_then ( |( before, after) | before. or ( after) )
21622211 }
21632212
@@ -2182,7 +2231,7 @@ impl Path {
21822231 /// ```
21832232 #[ stable( feature = "rust1" , since = "1.0.0" ) ]
21842233 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) )
21862235 }
21872236
21882237 /// Creates an owned [`PathBuf`] with `path` adjoined to `self`.
0 commit comments