use anyhow::Result;
use ghee_lang::{Key, Namespace};
use serde::{Deserialize, Serialize};
use std::{
env::current_dir,
ffi::OsString,
path::{Component, Path, PathBuf},
};
use thiserror::Error;
use crate::{GheeErr, HIDDEN_PREFIX};
#[derive(Error, Debug)]
pub enum RelativizeErr {
#[error("Base path {base:?} does not prefix path {path:?}")]
DoesNotPrefix { base: PathBuf, path: PathBuf },
}
pub trait PathBufExt {
fn relative_to_curdir(&self) -> Result<PathBuf> {
let cur = current_dir().unwrap();
self.relativize(cur)
}
fn relative_to_curdir_if_possible(&self) -> PathBuf {
let cur = current_dir().unwrap();
self.relativize_if_possible(cur)
}
fn relativize<P: AsRef<Path>>(&self, base: P) -> Result<PathBuf>;
fn resolve_curdir<P: AsRef<Path>>(&self, new_curdir: P) -> Result<PathBuf>;
fn relativize_if_possible<P: AsRef<Path>>(&self, base: P) -> PathBuf;
fn resolve_curdir_if_possible<P: AsRef<Path>>(&self, new_curdir: P) -> PathBuf;
}
impl PathBufExt for PathBuf {
fn relativize<P: AsRef<Path>>(&self, base: P) -> Result<PathBuf> {
if !self.is_absolute() {
return Err(GheeErr::PathNotAbsolute(self.clone()).into());
}
let base = base.as_ref();
if !base.is_absolute() {
return Err(GheeErr::PathNotAbsolute(base.to_path_buf()).into());
}
let mut path_comps_it = self.components();
for base_comp in base.components() {
let path_comp = path_comps_it
.next()
.ok_or_else(|| RelativizeErr::DoesNotPrefix {
base: base.to_path_buf(),
path: self.clone(),
})?;
if path_comp != base_comp {
return Err(RelativizeErr::DoesNotPrefix {
base: base.to_path_buf(),
path: self.clone(),
}
.into());
}
}
let relpath_it = std::iter::once(Component::CurDir).chain(path_comps_it);
Ok(PathBuf::from_iter(relpath_it))
}
fn resolve_curdir<P: AsRef<Path>>(&self, new_curdir: P) -> Result<PathBuf> {
if !self.is_relative() {
return Err(GheeErr::PathNotRelative(self.clone()).into());
}
let it = new_curdir
.as_ref()
.components()
.chain(self.components().skip(1));
Ok(PathBuf::from_iter(it))
}
fn relativize_if_possible<P: AsRef<Path>>(&self, base: P) -> PathBuf {
self.relativize(base).unwrap_or_else(|_e| self.clone())
}
fn resolve_curdir_if_possible<P: AsRef<Path>>(&self, new_curdir: P) -> PathBuf {
self.resolve_curdir(new_curdir)
.unwrap_or_else(|_e| self.clone())
}
}
#[derive(Clone, Serialize, Deserialize)]
pub struct PathBufs {
pub rel: Option<PathBuf>,
pub abs: PathBuf,
pub curdir: PathBuf,
}
impl PathBufs {
pub fn new(rel: Option<PathBuf>, abs: PathBuf) -> Self {
debug_assert!(abs.is_absolute());
let curdir = abs.relative_to_curdir().unwrap();
Self { rel, abs, curdir }
}
pub fn rel_else_abs(&self) -> &PathBuf {
self.rel.as_ref().unwrap_or(&self.abs)
}
}
#[derive(Error, Debug)]
pub enum SubIdxPathErr {
#[error("The provided key was empty")]
EmptyKey,
}
pub fn sub_idx_path(view_of: &PathBuf, key: &Key) -> Result<PathBuf> {
if key.is_empty() {
return Err(SubIdxPathErr::EmptyKey.into());
}
let mut sub_idx_dirname = OsString::new();
for k in key.iter() {
sub_idx_dirname.push(HIDDEN_PREFIX);
if k.namespace == Namespace::User {
sub_idx_dirname.push(k.attr.clone());
} else {
sub_idx_dirname.push(k.to_string());
}
}
let mut sub_idx_path = view_of.clone();
sub_idx_path.push(sub_idx_dirname);
Ok(sub_idx_path)
}
pub fn table_snapshots_path(table: &PathBuf) -> PathBuf {
let mut snapshot_path = table.clone();
snapshot_path.push(format!("{}snapshots", HIDDEN_PREFIX));
snapshot_path
}
pub fn table_snapshot_path<S: ToString>(table: &PathBuf, snapshot_uuid: S) -> PathBuf {
let mut p = table_snapshots_path(table);
p.push(snapshot_uuid.to_string());
p
}
#[cfg(test)]
mod test {
use std::path::PathBuf;
use crate::paths::PathBufExt;
#[test]
fn test_relativize() {
let p1 = PathBuf::from("/the/quick/brown/fox");
let p2 = PathBuf::from("/the/quick/brown/fox/jumped/over/the/lazy/dog");
let p3 = p2.relativize(&p1).unwrap();
assert_eq!(p3, PathBuf::from("./jumped/over/the/lazy/dog"));
}
#[test]
fn test_relativize_relative_base() {
let p1 = PathBuf::from("./the/quick/brown/fox");
let p2 = PathBuf::from("/the/quick/brown/fox/jumped/over/the/lazy/dog");
assert!(p2.relativize(&p1).is_err());
}
#[test]
fn test_relativize_relative_path() {
let p1 = PathBuf::from("/the/quick/brown/fox");
let p2 = PathBuf::from("./the/quick/brown/fox/jumped/over/the/lazy/dog");
assert!(p2.relativize(&p1).is_err());
}
#[test]
fn test_resolve_curdir() {
let p1 = PathBuf::from("/the/quick/brown/fox");
let p2 = PathBuf::from("/the/quick/brown/fox/jumped/over/the/lazy/dog");
let p3 = PathBuf::from("/jumped/over/the/lazy/dog");
let p4 = PathBuf::from("./jumped/over/the/lazy/dog");
assert!(p3.resolve_curdir(&p1).is_err());
assert_eq!(p4.resolve_curdir(p1).unwrap(), p2);
}
}