@@ -1519,6 +1519,77 @@ impl PathBuf {
15191519 true
15201520 }
15211521
1522+ /// Append [`self.extension`] with `extension`.
1523+ ///
1524+ /// Returns `false` and does nothing if [`self.file_name`] is [`None`],
1525+ /// returns `true` and updates the extension otherwise.
1526+ ///
1527+ /// If [`self.extension`] is [`None`], the extension is added; otherwise
1528+ /// it is appended.
1529+ ///
1530+ /// # Caveats
1531+ ///
1532+ /// The appended `extension` may contain dots and will be used in its entirety,
1533+ /// but only the part after the final dot will be reflected in
1534+ /// [`self.extension`].
1535+ ///
1536+ /// See the examples below.
1537+ ///
1538+ /// [`self.file_name`]: Path::file_name
1539+ /// [`self.extension`]: Path::extension
1540+ ///
1541+ /// # Examples
1542+ ///
1543+ /// ```
1544+ /// #![feature(path_add_extension)]
1545+ ///
1546+ /// use std::path::{Path, PathBuf};
1547+ ///
1548+ /// let mut p = PathBuf::from("/feel/the");
1549+ ///
1550+ /// p.add_extension("formatted");
1551+ /// assert_eq!(Path::new("/feel/the.formatted"), p.as_path());
1552+ ///
1553+ /// p.add_extension("dark.side");
1554+ /// assert_eq!(Path::new("/feel/the.formatted.dark.side"), p.as_path());
1555+ ///
1556+ /// p.set_extension("cookie");
1557+ /// assert_eq!(Path::new("/feel/the.formatted.dark.cookie"), p.as_path());
1558+ ///
1559+ /// p.set_extension("");
1560+ /// assert_eq!(Path::new("/feel/the.formatted.dark"), p.as_path());
1561+ ///
1562+ /// p.add_extension("");
1563+ /// assert_eq!(Path::new("/feel/the.formatted.dark"), p.as_path());
1564+ /// ```
1565+ #[unstable(feature = "path_add_extension", issue = "127292")]
1566+ pub fn add_extension<S: AsRef<OsStr>>(&mut self, extension: S) -> bool {
1567+ self._add_extension(extension.as_ref())
1568+ }
1569+
1570+ fn _add_extension(&mut self, extension: &OsStr) -> bool {
1571+ let file_name = match self.file_name() {
1572+ None => return false,
1573+ Some(f) => f.as_encoded_bytes(),
1574+ };
1575+
1576+ let new = extension;
1577+ if !new.is_empty() {
1578+ // truncate until right after the file name
1579+ // this is necessary for trimming the trailing slash
1580+ let end_file_name = file_name[file_name.len()..].as_ptr().addr();
1581+ let start = self.inner.as_encoded_bytes().as_ptr().addr();
1582+ self.inner.truncate(end_file_name.wrapping_sub(start));
1583+
1584+ // append the new extension
1585+ self.inner.reserve_exact(new.len() + 1);
1586+ self.inner.push(OsStr::new("."));
1587+ self.inner.push(new);
1588+ }
1589+
1590+ true
1591+ }
1592+
15221593 /// Yields a mutable reference to the underlying [`OsString`] instance.
15231594 ///
15241595 /// # Examples
@@ -2656,6 +2727,32 @@ impl Path {
26562727 new_path
26572728 }
26582729
2730+ /// Creates an owned [`PathBuf`] like `self` but with an extra extension.
2731+ ///
2732+ /// See [`PathBuf::add_extension`] for more details.
2733+ ///
2734+ /// # Examples
2735+ ///
2736+ /// ```
2737+ /// #![feature(path_add_extension)]
2738+ ///
2739+ /// use std::path::{Path, PathBuf};
2740+ ///
2741+ /// let path = Path::new("foo.rs");
2742+ /// assert_eq!(path.with_added_extension("txt"), PathBuf::from("foo.rs.txt"));
2743+ ///
2744+ /// let path = Path::new("foo.tar.gz");
2745+ /// assert_eq!(path.with_added_extension(""), PathBuf::from("foo.tar.gz"));
2746+ /// assert_eq!(path.with_added_extension("xz"), PathBuf::from("foo.tar.gz.xz"));
2747+ /// assert_eq!(path.with_added_extension("").with_added_extension("txt"), PathBuf::from("foo.tar.gz.txt"));
2748+ /// ```
2749+ #[unstable(feature = "path_add_extension", issue = "127292")]
2750+ pub fn with_added_extension<S: AsRef<OsStr>>(&self, extension: S) -> PathBuf {
2751+ let mut new_path = self.to_path_buf();
2752+ new_path.add_extension(extension);
2753+ new_path
2754+ }
2755+
26592756 /// Produces an iterator over the [`Component`]s of the path.
26602757 ///
26612758 /// When parsing the path, there is a small amount of normalization:
0 commit comments