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