Skip to content

Commit 9d72ed0

Browse files
borsgitbot
authored and
gitbot
committed
Auto merge of rust-lang#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 915cc87 + dc1f620 commit 9d72ed0

File tree

2 files changed

+372
-90
lines changed

2 files changed

+372
-90
lines changed

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
////////////////////////////////////////////////////////////////////////////////
@@ -2180,9 +2199,49 @@ impl Path {
21802199
/// assert_eq!("foo", Path::new("foo.rs").file_stem().unwrap());
21812200
/// assert_eq!("foo.tar", Path::new("foo.tar.gz").file_stem().unwrap());
21822201
/// ```
2202+
///
2203+
/// # See Also
2204+
/// This method is similar to [`Path::file_prefix`], which extracts the portion of the file name
2205+
/// before the *first* `.`
2206+
///
2207+
/// [`Path::file_prefix`]: Path::file_prefix
2208+
///
21832209
#[stable(feature = "rust1", since = "1.0.0")]
21842210
pub fn file_stem(&self) -> Option<&OsStr> {
2185-
self.file_name().map(split_file_at_dot).and_then(|(before, after)| before.or(after))
2211+
self.file_name().map(rsplit_file_at_dot).and_then(|(before, after)| before.or(after))
2212+
}
2213+
2214+
/// Extracts the prefix of [`self.file_name`].
2215+
///
2216+
/// The prefix is:
2217+
///
2218+
/// * [`None`], if there is no file name;
2219+
/// * The entire file name if there is no embedded `.`;
2220+
/// * The portion of the file name before the first non-beginning `.`;
2221+
/// * The entire file name if the file name begins with `.` and has no other `.`s within;
2222+
/// * The portion of the file name before the second `.` if the file name begins with `.`
2223+
///
2224+
/// [`self.file_name`]: Path::file_name
2225+
///
2226+
/// # Examples
2227+
///
2228+
/// ```
2229+
/// # #![feature(path_file_prefix)]
2230+
/// use std::path::Path;
2231+
///
2232+
/// assert_eq!("foo", Path::new("foo.rs").file_prefix().unwrap());
2233+
/// assert_eq!("foo", Path::new("foo.tar.gz").file_prefix().unwrap());
2234+
/// ```
2235+
///
2236+
/// # See Also
2237+
/// This method is similar to [`Path::file_stem`], which extracts the portion of the file name
2238+
/// before the *last* `.`
2239+
///
2240+
/// [`Path::file_stem`]: Path::file_stem
2241+
///
2242+
#[unstable(feature = "path_file_prefix", issue = "86319")]
2243+
pub fn file_prefix(&self) -> Option<&OsStr> {
2244+
self.file_name().map(split_file_at_dot).and_then(|(before, _after)| Some(before))
21862245
}
21872246

21882247
/// Extracts the extension of [`self.file_name`], if possible.
@@ -2206,7 +2265,7 @@ impl Path {
22062265
/// ```
22072266
#[stable(feature = "rust1", since = "1.0.0")]
22082267
pub fn extension(&self) -> Option<&OsStr> {
2209-
self.file_name().map(split_file_at_dot).and_then(|(before, after)| before.and(after))
2268+
self.file_name().map(rsplit_file_at_dot).and_then(|(before, after)| before.and(after))
22102269
}
22112270

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

0 commit comments

Comments
 (0)