use std::{cmp, ffi::OsStr, fmt::{self, Debug, Formatter}, hash::{Hash, Hasher}, ops::Deref, path::PathBuf}; use anyhow::Result; use crate::{loc::Loc, path::{PathBufLike, PathLike}, url::{Uri, Urn}}; #[derive(Clone, Default, Eq)] pub struct LocBuf { pub(super) inner: P, pub(super) uri: usize, pub(super) urn: usize, } impl

Deref for LocBuf

where P: PathBufLike, { type Target = P; fn deref(&self) -> &Self::Target { &self.inner } } impl

AsRef for LocBuf

where P: PathBufLike, { fn as_ref(&self) -> &P::Borrowed { self.inner.as_ref() } } impl

PartialEq for LocBuf

where P: PathBufLike + PartialEq, { fn eq(&self, other: &Self) -> bool { self.inner == other.inner } } impl

Ord for LocBuf

where P: PathBufLike + Ord, { fn cmp(&self, other: &Self) -> cmp::Ordering { self.inner.cmp(&other.inner) } } impl

PartialOrd for LocBuf

where P: PathBufLike + PartialOrd, { fn partial_cmp(&self, other: &Self) -> Option { self.inner.partial_cmp(&other.inner) } } // --- Hash impl

Hash for LocBuf

where P: PathBufLike, P::Borrowed: Hash, { fn hash(&self, state: &mut H) { self.as_loc().hash(state) } } impl

Debug for LocBuf

where P: PathBufLike + Debug, P::Borrowed: Debug, { fn fmt(&self, f: &mut Formatter) -> fmt::Result { f.debug_struct("LocBuf") .field("path", &self.inner) .field("uri", &self.uri()) .field("urn", &self.urn()) .finish() } } impl

From

for LocBuf

where P: PathBufLike, { fn from(path: P) -> Self { let Loc { inner, uri, urn } = Loc::from(&path); let len = inner.len(); let mut bytes = path.into_encoded_bytes(); bytes.truncate(len); Self { inner: unsafe { P::from_encoded_bytes(bytes) }, uri, urn } } } impl> From<&T> for LocBuf { fn from(value: &T) -> Self { Self::from(PathBuf::from(value)) } } impl

LocBuf

where P: PathBufLike, { pub fn new(path: P, base: &T, trail: &T) -> Self where T: AsRef + ?Sized, { let loc = Self::from(path); let Loc { inner, uri, urn } = Loc::new(&loc.inner, base, trail); debug_assert!(inner.encoded_bytes() == loc.inner.encoded_bytes()); Self { inner: loc.inner, uri, urn } } pub fn with(path: P, uri: usize, urn: usize) -> Result where P::Borrowed: PathLike, { let loc = Self::from(path); let Loc { inner, uri, urn } = Loc::with(&loc.inner, uri, urn)?; debug_assert!(inner.encoded_bytes() == loc.inner.encoded_bytes()); Ok(Self { inner: loc.inner, uri, urn }) } // FIXME: use `LocBuf::empty()` when Rust 1.91.0 released // pub const fn empty() -> Self { Self { inner: PathBuf::new(), uri: 0, urn: 0 } // } pub fn zeroed(path: T) -> Self where T: Into

, { let loc = Self::from(path.into()); let Loc { inner, uri, urn } = Loc::zeroed(&loc.inner); debug_assert!(inner.encoded_bytes() == loc.inner.encoded_bytes()); Self { inner: loc.inner, uri, urn } } pub fn floated(path: T, base: &U) -> Self where T: Into

, U: AsRef + ?Sized, { let loc = Self::from(path.into()); let Loc { inner, uri, urn } = Loc::floated(&loc.inner, base); debug_assert!(inner.encoded_bytes() == loc.inner.encoded_bytes()); Self { inner: loc.inner, uri, urn } } #[inline] pub fn as_loc<'a>(&'a self) -> Loc<'a, P::Borrowed> { Loc { inner: self.inner.as_ref(), uri: self.uri, urn: self.urn } } #[inline] pub fn to_path(&self) -> P where P: Clone, { self.inner.clone() } #[inline] pub fn into_path(self) -> P { self.inner } pub fn set_name(&mut self, name: T) where T: AsRef, { let old = self.inner.len(); self.mutate(|path| path.set_file_name(name)); let new = self.len(); if new == old { return; } if self.uri != 0 { if new > old { self.uri += new - old; } else { self.uri -= old - new; } } if self.urn != 0 { if new > old { self.urn += new - old; } else { self.urn -= old - new; } } } #[inline] pub fn rebase(&self, base: &P::Borrowed) -> Self where ::Owned: Into, { let mut loc: Self = base.join(self.uri()).into(); (loc.uri, loc.urn) = (self.uri, self.urn); loc } #[inline] fn mutate(&mut self, f: F) where P: Default, { let mut inner = std::mem::take(&mut self.inner); f(&mut inner); self.inner = Self::from(inner).inner; } } // FIXME: macro impl

LocBuf

where P: PathBufLike, { #[inline] pub fn uri(&self) -> &Uri { self.as_loc().uri() } #[inline] pub fn urn(&self) -> &Urn { self.as_loc().urn() } #[inline] pub fn base(&self) -> &Urn { self.as_loc().base() } #[inline] pub fn has_base(&self) -> bool { self.as_loc().has_base() } #[inline] pub fn trail(&self) -> &Urn { self.as_loc().trail() } #[inline] pub fn has_trail(&self) -> bool { self.as_loc().has_trail() } #[inline] pub fn name(&self) -> Option<&::Inner> { self.as_loc().name() } #[inline] pub fn stem(&self) -> Option<&::Inner> { self.as_loc().stem() } #[inline] pub fn ext(&self) -> Option<&::Inner> { self.as_loc().ext() } } #[cfg(test)] mod tests { use std::path::Path; use super::*; use crate::url::{UrlBuf, UrlLike}; #[test] fn test_new() { let loc: LocBuf = Path::new("/").into(); assert_eq!(loc.uri().as_os_str(), OsStr::new("/")); assert_eq!(loc.urn().as_os_str(), OsStr::new("")); assert_eq!(loc.name(), None); assert_eq!(loc.base().as_os_str(), OsStr::new("")); assert_eq!(loc.trail().as_os_str(), OsStr::new("/")); let loc: LocBuf = Path::new("/root").into(); assert_eq!(loc.uri().as_os_str(), OsStr::new("root")); assert_eq!(loc.urn().as_os_str(), OsStr::new("root")); assert_eq!(loc.name().unwrap(), OsStr::new("root")); assert_eq!(loc.base().as_os_str(), OsStr::new("/")); assert_eq!(loc.trail().as_os_str(), OsStr::new("/")); let loc: LocBuf = Path::new("/root/code/foo/").into(); assert_eq!(loc.uri().as_os_str(), OsStr::new("foo")); assert_eq!(loc.urn().as_os_str(), OsStr::new("foo")); assert_eq!(loc.name().unwrap(), OsStr::new("foo")); assert_eq!(loc.base().as_os_str(), OsStr::new("/root/code/")); assert_eq!(loc.trail().as_os_str(), OsStr::new("/root/code/")); } #[test] fn test_with() -> Result<()> { let loc = LocBuf::::with("/".into(), 0, 0)?; assert_eq!(loc.uri().as_os_str(), OsStr::new("")); assert_eq!(loc.urn().as_os_str(), OsStr::new("")); assert_eq!(loc.name(), None); assert_eq!(loc.base().as_os_str(), OsStr::new("/")); assert_eq!(loc.trail().as_os_str(), OsStr::new("/")); let loc = LocBuf::::with("/root/code/".into(), 1, 1)?; assert_eq!(loc.uri().as_os_str(), OsStr::new("code")); assert_eq!(loc.urn().as_os_str(), OsStr::new("code")); assert_eq!(loc.name().unwrap(), OsStr::new("code")); assert_eq!(loc.base().as_os_str(), OsStr::new("/root/")); assert_eq!(loc.trail().as_os_str(), OsStr::new("/root/")); let loc = LocBuf::::with("/root/code/foo//".into(), 2, 1)?; assert_eq!(loc.uri().as_os_str(), OsStr::new("code/foo")); assert_eq!(loc.urn().as_os_str(), OsStr::new("foo")); assert_eq!(loc.name().unwrap(), OsStr::new("foo")); assert_eq!(loc.base().as_os_str(), OsStr::new("/root/")); assert_eq!(loc.trail().as_os_str(), OsStr::new("/root/code/")); let loc = LocBuf::::with("/root/code/foo//".into(), 2, 2)?; assert_eq!(loc.uri().as_os_str(), OsStr::new("code/foo")); assert_eq!(loc.urn().as_os_str(), OsStr::new("code/foo")); assert_eq!(loc.name().unwrap(), OsStr::new("foo")); assert_eq!(loc.base().as_os_str(), OsStr::new("/root/")); assert_eq!(loc.trail().as_os_str(), OsStr::new("/root/")); let loc = LocBuf::::with("/root/code/foo//bar/".into(), 2, 2)?; assert_eq!(loc.uri().as_os_str(), OsStr::new("foo//bar")); assert_eq!(loc.urn().as_os_str(), OsStr::new("foo//bar")); assert_eq!(loc.name().unwrap(), OsStr::new("bar")); assert_eq!(loc.base().as_os_str(), OsStr::new("/root/code/")); assert_eq!(loc.trail().as_os_str(), OsStr::new("/root/code/")); let loc = LocBuf::::with("/root/code/foo//bar/".into(), 3, 2)?; assert_eq!(loc.uri().as_os_str(), OsStr::new("code/foo//bar")); assert_eq!(loc.urn().as_os_str(), OsStr::new("foo//bar")); assert_eq!(loc.name().unwrap(), OsStr::new("bar")); assert_eq!(loc.base().as_os_str(), OsStr::new("/root/")); assert_eq!(loc.trail().as_os_str(), OsStr::new("/root/code/")); Ok(()) } #[test] fn test_set_name() -> Result<()> { crate::init_tests(); let cases = [ // Regular ("/", "a", "/a"), ("/a/b", "c", "/a/c"), // Archive ("archive:////", "a.zip", "archive:////a.zip"), ("archive:////a.zip/b", "c", "archive:////a.zip/c"), ("archive://:2//a.zip/b", "c", "archive://:2//a.zip/c"), ("archive://:2:1//a.zip/b", "c", "archive://:2:1//a.zip/c"), // Empty ("/a", "", "/"), ("archive:////a.zip", "", "archive:////"), ("archive:////a.zip/b", "", "archive:////a.zip"), ("archive://:1:1//a.zip", "", "archive:////"), ("archive://:2//a.zip/b", "", "archive://:1//a.zip"), ("archive://:2:2//a.zip/b", "", "archive://:1:1//a.zip"), ]; for (input, name, expected) in cases { let mut a: UrlBuf = input.parse()?; let b: UrlBuf = expected.parse()?; a.set_name(name); assert_eq!( (a.name(), format!("{a:?}").replace(r"\", "/")), (b.name(), expected.replace(r"\", "/")) ); } Ok(()) } }