Skip to content

Commit 2ad56d5

Browse files
committed
Auto merge of #85166 - mbhall88:file-prefix, r=dtolnay
add file_prefix method to std::path This is an initial implementation of `std::path::Path::file_prefix`. It is effectively a "left" variant of the existing [`file_stem`](https://doc.rust-lang.org/std/path/struct.Path.html#method.file_stem) method. An illustration of the difference is ```rust use std::path::Path; let path = Path::new("foo.tar.gz"); assert_eq!(path.file_stem(), Some("foo.tar")); assert_eq!(path.file_prefix(), Some("foo")); ``` In my own development, I generally find I almost always want the prefix, rather than the stem, so I thought it might be best to suggest it's addition to libstd. Of course, as this is my first contribution, I expect there is probably more work that needs to be done. Additionally, if the libstd team feel this isn't appropriate then so be it. There has been some [discussion about this on Zulip](https://rust-lang.zulipchat.com/#narrow/stream/219381-t-libs/topic/file_lstem/near/238076313) and a user there suggested I open a PR to see whether someone in the libstd team thinks it is worth pursuing.
2 parents 50731df + 51cf318 commit 2ad56d5

File tree

2 files changed

+372
-90
lines changed

2 files changed

+372
-90
lines changed

library/std/src/path.rs

+62-3
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,25 @@ fn split_file_at_dot(file: &OsStr) -> (Option<&OsStr>, Option<&OsStr>) {
334334
}
335335
}
336336

337+
fn split_file_at_dot(file: &OsStr) -> (&OsStr, Option<&OsStr>) {
338+
let slice = os_str_as_u8_slice(file);
339+
if slice == b".." {
340+
return (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 => return (file, None),
350+
};
351+
let before = &slice[..i];
352+
let after = &slice[i + 1..];
353+
unsafe { (u8_slice_as_os_str(before), Some(u8_slice_as_os_str(after))) }
354+
}
355+
337356
////////////////////////////////////////////////////////////////////////////////
338357
// The core iterators
339358
////////////////////////////////////////////////////////////////////////////////
@@ -2213,9 +2232,49 @@ impl Path {
22132232
/// assert_eq!("foo", Path::new("foo.rs").file_stem().unwrap());
22142233
/// assert_eq!("foo.tar", Path::new("foo.tar.gz").file_stem().unwrap());
22152234
/// ```
2235+
///
2236+
/// # See Also
2237+
/// This method is similar to [`Path::file_prefix`], which extracts the portion of the file name
2238+
/// before the *first* `.`
2239+
///
2240+
/// [`Path::file_prefix`]: Path::file_prefix
2241+
///
22162242
#[stable(feature = "rust1", since = "1.0.0")]
22172243
pub fn file_stem(&self) -> Option<&OsStr> {
2218-
self.file_name().map(split_file_at_dot).and_then(|(before, after)| before.or(after))
2244+
self.file_name().map(rsplit_file_at_dot).and_then(|(before, after)| before.or(after))
2245+
}
2246+
2247+
/// Extracts the prefix of [`self.file_name`].
2248+
///
2249+
/// The prefix is:
2250+
///
2251+
/// * [`None`], if there is no file name;
2252+
/// * The entire file name if there is no embedded `.`;
2253+
/// * The portion of the file name before the first non-beginning `.`;
2254+
/// * The entire file name if the file name begins with `.` and has no other `.`s within;
2255+
/// * The portion of the file name before the second `.` if the file name begins with `.`
2256+
///
2257+
/// [`self.file_name`]: Path::file_name
2258+
///
2259+
/// # Examples
2260+
///
2261+
/// ```
2262+
/// # #![feature(path_file_prefix)]
2263+
/// use std::path::Path;
2264+
///
2265+
/// assert_eq!("foo", Path::new("foo.rs").file_prefix().unwrap());
2266+
/// assert_eq!("foo", Path::new("foo.tar.gz").file_prefix().unwrap());
2267+
/// ```
2268+
///
2269+
/// # See Also
2270+
/// This method is similar to [`Path::file_stem`], which extracts the portion of the file name
2271+
/// before the *last* `.`
2272+
///
2273+
/// [`Path::file_stem`]: Path::file_stem
2274+
///
2275+
#[unstable(feature = "path_file_prefix", issue = "86319")]
2276+
pub fn file_prefix(&self) -> Option<&OsStr> {
2277+
self.file_name().map(split_file_at_dot).and_then(|(before, _after)| Some(before))
22192278
}
22202279

22212280
/// Extracts the extension of [`self.file_name`], if possible.
@@ -2239,7 +2298,7 @@ impl Path {
22392298
/// ```
22402299
#[stable(feature = "rust1", since = "1.0.0")]
22412300
pub fn extension(&self) -> Option<&OsStr> {
2242-
self.file_name().map(split_file_at_dot).and_then(|(before, after)| before.and(after))
2301+
self.file_name().map(rsplit_file_at_dot).and_then(|(before, after)| before.and(after))
22432302
}
22442303

22452304
/// Creates an owned [`PathBuf`] with `path` adjoined to `self`.

0 commit comments

Comments
 (0)