1use std::ffi::{OsStr, OsString};
2use std::{fs, io, iter, ops};
3
4use crate::prelude::*;
5
6pub trait PathExt {
8 fn join_stem(&self, stem: &OsStr, extension: &str) -> PathBuf;
11
12 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
32pub trait PathBufExt {
34 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#[derive(Clone, Copy, Debug)]
55enum TempPathType {
56 File,
57 Dir,
58}
59
60#[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(); 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}