PathBuf has set_extension but no add_extension. Cannot cleanly turn .tar to .tar.gz

Just ran into this. I don't have a strong opinion about whether this should be in the std library, but I would like save 30-90 minutes for the people who have this need and find this page by searching.

First of all the easy one, appending:

use std::ffi::{OsStr, OsString};
use std::path::{PathBuf, Path};

/// Returns a path with a new dotted extension component appended to the end.
/// Note: does not check if the path is a file or directory; you should do that.
/// # Example
/// ```
/// use pathext::append_ext;
/// use std::path::PathBuf;
/// let path = PathBuf::from("foo/bar/baz.txt");
/// if !path.is_dir() {
///    assert_eq!(append_ext("app", path), PathBuf::from("foo/bar/baz.txt.app"));
/// }
/// ```
/// 
pub fn append_ext(ext: impl AsRef<OsStr>, path: PathBuf) -> PathBuf {
    let mut os_string: OsString = path.into();
    os_string.push(".");
    os_string.push(ext.as_ref());
    os_string.into()
}

Prepending is significantly harder, and I couldn't find a portable way to split an OsString, so I gave up making a function that inserts something just after the first component of a dotted filename, and made something that can insert just before the last component of a dotted filename.

/// Prepends something just in front of the very last dot component of your extension.
/// # Example
/// ```
/// use pathext::prepend_ext;
/// use std::path::PathBuf;
/// let path = PathBuf::from("foo/bar/baz.txt.gz");
/// if !path.is_dir() {
///    assert_eq!(prepend_ext("tmp", &path), PathBuf::from("foo/bar/baz.txt.tmp.gz"));
/// }
/// let no_ext = PathBuf::from("foo/bar");
/// assert_eq!(prepend_ext("tmp", &no_ext), PathBuf::from("foo/bar.tmp"));
/// ```
/// 
pub fn prepend_ext(ext: impl AsRef<OsStr>, path: &Path) -> PathBuf {
    let mut parent : PathBuf = path.parent().unwrap().to_owned().into();
    let stem = path.file_stem().unwrap();
    let orig_ext = path.extension();
    parent.push(stem);
    let mut parent_string : OsString = parent.into();
    parent_string.push(".");
    parent_string.push(ext.as_ref().to_owned());
    if (orig_ext.is_some()) {
        parent_string.push(".");
        parent_string.push(orig_ext.unwrap().to_owned());
    }

    parent_string.into()
}
7 Likes