use crate::color::{ColoredString, Colors, Elem};
use std::fs::Metadata;
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[cfg_attr(windows, allow(dead_code))]
pub enum FileType {
BlockDevice,
CharDevice,
Directory { uid: bool },
File { uid: bool, exec: bool },
SymLink { is_dir: bool },
Pipe,
Socket,
Special,
}
impl FileType {
#[cfg(windows)]
const EXECUTABLE_EXTENSIONS: &'static [&'static str] = &["exe", "msi", "bat", "ps1"];
#[cfg(unix)]
pub fn new(
meta: &Metadata,
symlink_meta: Option<&Metadata>,
permissions: &crate::meta::Permissions,
) -> Self {
use std::os::unix::fs::FileTypeExt;
let file_type = meta.file_type();
if file_type.is_file() {
FileType::File {
exec: permissions.is_executable(),
uid: permissions.setuid,
}
} else if file_type.is_dir() {
FileType::Directory {
uid: permissions.setuid,
}
} else if file_type.is_fifo() {
FileType::Pipe
} else if file_type.is_symlink() {
FileType::SymLink {
is_dir: symlink_meta.map(|m| m.is_dir()).unwrap_or_default(),
}
} else if file_type.is_char_device() {
FileType::CharDevice
} else if file_type.is_block_device() {
FileType::BlockDevice
} else if file_type.is_socket() {
FileType::Socket
} else {
FileType::Special
}
}
#[cfg(windows)]
pub fn new(meta: &Metadata, symlink_meta: Option<&Metadata>, path: &std::path::Path) -> Self {
let file_type = meta.file_type();
if file_type.is_file() {
let exec = path
.extension()
.map(|ext| {
Self::EXECUTABLE_EXTENSIONS
.iter()
.map(std::ffi::OsStr::new)
.any(|exec_ext| ext == exec_ext)
})
.unwrap_or(false);
FileType::File { exec, uid: false }
} else if file_type.is_dir() {
FileType::Directory { uid: false }
} else if file_type.is_symlink() {
FileType::SymLink {
is_dir: symlink_meta.map(|m| m.is_dir()).unwrap_or_default(),
}
} else {
FileType::Special
}
}
pub fn is_dirlike(self) -> bool {
matches!(
self,
FileType::Directory { .. } | FileType::SymLink { is_dir: true }
)
}
}
impl FileType {
pub fn render(self, colors: &Colors) -> ColoredString {
match self {
FileType::File { exec, .. } => colors.colorize('.', &Elem::File { exec, uid: false }),
FileType::Directory { .. } => colors.colorize('d', &Elem::Dir { uid: false }),
FileType::Pipe => colors.colorize('|', &Elem::Pipe),
FileType::SymLink { .. } => colors.colorize('l', &Elem::SymLink),
FileType::BlockDevice => colors.colorize('b', &Elem::BlockDevice),
FileType::CharDevice => colors.colorize('c', &Elem::CharDevice),
FileType::Socket => colors.colorize('s', &Elem::Socket),
FileType::Special => colors.colorize('?', &Elem::Special),
}
}
}
#[cfg(test)]
mod test {
use super::FileType;
use crate::color::{Colors, ThemeOption};
#[cfg(unix)]
use crate::flags::PermissionFlag;
#[cfg(unix)]
use crate::meta::Permissions;
#[cfg(unix)]
use crate::meta::permissions_or_attributes::PermissionsOrAttributes;
use crossterm::style::{Color, Stylize};
use std::fs::File;
#[cfg(unix)]
use std::os::unix::fs::symlink;
#[cfg(unix)]
use std::os::unix::net::UnixListener;
#[cfg(unix)]
use std::process::Command;
use tempfile::tempdir;
#[test]
#[cfg(unix)] fn test_file_type() {
let tmp_dir = tempdir().expect("failed to create temp dir");
let file_path = tmp_dir.path().join("file.txt");
File::create(&file_path).expect("failed to create file");
let meta = file_path.metadata().expect("failed to get metas");
let colors = Colors::new(ThemeOption::NoLscolors);
let file_type = FileType::new(&meta, None, &Permissions::from(&meta));
assert_eq!(
".".to_string().with(Color::AnsiValue(184)),
file_type.render(&colors)
);
}
#[test]
fn test_dir_type() {
let tmp_dir = tempdir().expect("failed to create temp dir");
#[cfg(not(windows))]
let meta = crate::meta::Meta::from_path(tmp_dir.path(), false, PermissionFlag::Rwx)
.expect("failed to get tempdir path");
let metadata = tmp_dir.path().metadata().expect("failed to get metas");
let colors = Colors::new(ThemeOption::NoLscolors);
#[cfg(not(windows))]
let file_type = match meta.permissions_or_attributes {
Some(PermissionsOrAttributes::Permissions(permissions)) => {
FileType::new(&metadata, None, &permissions)
}
_ => panic!("unexpected"),
};
#[cfg(windows)]
let file_type = FileType::new(&metadata, None, tmp_dir.path());
assert_eq!(
"d".to_string().with(Color::AnsiValue(33)),
file_type.render(&colors)
);
}
#[test]
#[cfg(unix)] fn test_symlink_type_file() {
let tmp_dir = tempdir().expect("failed to create temp dir");
let file_path = tmp_dir.path().join("file.tmp");
File::create(&file_path).expect("failed to create file");
let symlink_path = tmp_dir.path().join("target.tmp");
symlink(&file_path, &symlink_path).expect("failed to create symlink");
let meta = symlink_path
.symlink_metadata()
.expect("failed to get metas");
let colors = Colors::new(ThemeOption::NoLscolors);
let file_type = FileType::new(&meta, Some(&meta), &Permissions::from(&meta));
assert_eq!(
"l".to_string().with(Color::AnsiValue(44)),
file_type.render(&colors)
);
}
#[test]
#[cfg(unix)]
fn test_symlink_type_dir() {
let tmp_dir = tempdir().expect("failed to create temp dir");
let dir_path = tmp_dir.path().join("dir.d");
std::fs::create_dir(&dir_path).expect("failed to create dir");
let symlink_path = tmp_dir.path().join("target.d");
symlink(&dir_path, &symlink_path).expect("failed to create symlink");
let meta = symlink_path
.symlink_metadata()
.expect("failed to get metas");
let colors = Colors::new(ThemeOption::NoLscolors);
let file_type = FileType::new(&meta, Some(&meta), &Permissions::from(&meta));
assert_eq!(
"l".to_string().with(Color::AnsiValue(44)),
file_type.render(&colors)
);
}
#[test]
#[cfg(unix)] fn test_pipe_type() {
let tmp_dir = tempdir().expect("failed to create temp dir");
let pipe_path = tmp_dir.path().join("pipe.tmp");
let success = Command::new("mkfifo")
.arg(&pipe_path)
.status()
.expect("failed to exec mkfifo")
.success();
assert!(success, "failed to exec mkfifo");
let meta = pipe_path.metadata().expect("failed to get metas");
let colors = Colors::new(ThemeOption::NoLscolors);
let file_type = FileType::new(&meta, None, &Permissions::from(&meta));
assert_eq!(
"|".to_string().with(Color::AnsiValue(44)),
file_type.render(&colors)
);
}
#[test]
#[cfg(feature = "sudo")]
fn test_char_device_type() {
let tmp_dir = tempdir().expect("failed to create temp dir");
let char_device_path = tmp_dir.path().join("char-device.tmp");
let success = Command::new("sudo")
.arg("mknod")
.arg(&char_device_path)
.arg("c")
.arg("89")
.arg("1")
.status()
.expect("failed to exec mknod")
.success();
assert!(success, "failed to exec mknod");
let meta = char_device_path.metadata().expect("failed to get metas");
let colors = Colors::new(ThemeOption::NoLscolors);
let file_type = FileType::new(&meta, None, &Permissions::from(&meta));
assert_eq!(
"c".to_string().with(Color::AnsiValue(44)),
file_type.render(&colors)
);
}
#[test]
#[cfg(unix)] fn test_socket_type() {
let tmp_dir = tempdir().expect("failed to create temp dir");
let socket_path = tmp_dir.path().join("socket.tmp");
UnixListener::bind(&socket_path).expect("failed to create the socket");
let meta = socket_path.metadata().expect("failed to get metas");
let colors = Colors::new(ThemeOption::NoLscolors);
let file_type = FileType::new(&meta, None, &Permissions::from(&meta));
assert_eq!(
"s".to_string().with(Color::AnsiValue(44)),
file_type.render(&colors)
);
}
#[cfg(windows)]
#[test]
fn test_file_executable() {
let tmp_dir = tempdir().expect("failed to create temp dir");
for ext in FileType::EXECUTABLE_EXTENSIONS {
let file_path = tmp_dir.path().join(format!("file.{ext}"));
File::create(&file_path).expect("failed to create file");
let meta = file_path.metadata().expect("failed to get metas");
let colors = Colors::new(ThemeOption::NoLscolors);
let file_type = FileType::new(&meta, None, &file_path);
assert_eq!(
".".to_string().with(Color::AnsiValue(40)),
file_type.render(&colors)
);
}
}
#[cfg(windows)]
#[test]
fn test_file_not_executable() {
let tmp_dir = tempdir().expect("failed to create temp dir");
let file_path = tmp_dir.path().join("file.txt");
File::create(&file_path).expect("failed to create file");
let meta = file_path.metadata().expect("failed to get metas");
let colors = Colors::new(ThemeOption::NoLscolors);
let file_type = FileType::new(&meta, None, &file_path);
assert_eq!(
".".to_string().with(Color::AnsiValue(184)),
file_type.render(&colors)
);
}
}