use core::marker::PhantomData;
#[cfg(feature = "std")]
use std::ffi::OsString;
use alloc::string::String;
use crate::path::{Drive, Path, Prefix, Root};
pub struct StringPathBuilder<T> {
path: Path,
separator: char,
_phantom_data: PhantomData<T>,
}
pub enum Base {}
pub enum WithResolver {}
pub enum WithBase {}
pub enum WithSymlinkTraversal {}
pub enum WithResolverAndBase {}
pub enum WithResolverAndSymlinkTraversal {}
pub enum WithSymlinkTraversalAndBase {}
pub enum WithResolverSymlinkTraversalAndBase {}
impl StringPathBuilder<Base> {
pub fn new(path: impl Into<Path>) -> Self {
StringPathBuilder::<Base> {
path: path.into(),
separator: '/',
_phantom_data: PhantomData,
}
}
pub fn with_base(mut self, base: impl AsRef<Path>) -> Result<Self, &'static str> {
self.path = self.path.join(base)?;
Ok(self)
}
pub fn with_cwd_base(mut self) -> Result<Self, &'static str> {
self.path = self.path.with_cwd_base()?;
Ok(self)
}
pub fn with_separator(mut self, separator: impl Into<char>) -> Self {
self.separator = separator.into();
self
}
pub fn with_resolver(self) -> StringPathBuilder<WithResolver> {
StringPathBuilder::<WithResolver> {
path: self.path,
separator: self.separator,
_phantom_data: PhantomData,
}
}
pub fn traverse_symlinks(self) -> StringPathBuilder<WithSymlinkTraversal> {
StringPathBuilder::<WithSymlinkTraversal> {
path: self.path,
separator: self.separator,
_phantom_data: PhantomData,
}
}
pub fn build_string(self) -> String {
build_path(self)
}
#[cfg(feature = "std")]
pub fn build_os_string(self) -> OsString {
OsString::from(self.build_string())
}
#[cfg(feature = "std")]
pub fn build_std_path(self) -> std::path::PathBuf {
std::path::PathBuf::from(self.build_string())
}
}
impl StringPathBuilder<WithResolver> {
pub fn traverse_symlinks(self) -> StringPathBuilder<WithResolverAndSymlinkTraversal> {
StringPathBuilder::<WithResolverAndSymlinkTraversal> {
path: self.path,
separator: self.separator,
_phantom_data: PhantomData,
}
}
pub fn with_base(mut self, base: impl AsRef<Path>) -> Result<Self, &'static str> {
self.path = self.path.join(base)?;
Ok(self)
}
pub fn with_cwd_base(mut self) -> Result<Self, &'static str> {
self.path = self.path.with_cwd_base()?;
Ok(self)
}
pub fn build_string(mut self) -> Result<String, &'static str> {
self.path = self.path.resolve()?;
Ok(build_path(self))
}
#[cfg(feature = "std")]
pub fn build_os_string(self) -> Result<OsString, &'static str> {
self.build_string().map(|s| OsString::from(s))
}
#[cfg(feature = "std")]
pub fn build_std_path(self) -> Result<std::path::PathBuf, &'static str> {
self.build_string().map(|s| std::path::PathBuf::from(s))
}
}
#[cfg(feature = "std")]
impl StringPathBuilder<WithSymlinkTraversal> {
pub fn with_resolver(self) -> StringPathBuilder<WithResolverAndSymlinkTraversal> {
StringPathBuilder::<WithResolverAndSymlinkTraversal> {
path: self.path,
separator: self.separator,
_phantom_data: PhantomData,
}
}
pub fn with_base(mut self, base: impl AsRef<Path>) -> Result<Self, &'static str> {
self.path = self.path.join(base)?;
Ok(self)
}
pub fn with_cwd_base(mut self) -> Result<Self, &'static str> {
self.path = self.path.with_cwd_base()?;
Ok(self)
}
pub fn build_string(mut self) -> Result<String, &'static str> {
self.path = self.path.traverse_symlinks()?;
Ok(build_path(self))
}
pub fn build_os_string(self) -> Result<OsString, &'static str> {
self.build_string().map(|s| OsString::from(s))
}
pub fn build_std_path(self) -> Result<std::path::PathBuf, &'static str> {
self.build_string().map(|s| std::path::PathBuf::from(s))
}
}
#[cfg(feature = "std")]
impl StringPathBuilder<WithResolverAndSymlinkTraversal> {
pub fn build_string(mut self) -> Result<String, &'static str> {
self.path = self.path.resolve()?.traverse_symlinks()?;
Ok(build_path(self))
}
pub fn with_base(mut self, base: impl AsRef<Path>) -> Result<Self, &'static str> {
self.path = self.path.join(base)?;
Ok(self)
}
pub fn with_cwd_base(mut self) -> Result<Self, &'static str> {
self.path = self.path.with_cwd_base()?;
Ok(self)
}
pub fn build_os_string(self) -> Result<OsString, &'static str> {
self.build_string().map(|s| OsString::from(s))
}
pub fn build_std_path(self) -> Result<std::path::PathBuf, &'static str> {
self.build_string().map(|s| std::path::PathBuf::from(s))
}
}
fn build_path<T>(builder: StringPathBuilder<T>) -> String {
let mut result = String::new();
match builder.path.prefix {
Some(Prefix::ExtendedPath) => {
result.push(builder.separator);
result.push(builder.separator);
result.push('?');
result.push(builder.separator);
}
Some(Prefix::Device) => {
result.push(builder.separator);
result.push(builder.separator);
result.push('.');
result.push(builder.separator);
}
None => {}
}
if let Some(Drive { letter }) = builder.path.drive {
result.push(letter);
result.push(':');
}
match builder.path.root {
Some(Root::Normal) => {
if !builder.path.prefix.is_some_and(|p| p == Prefix::Device)
|| builder.path.drive.is_some()
{
result.push(builder.separator);
}
}
Some(Root::Unc) => {
if let Some(Prefix::ExtendedPath) = builder.path.prefix {
result.push_str("UNC");
result.push(builder.separator);
} else {
result.push(builder.separator);
result.push(builder.separator);
}
}
None => {}
}
let len = builder.path.segments.len();
for (i, segment) in builder.path.segments.into_iter().enumerate() {
result.push_str(&segment.0);
if i < len - 1 {
result.push(builder.separator);
}
}
result
}
#[cfg(test)]
mod test {
use core::str::FromStr;
use rstest::rstest;
use super::*;
#[rstest]
fn build_with_defaults() {
let path = Path::from_str("a/b/c").unwrap();
let string = StringPathBuilder::new(path).build_string();
assert_eq!(string, "a/b/c");
}
#[rstest]
fn build_with_backslash_separator() {
let path = Path::from_str("a/b/c").unwrap();
let string = StringPathBuilder::new(path)
.with_separator('\\')
.build_string();
assert_eq!(string, r"a\b\c");
}
#[rstest]
fn build_with_resolver() {
let path = Path::from_str("a/b/./c/../d").unwrap();
let string = StringPathBuilder::new(path)
.with_resolver()
.build_string()
.unwrap();
assert_eq!(string, "a/b/d");
}
#[rstest]
fn build_with_resolver2() {
let path = Path::from_str("../b/c").unwrap();
let string = StringPathBuilder::new(path)
.with_resolver()
.build_string()
.unwrap();
assert_eq!(string, "../b/c");
}
#[rstest]
fn build_with_resolver3() {
let path = Path::from_str("a/b/..").unwrap();
let string = StringPathBuilder::new(path)
.with_resolver()
.build_string()
.unwrap();
assert_eq!(string, "a");
}
#[rstest]
fn build_with_resolver4() {
let path = Path::from_str("..").unwrap();
let string = StringPathBuilder::new(path)
.with_resolver()
.build_string()
.unwrap();
assert_eq!(string, "..");
}
#[rstest]
fn build_with_resolver5() {
let path = Path::from_str("a/..").unwrap();
let string = StringPathBuilder::new(path)
.with_resolver()
.build_string()
.unwrap();
assert_eq!(string, "");
}
#[rstest]
fn build_with_resolver6() {
let path = Path::from_str(r"a/../b/c/../d").unwrap();
let string = path.builder().with_resolver().build_string().unwrap();
assert_eq!(string, "b/d");
}
#[cfg(not(feature = "std"))]
#[rstest]
fn build_with_resolver7() {
let path = Path::from_str(r"~/a/../b/c/../d").unwrap();
let string = path.builder().with_resolver().build_string().unwrap();
assert_eq!(string, "~/b/d");
}
#[rstest]
fn test_resolve() {
let path = Path::from_str("a/../b/c/../d").unwrap();
let path2 = path.clone().resolve().unwrap();
let path3 = Path::from_str("b/d").unwrap();
let path = path.builder().build_string();
let path2 = path2.builder().with_resolver().build_string().unwrap();
let path3 = path3.builder().with_resolver().build_string().unwrap();
assert_ne!(path, path2);
assert_eq!(path2, path3);
}
#[cfg(feature = "std")]
#[rstest]
fn test_resolve_home() {
let path = Path::from_str("~/.config").unwrap();
let resolved = path.resolve().unwrap();
assert!(resolved.prefix.is_none());
assert!(resolved.root.is_some());
assert_eq!(resolved.root.as_ref().unwrap(), &Root::Normal);
let home = dirs::home_dir().unwrap();
let home_path = Path::from_str(home.to_str().unwrap()).unwrap();
assert_eq!(
resolved,
home_path.join(Path::from_str(".config").unwrap()).unwrap()
);
}
#[cfg(feature = "std")]
#[rstest]
fn test_resolve_home2() {
let path = Path::from_str("~").unwrap();
let resolved = path.resolve().unwrap();
let home = dirs::home_dir().unwrap();
let home_path = Path::from_str(home.to_str().unwrap()).unwrap();
assert_eq!(resolved, home_path);
}
#[rstest]
fn tilde_segment_in_path() {
let path = Path::from_str("path/~/file.txt").unwrap();
let resolved = path.clone().resolve().unwrap();
assert_eq!(resolved, path);
}
}