use std::path::Component;
use std::{
borrow::Cow,
ffi::{OsStr, OsString},
path::{Path, PathBuf},
};
use bstr::{BStr, BString};
#[derive(Debug)]
pub struct Utf8Error;
impl std::fmt::Display for Utf8Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("Could not convert to UTF8 or from UTF8 due to ill-formed input")
}
}
impl std::error::Error for Utf8Error {}
pub fn os_str_into_bstr(path: &OsStr) -> Result<&BStr, Utf8Error> {
let path = try_into_bstr(Cow::Borrowed(path.as_ref()))?;
match path {
Cow::Borrowed(path) => Ok(path),
Cow::Owned(_) => unreachable!("borrowed cows stay borrowed"),
}
}
pub fn os_string_into_bstring(path: OsString) -> Result<BString, Utf8Error> {
let path = try_into_bstr(Cow::Owned(path.into()))?;
match path {
Cow::Borrowed(_path) => unreachable!("borrowed cows stay borrowed"),
Cow::Owned(path) => Ok(path),
}
}
pub fn try_os_str_into_bstr(path: Cow<'_, OsStr>) -> Result<Cow<'_, BStr>, Utf8Error> {
match path {
Cow::Borrowed(path) => os_str_into_bstr(path).map(Cow::Borrowed),
Cow::Owned(path) => os_string_into_bstring(path).map(Cow::Owned),
}
}
pub fn try_into_bstr<'a>(path: impl Into<Cow<'a, Path>>) -> Result<Cow<'a, BStr>, Utf8Error> {
let path = path.into();
let path_str = match path {
Cow::Owned(path) => Cow::Owned({
#[cfg(unix)]
let p: BString = {
use std::os::unix::ffi::OsStringExt;
path.into_os_string().into_vec().into()
};
#[cfg(target_os = "wasi")]
let p: BString = {
use std::os::wasi::ffi::OsStringExt;
path.into_os_string().into_vec().into()
};
#[cfg(not(any(unix, target_os = "wasi")))]
let p: BString = path.into_os_string().into_string().map_err(|_| Utf8Error)?.into();
p
}),
Cow::Borrowed(path) => Cow::Borrowed({
#[cfg(unix)]
let p: &BStr = {
use std::os::unix::ffi::OsStrExt;
path.as_os_str().as_bytes().into()
};
#[cfg(target_os = "wasi")]
let p: &BStr = {
use std::os::wasi::ffi::OsStrExt;
path.as_os_str().as_bytes().into()
};
#[cfg(not(any(unix, target_os = "wasi")))]
let p: &BStr = path.to_str().ok_or(Utf8Error)?.as_bytes().into();
p
}),
};
Ok(path_str)
}
pub fn into_bstr<'a>(path: impl Into<Cow<'a, Path>>) -> Cow<'a, BStr> {
try_into_bstr(path).expect("prefix path doesn't contain ill-formed UTF-8")
}
pub fn join_bstr_unix_pathsep<'a, 'b>(base: impl Into<Cow<'a, BStr>>, path: impl Into<&'b BStr>) -> Cow<'a, BStr> {
let mut base = base.into();
if !base.is_empty() && base.last() != Some(&b'/') {
base.to_mut().push(b'/');
}
base.to_mut().extend_from_slice(path.into());
base
}
pub fn try_from_byte_slice(input: &[u8]) -> Result<&Path, Utf8Error> {
#[cfg(unix)]
let p = {
use std::os::unix::ffi::OsStrExt;
OsStr::from_bytes(input).as_ref()
};
#[cfg(target_os = "wasi")]
let p: &Path = {
use std::os::wasi::ffi::OsStrExt;
OsStr::from_bytes(input).as_ref()
};
#[cfg(not(any(unix, target_os = "wasi")))]
let p = Path::new(std::str::from_utf8(input).map_err(|_| Utf8Error)?);
Ok(p)
}
pub fn try_from_bstr<'a>(input: impl Into<Cow<'a, BStr>>) -> Result<Cow<'a, Path>, Utf8Error> {
let input = input.into();
match input {
Cow::Borrowed(input) => try_from_byte_slice(input).map(Cow::Borrowed),
Cow::Owned(input) => try_from_bstring(input).map(Cow::Owned),
}
}
pub fn from_bstr<'a>(input: impl Into<Cow<'a, BStr>>) -> Cow<'a, Path> {
try_from_bstr(input).expect("prefix path doesn't contain ill-formed UTF-8")
}
pub fn try_from_bstring(input: impl Into<BString>) -> Result<PathBuf, Utf8Error> {
let input = input.into();
#[cfg(unix)]
let p = {
use std::os::unix::ffi::OsStringExt;
std::ffi::OsString::from_vec(input.into()).into()
};
#[cfg(target_os = "wasi")]
let p: PathBuf = {
use std::os::wasi::ffi::OsStringExt;
std::ffi::OsString::from_vec(input.into()).into()
};
#[cfg(not(any(unix, target_os = "wasi")))]
let p = {
use bstr::ByteVec;
PathBuf::from(
{
let v: Vec<_> = input.into();
v
}
.into_string()
.map_err(|_| Utf8Error)?,
)
};
Ok(p)
}
pub fn from_bstring(input: impl Into<BString>) -> PathBuf {
try_from_bstring(input).expect("well-formed UTF-8 on windows")
}
pub fn from_byte_slice(input: &[u8]) -> &Path {
try_from_byte_slice(input).expect("well-formed UTF-8 on windows")
}
fn replace<'a>(path: impl Into<Cow<'a, BStr>>, find: u8, replace: u8) -> Cow<'a, BStr> {
let path = path.into();
match path {
Cow::Owned(mut path) => {
for b in path.iter_mut().filter(|b| **b == find) {
*b = replace;
}
path.into()
}
Cow::Borrowed(path) => {
if !path.contains(&find) {
return path.into();
}
let mut path = path.to_owned();
for b in path.iter_mut().filter(|b| **b == find) {
*b = replace;
}
path.into()
}
}
}
pub fn to_native_separators<'a>(path: impl Into<Cow<'a, BStr>>) -> Cow<'a, BStr> {
#[cfg(not(windows))]
let p = to_unix_separators(path);
#[cfg(windows)]
let p = to_windows_separators(path);
p
}
pub fn to_native_path_on_windows<'a>(path: impl Into<Cow<'a, BStr>>) -> Cow<'a, std::path::Path> {
#[cfg(not(windows))]
{
crate::from_bstr(path)
}
#[cfg(windows)]
{
crate::from_bstr(to_windows_separators(path))
}
}
pub fn to_unix_separators_on_windows<'a>(path: impl Into<Cow<'a, BStr>>) -> Cow<'a, BStr> {
#[cfg(windows)]
{
replace(path, b'\\', b'/')
}
#[cfg(not(windows))]
{
path.into()
}
}
pub fn to_unix_separators<'a>(path: impl Into<Cow<'a, BStr>>) -> Cow<'a, BStr> {
replace(path, b'\\', b'/')
}
pub fn to_windows_separators<'a>(path: impl Into<Cow<'a, BStr>>) -> Cow<'a, BStr> {
replace(path, b'/', b'\\')
}
pub fn normalize<'a>(path: Cow<'a, Path>, current_dir: &Path) -> Option<Cow<'a, Path>> {
use std::path::Component::ParentDir;
if !path.components().any(|c| matches!(c, ParentDir)) {
return Some(path);
}
let mut current_dir_opt = Some(current_dir);
let was_relative = path.is_relative();
let components = path.components();
let mut path = PathBuf::new();
for component in components {
if let ParentDir = component {
let path_was_dot = path == Path::new(".");
if path.as_os_str().is_empty() || path_was_dot {
path.push(current_dir_opt.take()?);
}
if !path.pop() {
return None;
}
} else {
path.push(component);
}
}
if (path.as_os_str().is_empty() || path == current_dir) && was_relative {
Cow::Borrowed(Path::new("."))
} else {
path.into()
}
.into()
}
pub fn relativize_with_prefix<'a>(relative_path: &'a Path, prefix: &Path) -> Cow<'a, Path> {
if prefix.as_os_str().is_empty() {
return Cow::Borrowed(relative_path);
}
debug_assert!(
relative_path.components().all(|c| matches!(c, Component::Normal(_))),
"BUG: all input is expected to be normalized, but relative_path was not"
);
debug_assert!(
prefix.components().all(|c| matches!(c, Component::Normal(_))),
"BUG: all input is expected to be normalized, but prefix was not"
);
let mut buf = PathBuf::new();
let mut rpc = relative_path.components().peekable();
let mut equal_thus_far = true;
for pcomp in prefix.components() {
if equal_thus_far {
if let (Component::Normal(pname), Some(Component::Normal(rpname))) = (pcomp, rpc.peek()) {
if &pname == rpname {
rpc.next();
continue;
} else {
equal_thus_far = false;
}
}
}
buf.push(Component::ParentDir);
}
buf.extend(rpc);
if buf.as_os_str().is_empty() {
Cow::Borrowed(Path::new("."))
} else {
Cow::Owned(buf)
}
}