#[cfg(unix)]
use std::os::unix::fs::MetadataExt;
use std::{fs, io};
use camino::{Utf8Path, Utf8PathBuf};
#[cfg(not(unix))]
use is_executable::IsExecutable;
#[derive(Debug, Clone)]
pub enum FileSystemMetadata {
Directory,
File { executable: bool, length: u64 },
Symlink { target_path: Utf8PathBuf },
}
pub trait FileSystem {
type File: io::Read;
fn metadata(&self, path: &Utf8Path) -> io::Result<FileSystemMetadata>;
fn read_dir(&self, path: &Utf8Path) -> io::Result<Vec<String>>;
fn open(&self, path: &Utf8Path) -> io::Result<Self::File>;
}
#[derive(Debug, Clone, Copy, Default)]
pub struct NativeFileSystem {}
impl FileSystem for NativeFileSystem {
type File = fs::File;
fn open(&self, path: &Utf8Path) -> io::Result<Self::File> {
fs::File::open(path)
}
fn read_dir(&self, path: &Utf8Path) -> io::Result<Vec<String>> {
path.read_dir_utf8()?
.map(|i| i.map(|p| p.file_name().into()))
.collect()
}
fn metadata(&self, path: &Utf8Path) -> io::Result<FileSystemMetadata> {
let metadata = path.symlink_metadata()?;
if metadata.is_dir() {
Ok(FileSystemMetadata::Directory)
} else if metadata.is_symlink() {
Ok(FileSystemMetadata::Symlink {
target_path: path.read_link_utf8()?,
})
} else if metadata.is_file() {
#[cfg(unix)]
let executable = metadata.mode() & 0o111 != 0;
#[cfg(not(unix))]
let executable = path.as_std_path().is_executable();
Ok(FileSystemMetadata::File {
executable,
length: metadata.len(),
})
} else {
Err(io::Error::other(format!("unknown file type {path}")))
}
}
}