use std::ffi::{CStr, CString, OsString};
use std::fmt;
use std::hash::Hash;
use std::path::{Component, PathBuf};
use std::sync::Arc;
use metrohash::MetroHash128;
use nom::lib::std::fmt::Formatter;
use serde::de::{Error, Visitor};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use smallvec::SmallVec;
use stfu8::DecodeError;
use crate::arg;
use crate::arg::{from_stfu8, to_stfu8};
use crate::path::string::{c_to_os_str, os_to_c_str};
#[cfg(unix)]
pub const PATH_ESCAPE_CHAR: &str = "\\";
#[cfg(windows)]
pub const PATH_ESCAPE_CHAR: &str = "^";
#[cfg(unix)]
const ROOT_BYTES: &[u8] = b"/";
#[cfg(windows)]
const ROOT_BYTES: &[u8] = b"\\";
#[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct Path {
parent: Option<Arc<Path>>,
component: CString,
}
impl Path {
pub fn canonicalize(&self) -> Path {
let path_buf = self.to_path_buf();
match dunce::canonicalize(path_buf.clone()) {
Ok(p) => Path::from(p),
Err(_) => Path::from(path_buf),
}
}
pub fn is_absolute(&self) -> bool {
self.root().is_some()
}
pub fn is_relative(&self) -> bool {
self.root().is_none()
}
pub fn root(&self) -> Option<&Path> {
let mut result = self;
loop {
if result.component.as_bytes() == ROOT_BYTES {
return Some(result);
}
if let Some(parent) = &result.parent {
result = parent.as_ref()
} else {
break;
}
}
None
}
pub fn share(self) -> Arc<Self> {
Arc::new(self)
}
pub fn unshare(self: &Arc<Path>) -> Path {
self.as_ref().clone()
}
pub fn join<P: AsRef<Path>>(self: &Arc<Path>, path: P) -> Path {
let path = path.as_ref();
assert!(path.is_relative());
let components = path.components();
let mut iter = components.iter();
let mut result = self.push(CString::from(*iter.next().unwrap()));
for &c in iter {
result = Arc::new(result).push(CString::from(c));
}
result
}
pub fn resolve<P: AsRef<Path>>(self: &Arc<Path>, path: P) -> Path {
let path = path.as_ref();
if path.is_relative() {
self.join(path)
} else {
path.clone()
}
}
pub fn file_name(&self) -> Option<OsString> {
match self.component.as_bytes() {
b"/" => None,
b".." => None,
b"." => None,
_ => Some(c_to_os_str(self.component.as_c_str())),
}
}
pub fn file_name_cstr(&self) -> Option<&CStr> {
match self.component.as_bytes() {
b"/" => None,
b".." => None,
b"." => None,
_ => Some(self.component.as_c_str()),
}
}
pub fn parent(&self) -> Option<&Arc<Path>> {
self.parent.as_ref()
}
pub fn strip_prefix(&self, base: &Path) -> Option<Path> {
let mut self_components = self.components().into_iter().peekable();
let mut base_components = base.components().into_iter().peekable();
while let (Some(a), Some(b)) = (self_components.peek(), base_components.peek()) {
if a != b {
return None;
}
self_components.next();
base_components.next();
}
Some(Path::make(self_components))
}
pub fn strip_root(&self) -> Path {
if let Some(root) = self.root() {
self.strip_prefix(root).unwrap()
} else {
self.clone()
}
}
pub fn is_prefix_of(&self, other: &Path) -> bool {
let mut self_components = self.components().into_iter().peekable();
let mut other_components = other.components().into_iter().peekable();
while let (Some(a), Some(b)) = (self_components.peek(), other_components.peek()) {
if a != b {
return false;
}
self_components.next();
other_components.next();
}
self_components.peek().is_none()
}
pub fn to_path_buf(&self) -> PathBuf {
let mut result = PathBuf::from(OsString::with_capacity(self.capacity()));
self.for_each_component(|c| result.push(c_to_os_str(c)));
result
}
pub fn to_string_lossy(&self) -> String {
self.to_path_buf().to_string_lossy().to_string()
}
pub fn to_escaped_string(&self) -> String {
to_stfu8(self.to_path_buf().into_os_string())
}
pub fn from_escaped_string(encoded: &str) -> Result<Path, DecodeError> {
Ok(Path::from(from_stfu8(encoded)?))
}
pub fn quote(&self) -> String {
arg::quote(self.to_path_buf().into_os_string())
}
pub fn display(&self) -> String {
self.quote()
}
pub fn hash128(&self) -> u128 {
let mut hasher = MetroHash128::new();
self.hash(&mut hasher);
let (a, b) = hasher.finish128();
((a as u128) << 64) | (b as u128)
}
fn new(component: CString) -> Path {
Path {
component,
parent: None,
}
}
fn push(self: &Arc<Path>, component: CString) -> Path {
Path {
component,
parent: Some(self.clone()),
}
}
fn components(&self) -> SmallVec<[&CStr; 16]> {
let mut result = match &self.parent {
Some(p) => p.components(),
None => SmallVec::new(),
};
result.push(&self.component);
result
}
pub fn component_count(&self) -> usize {
let mut count = 0;
self.for_each_component(|_| count += 1);
count
}
fn for_each_component<F: FnMut(&CStr)>(&self, mut f: F) {
self.for_each_component_ref(&mut f)
}
fn for_each_component_ref<F: FnMut(&CStr)>(&self, f: &mut F) {
self.parent.iter().for_each(|p| p.for_each_component_ref(f));
(f)(self.component.as_c_str())
}
fn capacity(&self) -> usize {
let mut result: usize = 0;
self.for_each_component(|c| result += c.to_bytes().len() + 1);
result
}
fn make<'a, I>(components: I) -> Path
where
I: IntoIterator<Item = &'a CStr> + 'a,
{
let mut iter = components.into_iter();
let first = iter.next();
let mut result: Path = match first {
None => Path::new(CString::new(".").unwrap()),
Some(c) => Path::new(CString::from(c)),
};
for c in iter {
result = Arc::new(result).push(CString::from(c))
}
result
}
}
impl AsRef<Path> for Path {
fn as_ref(&self) -> &Path {
self
}
}
impl Default for Path {
fn default() -> Self {
Path::from(".")
}
}
fn component_to_c_string(c: &Component<'_>) -> CString {
os_to_c_str(c.as_os_str())
}
impl<P> From<P> for Path
where
P: AsRef<std::path::Path>,
{
fn from(p: P) -> Self {
let p = p.as_ref();
let mut components = p.components();
let mut result = Path::new(component_to_c_string(
&components.next().unwrap_or(Component::CurDir),
));
for c in components {
result = Arc::new(result).push(component_to_c_string(&c))
}
result
}
}
impl Serialize for Path {
fn serialize<S>(&self, serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>
where
S: Serializer,
{
serializer.collect_str(self.to_escaped_string().as_str())
}
}
struct PathVisitor;
impl Visitor<'_> for PathVisitor {
type Value = Path;
fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
formatter.write_str("path string")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: Error,
{
Path::from_escaped_string(v).map_err(|e| E::custom(format!("Invalid path: {e}")))
}
}
impl<'de> Deserialize<'de> for Path {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_str(PathVisitor)
}
}
impl fmt::Debug for Path {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self.to_path_buf())
}
}
mod string {
use std::ffi::{CStr, CString, OsStr, OsString};
#[cfg(unix)]
pub fn c_to_os_str(str: &CStr) -> OsString {
use std::os::unix::ffi::OsStrExt;
OsStr::from_bytes(str.to_bytes()).to_os_string()
}
#[cfg(unix)]
pub fn os_to_c_str(str: &OsStr) -> CString {
use std::os::unix::ffi::OsStrExt;
CString::new(str.as_bytes()).unwrap()
}
#[cfg(windows)]
pub fn c_to_os_str(str: &CStr) -> OsString {
OsString::from(str.to_str().unwrap())
}
#[cfg(windows)]
pub fn os_to_c_str(str: &OsStr) -> CString {
CString::new(str.to_str().unwrap().as_bytes()).unwrap()
}
}
#[cfg(test)]
mod test {
use super::*;
use serde_test::{assert_ser_tokens, Token};
fn test_convert(s: &str) {
assert_eq!(PathBuf::from(s), Path::from(s).to_path_buf());
}
#[test]
fn convert() {
test_convert("/");
test_convert("/bar");
test_convert("/foo/bar");
test_convert(".");
test_convert("./foo/bar");
test_convert("../foo/bar");
test_convert("..");
test_convert("foo");
test_convert("foo/bar/baz");
test_convert("foo/bar/baz");
}
#[test]
fn file_name() {
assert_eq!(
Path::from("foo").file_name_cstr(),
Some(CString::new("foo").unwrap().as_c_str())
);
assert_eq!(
Path::from("foo/bar").file_name_cstr(),
Some(CString::new("bar").unwrap().as_c_str())
);
assert_eq!(
Path::from("/foo").file_name_cstr(),
Some(CString::new("foo").unwrap().as_c_str())
);
assert_eq!(
Path::from("/foo/bar").file_name_cstr(),
Some(CString::new("bar").unwrap().as_c_str())
);
assert_eq!(Path::from("/").file_name_cstr(), None);
assert_eq!(Path::from(".").file_name_cstr(), None);
assert_eq!(Path::from("..").file_name_cstr(), None);
}
#[test]
fn parent() {
assert_eq!(
Path::from("foo/bar").parent(),
Some(&Arc::new(Path::from("foo")))
);
assert_eq!(
Path::from("/foo").parent(),
Some(&Arc::new(Path::from("/")))
);
assert_eq!(Path::from("/").parent(), None);
}
#[test]
fn share_parents() {
let parent = Path::from("/parent").share();
let child1 = parent.join(Path::from("c1"));
let child2 = parent.join(Path::from("c2"));
assert_eq!(PathBuf::from("/parent/c1"), child1.to_path_buf());
assert_eq!(PathBuf::from("/parent/c2"), child2.to_path_buf());
}
#[test]
fn is_absolute() {
assert!(Path::from("/foo/bar").is_absolute());
assert!(!Path::from("foo/bar").is_absolute());
assert!(!Path::from("./foo/bar").is_absolute());
assert!(!Path::from("../foo/bar").is_absolute());
}
#[test]
fn strip_prefix() {
assert_eq!(
Path::from("/foo/bar").strip_prefix(&Path::from("/foo")),
Some(Path::from("bar"))
);
assert_eq!(
Path::from("/foo/bar").strip_prefix(&Path::from("/foo/bar")),
Some(Path::from("."))
);
assert_eq!(
Path::from("/foo/bar").strip_prefix(&Path::from("/bar")),
None
);
}
#[test]
fn is_prefix_of() {
assert!(Path::from("/foo/bar").is_prefix_of(&Path::from("/foo/bar")));
assert!(Path::from("/foo/bar").is_prefix_of(&Path::from("/foo/bar/baz")));
assert!(!Path::from("/foo/bar").is_prefix_of(&Path::from("/foo")))
}
#[test]
fn encode_decode_stfu8() {
fn roundtrip(s: &str) {
assert_eq!(Path::from_escaped_string(s).unwrap().to_escaped_string(), s)
}
roundtrip("a/b/c");
roundtrip("ą/ś/ć");
roundtrip("a \\n b");
roundtrip("a \\t b");
roundtrip("a \\x7F b");
}
#[test]
fn root() {
assert!(Path::from("foo/bar").root().is_none());
assert_eq!(Path::from("/foo/bar").root().unwrap(), &Path::from("/"));
assert_eq!(Path::from("/foo/bar").strip_root(), Path::from("foo/bar"));
}
#[test]
fn serialize() {
assert_ser_tokens(&Path::from("a \n b"), &[Token::String("a \\n b")])
}
}