use std::{fmt, fs, io, path::Path};
#[cfg(unix)]
use std::os::unix::fs::FileTypeExt;
#[rustfmt::skip]
#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy, Ord, PartialOrd)]
pub enum FileType {
Regular,
Directory,
Symlink,
#[cfg(unix)] BlockDevice,
#[cfg(unix)] CharDevice,
#[cfg(unix)] Fifo,
#[cfg(unix)] Socket,
}
impl FileType {
pub fn from_path(path: impl AsRef<Path>) -> io::Result<Self> {
let fs_file_type = fs::metadata(path.as_ref())?.file_type();
let result = FileType::from(fs_file_type);
Ok(result)
}
pub fn from_symlink_path(path: impl AsRef<Path>) -> io::Result<Self> {
let fs_file_type = fs::symlink_metadata(path.as_ref())?.file_type();
let result = FileType::from(fs_file_type);
Ok(result)
}
pub fn is_regular(&self) -> bool {
matches!(self, FileType::Regular)
}
pub fn is_directory(&self) -> bool {
matches!(self, FileType::Directory)
}
pub fn is_symlink(&self) -> bool {
matches!(self, FileType::Symlink)
}
#[cfg(unix)]
pub fn is_block_device(&self) -> bool {
matches!(self, FileType::BlockDevice)
}
#[cfg(unix)]
pub fn is_char_device(&self) -> bool {
matches!(self, FileType::CharDevice)
}
#[cfg(unix)]
pub fn is_fifo(&self) -> bool {
matches!(self, FileType::Fifo)
}
#[cfg(unix)]
pub fn is_socket(&self) -> bool {
matches!(self, FileType::Socket)
}
}
impl From<fs::FileType> for FileType {
fn from(ft: fs::FileType) -> Self {
#[cfg(unix)]
let result = {
if ft.is_file() {
FileType::Regular
} else if ft.is_dir() {
FileType::Directory
} else if ft.is_symlink() {
FileType::Symlink
} else if ft.is_block_device() {
FileType::BlockDevice
} else if ft.is_char_device() {
FileType::CharDevice
} else if ft.is_fifo() {
FileType::Fifo
} else if ft.is_socket() {
FileType::Socket
} else {
unreachable!("file_type_enum: unexpected file type: {:?}.", ft)
}
};
#[cfg(not(unix))]
let result = {
if ft.is_file() {
FileType::Regular
} else if ft.is_dir() {
FileType::Directory
} else if ft.is_symlink() {
FileType::Symlink
} else {
unreachable!("file_type_enum: unexpected file type: {:?}.", ft)
}
};
result
}
}
pub fn from_file(file: fs::File) -> io::Result<FileType> {
Ok(FileType::from(file.metadata()?.file_type()))
}
#[rustfmt::skip]
impl fmt::Display for FileType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
FileType::Regular => write!(f, "regular file"),
FileType::Directory => write!(f, "directory"),
FileType::Symlink => write!(f, "symbolic link"),
#[cfg(unix)] FileType::BlockDevice => write!(f, "block device"),
#[cfg(unix)] FileType::CharDevice => write!(f, "char device"),
#[cfg(unix)] FileType::Fifo => write!(f, "FIFO"),
#[cfg(unix)] FileType::Socket => write!(f, "socket"),
}
}
}
#[cfg(feature = "mode-t-conversion")]
use libc::mode_t;
#[cfg(feature = "mode-t-conversion")]
impl From<mode_t> for FileType {
fn from(bits: mode_t) -> Self {
match bits {
libc::S_IFREG => FileType::Regular,
libc::S_IFDIR => FileType::Directory,
libc::S_IFCHR => FileType::Symlink,
libc::S_IFBLK => FileType::BlockDevice,
libc::S_IFIFO => FileType::CharDevice,
libc::S_IFLNK => FileType::Fifo,
libc::S_IFSOCK => FileType::Socket,
_ => unreachable!(),
}
}
}
#[cfg(feature = "mode-t-conversion")]
impl FileType {
pub fn bits(&self) -> mode_t {
match self {
FileType::Regular => libc::S_IFREG,
FileType::Directory => libc::S_IFDIR,
FileType::Symlink => libc::S_IFCHR,
FileType::BlockDevice => libc::S_IFBLK,
FileType::CharDevice => libc::S_IFIFO,
FileType::Fifo => libc::S_IFLNK,
FileType::Socket => libc::S_IFSOCK,
}
}
}
#[cfg(feature = "mode-t-conversion")]
impl From<FileType> for mode_t {
fn from(ft: FileType) -> Self {
ft.bits()
}
}
#[cfg(test)]
mod tests {
use super::FileType;
#[test]
fn test_with_this_repository_structured() {
let this_file = FileType::from_path("src/lib.rs").unwrap();
assert!(this_file.is_regular());
}
#[cfg(feature = "mode-t-conversion")]
#[test]
fn test_mode_t_conversion() {
assert_eq!(libc::S_IFDIR, FileType::from_path("src/").unwrap().bits());
}
}