Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 1b5e5ac

Browse files
authoredJul 6, 2024
Rollup merge of rust-lang#123600 - tisonkun:path_with_extension, r=dtolnay
impl PathBuf::add_extension and Path::with_added_extension See the ACP for motivation and discussions - rust-lang/libs-team#368
2 parents 625bcc4 + ba4c71a commit 1b5e5ac

File tree

2 files changed

+168
-0
lines changed

2 files changed

+168
-0
lines changed
 

‎std/src/path.rs

+94
Original file line numberDiff line numberDiff line change
@@ -1519,6 +1519,74 @@ 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+
/// # Caveats
1528+
///
1529+
/// The appended `extension` may contain dots and will be used in its entirety,
1530+
/// but only the part after the final dot will be reflected in
1531+
/// [`self.extension`].
1532+
///
1533+
/// See the examples below.
1534+
///
1535+
/// [`self.file_name`]: Path::file_name
1536+
/// [`self.extension`]: Path::extension
1537+
///
1538+
/// # Examples
1539+
///
1540+
/// ```
1541+
/// #![feature(path_add_extension)]
1542+
///
1543+
/// use std::path::{Path, PathBuf};
1544+
///
1545+
/// let mut p = PathBuf::from("/feel/the");
1546+
///
1547+
/// p.add_extension("formatted");
1548+
/// assert_eq!(Path::new("/feel/the.formatted"), p.as_path());
1549+
///
1550+
/// p.add_extension("dark.side");
1551+
/// assert_eq!(Path::new("/feel/the.formatted.dark.side"), p.as_path());
1552+
///
1553+
/// p.set_extension("cookie");
1554+
/// assert_eq!(Path::new("/feel/the.formatted.dark.cookie"), p.as_path());
1555+
///
1556+
/// p.set_extension("");
1557+
/// assert_eq!(Path::new("/feel/the.formatted.dark"), p.as_path());
1558+
///
1559+
/// p.add_extension("");
1560+
/// assert_eq!(Path::new("/feel/the.formatted.dark"), p.as_path());
1561+
/// ```
1562+
#[unstable(feature = "path_add_extension", issue = "127292")]
1563+
pub fn add_extension<S: AsRef<OsStr>>(&mut self, extension: S) -> bool {
1564+
self._add_extension(extension.as_ref())
1565+
}
1566+
1567+
fn _add_extension(&mut self, extension: &OsStr) -> bool {
1568+
let file_name = match self.file_name() {
1569+
None => return false,
1570+
Some(f) => f.as_encoded_bytes(),
1571+
};
1572+
1573+
let new = extension;
1574+
if !new.is_empty() {
1575+
// truncate until right after the file name
1576+
// this is necessary for trimming the trailing slash
1577+
let end_file_name = file_name[file_name.len()..].as_ptr().addr();
1578+
let start = self.inner.as_encoded_bytes().as_ptr().addr();
1579+
self.inner.truncate(end_file_name.wrapping_sub(start));
1580+
1581+
// append the new extension
1582+
self.inner.reserve_exact(new.len() + 1);
1583+
self.inner.push(OsStr::new("."));
1584+
self.inner.push(new);
1585+
}
1586+
1587+
true
1588+
}
1589+
15221590
/// Yields a mutable reference to the underlying [`OsString`] instance.
15231591
///
15241592
/// # Examples
@@ -2656,6 +2724,32 @@ impl Path {
26562724
new_path
26572725
}
26582726

2727+
/// Creates an owned [`PathBuf`] like `self` but with the extension added.
2728+
///
2729+
/// See [`PathBuf::add_extension`] for more details.
2730+
///
2731+
/// # Examples
2732+
///
2733+
/// ```
2734+
/// #![feature(path_add_extension)]
2735+
///
2736+
/// use std::path::{Path, PathBuf};
2737+
///
2738+
/// let path = Path::new("foo.rs");
2739+
/// assert_eq!(path.with_added_extension("txt"), PathBuf::from("foo.rs.txt"));
2740+
///
2741+
/// let path = Path::new("foo.tar.gz");
2742+
/// assert_eq!(path.with_added_extension(""), PathBuf::from("foo.tar.gz"));
2743+
/// assert_eq!(path.with_added_extension("xz"), PathBuf::from("foo.tar.gz.xz"));
2744+
/// assert_eq!(path.with_added_extension("").with_added_extension("txt"), PathBuf::from("foo.tar.gz.txt"));
2745+
/// ```
2746+
#[unstable(feature = "path_add_extension", issue = "127292")]
2747+
pub fn with_added_extension<S: AsRef<OsStr>>(&self, extension: S) -> PathBuf {
2748+
let mut new_path = self.to_path_buf();
2749+
new_path.add_extension(extension);
2750+
new_path
2751+
}
2752+
26592753
/// Produces an iterator over the [`Component`]s of the path.
26602754
///
26612755
/// When parsing the path, there is a small amount of normalization:

‎std/src/path/tests.rs

+74
Original file line numberDiff line numberDiff line change
@@ -1401,6 +1401,37 @@ pub fn test_set_extension() {
14011401
tfe!("/", "foo", "/", false);
14021402
}
14031403

1404+
#[test]
1405+
pub fn test_add_extension() {
1406+
macro_rules! tfe (
1407+
($path:expr, $ext:expr, $expected:expr, $output:expr) => ({
1408+
let mut p = PathBuf::from($path);
1409+
let output = p.add_extension($ext);
1410+
assert!(p.to_str() == Some($expected) && output == $output,
1411+
"adding extension of {:?} to {:?}: Expected {:?}/{:?}, got {:?}/{:?}",
1412+
$path, $ext, $expected, $output,
1413+
p.to_str().unwrap(), output);
1414+
});
1415+
);
1416+
1417+
tfe!("foo", "txt", "foo.txt", true);
1418+
tfe!("foo.bar", "txt", "foo.bar.txt", true);
1419+
tfe!("foo.bar.baz", "txt", "foo.bar.baz.txt", true);
1420+
tfe!(".test", "txt", ".test.txt", true);
1421+
tfe!("foo.txt", "", "foo.txt", true);
1422+
tfe!("foo", "", "foo", true);
1423+
tfe!("", "foo", "", false);
1424+
tfe!(".", "foo", ".", false);
1425+
tfe!("foo/", "bar", "foo.bar", true);
1426+
tfe!("foo/.", "bar", "foo.bar", true);
1427+
tfe!("..", "foo", "..", false);
1428+
tfe!("foo/..", "bar", "foo/..", false);
1429+
tfe!("/", "foo", "/", false);
1430+
1431+
// edge cases
1432+
tfe!("/foo.ext////", "bar", "/foo.ext.bar", true);
1433+
}
1434+
14041435
#[test]
14051436
pub fn test_with_extension() {
14061437
macro_rules! twe (
@@ -1441,6 +1472,49 @@ pub fn test_with_extension() {
14411472
twe!("ccc.bbb_bbb", "aaa_aaa_aaa", "ccc.aaa_aaa_aaa");
14421473
}
14431474

1475+
#[test]
1476+
pub fn test_with_added_extension() {
1477+
macro_rules! twe (
1478+
($input:expr, $extension:expr, $expected:expr) => ({
1479+
let input = Path::new($input);
1480+
let output = input.with_added_extension($extension);
1481+
1482+
assert!(
1483+
output.to_str() == Some($expected),
1484+
"calling Path::new({:?}).with_added_extension({:?}): Expected {:?}, got {:?}",
1485+
$input, $extension, $expected, output,
1486+
);
1487+
});
1488+
);
1489+
1490+
twe!("foo", "txt", "foo.txt");
1491+
twe!("foo.bar", "txt", "foo.bar.txt");
1492+
twe!("foo.bar.baz", "txt", "foo.bar.baz.txt");
1493+
twe!(".test", "txt", ".test.txt");
1494+
twe!("foo.txt", "", "foo.txt");
1495+
twe!("foo", "", "foo");
1496+
twe!("", "foo", "");
1497+
twe!(".", "foo", ".");
1498+
twe!("foo/", "bar", "foo.bar");
1499+
twe!("foo/.", "bar", "foo.bar");
1500+
twe!("..", "foo", "..");
1501+
twe!("foo/..", "bar", "foo/..");
1502+
twe!("/", "foo", "/");
1503+
1504+
// edge cases
1505+
twe!("/foo.ext////", "bar", "/foo.ext.bar");
1506+
1507+
// New extension is smaller than file name
1508+
twe!("aaa_aaa_aaa", "bbb_bbb", "aaa_aaa_aaa.bbb_bbb");
1509+
// New extension is greater than file name
1510+
twe!("bbb_bbb", "aaa_aaa_aaa", "bbb_bbb.aaa_aaa_aaa");
1511+
1512+
// New extension is smaller than previous extension
1513+
twe!("ccc.aaa_aaa_aaa", "bbb_bbb", "ccc.aaa_aaa_aaa.bbb_bbb");
1514+
// New extension is greater than previous extension
1515+
twe!("ccc.bbb_bbb", "aaa_aaa_aaa", "ccc.bbb_bbb.aaa_aaa_aaa");
1516+
}
1517+
14441518
#[test]
14451519
fn test_eq_receivers() {
14461520
use crate::borrow::Cow;

0 commit comments

Comments
 (0)
Failed to load comments.