use std::ffi::OsStr;
use std::ffi::OsString;
use std::iter;
use std::mem;
use std::ops::Range;
use std::ops::RangeFrom;
use std::ops::RangeFull;
use std::ops::RangeInclusive;
use std::ops::RangeTo;
use std::ops::RangeToInclusive;
use std::str;
use super::iter::RSplit;
use super::iter::Split;
use super::iter::Utf8Chunks;
use super::pattern::Encoded as EncodedPattern;
use super::util;
use super::util::MAX_UTF8_LENGTH;
use super::OsStrBytes;
use super::Pattern;
if_conversions! {
use super::imp::raw;
}
fn is_boundary(string: &OsStr, index: usize) -> bool {
let string = string.as_encoded_bytes();
debug_assert!(index < string.len());
if index == 0 {
return true;
}
let byte = string[index];
if byte.is_ascii() {
return true;
}
if !util::is_continuation(byte) {
let bytes = &string[index..];
if !str::from_utf8(&bytes[..bytes.len().min(MAX_UTF8_LENGTH)])
.is_err_and(|x| x.valid_up_to() == 0)
{
return true;
}
}
(0..index)
.rev()
.take(MAX_UTF8_LENGTH)
.find(|&x| !util::is_continuation(string[x]))
.is_some_and(|x| str::from_utf8(&string[x..index]).is_ok())
}
#[track_caller]
pub(super) fn check_bound(string: &OsStr, index: usize) {
assert!(
index >= string.as_encoded_bytes().len() || is_boundary(string, index),
"byte index {} is not a valid boundary",
index,
);
}
macro_rules! r#impl {
( $($name:ident),+ ) => {
$(
#[cfg(feature = "memchr")]
use memchr::memmem::$name;
#[cfg(not(feature = "memchr"))]
fn $name(string: &[u8], pat: &[u8]) -> Option<usize> {
(pat.len()..=string.len())
.$name(|&x| string[..x].ends_with(pat))
.map(|x| x - pat.len())
}
)+
};
}
r#impl!(find, rfind);
pub(super) unsafe fn os_str(string: &[u8]) -> &OsStr {
unsafe { OsStr::from_encoded_bytes_unchecked(string) }
}
fn split_once<'a, 'b, P>(
string: &'a OsStr,
pat: &'b P,
find_fn: fn(&OsStr, &'b str) -> Option<usize>,
) -> Option<(&'a OsStr, &'a OsStr)>
where
P: EncodedPattern,
{
let pat = pat.__as_str();
let index = find_fn(string, pat)?;
let string = string.as_encoded_bytes();
let prefix = &string[..index];
let suffix = &string[index + pat.len()..];
Some(unsafe { (os_str(prefix), os_str(suffix)) })
}
fn trim_matches<'a, 'b, P>(
mut string: &'a OsStr,
pat: &'b P,
strip_fn: for<'c> fn(&'c OsStr, &'b str) -> Option<&'c OsStr>,
) -> &'a OsStr
where
P: EncodedPattern,
{
let pat = pat.__as_str();
if !pat.is_empty() {
while let Some(substring) = strip_fn(string, pat) {
string = substring;
}
}
string
}
fn trim_end_matches<'a, P>(string: &'a OsStr, pat: &P) -> &'a OsStr
where
P: EncodedPattern,
{
trim_matches(string, pat, OsStrBytesExt::strip_suffix)
}
fn trim_start_matches<'a, P>(string: &'a OsStr, pat: &P) -> &'a OsStr
where
P: EncodedPattern,
{
trim_matches(string, pat, OsStrBytesExt::strip_prefix)
}
#[cfg_attr(not(feature = "conversions"), allow(private_bounds))]
#[cfg_attr(os_str_bytes_docs_rs, doc(cfg(feature = "raw_os_str")))]
pub trait OsStrBytesExt: OsStrBytes {
#[must_use]
fn contains<P>(&self, pat: P) -> bool
where
P: Pattern;
#[must_use]
fn ends_with<P>(&self, pat: P) -> bool
where
P: Pattern;
if_conversions! {
#[cfg_attr(
os_str_bytes_docs_rs,
doc(cfg(feature = "conversions"))
)]
#[must_use]
fn ends_with_os(&self, pat: &Self) -> bool;
}
#[must_use]
fn find<P>(&self, pat: P) -> Option<usize>
where
P: Pattern;
#[must_use]
#[track_caller]
unsafe fn get_unchecked<I>(&self, index: I) -> &Self
where
I: SliceIndex;
#[must_use]
#[track_caller]
fn index<I>(&self, index: I) -> &Self
where
I: SliceIndex;
#[must_use]
fn repeat(&self, n: usize) -> Self::Owned;
#[must_use]
fn rfind<P>(&self, pat: P) -> Option<usize>
where
P: Pattern;
#[track_caller]
fn rsplit<P>(&self, pat: P) -> RSplit<'_, P>
where
P: Pattern;
#[must_use]
fn rsplit_once<P>(&self, pat: P) -> Option<(&Self, &Self)>
where
P: Pattern;
#[track_caller]
fn split<P>(&self, pat: P) -> Split<'_, P>
where
P: Pattern;
#[must_use]
#[track_caller]
fn split_at(&self, mid: usize) -> (&Self, &Self);
#[must_use]
fn split_once<P>(&self, pat: P) -> Option<(&Self, &Self)>
where
P: Pattern;
#[must_use]
fn starts_with<P>(&self, pat: P) -> bool
where
P: Pattern;
if_conversions! {
#[cfg_attr(
os_str_bytes_docs_rs,
doc(cfg(feature = "conversions"))
)]
#[must_use]
fn starts_with_os(&self, pat: &Self) -> bool;
}
#[must_use]
fn strip_prefix<P>(&self, pat: P) -> Option<&Self>
where
P: Pattern;
#[must_use]
fn strip_suffix<P>(&self, pat: P) -> Option<&Self>
where
P: Pattern;
#[must_use]
fn trim_end_matches<P>(&self, pat: P) -> &Self
where
P: Pattern;
#[must_use]
fn trim_matches<P>(&self, pat: P) -> &Self
where
P: Pattern;
#[must_use]
fn trim_start_matches<P>(&self, pat: P) -> &Self
where
P: Pattern;
fn utf8_chunks(&self) -> Utf8Chunks<'_>;
}
impl OsStrBytesExt for OsStr {
#[inline]
fn contains<P>(&self, pat: P) -> bool
where
P: Pattern,
{
self.find(pat).is_some()
}
#[inline]
fn ends_with<P>(&self, pat: P) -> bool
where
P: Pattern,
{
let pat = pat.__encode();
let pat = pat.__as_bytes();
self.as_encoded_bytes().ends_with(pat)
}
if_conversions! {
#[inline]
fn ends_with_os(&self, pat: &Self) -> bool {
raw::ends_with(&self.to_raw_bytes(), &pat.to_raw_bytes())
}
}
#[inline]
fn find<P>(&self, pat: P) -> Option<usize>
where
P: Pattern,
{
let pat = pat.__encode();
let pat = pat.__as_bytes();
find(self.as_encoded_bytes(), pat)
}
#[inline]
unsafe fn get_unchecked<I>(&self, index: I) -> &Self
where
I: SliceIndex,
{
unsafe { index.get_unchecked(self) }
}
#[inline]
fn index<I>(&self, index: I) -> &Self
where
I: SliceIndex,
{
index.index(self)
}
#[inline]
fn repeat(&self, n: usize) -> Self::Owned {
let mut string = OsString::new();
string.extend(iter::repeat(self).take(n));
string
}
#[inline]
fn rfind<P>(&self, pat: P) -> Option<usize>
where
P: Pattern,
{
let pat = pat.__encode();
let pat = pat.__as_bytes();
rfind(self.as_encoded_bytes(), pat)
}
#[inline]
fn rsplit<P>(&self, pat: P) -> RSplit<'_, P>
where
P: Pattern,
{
RSplit::new(self, pat)
}
#[inline]
fn rsplit_once<P>(&self, pat: P) -> Option<(&Self, &Self)>
where
P: Pattern,
{
split_once(self, &pat.__encode(), Self::rfind)
}
#[inline]
fn split<P>(&self, pat: P) -> Split<'_, P>
where
P: Pattern,
{
Split::new(self, pat)
}
#[inline]
fn split_at(&self, mid: usize) -> (&Self, &Self) {
check_bound(self, mid);
let (prefix, suffix) = self.as_encoded_bytes().split_at(mid);
unsafe { (os_str(prefix), os_str(suffix)) }
}
#[inline]
fn split_once<P>(&self, pat: P) -> Option<(&Self, &Self)>
where
P: Pattern,
{
split_once(self, &pat.__encode(), Self::find)
}
#[inline]
fn starts_with<P>(&self, pat: P) -> bool
where
P: Pattern,
{
let pat = pat.__encode();
let pat = pat.__as_bytes();
self.as_encoded_bytes().starts_with(pat)
}
if_conversions! {
#[inline]
fn starts_with_os(&self, pat: &Self) -> bool {
raw::starts_with(&self.to_raw_bytes(), &pat.to_raw_bytes())
}
}
#[inline]
fn strip_prefix<P>(&self, pat: P) -> Option<&Self>
where
P: Pattern,
{
let pat = pat.__encode();
let pat = pat.__as_bytes();
self.as_encoded_bytes()
.strip_prefix(pat)
.map(|x| unsafe { os_str(x) })
}
#[inline]
fn strip_suffix<P>(&self, pat: P) -> Option<&Self>
where
P: Pattern,
{
let pat = pat.__encode();
let pat = pat.__as_bytes();
self.as_encoded_bytes()
.strip_suffix(pat)
.map(|x| unsafe { os_str(x) })
}
#[inline]
fn trim_end_matches<P>(&self, pat: P) -> &Self
where
P: Pattern,
{
trim_end_matches(self, &pat.__encode())
}
#[inline]
fn trim_matches<P>(&self, pat: P) -> &Self
where
P: Pattern,
{
let pat = pat.__encode();
trim_end_matches(trim_start_matches(self, &pat), &pat)
}
#[inline]
fn trim_start_matches<P>(&self, pat: P) -> &Self
where
P: Pattern,
{
trim_start_matches(self, &pat.__encode())
}
#[inline]
fn utf8_chunks(&self) -> Utf8Chunks<'_> {
Utf8Chunks::new(self)
}
}
pub trait SliceIndex {
unsafe fn get_unchecked(self, string: &OsStr) -> &OsStr;
fn index(self, string: &OsStr) -> &OsStr;
}
macro_rules! r#impl {
( $type:ty $(, $var:ident , $($bound:expr),+)? ) => {
impl SliceIndex for $type {
#[inline]
unsafe fn get_unchecked(self, string: &OsStr) -> &OsStr {
unsafe {
os_str(string.as_encoded_bytes().get_unchecked(self))
}
}
#[inline]
fn index(self, string: &OsStr) -> &OsStr {
$(
let $var = &self;
$(check_bound(string, $bound);)+
)?
unsafe { os_str(&string.as_encoded_bytes()[self]) }
}
}
};
}
r#impl!(Range<usize>, x, x.start, x.end);
r#impl!(RangeFrom<usize>, x, x.start);
r#impl!(RangeFull);
#[rustfmt::skip]
r#impl!(RangeInclusive<usize>, x, *x.start(), x.end().wrapping_add(1));
r#impl!(RangeTo<usize>, x, x.end);
r#impl!(RangeToInclusive<usize>, x, x.end.wrapping_add(1));
#[derive(Debug)]
#[cfg_attr(os_str_bytes_docs_rs, doc(cfg(feature = "raw_os_str")))]
#[repr(transparent)]
pub struct NonUnicodeOsStr(OsStr);
impl NonUnicodeOsStr {
unsafe fn from_inner(string: &OsStr) -> &Self {
unsafe { mem::transmute(string) }
}
pub(super) unsafe fn new_unchecked(string: &[u8]) -> &Self {
unsafe { Self::from_inner(os_str(string)) }
}
#[inline]
#[must_use]
pub fn as_os_str(&self) -> &OsStr {
&self.0
}
}
impl AsRef<OsStr> for NonUnicodeOsStr {
#[inline]
fn as_ref(&self) -> &OsStr {
&self.0
}
}