use crate::{Error, Result, SFile};
use core::fmt;
use std::fs;
use std::path::{Path, PathBuf};
use std::time::{SystemTime, UNIX_EPOCH};
#[derive(Debug, Clone)]
pub struct SPath {
path: PathBuf,
}
impl SPath {
pub fn new(path: impl Into<PathBuf>) -> Result<Self> {
let path = path.into();
validate_spath_for_result(&path)?;
Ok(Self { path })
}
pub fn from_path(path: impl AsRef<Path>) -> Result<Self> {
let path = path.as_ref();
validate_spath_for_result(path)?;
Ok(Self {
path: path.to_path_buf(),
})
}
pub fn from_path_ok(path: impl AsRef<Path>) -> Option<Self> {
let path = path.as_ref();
validate_spath_for_option(path)?;
Some(Self {
path: path.to_path_buf(),
})
}
pub fn from_path_buf_ok(path_buf: PathBuf) -> Option<Self> {
validate_spath_for_option(&path_buf)?;
Some(Self { path: path_buf })
}
pub fn from_fs_entry_ok(fs_entry: fs::DirEntry) -> Option<Self> {
let path_buf = fs_entry.path();
validate_spath_for_option(&path_buf)?;
Some(Self { path: path_buf })
}
pub fn from_walkdir_entry_ok(wd_entry: walkdir::DirEntry) -> Option<Self> {
let path = wd_entry.path();
validate_spath_for_option(path)?;
Some(Self {
path: wd_entry.into_path(),
})
}
}
impl SPath {
pub fn into_path_buf(self) -> PathBuf {
self.path
}
pub fn path(&self) -> &Path {
&self.path
}
}
impl SPath {
pub fn to_str(&self) -> &str {
self.path.to_str().unwrap_or_default()
}
pub fn file_name(&self) -> Option<&str> {
self.path.file_name().and_then(|n| n.to_str())
}
pub fn name(&self) -> &str {
self.file_name().unwrap_or_default()
}
pub fn file_stem(&self) -> Option<&str> {
self.path.file_stem().and_then(|n| n.to_str())
}
pub fn stem(&self) -> &str {
self.file_stem().unwrap_or_default()
}
pub fn extension(&self) -> Option<&str> {
self.path.extension().and_then(|os_str| os_str.to_str())
}
pub fn ext(&self) -> &str {
self.extension().unwrap_or_default()
}
pub fn is_dir(&self) -> bool {
self.path.is_dir()
}
pub fn is_file(&self) -> bool {
self.path.is_file()
}
pub fn exists(&self) -> bool {
self.path.exists()
}
pub fn modified(&self) -> Result<SystemTime> {
let path = self.path();
let metadata = fs::metadata(path).map_err(|ex| Error::CantGetMetadata((path, ex).into()))?;
let last_modified = metadata
.modified()
.map_err(|ex| Error::CantGetMetadataModified((path, ex).into()))?;
Ok(last_modified)
}
pub fn modified_us(&self) -> Result<i64> {
let modified = self.modified()?;
let since_the_epoch = modified
.duration_since(UNIX_EPOCH)
.map_err(Error::CantGetDurationSystemTimeError)?;
let modified_us = since_the_epoch.as_micros().min(i64::MAX as u128) as i64;
Ok(modified_us)
}
pub fn parent(&self) -> Option<SPath> {
self.path().parent().and_then(SPath::from_path_ok)
}
pub fn join(&self, leaf_path: impl AsRef<Path>) -> Result<SPath> {
let leaf_path = leaf_path.as_ref();
let joined = self.path().join(leaf_path);
validate_spath_for_result(&joined)?;
Ok(SPath { path: joined })
}
pub fn new_sibling(&self, leaf_path: impl AsRef<Path>) -> Result<SPath> {
let leaf_path = leaf_path.as_ref();
match self.path().parent() {
Some(parent_dir) => SPath::new(parent_dir.join(leaf_path)),
None => SPath::from_path(leaf_path),
}
}
}
impl AsRef<Path> for SPath {
fn as_ref(&self) -> &Path {
self.path.as_ref()
}
}
impl fmt::Display for SPath {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.to_str())
}
}
impl From<SPath> for String {
fn from(val: SPath) -> Self {
val.to_str().to_string()
}
}
impl From<&SPath> for String {
fn from(val: &SPath) -> Self {
val.to_str().to_string()
}
}
impl From<SFile> for SPath {
fn from(sfile: SFile) -> Self {
SPath { path: sfile.into() }
}
}
impl From<SPath> for PathBuf {
fn from(val: SPath) -> Self {
val.into_path_buf()
}
}
impl From<&SPath> for PathBuf {
fn from(val: &SPath) -> Self {
val.path.clone()
}
}
impl TryFrom<&str> for SPath {
type Error = Error;
fn try_from(path: &str) -> Result<SPath> {
let path = Path::new(path);
validate_spath_for_result(path)?;
Ok(Self {
path: path.to_path_buf(),
})
}
}
impl TryFrom<String> for SPath {
type Error = Error;
fn try_from(path: String) -> Result<SPath> {
SPath::try_from(path.as_str())
}
}
impl TryFrom<&String> for SPath {
type Error = Error;
fn try_from(path: &String) -> Result<SPath> {
SPath::try_from(path.as_str())
}
}
impl TryFrom<PathBuf> for SPath {
type Error = Error;
fn try_from(path_buf: PathBuf) -> Result<SPath> {
validate_spath_for_result(&path_buf)?;
Ok(Self { path: path_buf })
}
}
impl TryFrom<fs::DirEntry> for SPath {
type Error = Error;
fn try_from(fs_entry: fs::DirEntry) -> Result<SPath> {
let path_buf = fs_entry.path();
validate_spath_for_result(&path_buf)?;
Ok(Self { path: path_buf })
}
}
impl TryFrom<walkdir::DirEntry> for SPath {
type Error = Error;
fn try_from(wd_entry: walkdir::DirEntry) -> Result<SPath> {
let path = wd_entry.path();
validate_spath_for_result(path)?;
Ok(Self {
path: wd_entry.into_path(),
})
}
}
pub(crate) fn validate_spath_for_result(path: &Path) -> Result<()> {
if path.to_str().is_none() {
return Err(Error::PathNotUtf8(path.to_string_lossy().to_string()));
}
Ok(())
}
pub(crate) fn validate_spath_for_option(path: &Path) -> Option<()> {
path.to_str()?;
path.file_name()?;
Some(())
}