Skip to content

Commit

Permalink
Separate split and rsplit
Browse files Browse the repository at this point in the history
  • Loading branch information
dylni committed Nov 17, 2023
1 parent 738d219 commit 911d768
Show file tree
Hide file tree
Showing 3 changed files with 186 additions and 151 deletions.
28 changes: 28 additions & 0 deletions src/ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ 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;
Expand Down Expand Up @@ -275,6 +276,25 @@ pub trait OsStrBytesExt: OsStrBytes {
where
P: Pattern;

/// Equivalent to [`str::rsplit`], but empty patterns are not accepted.
///
/// # Panics
///
/// Panics if the pattern is empty.
///
/// # Examples
///
/// ```
/// use os_str_bytes::RawOsStr;
///
/// let raw = RawOsStr::new("foobar");
/// assert!(raw.rsplit("o").eq(["bar", "", "f"]));
/// ```
#[track_caller]
fn rsplit<P>(&self, pat: P) -> RSplit<'_, P>
where
P: Pattern;

/// Equivalent to [`str::rsplit_once`].
///
/// # Examples
Expand Down Expand Up @@ -588,6 +608,14 @@ impl OsStrBytesExt for OsStr {
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
Expand Down
289 changes: 138 additions & 151 deletions src/iter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,171 +17,158 @@ use super::OsStrBytesExt;
use super::Pattern;
use super::RawOsStr;

// [memchr::memmem::FindIter] is not currently used, since this struct would
// become self-referential. Additionally, that iterator does not implement
// [DoubleEndedIterator], and its implementation would likely require
// significant changes to implement that trait.
/// The iterator returned by [`OsStrBytesExt::split`].
#[must_use]
pub struct Split<'a, P>
where
P: Pattern,
{
string: Option<&'a OsStr>,
pat: P::__Encoded,
}

impl<'a, P> Split<'a, P>
where
P: Pattern,
{
#[track_caller]
pub(super) fn new(string: &'a OsStr, pat: P) -> Self {
let pat = pat.__encode();
assert!(
!pat.__as_str().is_empty(),
"cannot split using an empty pattern",
);
Self {
string: Some(string),
pat,
macro_rules! r#impl {
(
$(#[$attr:meta])* $name:ident ,
$(#[$raw_attr:meta])* $raw_name:ident ,
$split_method:ident ,
$reverse:expr ,
) => {
// [memchr::memmem::FindIter] would make this struct self-referential.
#[must_use]
$(#[$attr])*
pub struct $name<'a, P>
where
P: Pattern,
{
string: Option<&'a OsStr>,
pat: P::__Encoded,
}
}
}

macro_rules! impl_next {
( $self:ident , $split_method:ident , $swap:expr ) => {{
$self
.string?
.$split_method($self.pat.__as_str())
.map(|(mut substring, mut string)| {
if $swap {
mem::swap(&mut substring, &mut string);
impl<'a, P> $name<'a, P>
where
P: Pattern,
{
#[track_caller]
pub(super) fn new(string: &'a OsStr, pat: P) -> Self {
let pat = pat.__encode();
assert!(
!pat.__as_str().is_empty(),
"cannot split using an empty pattern",
);
Self {
string: Some(string),
pat,
}
$self.string = Some(string);
substring
})
.or_else(|| $self.string.take())
}};
}

impl<P> Clone for Split<'_, P>
where
P: Pattern,
{
#[inline]
fn clone(&self) -> Self {
Self {
string: self.string,
pat: self.pat.clone(),
}
}
}
}

impl<P> Debug for Split<'_, P>
where
P: Pattern,
{
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("Split")
.field("string", &self.string)
.field("pat", &self.pat)
.finish()
}
}

impl<P> DoubleEndedIterator for Split<'_, P>
where
P: Pattern,
{
fn next_back(&mut self) -> Option<Self::Item> {
impl_next!(self, rsplit_once, true)
}
}

impl<P> FusedIterator for Split<'_, P> where P: Pattern {}

impl<'a, P> Iterator for Split<'a, P>
where
P: Pattern,
{
type Item = &'a OsStr;

#[inline]
fn last(mut self) -> Option<Self::Item> {
self.next_back()
}

fn next(&mut self) -> Option<Self::Item> {
impl_next!(self, split_once, false)
}
}
impl<P> Clone for $name<'_, P>
where
P: Pattern,
{
#[inline]
fn clone(&self) -> Self {
Self {
string: self.string,
pat: self.pat.clone(),
}
}
}

/// The iterator returned by [`RawOsStr::split`].
#[must_use]
pub struct RawSplit<'a, P>(Split<'a, P>)
where
P: Pattern;

impl<'a, P> RawSplit<'a, P>
where
P: Pattern,
{
#[track_caller]
pub(super) fn new(string: &'a RawOsStr, pat: P) -> Self {
Self(Split::new(string.as_os_str(), pat))
}
}
impl<P> Debug for $name<'_, P>
where
P: Pattern,
{
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct(stringify!($name))
.field("string", &self.string)
.field("pat", &self.pat)
.finish()
}
}

impl<P> Clone for RawSplit<'_, P>
where
P: Pattern,
{
#[inline]
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl<P> FusedIterator for $name<'_, P> where P: Pattern {}

impl<'a, P> Iterator for $name<'a, P>
where
P: Pattern,
{
type Item = &'a OsStr;

#[inline]
fn next(&mut self) -> Option<Self::Item> {
self.string?
.$split_method(self.pat.__as_str())
.map(|(mut substring, mut string)| {
if $reverse {
mem::swap(&mut substring, &mut string);
}
self.string = Some(string);
substring
})
.or_else(|| self.string.take())
}
}

impl<P> Debug for RawSplit<'_, P>
where
P: Pattern,
{
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_tuple("RawSplit").field(&self.0).finish()
}
}
#[must_use]
$(#[$raw_attr])*
pub struct $raw_name<'a, P>($name<'a, P>)
where
P: Pattern;

impl<'a, P> $raw_name<'a, P>
where
P: Pattern,
{
#[track_caller]
pub(super) fn new(string: &'a RawOsStr, pat: P) -> Self {
Self($name::new(string.as_os_str(), pat))
}
}

impl<P> DoubleEndedIterator for RawSplit<'_, P>
where
P: Pattern,
{
#[inline]
fn next_back(&mut self) -> Option<Self::Item> {
self.0.next_back().map(RawOsStr::new)
}
}
impl<P> Clone for $raw_name<'_, P>
where
P: Pattern,
{
#[inline]
fn clone(&self) -> Self {
Self(self.0.clone())
}
}

impl<P> FusedIterator for RawSplit<'_, P> where P: Pattern {}
impl<P> Debug for $raw_name<'_, P>
where
P: Pattern,
{
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_tuple(stringify!($raw_name)).field(&self.0).finish()
}
}

impl<'a, P> Iterator for RawSplit<'a, P>
where
P: Pattern,
{
type Item = &'a RawOsStr;
impl<P> FusedIterator for $raw_name<'_, P> where P: Pattern {}

#[inline]
fn last(self) -> Option<Self::Item> {
self.0.last().map(RawOsStr::new)
}
impl<'a, P> Iterator for $raw_name<'a, P>
where
P: Pattern,
{
type Item = &'a RawOsStr;

#[inline]
fn next(&mut self) -> Option<Self::Item> {
self.0.next().map(RawOsStr::new)
}
#[inline]
fn next(&mut self) -> Option<Self::Item> {
self.0.next().map(RawOsStr::new)
}
}
};
}
r#impl!(
/// The iterator returned by [`OsStrBytesExt::split`].
Split,
/// The iterator returned by [`RawOsStr::split`].
RawSplit,
split_once,
false,
);
r#impl!(
/// The iterator returned by [`OsStrBytesExt::rsplit`].
RSplit,
/// The iterator returned by [`RawOsStr::rsplit`].
RawRSplit,
rsplit_once,
true,
);

/// The iterator returned by [`OsStrBytesExt::utf8_chunks`].
///
Expand Down
20 changes: 20 additions & 0 deletions src/raw_str.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use std::str;

use super::ext;
use super::iter::RawSplit;
use super::iter::RawRSplit;
use super::iter::Utf8Chunks;
use super::private;
use super::OsStrBytesExt;
Expand Down Expand Up @@ -427,6 +428,25 @@ impl RawOsStr {
self.as_os_str().rfind(pat)
}

/// Equivalent to [`OsStrBytesExt::rsplit`].
///
/// # Examples
///
/// ```
/// use os_str_bytes::RawOsStr;
///
/// let raw = RawOsStr::new("foobar");
/// assert!(raw.rsplit("o").eq(["bar", "", "f"]));
/// ```
#[inline]
#[track_caller]
pub fn rsplit<P>(&self, pat: P) -> RawRSplit<'_, P>
where
P: Pattern,
{
RawRSplit::new(self, pat)
}

/// Equivalent to [`OsStrBytesExt::rsplit_once`].
///
/// # Examples
Expand Down

0 comments on commit 911d768

Please sign in to comment.