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