#![expect(clippy::module_name_repetitions)]
use std::{fs, io, path};
pub fn file_new_append_incremental (file_path : &path::Path)
-> Result <(path::PathBuf, fs::File), io::Error>
{
let file_pathbuf = file_path_incremental (file_path)?;
let file = file_new_append (file_pathbuf.as_path())?;
Ok ((file_pathbuf, file))
}
pub fn file_new_append (file_path : &path::Path) -> Result <fs::File, io::Error> {
if !is_file (file_path)? {
return Err (io::Error::new (io::ErrorKind::InvalidInput, "not a file"))
}
let dir = file_path.parent().unwrap_or_else (|| path::Path::new (""));
fs::create_dir_all (dir)?;
fs::OpenOptions::new().append (true).create_new (true).open (file_path)
}
pub fn file_path_incremental (file_path : &path::Path)
-> Result <path::PathBuf, io::Error>
{
if !is_file (file_path)? {
return Err (io::Error::new (io::ErrorKind::InvalidInput, "not a file"))
}
let file_name = file_path.file_name().expect ("fatal: path should be a valid file")
.to_str().unwrap_or_else (||
panic!("fatal: `file_path.file_name()` returned invalid os str: {:?}",
file_path.file_name()));
let dir = file_path.parent().unwrap_or_else (|| path::Path::new (""));
for i in 0.. {
let name = String::from (file_name) + &format!("-{i}");
let fp = dir.join (name);
if !fp.exists() {
return Ok (fp)
}
}
unreachable!("fatal: incremental file name loop should have returned")
}
pub fn file_path_incremental_with_extension (file_path : &path::Path)
-> Result <path::PathBuf, io::Error>
{
if !is_file (file_path)? {
return Err (io::Error::new (io::ErrorKind::InvalidInput, "not a file"))
}
if file_path.extension().is_none() {
return file_path_incremental (file_path)
}
let extension = file_path.extension().unwrap().to_str().unwrap();
let file_stem = file_path.file_stem()
.expect ("fatal: path should be a valid file").to_str()
.unwrap_or_else (||
panic!("fatal: `file_path.file_name()` returned invalid os str: {:?}",
file_path.file_name()));
let dir = file_path.parent().unwrap_or_else (|| path::Path::new (""));
for i in 0.. {
let name = &format!("{file_stem}-{i}.{extension}");
let fp = dir.join (name);
if !fp.exists() {
return Ok (fp)
}
}
unreachable!("fatal: incremental file name loop should have returned")
}
pub fn is_file (file_path : &path::Path) -> Result <bool, io::Error> {
let s = file_path.to_str().ok_or (io::Error::new (
io::ErrorKind::InvalidInput, "not valid unicode"))?;
if s.ends_with (path::MAIN_SEPARATOR) {
return Ok (false)
}
if path::Path::new (file_path).file_name().is_none() {
return Ok (false)
}
Ok (true)
}
#[cfg(test)]
mod tests {
use tempfile;
use quickcheck;
use super::*;
#[ignore] #[quickcheck_macros::quickcheck]
fn prop_is_file_implies_not_directory (file_path : String) -> quickcheck::TestResult {
let file_path = path::Path::new (file_path.as_str());
if !is_file (file_path).unwrap() {
return quickcheck::TestResult::discard()
}
if let Some (s) = file_path.parent() && !s.to_str().unwrap().is_empty() {
return quickcheck::TestResult::discard()
}
let temp_dir = tempfile::Builder::new().prefix ("tmp").tempdir().unwrap();
let file_path = temp_dir.path().join (file_path);
quickcheck::TestResult::from_bool (
if let Err(e) = fs::OpenOptions::new().append (true).create (true)
.open (file_path)
{
e.kind() != io::ErrorKind::Other
} else {
true
}
)
}
}