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`.