#![cfg_attr(docsrs, feature(doc_cfg))]
#![warn(missing_docs)]
#![warn(rust_2018_idioms)]
mod cleanup;
mod named_file;
pub use cleanup::cleanup_orphans;
pub use named_file::{NamedTempFile, PersistAtomicError};
use std::io;
use std::path::{Path, PathBuf};
#[cfg(not(feature = "mod-rand"))]
use std::sync::atomic::{AtomicU64, Ordering};
#[cfg(not(feature = "mod-rand"))]
use std::time::{SystemTime, UNIX_EPOCH};
#[derive(Debug)]
pub struct TempDir {
path: PathBuf,
cleanup_on_drop: bool,
}
impl TempDir {
pub fn new() -> io::Result<Self> {
let name = unique_name(12);
let pid = std::process::id();
let path = std::env::temp_dir().join(format!(".tmp-{pid}-{name}"));
std::fs::create_dir(&path)?;
Ok(Self {
path,
cleanup_on_drop: true,
})
}
pub fn with_prefix(prefix: &str) -> io::Result<Self> {
let name = unique_name(12);
let path = std::env::temp_dir().join(format!("{prefix}-{name}"));
std::fs::create_dir(&path)?;
Ok(Self {
path,
cleanup_on_drop: true,
})
}
pub fn path(&self) -> &Path {
&self.path
}
pub fn persist(mut self) -> PathBuf {
self.cleanup_on_drop = false;
self.path.clone()
}
pub fn cleanup_on_drop(&self) -> bool {
self.cleanup_on_drop
}
}
impl Drop for TempDir {
fn drop(&mut self) {
if self.cleanup_on_drop {
let _ = std::fs::remove_dir_all(&self.path);
}
}
}
#[cfg(feature = "mod-rand")]
#[inline]
pub(crate) fn unique_name(len: usize) -> String {
mod_rand::tier2::unique_name(len)
}
#[cfg(not(feature = "mod-rand"))]
pub(crate) fn unique_name(len: usize) -> String {
static COUNTER: AtomicU64 = AtomicU64::new(0);
const ALPHABET: &[u8] = b"0123456789ABCDEFGHJKMNPQRSTVWXYZ";
let pid = std::process::id() as u64;
let nanos = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_nanos() as u64)
.unwrap_or(0);
let counter = COUNTER.fetch_add(1, Ordering::Relaxed);
let mut state = pid.wrapping_mul(0x9E3779B97F4A7C15)
^ nanos.wrapping_mul(0xBF58476D1CE4E5B9)
^ counter.wrapping_mul(0x94D049BB133111EB);
let mut out = String::with_capacity(len);
while out.len() < len {
out.push(ALPHABET[(state & 31) as usize] as char);
state >>= 5;
if state == 0 {
state = nanos.wrapping_mul(counter.wrapping_add(1));
}
}
out
}
#[cfg(feature = "mod-rand")]
#[doc(hidden)]
pub fn __unique_name_for_tests(len: usize) -> String {
unique_name(len)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn creates_dir() {
let dir = TempDir::new().unwrap();
assert!(dir.path().exists());
assert!(dir.path().is_dir());
}
#[test]
fn auto_cleanup() {
let path = {
let dir = TempDir::new().unwrap();
dir.path().to_path_buf()
};
assert!(!path.exists());
}
#[test]
fn persist_disables_cleanup() {
let dir = TempDir::new().unwrap();
let path = dir.persist();
assert!(path.exists());
std::fs::remove_dir_all(&path).unwrap();
}
#[test]
fn with_prefix_works() {
let dir = TempDir::with_prefix("test").unwrap();
let name = dir.path().file_name().unwrap().to_string_lossy();
assert!(name.starts_with("test-"));
}
#[test]
fn two_dirs_unique() {
let a = TempDir::new().unwrap();
let b = TempDir::new().unwrap();
assert_ne!(a.path(), b.path());
}
}