pub use af_core_macros::{path_join as join, path_normalize as normalize, path_resolve as resolve};
#[doc(inline)]
pub use std::path::{is_separator, MAIN_SEPARATOR as SEPARATOR};
use crate::{env, prelude::*};
use std::cell::RefCell;
use std::path::Path;
thread_local! {
static THREAD_BUFFER: RefCell<String> = default();
}
pub fn append(base: &mut String, relative: &str) {
if is_absolute(relative) {
base.replace_range(.., relative);
return;
}
match base.chars().rev().next() {
None => base.replace_range(.., relative),
Some(c) if is_separator(c) => base.push_str(relative),
Some(_) => {
base.reserve(relative.len() + 1);
base.push(SEPARATOR);
base.push_str(relative);
}
}
}
pub fn is_absolute(path: &str) -> bool {
as_std(path).is_absolute()
}
pub fn join<'a, 'b>(base: impl PathLike<'b>, relative: impl PathLike<'a>) -> Cow<'a, str> {
let relative = relative.to_cow();
if is_absolute(&relative) {
return relative;
}
let mut output = base.to_owned();
append(&mut output, &relative);
output.into()
}
pub fn last(path: &str) -> Option<&str> {
as_std(path).file_name()?.to_str()
}
pub fn normalize(path: &mut String) {
THREAD_BUFFER.with(|buffer| {
let mut buffer = buffer.borrow_mut();
buffer.clear();
normalize_into(path, &mut buffer);
path.replace_range(.., &buffer);
})
}
pub fn normalized<'a>(path: impl PathLike<'a>) -> Cow<'a, str> {
let path = path.to_cow();
THREAD_BUFFER.with(|buffer| {
let mut buffer = buffer.borrow_mut();
buffer.clear();
normalize_into(&path, &mut buffer);
match path.as_ref() == *buffer {
true => path,
false => buffer.clone().into(),
}
})
}
pub fn parent(path: &str) -> Option<&str> {
as_std(path).parent()?.to_str()
}
pub fn pop(path: &mut String) -> Option<String> {
let split_at = parent(&path)?.len();
let lead_seps = path[split_at..].chars().take_while(|c| is_separator(*c)).count();
let trail_seps = path.chars().rev().take_while(|c| is_separator(*c)).count();
let mut last = path.split_off(split_at + lead_seps);
path.truncate(split_at);
last.truncate(last.len() - trail_seps);
Some(last)
}
pub fn resolve(path: &mut String) -> Result<(), env::WorkingPathError> {
if !is_absolute(&path) {
let mut buf = env::working_path()?;
mem::swap(path, &mut buf);
append(path, &buf);
}
normalize(path);
Ok(())
}
pub fn resolved<'a>(path: impl PathLike<'a>) -> Result<Cow<'a, str>, env::WorkingPathError> {
let mut path = path.to_cow();
if is_absolute(&path) {
return Ok(normalized(path));
}
resolve(path.to_mut())?;
Ok(path)
}
pub fn starts_with(path: &str, prefix: &str) -> bool {
as_std(path).starts_with(prefix)
}
pub fn with_trailing_sep<'a>(path: impl PathLike<'a>) -> Cow<'a, str> {
let mut path = path.to_cow();
match path.chars().rev().next() {
Some(c) if is_separator(c) => path,
_ => {
path.to_mut().push(SEPARATOR);
path
}
}
}
pub fn as_std(path: &str) -> &Path {
path.as_ref()
}
fn normalize_into(path: &str, output: &mut String) {
for component in as_std(path).components() {
match component {
std::path::Component::CurDir => continue,
std::path::Component::Normal(component) => append(output, component.to_str().unwrap()),
std::path::Component::Prefix(prefix) => output.push_str(prefix.as_os_str().to_str().unwrap()),
std::path::Component::RootDir => output.push(SEPARATOR),
std::path::Component::ParentDir => {
pop(output);
}
}
}
}
pub trait PathLike<'a>: Sized {
fn to_cow(self) -> Cow<'a, str>;
fn to_owned(self) -> String {
self.to_cow().into()
}
}
impl<'a> PathLike<'a> for &'a str {
fn to_cow(self) -> Cow<'a, str> {
self.into()
}
}
impl<'a> PathLike<'a> for &'a &'_ mut str {
fn to_cow(self) -> Cow<'a, str> {
(&**self).into()
}
}
impl<'a> PathLike<'a> for String {
fn to_cow(self) -> Cow<'a, str> {
self.into()
}
}
impl<'a> PathLike<'a> for &'a String {
fn to_cow(self) -> Cow<'a, str> {
self.into()
}
}
impl<'a> PathLike<'a> for &'a &'_ mut String {
fn to_cow(self) -> Cow<'a, str> {
(&**self).into()
}
}
impl<'a> PathLike<'a> for Cow<'a, str> {
fn to_cow(self) -> Cow<'a, str> {
self
}
}
impl<'a> PathLike<'a> for &'a Cow<'_, str> {
fn to_cow(self) -> Cow<'a, str> {
self.as_ref().into()
}
}
impl<'a> PathLike<'a> for &'a &'_ mut Cow<'_, str> {
fn to_cow(self) -> Cow<'a, str> {
self.as_ref().into()
}
}
impl<'a> PathLike<'a> for &'a std::path::PathBuf {
fn to_cow(self) -> Cow<'a, str> {
self.to_string_lossy().to_cow()
}
}