Skip to content

Commit 3e2206a

Browse files
committed
add file_prefix method
1 parent d29289c commit 3e2206a

File tree

2 files changed

+191
-36
lines changed

2 files changed

+191
-36
lines changed

library/std/src/path.rs

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)