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.
This commit is contained in:
commit
2ad56d5c90
2 changed files with 372 additions and 90 deletions
|
@ -315,7 +315,7 @@ fn has_physical_root(s: &[u8], prefix: Option<Prefix<'_>>) -> bool {
|
|||
}
|
||||
|
||||
// basic workhorse for splitting stem and extension
|
||||
fn split_file_at_dot(file: &OsStr) -> (Option<&OsStr>, Option<&OsStr>) {
|
||||
fn rsplit_file_at_dot(file: &OsStr) -> (Option<&OsStr>, Option<&OsStr>) {
|
||||
if os_str_as_u8_slice(file) == b".." {
|
||||
return (Some(file), None);
|
||||
}
|
||||
|
@ -334,6 +334,25 @@ fn split_file_at_dot(file: &OsStr) -> (Option<&OsStr>, Option<&OsStr>) {
|
|||
}
|
||||
}
|
||||
|
||||
fn split_file_at_dot(file: &OsStr) -> (&OsStr, Option<&OsStr>) {
|
||||
let slice = os_str_as_u8_slice(file);
|
||||
if slice == b".." {
|
||||
return (file, None);
|
||||
}
|
||||
|
||||
// The unsafety here stems from converting between &OsStr and &[u8]
|
||||
// and back. This is safe to do because (1) we only look at ASCII
|
||||
// contents of the encoding and (2) new &OsStr values are produced
|
||||
// only from ASCII-bounded slices of existing &OsStr values.
|
||||
let i = match slice[1..].iter().position(|b| *b == b'.') {
|
||||
Some(i) => i + 1,
|
||||
None => return (file, None),
|
||||
};
|
||||
let before = &slice[..i];
|
||||
let after = &slice[i + 1..];
|
||||
unsafe { (u8_slice_as_os_str(before), Some(u8_slice_as_os_str(after))) }
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// The core iterators
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -2213,9 +2232,49 @@ impl Path {
|
|||
/// assert_eq!("foo", Path::new("foo.rs").file_stem().unwrap());
|
||||
/// assert_eq!("foo.tar", Path::new("foo.tar.gz").file_stem().unwrap());
|
||||
/// ```
|
||||
///
|
||||
/// # See Also
|
||||
/// This method is similar to [`Path::file_prefix`], which extracts the portion of the file name
|
||||
/// before the *first* `.`
|
||||
///
|
||||
/// [`Path::file_prefix`]: Path::file_prefix
|
||||
///
|
||||
#[stable(feature = "rust1", since = "1.0.0")]
|
||||
pub fn file_stem(&self) -> Option<&OsStr> {
|
||||
self.file_name().map(split_file_at_dot).and_then(|(before, after)| before.or(after))
|
||||
self.file_name().map(rsplit_file_at_dot).and_then(|(before, after)| before.or(after))
|
||||
}
|
||||
|
||||
/// Extracts the prefix of [`self.file_name`].
|
||||
///
|
||||
/// The prefix is:
|
||||
///
|
||||
/// * [`None`], if there is no file name;
|
||||
/// * The entire file name if there is no embedded `.`;
|
||||
/// * The portion of the file name before the first non-beginning `.`;
|
||||
/// * The entire file name if the file name begins with `.` and has no other `.`s within;
|
||||
/// * The portion of the file name before the second `.` if the file name begins with `.`
|
||||
///
|
||||
/// [`self.file_name`]: Path::file_name
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # #![feature(path_file_prefix)]
|
||||
/// use std::path::Path;
|
||||
///
|
||||
/// assert_eq!("foo", Path::new("foo.rs").file_prefix().unwrap());
|
||||
/// assert_eq!("foo", Path::new("foo.tar.gz").file_prefix().unwrap());
|
||||
/// ```
|
||||
///
|
||||
/// # See Also
|
||||
/// This method is similar to [`Path::file_stem`], which extracts the portion of the file name
|
||||
/// before the *last* `.`
|
||||
///
|
||||
/// [`Path::file_stem`]: Path::file_stem
|
||||
///
|
||||
#[unstable(feature = "path_file_prefix", issue = "86319")]
|
||||
pub fn file_prefix(&self) -> Option<&OsStr> {
|
||||
self.file_name().map(split_file_at_dot).and_then(|(before, _after)| Some(before))
|
||||
}
|
||||
|
||||
/// Extracts the extension of [`self.file_name`], if possible.
|
||||
|
@ -2239,7 +2298,7 @@ impl Path {
|
|||
/// ```
|
||||
#[stable(feature = "rust1", since = "1.0.0")]
|
||||
pub fn extension(&self) -> Option<&OsStr> {
|
||||
self.file_name().map(split_file_at_dot).and_then(|(before, after)| before.and(after))
|
||||
self.file_name().map(rsplit_file_at_dot).and_then(|(before, after)| before.and(after))
|
||||
}
|
||||
|
||||
/// Creates an owned [`PathBuf`] with `path` adjoined to `self`.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue