use alloc::borrow::Cow;
use alloc::collections::TryReserveError;
use alloc::string::FromUtf8Error;
use core::borrow::Borrow;
use core::hash::{Hash, Hasher};
use core::iter::{Extend, FromIterator};
use core::marker::PhantomData;
use core::ops::Deref;
use core::str::FromStr;
use core::{cmp, fmt};
#[cfg(feature = "serde")]
use serde_core::{de, de::Visitor, Deserialize, Deserializer, Serialize, Serializer};
use crate::no_std_compat::*;
use crate::{CheckedPathError, Encoding, PathBuf, Utf8Encoding, Utf8Iter, Utf8Path};
pub struct Utf8PathBuf<T>
where
T: Utf8Encoding,
{
pub(crate) _encoding: PhantomData<T>,
pub(crate) inner: String,
}
impl<T> Utf8PathBuf<T>
where
T: Utf8Encoding,
{
pub fn new() -> Self {
Utf8PathBuf {
_encoding: PhantomData,
inner: String::new(),
}
}
#[inline]
pub fn with_capacity(capacity: usize) -> Self {
Utf8PathBuf {
_encoding: PhantomData,
inner: String::with_capacity(capacity),
}
}
#[inline]
pub fn as_path(&self) -> &Utf8Path<T> {
self
}
pub fn push<P: AsRef<Utf8Path<T>>>(&mut self, path: P) {
T::push(&mut self.inner, path.as_ref().as_str());
}
pub fn push_checked<P: AsRef<Utf8Path<T>>>(&mut self, path: P) -> Result<(), CheckedPathError> {
T::push_checked(&mut self.inner, path.as_ref().as_str())
}
pub fn pop(&mut self) -> bool {
match self.parent().map(|p| p.as_str().len()) {
Some(len) => {
self.inner.truncate(len);
true
}
None => false,
}
}
pub fn set_file_name<S: AsRef<str>>(&mut self, file_name: S) {
self._set_file_name(file_name.as_ref())
}
fn _set_file_name(&mut self, file_name: &str) {
if self.file_name().is_some() {
let popped = self.pop();
debug_assert!(popped);
}
self.push(file_name);
}
pub fn set_extension<S: AsRef<str>>(&mut self, extension: S) -> bool {
self._set_extension(extension.as_ref())
}
fn _set_extension(&mut self, extension: &str) -> bool {
let end_of_stem = match self.file_stem() {
Some(stem) => (stem.as_ptr() as usize - self.inner.as_ptr() as usize) + stem.len(),
None => return false,
};
self.inner.truncate(end_of_stem);
if !extension.is_empty() {
self.inner.push('.');
self.inner.push_str(extension);
}
true
}
#[inline]
pub fn into_string(self) -> String {
self.inner
}
#[inline]
pub fn into_boxed_path(self) -> Box<Utf8Path<T>> {
let rw = Box::into_raw(self.inner.into_boxed_str()) as *mut Utf8Path<T>;
unsafe { Box::from_raw(rw) }
}
#[inline]
pub fn capacity(&self) -> usize {
self.inner.capacity()
}
#[inline]
pub fn clear(&mut self) {
self.inner.clear()
}
#[inline]
pub fn reserve(&mut self, additional: usize) {
self.inner.reserve(additional)
}
#[inline]
pub fn try_reserve(&mut self, additional: usize) -> Result<(), TryReserveError> {
self.inner.try_reserve(additional)
}
#[inline]
pub fn reserve_exact(&mut self, additional: usize) {
self.inner.reserve_exact(additional)
}
#[inline]
pub fn try_reserve_exact(&mut self, additional: usize) -> Result<(), TryReserveError> {
self.inner.try_reserve_exact(additional)
}
#[inline]
pub fn shrink_to_fit(&mut self) {
self.inner.shrink_to_fit()
}
#[inline]
pub fn shrink_to(&mut self, min_capacity: usize) {
self.inner.shrink_to(min_capacity)
}
pub fn from_bytes_path_buf<U>(path_buf: PathBuf<U>) -> Result<Self, FromUtf8Error>
where
U: Encoding,
{
Ok(Self {
_encoding: PhantomData,
inner: String::from_utf8(path_buf.inner)?,
})
}
pub unsafe fn from_bytes_path_buf_unchecked<U>(path_buf: PathBuf<U>) -> Self
where
U: Encoding,
{
Self {
_encoding: PhantomData,
inner: String::from_utf8_unchecked(path_buf.inner),
}
}
pub fn into_bytes_path_buf<U>(self) -> PathBuf<U>
where
U: Encoding,
{
PathBuf {
_encoding: PhantomData,
inner: self.inner.into_bytes(),
}
}
}
impl<T> Clone for Utf8PathBuf<T>
where
T: Utf8Encoding,
{
#[inline]
fn clone(&self) -> Self {
Self {
_encoding: self._encoding,
inner: self.inner.clone(),
}
}
}
impl<T> fmt::Debug for Utf8PathBuf<T>
where
T: Utf8Encoding,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Utf8PathBuf")
.field("_encoding", &T::label())
.field("inner", &self.inner)
.finish()
}
}
impl<T> AsRef<[u8]> for Utf8PathBuf<T>
where
T: Utf8Encoding,
{
#[inline]
fn as_ref(&self) -> &[u8] {
self.as_str().as_bytes()
}
}
impl<T> AsRef<str> for Utf8PathBuf<T>
where
T: Utf8Encoding,
{
#[inline]
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl<T> AsRef<Utf8Path<T>> for Utf8PathBuf<T>
where
T: Utf8Encoding,
{
#[inline]
fn as_ref(&self) -> &Utf8Path<T> {
self
}
}
impl<T> Borrow<Utf8Path<T>> for Utf8PathBuf<T>
where
T: Utf8Encoding,
{
#[inline]
fn borrow(&self) -> &Utf8Path<T> {
self.deref()
}
}
impl<T> Default for Utf8PathBuf<T>
where
T: Utf8Encoding,
{
#[inline]
fn default() -> Utf8PathBuf<T> {
Utf8PathBuf::new()
}
}
impl<T> fmt::Display for Utf8PathBuf<T>
where
T: Utf8Encoding,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(&self.inner, f)
}
}
impl<T> Deref for Utf8PathBuf<T>
where
T: Utf8Encoding,
{
type Target = Utf8Path<T>;
#[inline]
fn deref(&self) -> &Utf8Path<T> {
Utf8Path::new(&self.inner)
}
}
impl<T> Eq for Utf8PathBuf<T> where T: Utf8Encoding {}
impl<T> PartialEq for Utf8PathBuf<T>
where
T: Utf8Encoding,
{
fn eq(&self, other: &Self) -> bool {
self.components() == other.components()
}
}
impl<T, P> Extend<P> for Utf8PathBuf<T>
where
T: Utf8Encoding,
P: AsRef<Utf8Path<T>>,
{
fn extend<I: IntoIterator<Item = P>>(&mut self, iter: I) {
iter.into_iter().for_each(move |p| self.push(p.as_ref()));
}
}
impl<T> From<Box<Utf8Path<T>>> for Utf8PathBuf<T>
where
T: Utf8Encoding,
{
fn from(boxed: Box<Utf8Path<T>>) -> Self {
boxed.into_path_buf()
}
}
impl<T, V> From<&V> for Utf8PathBuf<T>
where
T: Utf8Encoding,
V: ?Sized + AsRef<str>,
{
#[inline]
fn from(s: &V) -> Self {
Utf8PathBuf::from(s.as_ref().to_string())
}
}
impl<T> From<String> for Utf8PathBuf<T>
where
T: Utf8Encoding,
{
#[inline]
fn from(inner: String) -> Self {
Utf8PathBuf {
_encoding: PhantomData,
inner,
}
}
}
impl<T> From<Utf8PathBuf<T>> for String
where
T: Utf8Encoding,
{
#[inline]
fn from(path_buf: Utf8PathBuf<T>) -> Self {
path_buf.inner
}
}
impl<T> FromStr for Utf8PathBuf<T>
where
T: Utf8Encoding,
{
type Err = core::convert::Infallible;
#[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Utf8PathBuf::from(s))
}
}
impl<'a, T> From<Cow<'a, Utf8Path<T>>> for Utf8PathBuf<T>
where
T: Utf8Encoding,
{
#[inline]
fn from(p: Cow<'a, Utf8Path<T>>) -> Self {
p.into_owned()
}
}
impl<T, P> FromIterator<P> for Utf8PathBuf<T>
where
T: Utf8Encoding,
P: AsRef<Utf8Path<T>>,
{
fn from_iter<I: IntoIterator<Item = P>>(iter: I) -> Self {
let mut buf = Utf8PathBuf::new();
buf.extend(iter);
buf
}
}
impl<T> Hash for Utf8PathBuf<T>
where
T: Utf8Encoding,
{
fn hash<H: Hasher>(&self, h: &mut H) {
self.as_path().hash(h)
}
}
impl<'a, T> IntoIterator for &'a Utf8PathBuf<T>
where
T: Utf8Encoding,
{
type IntoIter = Utf8Iter<'a, T>;
type Item = &'a str;
#[inline]
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
impl<T> cmp::PartialOrd for Utf8PathBuf<T>
where
T: Utf8Encoding,
{
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
Some(self.cmp(other))
}
}
impl<T> cmp::Ord for Utf8PathBuf<T>
where
T: Utf8Encoding,
{
#[inline]
fn cmp(&self, other: &Self) -> cmp::Ordering {
self.components().cmp(other.components())
}
}
#[cfg(feature = "serde")]
struct Utf8PathBufVisitor<T>(PhantomData<T>)
where
T: Utf8Encoding;
#[cfg(feature = "serde")]
impl<'de, T> Visitor<'de> for Utf8PathBufVisitor<T>
where
T: Utf8Encoding,
{
type Value = Utf8PathBuf<T>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("UTF-8 path string")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(Utf8PathBuf::from(v))
}
fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(Utf8PathBuf::from(v))
}
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
where
E: de::Error,
{
core::str::from_utf8(v)
.map(Utf8PathBuf::from)
.map_err(|_| de::Error::invalid_value(de::Unexpected::Bytes(v), &self))
}
fn visit_byte_buf<E>(self, v: Vec<u8>) -> Result<Self::Value, E>
where
E: de::Error,
{
String::from_utf8(v)
.map(Utf8PathBuf::from)
.map_err(|e| de::Error::invalid_value(de::Unexpected::Bytes(&e.into_bytes()), &self))
}
}
#[cfg(feature = "serde")]
impl<T> Serialize for Utf8PathBuf<T>
where
T: Utf8Encoding,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.as_str().serialize(serializer)
}
}
#[cfg(feature = "serde")]
impl<'de, T> Deserialize<'de> for Utf8PathBuf<T>
where
T: Utf8Encoding,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_string(Utf8PathBufVisitor(PhantomData))
}
}
#[cfg(any(
unix,
all(target_vendor = "fortanix", target_env = "sgx"),
target_os = "solid_asp3",
target_os = "hermit",
all(
target_os = "wasi",
any(not(target_env = "p2"), feature = "nightly-wasip2")
)
))]
#[cfg(feature = "std")]
mod std_conversions {
use std::ffi::{OsStr, OsString};
#[cfg(all(target_vendor = "fortanix", target_env = "sgx"))]
use std::os::fortanix_sgx as os;
#[cfg(target_os = "solid_asp3")]
use std::os::solid as os;
#[cfg(any(target_os = "hermit", unix))]
use std::os::unix as os;
#[cfg(target_os = "wasi")]
use std::os::wasi as os;
use os::ffi::{OsStrExt, OsStringExt};
use super::*;
impl<T> From<Utf8PathBuf<T>> for OsString
where
T: Utf8Encoding,
{
#[inline]
fn from(path_buf: Utf8PathBuf<T>) -> Self {
OsStringExt::from_vec(path_buf.into_string().into_bytes())
}
}
impl<T> AsRef<OsStr> for Utf8PathBuf<T>
where
T: Utf8Encoding,
{
#[inline]
fn as_ref(&self) -> &OsStr {
OsStrExt::from_bytes(self.as_str().as_bytes())
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Utf8UnixEncoding, Utf8WindowsEncoding};
#[test]
fn set_extension_should_replace_the_extension_of_the_file_name() {
let mut path = Utf8PathBuf::<Utf8UnixEncoding>::from("/feel/the");
assert!(path.set_extension("force"));
assert_eq!(path, Utf8PathBuf::from("/feel/the.force"));
assert!(path.set_extension("dark_side"));
assert_eq!(path, Utf8PathBuf::from("/feel/the.dark_side"));
let mut path = Utf8PathBuf::<Utf8UnixEncoding>::from("foo.tar.gz");
assert!(path.set_extension("xz"));
assert_eq!(path, Utf8PathBuf::from("foo.tar.xz"));
}
#[test]
fn set_extension_should_remove_the_extension_when_given_an_empty_extension() {
let mut path = Utf8PathBuf::<Utf8UnixEncoding>::from("foo.tar.gz");
assert!(path.set_extension(""));
assert_eq!(path, Utf8PathBuf::from("foo.tar"));
let mut path = Utf8PathBuf::<Utf8UnixEncoding>::from("foo.");
assert!(path.set_extension(""));
assert_eq!(path, Utf8PathBuf::from("foo"));
}
#[test]
fn set_extension_should_discard_anything_trailing_the_file_name() {
let mut path = Utf8PathBuf::<Utf8UnixEncoding>::from("a.bc/");
assert!(path.set_extension("z"));
assert_eq!(path, Utf8PathBuf::from("a.z"));
let mut path = Utf8PathBuf::<Utf8UnixEncoding>::from("a.bc/////");
assert!(path.set_extension("z"));
assert_eq!(path, Utf8PathBuf::from("a.z"));
let mut path = Utf8PathBuf::<Utf8UnixEncoding>::from("test.reallylongext/");
assert!(path.set_extension("a"));
assert_eq!(path, Utf8PathBuf::from("test.a"));
let mut path = Utf8PathBuf::<Utf8UnixEncoding>::from("/usr/bin/");
assert!(path.set_extension("z"));
assert_eq!(path, Utf8PathBuf::from("/usr/bin.z"));
let mut path = Utf8PathBuf::<Utf8UnixEncoding>::from("foo.txt/.//");
assert!(path.set_extension("z"));
assert_eq!(path, Utf8PathBuf::from("foo.z"));
let mut path = Utf8PathBuf::<Utf8UnixEncoding>::from("foo.tar.gz/");
assert!(path.set_extension(""));
assert_eq!(path, Utf8PathBuf::from("foo.tar"));
let mut path = Utf8PathBuf::<Utf8WindowsEncoding>::from(r"C:\a.bc\");
assert!(path.set_extension("z"));
assert_eq!(path, Utf8PathBuf::from(r"C:\a.z"));
let mut path = Utf8PathBuf::<Utf8WindowsEncoding>::from("C:/a.bc/");
assert!(path.set_extension("z"));
assert_eq!(path, Utf8PathBuf::from("C:/a.z"));
}
#[test]
fn set_extension_should_do_nothing_when_there_is_no_file_name() {
let mut path = Utf8PathBuf::<Utf8UnixEncoding>::from("/");
assert!(!path.set_extension("z"));
assert_eq!(path, Utf8PathBuf::from("/"));
let mut path = Utf8PathBuf::<Utf8UnixEncoding>::from("foo/..");
assert!(!path.set_extension("z"));
assert_eq!(path, Utf8PathBuf::from("foo/.."));
let mut path = Utf8PathBuf::<Utf8WindowsEncoding>::from(r"C:\");
assert!(!path.set_extension("z"));
assert_eq!(path, Utf8PathBuf::from(r"C:\"));
}
#[test]
fn set_extension_should_support_multibyte_file_names() {
let mut path = Utf8PathBuf::<Utf8UnixEncoding>::from("/tmp/日本語.txt/");
assert!(path.set_extension("z"));
assert_eq!(path, Utf8PathBuf::from("/tmp/日本語.z"));
}
#[cfg(feature = "serde")]
#[test]
fn deserialize_invalid_utf8() {
use crate::Utf8WindowsPathBuf;
let bytes = vec![b'"', b'f', b'o', b'o', b'\\', 0xff, b'"'];
assert!(serde_json::from_slice::<Utf8WindowsPathBuf>(&bytes).is_err());
}
}