run_git/
path_util.rs

1use std::{
2    ffi::{OsStr, OsString},
3    os::unix::prelude::OsStringExt,
4    path::{Path, PathBuf},
5};
6
7use anyhow::{anyhow, Context, Result};
8
9/// Make it easy to append a segment to an existing path.
10pub trait AppendToPath {
11    /// Note: `segment` should be a single file/folder name and *not*
12    /// contain `/` or `\\` characters!
13    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
39// Add an extension to a path with a filename. Returns none if the
40// path does not in fact have a filename. `extension` must not include
41// the dot. If `extension` is empty, nothing is appended (not even the
42// dot).
43pub 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
52// Add an extension to a path with a filename. Returns false if it
53// does not in fact have a filename. `extension` must not include the
54// dot. If `extension` is empty, nothing is appended (not even the
55// dot). This function exists because the `add_extension` method in
56// std is currently an unstable library feature.
57pub 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        // "truncate until right after the file name
70        // this is necessary for trimming the trailing slash"
71
72        // Hmm, dunno. This is not going to behave the same, but I'm
73        // happy with just appending a dot and the extension, please.
74
75        let mut file_name: Vec<u8> = Vec::from(file_name);
76        file_name.push(b'.');
77        file_name.append(&mut new);
78
79        // XX this depends on Unix, sigh.
80        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"); // XX oh, buggy. todo fix
104}
105
106/// Just adds error wrapper that mentions the path.
107pub fn canonicalize(path: &Path) -> Result<PathBuf> {
108    path.canonicalize()
109        .with_context(|| anyhow!("canonicalizing {path:?}"))
110}