use crate::pack::{Pack, PackError};
use arcstr::{literal, ArcStr};
use bytes::{Buf, BufMut};
use escaping::Escape;
use std::{
borrow::{Borrow, Cow},
cell::RefCell,
cmp::{Eq, Ord, PartialEq, PartialOrd},
convert::{AsRef, From},
fmt,
iter::{DoubleEndedIterator, Iterator},
ops::Deref,
result::Result,
str::{self, FromStr},
sync::LazyLock,
};
pub const SEP: char = '/';
pub const ROOT: &str = "/";
pub const PATH_ESC: LazyLock<Escape> =
LazyLock::new(|| Escape::new('\\', &['\\', '/'], &[], None).unwrap());
fn is_canonical(s: &str) -> bool {
for _ in Path::parts(s).filter(|p| *p == "") {
return false;
}
true
}
fn canonize(s: &str) -> String {
let mut res = String::with_capacity(s.len());
if s.len() > 0 {
if s.starts_with(SEP) {
res.push(SEP)
}
let mut first = true;
for p in Path::parts(s).filter(|p| *p != "") {
if first {
first = false;
} else {
res.push(SEP)
}
res.push_str(p);
}
}
res
}
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Path(ArcStr);
impl Pack for Path {
fn encoded_len(&self) -> usize {
<ArcStr as Pack>::encoded_len(&self.0)
}
fn encode(&self, buf: &mut impl BufMut) -> Result<(), PackError> {
<ArcStr as Pack>::encode(&self.0, buf)
}
fn decode(buf: &mut impl Buf) -> Result<Self, PackError> {
Ok(Path::from(<ArcStr as Pack>::decode(buf)?))
}
}
impl fmt::Display for Path {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
impl AsRef<str> for Path {
fn as_ref(&self) -> &str {
&*self.0
}
}
impl Borrow<str> for Path {
fn borrow(&self) -> &str {
&*self.0
}
}
impl Deref for Path {
type Target = str;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl From<String> for Path {
fn from(s: String) -> Path {
if is_canonical(&s) {
Path(ArcStr::from(s))
} else {
Path(ArcStr::from(canonize(&s)))
}
}
}
impl From<&'static str> for Path {
fn from(s: &'static str) -> Path {
if is_canonical(s) {
Path(ArcStr::from(s))
} else {
Path(ArcStr::from(canonize(s)))
}
}
}
impl<'a> From<&'a String> for Path {
fn from(s: &String) -> Path {
if is_canonical(s.as_str()) {
Path(ArcStr::from(s.clone()))
} else {
Path(ArcStr::from(canonize(s.as_str())))
}
}
}
impl From<ArcStr> for Path {
fn from(s: ArcStr) -> Path {
if is_canonical(&*s) {
Path(s)
} else {
Path(ArcStr::from(canonize(&*s)))
}
}
}
impl From<&ArcStr> for Path {
fn from(s: &ArcStr) -> Path {
if is_canonical(s) {
Path(s.clone())
} else {
Path(ArcStr::from(canonize(s)))
}
}
}
impl<C: Borrow<str>> FromIterator<C> for Path {
fn from_iter<T: IntoIterator<Item = C>>(iter: T) -> Self {
thread_local! {
static BUF: RefCell<String> = RefCell::new(String::new());
}
BUF.with_borrow_mut(|buf| {
buf.clear();
buf.push(SEP);
for c in iter {
PATH_ESC.escape_to(c.borrow(), buf);
buf.push(SEP)
}
if buf.len() > 1 {
buf.pop(); }
Self(ArcStr::from(buf.as_str()))
})
}
}
impl FromStr for Path {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if is_canonical(s) {
Ok(Path(ArcStr::from(s)))
} else {
Ok(Path(ArcStr::from(canonize(s))))
}
}
}
impl Into<ArcStr> for Path {
fn into(self) -> ArcStr {
self.0
}
}
pub enum DirNames<'a> {
Root(bool),
Path { cur: &'a str, all: &'a str, base: usize },
}
impl<'a> Iterator for DirNames<'a> {
type Item = &'a str;
fn next(&mut self) -> Option<Self::Item> {
match self {
DirNames::Path { cur, all, base } => {
if *base >= all.len() {
None
} else {
match Path::find_sep(cur) {
None => {
*base = all.len();
Some(all)
}
Some(p) => {
*base += p + 1;
*cur = &all[*base..];
Some(&all[0..*base - 1])
}
}
}
}
DirNames::Root(true) => {
*self = DirNames::Root(false);
Some("/")
}
DirNames::Root(false) => None,
}
}
}
impl<'a> DoubleEndedIterator for DirNames<'a> {
fn next_back(&mut self) -> Option<Self::Item> {
match self {
DirNames::Path { cur: _, all, base: _ } => match Path::dirname(*all) {
Some(dn) => {
let res = *all;
*all = dn;
Some(res)
}
None => {
if all == &ROOT {
*self = DirNames::Root(false);
Some("/")
} else {
let res = *all;
*all = &ROOT;
Some(res)
}
}
},
DirNames::Root(true) => {
*self = DirNames::Root(false);
Some("/")
}
DirNames::Root(false) => None,
}
}
}
impl Path {
pub const SEP: char = SEP;
pub const ROOT: &str = ROOT;
pub fn from_str(s: &str) -> Self {
if is_canonical(s) {
Path(ArcStr::from(s))
} else {
Path(ArcStr::from(canonize(s)))
}
}
pub fn root() -> Path {
Path(literal!("/"))
}
pub fn is_absolute<T: AsRef<str> + ?Sized>(p: &T) -> bool {
p.as_ref().starts_with(SEP)
}
pub fn is_parent<T: AsRef<str> + ?Sized, U: AsRef<str> + ?Sized>(
parent: &T,
other: &U,
) -> bool {
let parent = parent.as_ref();
let other = other.as_ref();
parent == "/"
|| (other.starts_with(parent)
&& (other.len() == parent.len()
|| other.as_bytes()[parent.len()] == SEP as u8))
}
pub fn is_immediate_parent<T: AsRef<str> + ?Sized, U: AsRef<str> + ?Sized>(
parent: &T,
other: &U,
) -> bool {
let parent = if parent.as_ref() == "/" { None } else { Some(parent.as_ref()) };
other.as_ref().len() > 0
&& other.as_ref() != "/"
&& Path::dirname(other) == parent
}
pub fn strip_prefix<'a, T: AsRef<str> + ?Sized, U: AsRef<str> + ?Sized>(
prefix: &T,
path: &'a U,
) -> Option<&'a str> {
if Path::is_parent(prefix, path) {
path.as_ref()
.strip_prefix(prefix.as_ref())
.map(|s| s.strip_prefix("/").unwrap_or(s))
} else {
None
}
}
pub fn lcp<'a, T: AsRef<str> + ?Sized, U: AsRef<str> + ?Sized>(
path0: &'a T,
path1: &'a U,
) -> &'a str {
let (mut p0, p1) = if path0.as_ref().len() <= path1.as_ref().len() {
(path0.as_ref(), path1.as_ref())
} else {
(path1.as_ref(), path0.as_ref())
};
loop {
if Path::is_parent(p0, p1) {
return p0;
} else {
match Path::dirname(p0) {
Some(p) => p0 = p,
None => return "/",
}
}
}
}
pub fn escape<'a, T: AsRef<str> + ?Sized>(s: &'a T) -> Cow<'a, str> {
PATH_ESC.escape(s)
}
pub fn unescape<'a, T: AsRef<str> + ?Sized>(s: &'a T) -> Cow<'a, str> {
PATH_ESC.unescape(s)
}
pub fn append<T: AsRef<str> + ?Sized>(&self, other: &T) -> Self {
let other = other.as_ref();
if other.len() == 0 {
self.clone()
} else {
let mut res = String::with_capacity(self.as_ref().len() + other.len());
res.push_str(self.as_ref());
res.push(SEP);
res.push_str(other);
Path::from(res)
}
}
pub fn join<T: AsRef<str> + ?Sized>(&self, other: &T) -> Self {
self.append(other)
}
pub fn parts<T: AsRef<str> + ?Sized>(s: &T) -> impl Iterator<Item = &str> {
let s = s.as_ref();
let skip = if s == "/" {
2
} else if s.starts_with("/") {
1
} else {
0
};
let e = PATH_ESC.clone();
e.split(s, SEP).skip(skip)
}
pub fn dirnames<'a, T: AsRef<str> + ?Sized>(s: &'a T) -> DirNames<'a> {
let s = s.as_ref();
if s == "/" {
DirNames::Root(true)
} else {
DirNames::Path { cur: s, all: s, base: 1 }
}
}
pub fn levels<T: AsRef<str> + ?Sized>(s: &T) -> usize {
let mut p = 0;
for _ in Path::parts(s) {
p += 1
}
p
}
pub fn dirname<'a, T: AsRef<str> + ?Sized>(s: &'a T) -> Option<&'a str> {
let s = s.as_ref();
Path::rfind_sep(s).and_then(|i| if i == 0 { None } else { Some(&s[0..i]) })
}
pub fn dirname_with_sep<T: AsRef<str> + ?Sized>(s: &T) -> Option<&str> {
let s = s.as_ref();
Path::rfind_sep(s).and_then(|i| if i == 0 { None } else { Some(&s[0..i + 1]) })
}
pub fn basename<T: AsRef<str> + ?Sized>(s: &T) -> Option<&str> {
let s = s.as_ref();
match Path::rfind_sep(s) {
None => {
if s.len() > 0 {
Some(s)
} else {
None
}
}
Some(i) => {
if s.len() <= 1 {
None
} else {
Some(&s[i + 1..s.len()])
}
}
}
}
fn find_sep_int<F: Fn(&str) -> Option<usize>>(mut s: &str, f: F) -> Option<usize> {
if s.len() == 0 {
None
} else {
loop {
match f(s) {
None => return None,
Some(i) => {
if !PATH_ESC.is_escaped(s, i) {
return Some(i);
} else {
s = &s[0..i];
}
}
}
}
}
}
pub fn rfind_sep<T: AsRef<str> + ?Sized>(s: &T) -> Option<usize> {
let s = s.as_ref();
Path::find_sep_int(s, |s| s.rfind(SEP))
}
pub fn find_sep<T: AsRef<str> + ?Sized>(s: &T) -> Option<usize> {
let s = s.as_ref();
Path::find_sep_int(s, |s| s.find(SEP))
}
}