#![forbid(unsafe_code)]
use core::sync::atomic::{AtomicU32, Ordering};
use std::path::{Path, PathBuf};
#[doc(hidden)]
pub static INTERNAL_COUNTER: AtomicU32 = AtomicU32::new(0);
pub struct TempFileBuilder {
dir_path: Option<PathBuf>,
prefix: Option<String>,
suffix: Option<String>,
}
impl TempFileBuilder {
#[allow(clippy::new_without_default)]
#[must_use]
pub fn new() -> Self {
Self {
dir_path: None,
prefix: None,
suffix: None,
}
}
#[must_use]
pub fn in_dir(mut self, p: impl AsRef<Path>) -> Self {
self.dir_path = Some(p.as_ref().to_path_buf());
self
}
#[must_use]
pub fn prefix(mut self, s: impl AsRef<str>) -> Self {
self.prefix = Some(s.as_ref().to_string());
self
}
#[must_use]
pub fn suffix(mut self, s: impl AsRef<str>) -> Self {
self.suffix = Some(s.as_ref().to_string());
self
}
pub fn build(self) -> Result<TempFile, std::io::Error> {
TempFile::internal_new(
self.dir_path.as_deref(),
self.prefix.as_ref().map(AsRef::as_ref),
self.suffix.as_ref().map(AsRef::as_ref),
)
}
}
#[derive(Clone, PartialOrd, Ord, PartialEq, Eq, Hash, Debug)]
pub struct TempFile {
path_buf: PathBuf,
delete_on_drop: bool,
panic_on_delete_err: bool,
}
impl TempFile {
fn internal_new(
dir: Option<&Path>,
prefix: Option<&str>,
suffix: Option<&str>,
) -> Result<Self, std::io::Error> {
let dir = dir.map_or_else(std::env::temp_dir, Path::to_path_buf);
let filename = format!(
"{}{:x}{:x}{}",
prefix.unwrap_or(""),
std::process::id(),
INTERNAL_COUNTER.fetch_add(1, Ordering::AcqRel),
suffix.unwrap_or(""),
);
let file_path = dir.join(filename);
let mut open_opts = std::fs::OpenOptions::new();
open_opts.create_new(true);
open_opts.write(true);
open_opts.open(&file_path).map_err(|e| {
std::io::Error::new(
e.kind(),
format!("error creating file {:?}: {}", &file_path, e),
)
})?;
Ok(Self {
path_buf: file_path,
delete_on_drop: true,
panic_on_delete_err: false,
})
}
fn remove_file(path: &Path) -> Result<(), std::io::Error> {
match std::fs::remove_file(path) {
Ok(()) => Ok(()),
Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(()),
Err(e) => Err(std::io::Error::new(
e.kind(),
format!("error removing file {:?}: {}", path, e),
)),
}
}
pub fn new() -> Result<Self, std::io::Error> {
Self::internal_new(None, None, None)
}
pub fn in_dir(dir: impl AsRef<Path>) -> Result<Self, std::io::Error> {
Self::internal_new(Some(dir.as_ref()), None, None)
}
pub fn with_prefix(prefix: impl AsRef<str>) -> Result<Self, std::io::Error> {
Self::internal_new(None, Some(prefix.as_ref()), None)
}
pub fn with_suffix(suffix: impl AsRef<str>) -> Result<Self, std::io::Error> {
Self::internal_new(None, None, Some(suffix.as_ref()))
}
#[allow(clippy::missing_panics_doc)]
pub fn with_contents(self, contents: &[u8]) -> Result<Self, std::io::Error> {
let path = self.path_buf.as_path();
std::fs::write(path, contents).map_err(|e| {
std::io::Error::new(e.kind(), format!("error writing file {:?}: {}", path, e))
})?;
Ok(self)
}
#[allow(clippy::missing_panics_doc)]
pub fn cleanup(mut self) -> Result<(), std::io::Error> {
let result = Self::remove_file(&self.path_buf);
if result.is_ok() {
self.delete_on_drop = false;
}
result
}
#[must_use]
pub fn panic_on_cleanup_error(mut self) -> Self {
self.panic_on_delete_err = true;
self
}
pub fn leak(mut self) {
self.delete_on_drop = false;
}
#[must_use]
#[allow(clippy::missing_panics_doc)]
pub fn path(&self) -> &Path {
self.path_buf.as_path()
}
}
impl Drop for TempFile {
fn drop(&mut self) {
if self.delete_on_drop {
let result = Self::remove_file(self.path_buf.as_path());
if self.panic_on_delete_err {
if let Err(e) = result {
panic!("{}", e);
}
}
}
}
}
#[must_use]
pub fn empty() -> TempFile {
TempFile::new().unwrap()
}
#[must_use]
pub fn with_contents(contents: &[u8]) -> TempFile {
TempFile::new().unwrap().with_contents(contents).unwrap()
}