#![forbid(unsafe_code)]
#![warn(clippy::dbg_macro, clippy::print_stdout)]
use std::borrow::Cow;
use std::ffi::OsStr;
use std::path::{Path, PathBuf, MAIN_SEPARATOR};
#[cfg(target_os = "windows")]
mod windows {
use super::*;
use std::os::windows::ffi::OsStrExt as _;
pub(crate) fn ends_with_main_sep(p: &Path) -> bool {
p.as_os_str().encode_wide().last() == Some(MAIN_SEPARATOR as u16)
}
}
fn str_to_path(s: &str, sep: char) -> Cow<'_, Path> {
let mut buf = String::new();
for (i, c) in s.char_indices() {
if c == sep {
if buf.is_empty() {
buf.reserve(s.len());
buf.push_str(&s[..i]);
}
buf.push(MAIN_SEPARATOR);
} else if !buf.is_empty() {
buf.push(c);
}
}
if buf.is_empty() {
Cow::Borrowed(Path::new(s))
} else {
Cow::Owned(PathBuf::from(buf))
}
}
fn str_to_pathbuf<S: AsRef<str>>(s: S, sep: char) -> PathBuf {
let s = s
.as_ref()
.chars()
.map(|c| if c == sep { MAIN_SEPARATOR } else { c })
.collect::<String>();
PathBuf::from(s)
}
pub trait PathExt {
fn to_slash(&self) -> Option<Cow<'_, str>>;
fn to_slash_lossy(&self) -> Cow<'_, str>;
}
impl PathExt for Path {
#[cfg(not(target_os = "windows"))]
fn to_slash_lossy(&self) -> Cow<'_, str> {
self.to_string_lossy()
}
#[cfg(target_os = "windows")]
fn to_slash_lossy(&self) -> Cow<'_, str> {
use std::path::Component;
let mut buf = String::new();
for c in self.components() {
match c {
Component::RootDir => { }
Component::CurDir => buf.push('.'),
Component::ParentDir => buf.push_str(".."),
Component::Prefix(prefix) => {
buf.push_str(&prefix.as_os_str().to_string_lossy());
continue;
}
Component::Normal(s) => buf.push_str(&s.to_string_lossy()),
}
buf.push('/');
}
if !windows::ends_with_main_sep(self) && buf != "/" && buf.ends_with('/') {
buf.pop(); }
Cow::Owned(buf)
}
#[cfg(not(target_os = "windows"))]
fn to_slash(&self) -> Option<Cow<'_, str>> {
self.to_str().map(Cow::Borrowed)
}
#[cfg(target_os = "windows")]
fn to_slash(&self) -> Option<Cow<'_, str>> {
use std::path::Component;
let mut buf = String::new();
for c in self.components() {
match c {
Component::RootDir => { }
Component::CurDir => buf.push('.'),
Component::ParentDir => buf.push_str(".."),
Component::Prefix(prefix) => {
buf.push_str(prefix.as_os_str().to_str()?);
continue;
}
Component::Normal(s) => buf.push_str(s.to_str()?),
}
buf.push('/');
}
if !windows::ends_with_main_sep(self) && buf != "/" && buf.ends_with('/') {
buf.pop(); }
Some(Cow::Owned(buf))
}
}
pub trait PathBufExt {
fn from_slash<S: AsRef<str>>(s: S) -> Self;
fn from_slash_lossy<S: AsRef<OsStr>>(s: S) -> Self;
fn from_backslash<S: AsRef<str>>(s: S) -> Self;
fn from_backslash_lossy<S: AsRef<OsStr>>(s: S) -> Self;
fn to_slash(&self) -> Option<Cow<'_, str>>;
fn to_slash_lossy(&self) -> Cow<'_, str>;
}
impl PathBufExt for PathBuf {
#[cfg(not(target_os = "windows"))]
fn from_slash<S: AsRef<str>>(s: S) -> Self {
PathBuf::from(s.as_ref())
}
#[cfg(target_os = "windows")]
fn from_slash<S: AsRef<str>>(s: S) -> Self {
str_to_pathbuf(s, '/')
}
#[cfg(not(target_os = "windows"))]
fn from_slash_lossy<S: AsRef<OsStr>>(s: S) -> Self {
PathBuf::from(s.as_ref())
}
#[cfg(target_os = "windows")]
fn from_slash_lossy<S: AsRef<OsStr>>(s: S) -> Self {
Self::from_slash(&s.as_ref().to_string_lossy())
}
#[cfg(not(target_os = "windows"))]
fn from_backslash<S: AsRef<str>>(s: S) -> Self {
str_to_pathbuf(s, '\\')
}
#[cfg(target_os = "windows")]
fn from_backslash<S: AsRef<str>>(s: S) -> Self {
PathBuf::from(s.as_ref())
}
#[cfg(not(target_os = "windows"))]
fn from_backslash_lossy<S: AsRef<OsStr>>(s: S) -> Self {
str_to_pathbuf(&s.as_ref().to_string_lossy(), '\\')
}
#[cfg(target_os = "windows")]
fn from_backslash_lossy<S: AsRef<OsStr>>(s: S) -> Self {
PathBuf::from(s.as_ref())
}
fn to_slash(&self) -> Option<Cow<'_, str>> {
self.as_path().to_slash()
}
fn to_slash_lossy(&self) -> Cow<'_, str> {
self.as_path().to_slash_lossy()
}
}
pub trait CowExt<'a> {
fn from_slash(s: &'a str) -> Self;
fn from_slash_lossy(s: &'a OsStr) -> Self;
fn from_backslash(s: &'a str) -> Self;
fn from_backslash_lossy(s: &'a OsStr) -> Self;
fn to_slash(&self) -> Option<Cow<'_, str>>;
fn to_slash_lossy(&self) -> Cow<'_, str>;
}
impl<'a> CowExt<'a> for Cow<'a, Path> {
#[cfg(not(target_os = "windows"))]
fn from_slash(s: &'a str) -> Self {
Cow::Borrowed(Path::new(s))
}
#[cfg(target_os = "windows")]
fn from_slash(s: &'a str) -> Self {
str_to_path(s, '/')
}
#[cfg(not(target_os = "windows"))]
fn from_slash_lossy(s: &'a OsStr) -> Self {
Cow::Borrowed(Path::new(s))
}
#[cfg(target_os = "windows")]
fn from_slash_lossy(s: &'a OsStr) -> Self {
match s.to_string_lossy() {
Cow::Borrowed(s) => str_to_path(s, '/'),
Cow::Owned(s) => Cow::Owned(str_to_pathbuf(&s, '/')),
}
}
#[cfg(not(target_os = "windows"))]
fn from_backslash(s: &'a str) -> Self {
str_to_path(s, '\\')
}
#[cfg(target_os = "windows")]
fn from_backslash(s: &'a str) -> Self {
Cow::Borrowed(Path::new(s))
}
#[cfg(not(target_os = "windows"))]
fn from_backslash_lossy(s: &'a OsStr) -> Self {
match s.to_string_lossy() {
Cow::Borrowed(s) => str_to_path(s, '\\'),
Cow::Owned(s) => Cow::Owned(str_to_pathbuf(&s, '\\')),
}
}
#[cfg(target_os = "windows")]
fn from_backslash_lossy(s: &'a OsStr) -> Self {
Cow::Borrowed(Path::new(s))
}
fn to_slash(&self) -> Option<Cow<'_, str>> {
self.as_ref().to_slash()
}
fn to_slash_lossy(&self) -> Cow<'_, str> {
self.as_ref().to_slash_lossy()
}
}