#![allow(clippy::missing_const_for_fn)]
use crate::borrow::Cow;
use crate::string::String;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct PathBuf(String);
impl PathBuf {
pub fn new() -> Self {
Self(String::new())
}
pub fn push(&mut self, component: &str) {
if !self.0.is_empty() && !matches!(self.0.as_bytes().last(), Some(b'/' | b'\\')) {
self.0.push('/');
}
self.0.push_str(component);
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
#[must_use]
pub fn as_bytes(&self) -> &[u8] {
self.0.as_bytes()
}
pub fn as_mut_string(&mut self) -> &mut String {
&mut self.0
}
}
impl From<&str> for PathBuf {
fn from(s: &str) -> Self {
Self(s.into())
}
}
impl From<String> for PathBuf {
fn from(s: String) -> Self {
Self(s)
}
}
impl AsRef<str> for PathBuf {
fn as_ref(&self) -> &str {
&self.0
}
}
impl core::fmt::Display for PathBuf {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(&self.0)
}
}
#[cfg(any(windows, target_os = "redox"))]
const PATH_SEPARATORS: [char; 2] = ['/', '\\'];
#[cfg(not(any(windows, target_os = "redox")))]
const PATH_SEPARATOR: char = '/';
#[must_use]
pub fn contains_separator(s: &str) -> bool {
#[cfg(any(windows, target_os = "redox"))]
{
s.contains(PATH_SEPARATORS)
}
#[cfg(not(any(windows, target_os = "redox")))]
{
s.contains(PATH_SEPARATOR)
}
}
#[must_use]
pub fn ends_with_separator(s: &str) -> bool {
#[cfg(any(windows, target_os = "redox"))]
{
s.ends_with(PATH_SEPARATORS)
}
#[cfg(not(any(windows, target_os = "redox")))]
{
s.ends_with(PATH_SEPARATOR)
}
}
#[must_use]
pub fn strip_separator_suffix(s: &str) -> &str {
#[cfg(any(windows, target_os = "redox"))]
{
s.strip_suffix(PATH_SEPARATORS).unwrap_or(s)
}
#[cfg(not(any(windows, target_os = "redox")))]
{
s.strip_suffix(PATH_SEPARATOR).unwrap_or(s)
}
}
#[must_use]
pub fn rfind_separator(s: &str) -> Option<usize> {
#[cfg(any(windows, target_os = "redox"))]
{
s.rfind(PATH_SEPARATORS)
}
#[cfg(not(any(windows, target_os = "redox")))]
{
s.rfind(PATH_SEPARATOR)
}
}
pub fn split_for_pattern(s: &str) -> impl Iterator<Item = &str> {
#[cfg(any(windows, target_os = "redox"))]
{
s.split(PATH_SEPARATORS)
}
#[cfg(not(any(windows, target_os = "redox")))]
{
s.split(PATH_SEPARATOR)
}
}
#[must_use]
pub fn pattern_root(first_component: &str) -> Option<PathBuf> {
if first_component.is_empty() {
return Some(PathBuf::from("/"));
}
#[cfg(windows)]
{
if first_component.len() == 2
&& first_component.as_bytes()[0].is_ascii_alphabetic()
&& first_component.as_bytes()[1] == b':'
{
let mut root = String::with_capacity(3);
root.push_str(first_component);
root.push('/');
return Some(PathBuf::from(root));
}
}
None
}
pub fn push_for_pattern(path: &mut PathBuf, component: &str) {
#[cfg(not(windows))]
{
path.push(component);
}
#[cfg(windows)]
{
let bytes = path.as_bytes();
let needs_sep = !bytes.is_empty() && !matches!(bytes.last(), Some(b'/' | b'\\'));
let buf = path.as_mut_string();
if needs_sep {
buf.push_str("/");
}
buf.push_str(component);
}
}
#[must_use]
pub fn normalize_separators(s: &str) -> Cow<'_, str> {
#[cfg(windows)]
{
if s.contains('\\') {
return Cow::Owned(s.replace('\\', "/"));
}
}
Cow::Borrowed(s)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::vec::Vec;
use alloc::vec;
#[test]
fn separators_basic() {
assert!(contains_separator("foo/bar"));
assert!(!contains_separator("foobar"));
assert!(ends_with_separator("foo/"));
assert!(!ends_with_separator("foo"));
assert_eq!(strip_separator_suffix("foo/"), "foo");
assert_eq!(strip_separator_suffix("foo"), "foo");
assert_eq!(rfind_separator("a/b/c"), Some(3));
assert_eq!(rfind_separator("abc"), None);
}
#[test]
fn split_basic() {
let parts: Vec<_> = split_for_pattern("a/b/c").collect();
assert_eq!(parts, vec!["a", "b", "c"]);
}
#[test]
fn pattern_root_leading_slash() {
assert_eq!(pattern_root(""), Some(PathBuf::from("/")));
assert_eq!(pattern_root("foo"), None);
}
}