1use std::{
2 ffi::{OsStr, OsString},
3 os::unix::prelude::OsStringExt,
4 path::{Path, PathBuf},
5};
6
7use anyhow::{anyhow, Context, Result};
8
9pub trait AppendToPath {
11 fn append<P: AsRef<Path>>(self, segment: P) -> PathBuf;
14}
15
16impl<'p> AppendToPath for &'p Path {
17 fn append<P: AsRef<Path>>(self, segment: P) -> PathBuf {
18 let mut path = self.to_owned();
19 path.push(segment);
20 path
21 }
22}
23
24impl<'p> AppendToPath for &'p PathBuf {
25 fn append<P: AsRef<Path>>(self, segment: P) -> PathBuf {
26 let mut path = self.clone();
27 path.push(segment);
28 path
29 }
30}
31
32impl AppendToPath for PathBuf {
33 fn append<P: AsRef<Path>>(mut self, segment: P) -> PathBuf {
34 self.push(segment);
35 self
36 }
37}
38
39pub fn add_extension<P: AsRef<Path>, S: AsRef<OsStr>>(this: P, extension: S) -> Option<PathBuf> {
44 let mut path = this.as_ref().to_owned();
45 if !_add_extension_mut(&mut path, extension.as_ref()) {
46 None
47 } else {
48 Some(path)
49 }
50}
51
52pub fn add_extension_mut<S: AsRef<OsStr>>(this: &mut PathBuf, extension: S) -> bool {
58 _add_extension_mut(this, extension.as_ref())
59}
60
61fn _add_extension_mut(this: &mut PathBuf, extension: &OsStr) -> bool {
62 let file_name = match this.file_name() {
63 None => return false,
64 Some(f) => f.as_encoded_bytes(),
65 };
66
67 let mut new = extension.as_encoded_bytes().to_vec();
68 if !new.is_empty() {
69 let mut file_name: Vec<u8> = Vec::from(file_name);
76 file_name.push(b'.');
77 file_name.append(&mut new);
78
79 let file_name = OsString::from_vec(file_name);
81 this.set_file_name(file_name);
82 }
83
84 true
85}
86
87#[test]
88fn t_add_extension() {
89 let t = |path: &str, ext: &str| {
90 let mut path = PathBuf::from(path);
91 if add_extension_mut(&mut path, ext) {
92 path.to_string_lossy().to_string()
93 } else {
94 format!("{path:?} -- unchanged")
95 }
96 };
97
98 assert_eq!(t("hello", ""), "hello");
99 assert_eq!(t("hello", "foo"), "hello.foo");
100 assert_eq!(t("hello.foo", "bar"), "hello.foo.bar");
101 assert_eq!(t("hello", ".foo"), "hello..foo");
102 assert_eq!(t("/", ".foo"), "\"/\" -- unchanged");
103 assert_eq!(t("hello/", ".foo"), "hello..foo"); }
105
106pub fn canonicalize(path: &Path) -> Result<PathBuf> {
108 path.canonicalize()
109 .with_context(|| anyhow!("canonicalizing {path:?}"))
110}