diff options
| author | Michael Goulet <michael@errs.io> | 2024-07-05 20:49:31 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-07-05 20:49:31 -0400 |
| commit | f20307851ead9fbbb9fa88bbffb3258a069230a6 (patch) | |
| tree | 1f61a82dff562e7a1898b96c6e921a090e901157 /library/std/src | |
| parent | 524d806c62a82ecc0cf8634b94997ae506f4d6f9 (diff) | |
| parent | 3aa2abdd3db150394ce883dbefddf0f0d5f4f029 (diff) | |
| download | rust-f20307851ead9fbbb9fa88bbffb3258a069230a6.tar.gz rust-f20307851ead9fbbb9fa88bbffb3258a069230a6.zip | |
Rollup merge of #123600 - tisonkun:path_with_extension, r=dtolnay
impl PathBuf::add_extension and Path::with_added_extension See the ACP for motivation and discussions - https://github.com/rust-lang/libs-team/issues/368
Diffstat (limited to 'library/std/src')
| -rw-r--r-- | library/std/src/path.rs | 94 | ||||
| -rw-r--r-- | library/std/src/path/tests.rs | 74 |
2 files changed, 168 insertions, 0 deletions
diff --git a/library/std/src/path.rs b/library/std/src/path.rs index caae8f924d2..8d565e26a16 100644 --- a/library/std/src/path.rs +++ b/library/std/src/path.rs @@ -1519,6 +1519,74 @@ impl PathBuf { true } + /// Append [`self.extension`] with `extension`. + /// + /// Returns `false` and does nothing if [`self.file_name`] is [`None`], + /// returns `true` and updates the extension otherwise. + /// + /// # Caveats + /// + /// The appended `extension` may contain dots and will be used in its entirety, + /// but only the part after the final dot will be reflected in + /// [`self.extension`]. + /// + /// See the examples below. + /// + /// [`self.file_name`]: Path::file_name + /// [`self.extension`]: Path::extension + /// + /// # Examples + /// + /// ``` + /// #![feature(path_add_extension)] + /// + /// use std::path::{Path, PathBuf}; + /// + /// let mut p = PathBuf::from("/feel/the"); + /// + /// p.add_extension("formatted"); + /// assert_eq!(Path::new("/feel/the.formatted"), p.as_path()); + /// + /// p.add_extension("dark.side"); + /// assert_eq!(Path::new("/feel/the.formatted.dark.side"), p.as_path()); + /// + /// p.set_extension("cookie"); + /// assert_eq!(Path::new("/feel/the.formatted.dark.cookie"), p.as_path()); + /// + /// p.set_extension(""); + /// assert_eq!(Path::new("/feel/the.formatted.dark"), p.as_path()); + /// + /// p.add_extension(""); + /// assert_eq!(Path::new("/feel/the.formatted.dark"), p.as_path()); + /// ``` + #[unstable(feature = "path_add_extension", issue = "127292")] + pub fn add_extension<S: AsRef<OsStr>>(&mut self, extension: S) -> bool { + self._add_extension(extension.as_ref()) + } + + fn _add_extension(&mut self, extension: &OsStr) -> bool { + let file_name = match self.file_name() { + None => return false, + Some(f) => f.as_encoded_bytes(), + }; + + let new = extension; + if !new.is_empty() { + // truncate until right after the file name + // this is necessary for trimming the trailing slash + let end_file_name = file_name[file_name.len()..].as_ptr().addr(); + let start = self.inner.as_encoded_bytes().as_ptr().addr(); + self.inner.truncate(end_file_name.wrapping_sub(start)); + + // append the new extension + self.inner.reserve_exact(new.len() + 1); + self.inner.push(OsStr::new(".")); + self.inner.push(new); + } + + true + } + /// Yields a mutable reference to the underlying [`OsString`] instance. /// /// # Examples @@ -2656,6 +2724,32 @@ impl Path { new_path } + /// Creates an owned [`PathBuf`] like `self` but with the extension added. + /// + /// See [`PathBuf::add_extension`] for more details. + /// + /// # Examples + /// + /// ``` + /// #![feature(path_add_extension)] + /// + /// use std::path::{Path, PathBuf}; + /// + /// let path = Path::new("foo.rs"); + /// assert_eq!(path.with_added_extension("txt"), PathBuf::from("foo.rs.txt")); + /// + /// let path = Path::new("foo.tar.gz"); + /// assert_eq!(path.with_added_extension(""), PathBuf::from("foo.tar.gz")); + /// assert_eq!(path.with_added_extension("xz"), PathBuf::from("foo.tar.gz.xz")); + /// assert_eq!(path.with_added_extension("").with_added_extension("txt"), PathBuf::from("foo.tar.gz.txt")); + /// ``` + #[unstable(feature = "path_add_extension", issue = "127292")] + pub fn with_added_extension<S: AsRef<OsStr>>(&self, extension: S) -> PathBuf { + let mut new_path = self.to_path_buf(); + new_path.add_extension(extension); + new_path + } + /// Produces an iterator over the [`Component`]s of the path. /// /// When parsing the path, there is a small amount of normalization: diff --git a/library/std/src/path/tests.rs b/library/std/src/path/tests.rs index 92702b395df..bb6126e4941 100644 --- a/library/std/src/path/tests.rs +++ b/library/std/src/path/tests.rs @@ -1402,6 +1402,37 @@ pub fn test_set_extension() { } #[test] +pub fn test_add_extension() { + macro_rules! tfe ( + ($path:expr, $ext:expr, $expected:expr, $output:expr) => ({ + let mut p = PathBuf::from($path); + let output = p.add_extension($ext); + assert!(p.to_str() == Some($expected) && output == $output, + "adding extension of {:?} to {:?}: Expected {:?}/{:?}, got {:?}/{:?}", + $path, $ext, $expected, $output, + p.to_str().unwrap(), output); + }); + ); + + tfe!("foo", "txt", "foo.txt", true); + tfe!("foo.bar", "txt", "foo.bar.txt", true); + tfe!("foo.bar.baz", "txt", "foo.bar.baz.txt", true); + tfe!(".test", "txt", ".test.txt", true); + tfe!("foo.txt", "", "foo.txt", true); + tfe!("foo", "", "foo", true); + tfe!("", "foo", "", false); + tfe!(".", "foo", ".", false); + tfe!("foo/", "bar", "foo.bar", true); + tfe!("foo/.", "bar", "foo.bar", true); + tfe!("..", "foo", "..", false); + tfe!("foo/..", "bar", "foo/..", false); + tfe!("/", "foo", "/", false); + + // edge cases + tfe!("/foo.ext////", "bar", "/foo.ext.bar", true); +} + +#[test] pub fn test_with_extension() { macro_rules! twe ( ($input:expr, $extension:expr, $expected:expr) => ({ @@ -1442,6 +1473,49 @@ pub fn test_with_extension() { } #[test] +pub fn test_with_added_extension() { + macro_rules! twe ( + ($input:expr, $extension:expr, $expected:expr) => ({ + let input = Path::new($input); + let output = input.with_added_extension($extension); + + assert!( + output.to_str() == Some($expected), + "calling Path::new({:?}).with_added_extension({:?}): Expected {:?}, got {:?}", + $input, $extension, $expected, output, + ); + }); + ); + + twe!("foo", "txt", "foo.txt"); + twe!("foo.bar", "txt", "foo.bar.txt"); + twe!("foo.bar.baz", "txt", "foo.bar.baz.txt"); + twe!(".test", "txt", ".test.txt"); + twe!("foo.txt", "", "foo.txt"); + twe!("foo", "", "foo"); + twe!("", "foo", ""); + twe!(".", "foo", "."); + twe!("foo/", "bar", "foo.bar"); + twe!("foo/.", "bar", "foo.bar"); + twe!("..", "foo", ".."); + twe!("foo/..", "bar", "foo/.."); + twe!("/", "foo", "/"); + + // edge cases + twe!("/foo.ext////", "bar", "/foo.ext.bar"); + + // New extension is smaller than file name + twe!("aaa_aaa_aaa", "bbb_bbb", "aaa_aaa_aaa.bbb_bbb"); + // New extension is greater than file name + twe!("bbb_bbb", "aaa_aaa_aaa", "bbb_bbb.aaa_aaa_aaa"); + + // New extension is smaller than previous extension + twe!("ccc.aaa_aaa_aaa", "bbb_bbb", "ccc.aaa_aaa_aaa.bbb_bbb"); + // New extension is greater than previous extension + twe!("ccc.bbb_bbb", "aaa_aaa_aaa", "ccc.bbb_bbb.aaa_aaa_aaa"); +} + +#[test] fn test_eq_receivers() { use crate::borrow::Cow; |
