use std::{
fmt::{Display, Formatter},
path::{Path, PathBuf},
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct AbsPath(PathBuf);
impl AbsPath {
pub(crate) fn new(path: impl AsRef<Path>) -> Result<Self, AbsPathError> {
let path = path.as_ref();
if path.as_os_str().is_empty() {
return Err(AbsPathError::PathIsEmpty(path.to_owned()));
}
if path.is_relative() {
let absolute = std::path::absolute(path)
.map_err(|error| AbsPathError::CannotReadCWD(path.to_owned(), error))?;
Ok(Self(absolute))
} else {
Ok(Self(path.to_owned()))
}
}
pub(crate) fn read_dir(&self) -> Result<Vec<AbsPath>, std::io::Error> {
std::fs::read_dir(&self.0)?
.map(|entry| entry.map(|e| e.path()).map(AbsPath))
.collect::<Result<Vec<AbsPath>, std::io::Error>>()
}
pub(crate) fn parent(&self) -> Option<Self> {
let parent = self.0.parent()?;
Some(AbsPath(parent.to_path_buf()))
}
pub(crate) fn each_parent(&self) -> AbsParentDirs {
AbsParentDirs {
current: self.parent(),
}
}
#[allow(dead_code)]
pub(crate) fn root(&self) -> Self {
self.each_parent().last().unwrap_or_else(|| self.clone())
}
}
impl Display for AbsPath {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "`{}`", self.0.display())
}
}
impl AsRef<Path> for AbsPath {
fn as_ref(&self) -> &Path {
self.0.as_ref()
}
}
pub(crate) fn try_readlink(absolute: &AbsPath) -> Result<Option<AbsPath>, std::io::Error> {
let path = absolute.as_ref();
if path.is_symlink() {
std::fs::read_link(path)
.map(|target| {
if target.is_relative() {
AbsPath(absolute.as_ref().join(target))
} else {
AbsPath(target)
}
})
.map(Some)
} else {
Ok(None)
}
}
pub(crate) struct AbsParentDirs {
current: Option<AbsPath>,
}
impl Iterator for AbsParentDirs {
type Item = AbsPath;
fn next(&mut self) -> Option<Self::Item> {
if let Some(current) = self.current.take() {
self.current = current.parent();
Some(current)
} else {
None
}
}
}
#[derive(Debug)]
pub(crate) enum AbsPathError {
PathIsEmpty(PathBuf),
CannotReadCWD(PathBuf, std::io::Error),
}
impl AbsPathError {
#[allow(dead_code)]
pub(crate) fn path(&self) -> &Path {
match self {
AbsPathError::PathIsEmpty(path) => path.as_ref(),
AbsPathError::CannotReadCWD(path, _) => path.as_ref(),
}
}
}