use super::FsError;
use super::FsResult;
use super::fs_ext;
use super::path_ext;
use crate::prelude::*;
use path_clean::PathClean;
use std::path::Path;
use std::path::PathBuf;
use std::str::FromStr;
#[macro_export]
macro_rules! abs_file {
() => {
AbsPathBuf::new_workspace_rel(file!()).unwrap()
};
}
#[derive(
Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, bevy::reflect::Reflect,
)]
pub struct AbsPathBuf(pub PathBuf);
impl Default for AbsPathBuf {
fn default() -> Self {
Self::new(std::env::current_dir().unwrap()).unwrap()
}
}
impl std::fmt::Display for AbsPathBuf {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0.to_string_lossy())
}
}
impl AbsPathBuf {
pub fn new(path: impl AsRef<Path>) -> FsResult<Self> {
let path = path.as_ref();
let path = path_ext::absolute(path)?;
let path = path.clean();
Ok(Self(path))
}
pub fn join(&self, path: impl AsRef<Path>) -> Self {
let path = path_ext::join_relative(&self.0, path);
Self(path)
}
pub fn parent(&self) -> Option<Self> {
self.0.parent().map(Self::new_unchecked)
}
pub fn with_extension(mut self, ext: &str) -> Self {
self.0.set_extension(ext);
self
}
pub fn join_checked(&self, path: impl AsRef<Path>) -> FsResult<Self> {
let path = self.0.join(path);
Self::new(path)
}
pub fn new_workspace_rel(path: impl AsRef<Path>) -> FsResult<Self> {
Self::new(fs_ext::workspace_root()).map(|abs| abs.join(path))
}
pub fn new_manifest_rel(path: impl AsRef<Path>) -> FsResult<Self> {
std::env::var("CARGO_MANIFEST_DIR")
.unwrap()
.xref()
.xmap(Path::new)
.join(path)
.xmap(Self::new)
}
pub fn new_unchecked(path: impl AsRef<Path>) -> Self {
let path = path.as_ref().clean();
Self(path)
}
pub fn into_ws_path(&self) -> FsResult<WsPathBuf> {
let path = path_ext::strip_prefix(&self.0, &fs_ext::workspace_root())?;
Ok(WsPathBuf::new(path))
}
}
impl FromStr for AbsPathBuf {
type Err = FsError;
fn from_str(val: &str) -> Result<Self, Self::Err> { Self::new(val) }
}
impl AsRef<Path> for AbsPathBuf {
fn as_ref(&self) -> &Path { self.0.as_ref() }
}
impl Into<PathBuf> for AbsPathBuf {
fn into(self) -> PathBuf { self.0 }
}
impl Into<PathBuf> for &AbsPathBuf {
fn into(self) -> PathBuf { self.0.to_path_buf() }
}
impl std::ops::Deref for AbsPathBuf {
type Target = PathBuf;
fn deref(&self) -> &Self::Target { &self.0 }
}
impl std::ops::DerefMut for AbsPathBuf {
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 }
}
#[cfg(feature = "serde")]
impl serde::Serialize for AbsPathBuf {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let workspace_root = fs_ext::workspace_root();
let rel_path = pathdiff::diff_paths(&self.0, &workspace_root)
.ok_or_else(|| {
serde::ser::Error::custom(
"failed to make path relative to workspace root",
)
})?;
rel_path.serialize(serializer)
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for AbsPathBuf {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let rel_path = PathBuf::deserialize(deserializer)?;
let abs_path = fs_ext::workspace_root().join(rel_path);
let abs_path = AbsPathBuf::new(abs_path).map_err(|err| {
serde::de::Error::custom(format!(
"failed to create AbsPathBuf: {}",
err
))
})?;
Ok(abs_path)
}
}
#[cfg(test)]
#[cfg(not(target_arch = "wasm32"))]
mod test {
use crate::prelude::*;
#[test]
fn canonicalizes() { let _buf = AbsPathBuf::new("Cargo.toml").unwrap(); }
#[test]
fn resolves_relative_non_exist() {
#[cfg(not(target_os = "windows"))]
let expected = "foo/bar/boo.rs";
#[cfg(target_os = "windows")]
let expected = "foo\\bar\\boo.rs";
assert!(
AbsPathBuf::new("foo/bar/bazz/../boo.rs")
.unwrap()
.to_string_lossy()
.ends_with(expected)
);
}
#[test]
fn abs_file() {
assert!(abs_file!().to_string_lossy().ends_with("abs_path_buf.rs"));
}
#[test]
fn workspace_rel() {
let file = file!();
let buf = AbsPathBuf::new_workspace_rel(file).unwrap();
assert_eq!(buf, abs_file!());
let workspace_rel = buf.into_ws_path().unwrap();
assert_eq!(workspace_rel.to_string_lossy(), file);
}
#[test]
fn workspace_rel_leading_slash() {
let file = file!();
let buf = AbsPathBuf::new_workspace_rel(format!("/{file}")).unwrap();
assert_eq!(buf, abs_file!());
let workspace_rel = buf.into_ws_path().unwrap();
assert_eq!(workspace_rel.to_string_lossy(), file);
}
#[test]
fn manifest_rel() {
let buf =
AbsPathBuf::new_manifest_rel("src/path_utils/abs_path_buf.rs")
.unwrap();
assert_eq!(buf, abs_file!());
}
#[test]
#[cfg(feature = "serde")]
fn serde_roundtrip() {
let original = abs_file!();
let serialized = serde_json::to_string(&original).unwrap();
let deserialized: AbsPathBuf =
serde_json::from_str(&serialized).unwrap();
assert_eq!(original, deserialized);
}
}