use crate::error::PathError;
use crate::fs::normalize_path;
use derivative::Derivative;
use error_stack::{Report, Result, ResultExt};
use std::fs;
use std::path::{Path, PathBuf};
use super::TxtppPath;
pub const TXTPP_EXT: &str = "txtpp";
#[derive(Debug, Clone, Derivative)]
#[derivative(PartialEq, Eq, Hash)]
pub struct AbsPath {
#[derivative(PartialEq = "ignore", Hash = "ignore")]
b: PathBuf,
p: PathBuf,
}
impl AbsPath {
#[inline]
pub fn as_path_buf(&self) -> &PathBuf {
&self.p
}
#[inline]
pub fn into_path_buf(self) -> PathBuf {
self.p
}
#[inline]
pub fn as_path(&self) -> &Path {
self.p.as_path()
}
}
impl From<AbsPath> for PathBuf {
#[inline]
fn from(p: AbsPath) -> Self {
p.p
}
}
impl AsRef<PathBuf> for AbsPath {
#[inline]
fn as_ref(&self) -> &PathBuf {
self.as_path_buf()
}
}
impl AsRef<Path> for AbsPath {
#[inline]
fn as_ref(&self) -> &Path {
self.as_path()
}
}
impl AbsPath {
pub fn new(p: PathBuf) -> Self {
Self { b: p.clone(), p }
}
pub fn create_base(p: PathBuf) -> Result<Self, PathError> {
let p_abs = Self::make_abs(p)?;
Ok(Self {
b: p_abs.clone(),
p: p_abs,
})
}
pub fn share_base(&self, p: PathBuf) -> Result<Self, PathError> {
Ok(Self {
b: self.b.clone(),
p: Self::make_abs(p)?,
})
}
fn make_abs(p: PathBuf) -> Result<PathBuf, PathError> {
if !p.exists() {
return Err(Report::new(PathError::from(&p)).attach_printable("path does not exist"));
}
p.canonicalize()
.change_context_lazy(|| PathError::from(&p))
.attach_printable("cannot convert path to absolute")
}
pub fn try_resolve<P>(&self, ext: &P, create: bool) -> Result<Self, PathError>
where
P: AsRef<Path>,
{
let path: &Path = ext.as_ref();
let path_abs = if path.is_absolute() {
path.to_path_buf()
} else {
self.p.join(path)
};
if !path_abs.exists() && create {
create_file(&path_abs)?;
}
self.share_base(path_abs)
}
pub fn parent(&self) -> Result<Self, PathError> {
let p_parent_abs = match self.p.parent() {
Some(p) => p,
None => {
return Err(Report::new(PathError::from(self))
.attach_printable("cannot get parent directory"))
}
};
self.share_base(p_parent_abs.to_path_buf())
}
pub fn trim_txtpp(&self) -> Result<String, PathError> {
let p = self.p.remove_txtpp()?;
Ok(path_string_from_base(&self.b, &p))
}
}
impl std::fmt::Display for AbsPath {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", path_string_from_base(&self.b, &self.p))
}
}
fn create_file<P>(p: &P) -> Result<(), PathError>
where
P: AsRef<Path>,
{
log::debug!("creating file: {}", p.as_ref().display());
fs::File::create(p)
.change_context_lazy(|| PathError::from(p))
.attach_printable("cannot create file")?;
Ok(())
}
fn path_string_from_base<P>(base: &P, path: &P) -> String
where
P: AsRef<Path>,
{
let base = base.as_ref();
let path = path.as_ref();
let path = if base == path {
path
} else {
match path.strip_prefix(base) {
Ok(p) => p,
Err(_) => path,
}
};
normalize_path(&path.display().to_string()).to_string()
}