camino_tempfile/dir.rs
1// Copyright (c) The camino-tempfile Contributors
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4use crate::Builder;
5use camino::{Utf8Path, Utf8PathBuf};
6use std::{
7 convert::{TryFrom, TryInto},
8 fmt, io,
9 path::Path,
10};
11use tempfile::TempDir;
12
13/// Create a new temporary directory.
14///
15/// The `tempdir` function creates a directory in the file system and returns a [`Utf8TempDir`]. The
16/// directory will be automatically deleted when the [`Utf8TempDir`]'s destructor is run.
17///
18/// # Resource Leaking
19///
20/// See [the resource leaking][resource-leaking] docs on `Utf8TempDir`.
21///
22/// # Errors
23///
24/// If the directory can not be created, `Err` is returned.
25///
26/// # Examples
27///
28/// ```
29/// use camino_tempfile::tempdir;
30/// use std::{
31/// fs::File,
32/// io::{self, Write},
33/// };
34///
35/// # fn main() {
36/// # if let Err(_) = run() {
37/// # ::std::process::exit(1);
38/// # }
39/// # }
40/// # fn run() -> Result<(), io::Error> {
41/// // Create a directory inside of `std::env::temp_dir()`
42/// let dir = tempdir()?;
43///
44/// let file_path = dir.path().join("my-temporary-note.txt");
45/// let mut file = File::create(file_path)?;
46/// writeln!(file, "Brian was here. Briefly.")?;
47///
48/// // `tmp_dir` goes out of scope, the directory as well as
49/// // `tmp_file` will be deleted here.
50/// drop(file);
51/// dir.close()?;
52/// # Ok(())
53/// # }
54/// ```
55///
56/// [resource-leaking]: struct.Utf8TempDir.html#resource-leaking
57pub fn tempdir() -> io::Result<Utf8TempDir> {
58 Utf8TempDir::new()
59}
60
61/// Create a new temporary directory in a specific directory.
62///
63/// The `tempdir_in` function creates a directory in the specified directory and returns a
64/// [`Utf8TempDir`]. The directory will be automatically deleted when the [`Utf8TempDir`]'s
65/// destructor is run.
66///
67/// # Resource Leaking
68///
69/// See [the resource leaking][resource-leaking] docs on `Utf8TempDir`.
70///
71/// # Errors
72///
73/// If the directory can not be created, `Err` is returned.
74///
75/// # Examples
76///
77/// ```
78/// use camino_tempfile::tempdir_in;
79/// use std::{
80/// fs::File,
81/// io::{self, Write},
82/// };
83///
84/// # fn main() {
85/// # if let Err(_) = run() {
86/// # ::std::process::exit(1);
87/// # }
88/// # }
89/// # fn run() -> Result<(), io::Error> {
90/// // Create a directory inside of the current directory.
91/// let dir = tempdir_in(".")?;
92///
93/// let file_path = dir.path().join("my-temporary-note.txt");
94/// let mut file = File::create(file_path)?;
95/// writeln!(file, "Brian was here. Briefly.")?;
96///
97/// // `tmp_dir` goes out of scope, the directory as well as
98/// // `tmp_file` will be deleted here.
99/// drop(file);
100/// dir.close()?;
101/// # Ok(())
102/// # }
103/// ```
104///
105/// [resource-leaking]: struct.TempDir.html#resource-leaking
106pub fn tempdir_in<P: AsRef<Utf8Path>>(dir: P) -> io::Result<Utf8TempDir> {
107 Utf8TempDir::new_in(dir)
108}
109
110/// A directory in the filesystem that is automatically deleted when it goes out of scope.
111///
112/// The [`Utf8TempDir`] type creates a directory on the file system that is deleted once it goes out
113/// of scope. At construction, the [`Utf8TempDir`] creates a new directory with a randomly generated
114/// name.
115///
116/// The default constructor, [`Utf8TempDir::new()`], creates directories in the location returned by
117/// [`std::env::temp_dir()`], but `Utf8TempDir` can be configured to manage a temporary directory in
118/// any location by constructing with a [`Builder`].
119///
120/// After creating a `Utf8TempDir`, work with the file system by doing standard [`std::fs`] file
121/// system operations on its [`Utf8Path`], which can be retrieved with [`Utf8TempDir::path()`]. Once
122/// the `Utf8TempDir` value is dropped, the directory at the path will be deleted, along with any
123/// files and directories it contains. It is your responsibility to ensure that no further file
124/// system operations are attempted inside the temporary directory once it has been deleted.
125///
126/// # Resource Leaking
127///
128/// Various platform-specific conditions may cause `Utf8TempDir` to fail to delete the underlying
129/// directory. It's important to ensure that handles (like [`File`](std::fs::File) and
130/// [`ReadDir`](std::fs::ReadDir)) to files inside the directory are dropped before the `TempDir`
131/// goes out of scope. The `Utf8TempDir` destructor will silently ignore any errors in deleting the
132/// directory; to instead handle errors call [`Utf8TempDir::close()`].
133///
134/// Note that if the program exits before the `Utf8TempDir` destructor is run, such as via
135/// [`std::process::exit()`], by segfaulting, or by receiving a signal like `SIGINT`, then the
136/// temporary directory will not be deleted.
137///
138/// # Examples
139///
140/// Create a temporary directory with a generated name:
141///
142/// ```
143/// use camino_tempfile::Utf8TempDir;
144/// use std::{fs::File, io::Write};
145///
146/// # use std::io;
147/// # fn run() -> Result<(), io::Error> {
148/// // Create a directory inside of `std::env::temp_dir()`
149/// let tmp_dir = Utf8TempDir::new()?;
150/// # Ok(())
151/// # }
152/// ```
153///
154/// Create a temporary directory with a prefix in its name:
155///
156/// ```
157/// use camino_tempfile::Builder;
158/// use std::{fs::File, io::Write};
159///
160/// # use std::io;
161/// # fn run() -> Result<(), io::Error> {
162/// // Create a directory inside of `std::env::temp_dir()`,
163/// // whose name will begin with 'example'.
164/// let tmp_dir = Builder::new().prefix("example").tempdir()?;
165/// # Ok(())
166/// # }
167/// ```
168pub struct Utf8TempDir {
169 inner: TempDir,
170}
171
172impl Utf8TempDir {
173 pub(crate) fn from_temp_dir(inner: TempDir) -> io::Result<Self> {
174 let path = inner.path();
175 // This produces a better error message.
176 Utf8PathBuf::try_from(path.to_path_buf()).map_err(|error| error.into_io_error())?;
177 Ok(Self { inner })
178 }
179
180 /// Attempts to make a temporary directory inside of `env::temp_dir()`.
181 ///
182 /// See [`Builder`] for more configuration.
183 ///
184 /// The directory and everything inside it will be automatically deleted once the returned
185 /// `Utf8TempDir` is destroyed.
186 ///
187 /// # Errors
188 ///
189 /// If the directory can not be created, or if `env::temp_dir` is a non-UTF-8 path, `Err` is
190 /// returned.
191 ///
192 /// # Examples
193 ///
194 /// ```
195 /// use camino_tempfile::Utf8TempDir;
196 /// use std::{fs::File, io::Write};
197 ///
198 /// # use std::io;
199 /// # fn run() -> Result<(), io::Error> {
200 /// // Create a directory inside of `std::env::temp_dir()`
201 /// let tmp_dir = Utf8TempDir::new()?;
202 ///
203 /// let file_path = tmp_dir.path().join("my-temporary-note.txt");
204 /// let mut tmp_file = File::create(file_path)?;
205 /// writeln!(tmp_file, "Brian was here. Briefly.")?;
206 ///
207 /// // `tmp_dir` goes out of scope, the directory as well as
208 /// // `tmp_file` will be deleted here.
209 /// # Ok(())
210 /// # }
211 /// ```
212 pub fn new() -> io::Result<Utf8TempDir> {
213 Builder::new().tempdir()
214 }
215
216 /// Attempts to make a temporary directory inside of `dir`. The directory and everything inside
217 /// it will be automatically deleted once the returned `Utf8TempDir` is destroyed.
218 ///
219 /// # Errors
220 ///
221 /// If the directory can not be created, `Err` is returned.
222 ///
223 /// # Examples
224 ///
225 /// ```
226 /// use camino_tempfile::Utf8TempDir;
227 /// use std::{
228 /// fs::{self, File},
229 /// io::Write,
230 /// };
231 ///
232 /// # use std::io;
233 /// # fn run() -> Result<(), io::Error> {
234 /// // Create a directory inside of the current directory
235 /// let tmp_dir = Utf8TempDir::new_in(".")?;
236 /// let file_path = tmp_dir.path().join("my-temporary-note.txt");
237 /// let mut tmp_file = File::create(file_path)?;
238 /// writeln!(tmp_file, "Brian was here. Briefly.")?;
239 /// # Ok(())
240 /// # }
241 /// ```
242 pub fn new_in<P: AsRef<Utf8Path>>(dir: P) -> io::Result<Utf8TempDir> {
243 Builder::new().tempdir_in(dir)
244 }
245
246 /// Attempts to make a temporary directory with the specified prefix inside of
247 /// `env::temp_dir()`. The directory and everything inside it will be automatically
248 /// deleted once the returned `Utf8TempDir` is destroyed.
249 ///
250 /// # Errors
251 ///
252 /// If the directory can not be created, `Err` is returned.
253 ///
254 /// # Examples
255 ///
256 /// ```
257 /// use camino_tempfile::Utf8TempDir;
258 /// use std::{
259 /// fs::{self, File},
260 /// io::Write,
261 /// };
262 ///
263 /// # use std::io;
264 /// # fn run() -> Result<(), io::Error> {
265 /// // Create a directory inside of the current directory
266 /// let tmp_dir = Utf8TempDir::with_prefix("foo-")?;
267 /// let tmp_name = tmp_dir.path().file_name().unwrap();
268 /// assert!(tmp_name.starts_with("foo-"));
269 /// # Ok(())
270 /// # }
271 /// ```
272 pub fn with_prefix<S: AsRef<str>>(prefix: S) -> io::Result<Utf8TempDir> {
273 Builder::new().prefix(&prefix).tempdir()
274 }
275
276 /// Attempts to make a temporary directory with the specified prefix inside
277 /// the specified directory. The directory and everything inside it will be
278 /// automatically deleted once the returned `Utf8TempDir` is destroyed.
279 ///
280 /// # Errors
281 ///
282 /// If the directory can not be created, `Err` is returned.
283 ///
284 /// # Examples
285 ///
286 /// ```
287 /// use camino_tempfile::Utf8TempDir;
288 /// use std::{
289 /// fs::{self, File},
290 /// io::Write,
291 /// };
292 ///
293 /// # use std::io;
294 /// # fn run() -> Result<(), io::Error> {
295 /// // Create a directory inside of the current directory
296 /// let tmp_dir = Utf8TempDir::with_prefix_in("foo-", ".")?;
297 /// let tmp_name = tmp_dir.path().file_name().unwrap();
298 /// assert!(tmp_name.starts_with("foo-"));
299 /// # Ok(())
300 /// # }
301 /// ```
302 pub fn with_prefix_in<S: AsRef<str>, P: AsRef<Utf8Path>>(
303 prefix: S,
304 dir: P,
305 ) -> io::Result<Utf8TempDir> {
306 Builder::new().prefix(&prefix).tempdir_in(dir)
307 }
308
309 /// Attempts to make a temporary directory with the specified suffix inside of
310 /// `env::temp_dir()`. The directory and everything inside it will be automatically
311 /// deleted once the returned `TempDir` is destroyed.
312 ///
313 /// # Errors
314 ///
315 /// If the directory can not be created, `Err` is returned.
316 ///
317 /// # Examples
318 ///
319 /// ```
320 /// use camino_tempfile::Utf8TempDir;
321 /// use std::{
322 /// fs::{self, File},
323 /// io::Write,
324 /// };
325 ///
326 /// // Create a directory inside of the current directory
327 /// let tmp_dir = Utf8TempDir::with_suffix("-foo")?;
328 /// let tmp_name = tmp_dir.path().file_name().unwrap();
329 /// assert!(tmp_name.ends_with("-foo"));
330 /// # Ok::<(), std::io::Error>(())
331 /// ```
332 pub fn with_suffix<S: AsRef<str>>(suffix: S) -> io::Result<Utf8TempDir> {
333 Builder::new().suffix(&suffix).tempdir()
334 }
335
336 /// Attempts to make a temporary directory with the specified suffix inside
337 /// the specified directory. The directory and everything inside it will be
338 /// automatically deleted once the returned `TempDir` is destroyed.
339 ///
340 /// # Errors
341 ///
342 /// If the directory can not be created, `Err` is returned.
343 ///
344 /// # Examples
345 ///
346 /// ```
347 /// use camino_tempfile::Utf8TempDir;
348 /// use std::{
349 /// fs::{self, File},
350 /// io::Write,
351 /// };
352 ///
353 /// // Create a directory inside of the current directory
354 /// let tmp_dir = Utf8TempDir::with_suffix_in("-foo", ".")?;
355 /// let tmp_name = tmp_dir.path().file_name().unwrap();
356 /// assert!(tmp_name.ends_with("-foo"));
357 /// # Ok::<(), std::io::Error>(())
358 /// ```
359 pub fn with_suffix_in<S: AsRef<str>, P: AsRef<Utf8Path>>(
360 suffix: S,
361 dir: P,
362 ) -> io::Result<Utf8TempDir> {
363 Builder::new().suffix(&suffix).tempdir_in(dir)
364 }
365
366 /// Accesses the [`Utf8Path`] to the temporary directory.
367 ///
368 /// # Examples
369 ///
370 /// ```
371 /// use camino_tempfile::Utf8TempDir;
372 ///
373 /// # use std::io;
374 /// # fn run() -> Result<(), io::Error> {
375 /// let tmp_path;
376 ///
377 /// {
378 /// let tmp_dir = Utf8TempDir::new()?;
379 /// tmp_path = tmp_dir.path().to_owned();
380 ///
381 /// // Check that the temp directory actually exists.
382 /// assert!(tmp_path.exists());
383 ///
384 /// // End of `tmp_dir` scope, directory will be deleted
385 /// }
386 ///
387 /// // Temp directory should be deleted by now
388 /// assert_eq!(tmp_path.exists(), false);
389 /// # Ok(())
390 /// # }
391 /// ```
392 #[must_use]
393 pub fn path(&self) -> &Utf8Path {
394 self.as_ref()
395 }
396
397 /// Deprecated alias for [`Utf8TempDir::keep`].
398 #[must_use]
399 #[deprecated(since = "1.4.0", note = "use Utf8TempDir::keep")]
400 pub fn into_path(self) -> Utf8PathBuf {
401 self.keep()
402 }
403
404 /// Persist the temporary directory to disk, returning the [`Utf8PathBuf`] where it is located.
405 ///
406 /// This consumes the [`Utf8TempDir`] without deleting directory on the filesystem, meaning that
407 /// the directory will no longer be automatically deleted.
408 ///
409 /// # Examples
410 ///
411 /// ```
412 /// use camino_tempfile::Utf8TempDir;
413 /// use std::fs;
414 ///
415 /// # use std::io;
416 /// # fn run() -> Result<(), io::Error> {
417 /// let tmp_dir = Utf8TempDir::new()?;
418 ///
419 /// // Persist the temporary directory to disk,
420 /// // getting the path where it is.
421 /// let tmp_path = tmp_dir.keep();
422 ///
423 /// // Delete the temporary directory ourselves.
424 /// fs::remove_dir_all(tmp_path)?;
425 /// # Ok(())
426 /// # }
427 /// ```
428 #[must_use]
429 pub fn keep(self) -> Utf8PathBuf {
430 self.inner
431 .keep()
432 .try_into()
433 .expect("invariant: path is valid UTF-8")
434 }
435
436 /// Disable cleanup of the temporary directory. If `disable_cleanup` is
437 /// `true`, the temporary directory will not be deleted when this
438 /// `Utf8TempDir` is dropped. This method is equivalent to calling
439 /// [`Builder::disable_cleanup`] when creating the `Utf8TempDir`.
440 ///
441 /// **NOTE:** this method is primarily useful for testing/debugging. If you
442 /// want to simply turn a temporary directory into a non-temporary
443 /// directory, prefer [`Utf8TempDir::keep`].
444 pub fn disable_cleanup(&mut self, disable_cleanup: bool) {
445 self.inner.disable_cleanup(disable_cleanup);
446 }
447
448 /// Closes and removes the temporary directory, returning a `Result`.
449 ///
450 /// Although `Utf8TempDir` removes the directory on drop, in the destructor any errors are
451 /// ignored. To detect errors cleaning up the temporary directory, call `close` instead.
452 ///
453 /// # Errors
454 ///
455 /// This function may return a variety of [`std::io::Error`]s that result from deleting the
456 /// files and directories contained with the temporary directory, as well as from deleting the
457 /// temporary directory itself. These errors may be platform specific.
458 ///
459 /// # Examples
460 ///
461 /// ```
462 /// use camino_tempfile::Utf8TempDir;
463 /// use std::{fs::File, io::Write};
464 ///
465 /// # use std::io;
466 /// # fn run() -> Result<(), io::Error> {
467 /// // Create a directory inside of `std::env::temp_dir()`.
468 /// let tmp_dir = Utf8TempDir::new()?;
469 /// let file_path = tmp_dir.path().join("my-temporary-note.txt");
470 /// let mut tmp_file = File::create(file_path)?;
471 /// writeln!(tmp_file, "Brian was here. Briefly.")?;
472 ///
473 /// // By closing the `Utf8TempDir` explicitly we can check that it has
474 /// // been deleted successfully. If we don't close it explicitly,
475 /// // the directory will still be deleted when `tmp_dir` goes out
476 /// // of scope, but we won't know whether deleting the directory
477 /// // succeeded.
478 /// drop(tmp_file);
479 /// tmp_dir.close()?;
480 /// # Ok(())
481 /// # }
482 /// ```
483 pub fn close(self) -> io::Result<()> {
484 self.inner.close()
485 }
486}
487
488impl AsRef<Utf8Path> for Utf8TempDir {
489 #[inline]
490 fn as_ref(&self) -> &Utf8Path {
491 let path: &Path = self.as_ref();
492 path.try_into().expect("invariant: path is valid UTF-8")
493 }
494}
495
496impl AsRef<Path> for Utf8TempDir {
497 fn as_ref(&self) -> &Path {
498 self.inner.path()
499 }
500}
501
502impl fmt::Debug for Utf8TempDir {
503 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
504 f.debug_struct("Utf8TempDir")
505 .field("path", &self.path())
506 .finish()
507 }
508}
509
510// The Drop impl is implicit since `Utf8TempDir` wraps a `TempDir`.