bard/util/
path.rs

1use std::ffi::{OsStr, OsString};
2use std::{fs, io, iter, ops};
3
4use crate::prelude::*;
5
6/// Path extension
7pub trait PathExt {
8    /// Join a `stem` (eg. from some other filename) with this path
9    /// and add an `extenion`.
10    fn join_stem(&self, stem: &OsStr, extension: &str) -> PathBuf;
11
12    /// Returns true if filename (last path component)
13    /// end in `suffix`.
14    fn file_ends_with(&self, suffix: &str) -> bool;
15}
16
17impl PathExt for Path {
18    fn join_stem(&self, stem: &OsStr, extension: &str) -> PathBuf {
19        let mut res: OsString = self.join(stem).into();
20        res.push(extension);
21        res.into()
22    }
23
24    fn file_ends_with(&self, suffix: &str) -> bool {
25        self.file_name()
26            .and_then(OsStr::to_str)
27            .map(|s| s.ends_with(suffix))
28            .unwrap_or(false)
29    }
30}
31
32/// PathBuf extension
33pub trait PathBufExt {
34    /// If the path is relative, resolve it as absolute wrt. `base_dir`
35    fn resolve(&mut self, base_dir: &Path);
36    fn resolved(self, base_dir: &Path) -> Self;
37}
38
39impl PathBufExt for PathBuf {
40    fn resolve(&mut self, base_dir: &Path) {
41        if self.is_relative() {
42            *self = base_dir.join(&self);
43        }
44    }
45
46    fn resolved(mut self, base_dir: &Path) -> Self {
47        self.resolve(base_dir);
48        self
49    }
50}
51
52// TempPath
53
54#[derive(Clone, Copy, Debug)]
55enum TempPathType {
56    File,
57    Dir,
58}
59
60/// A path that may be removed on drop. Also provides temp dir creation via `make_temp_dir()`.
61#[derive(Debug)]
62pub struct TempPath {
63    path: PathBuf,
64    typ: TempPathType,
65    remove: bool,
66}
67
68impl TempPath {
69    const RAND_CHARS: u32 = 6;
70    const RETRIES: u32 = 9001;
71
72    pub fn new_file(path: impl Into<PathBuf>, remove: bool) -> Self {
73        Self {
74            path: path.into(),
75            typ: TempPathType::File,
76            remove,
77        }
78    }
79
80    pub fn new_dir(path: impl Into<PathBuf>, remove: bool) -> Self {
81        Self {
82            path: path.into(),
83            typ: TempPathType::Dir,
84            remove,
85        }
86    }
87
88    pub fn make_temp_dir(prefix: impl Into<OsString>, remove: bool) -> Result<Self> {
89        let prefix = prefix.into();
90
91        let mut sufffix = String::with_capacity(Self::RAND_CHARS as usize + 1);
92        for _ in 0..Self::RETRIES {
93            sufffix.clear();
94            sufffix.push('.');
95            for c in iter::repeat_with(fastrand::alphanumeric).take(Self::RAND_CHARS as usize) {
96                sufffix.push(c)
97            }
98
99            let mut path = prefix.clone(); // have to clone due to the limited OsString API
100            path.push(&sufffix);
101            if Self::create_dir(&path)? {
102                return Ok(Self::new_dir(path, remove));
103            }
104        }
105
106        bail!(
107            "Could not create temporary directory, prefix: {:?}",
108            Path::new(&prefix)
109        );
110    }
111
112    fn create_dir(path: impl AsRef<OsStr>) -> Result<bool> {
113        let path = Path::new(path.as_ref());
114        match fs::create_dir(path) {
115            Ok(_) => Ok(true),
116            Err(err) if err.kind() == io::ErrorKind::AlreadyExists => Ok(false),
117            Err(err) => Err(err).with_context(|| format!("Could not create directory {:?}", path)),
118        }
119    }
120
121    pub fn set_remove(&mut self, remove: bool) {
122        self.remove = remove;
123    }
124
125    pub fn to_os_string(&self) -> OsString {
126        self.path.as_os_str().to_owned()
127    }
128}
129
130impl Drop for TempPath {
131    fn drop(&mut self) {
132        if !self.remove {
133            return;
134        }
135
136        let _ = match self.typ {
137            TempPathType::File => fs::remove_file(&self.path),
138            TempPathType::Dir => fs::remove_dir_all(&self.path),
139        };
140    }
141}
142
143impl AsRef<Path> for TempPath {
144    fn as_ref(&self) -> &Path {
145        self.path.as_ref()
146    }
147}
148
149impl ops::Deref for TempPath {
150    type Target = Path;
151
152    fn deref(&self) -> &Self::Target {
153        self.as_ref()
154    }
155}