str_chunks/str_chunks.rs
1/*
2 * --------------------
3 * THIS FILE IS LICENSED UNDER THE FOLLOWING TERMS
4 *
5 * all rights reserved. be gay, do crime
6 *
7 * THE FOLLOWING MESSAGE IS NOT A LICENSE
8 *
9 * <barrow@tilde.team> wrote this file.
10 * by reading this text, you are reading "TRANS RIGHTS".
11 * this file and the content within it is the gay agenda.
12 * if we meet some day, and you think this stuff is worth it,
13 * you can buy me a beer, tea, or something stronger.
14 * -Ezra Barrow
15 * --------------------
16 */
17#![no_std]
18
19//! # str chunks
20//! implements char-wise chunked iteration of str
21//!
22//! the methods [`str_chunks`], [`str_chunks_exact`], [`str_rchunks`], and [`str_rchunks_exact`]
23//! behave like the similarly named methods on slice,
24//! but return string slices that are `chunk_size` chars long.
25//! take note: these slices are not necessarily `chunk_size` *bytes* long. `chunk.len() != chunk_size`
26//!
27//! import [`ImplStrChunks`] to get methods on [`&str`]
28//!
29//! For [`DoubleEndedIterator`] support, use the `reversable` method.
30//! This is not a trivial operation, and it is recommended to avoid it if possible.
31//!
32//! [`&str`]: str
33//! [`DoubleEndedIterator`]: core::iter::DoubleEndedIterator
34//! [`str_chunks`]: ImplStrChunks::str_chunks
35//! [`str_chunks_exact`]: ImplStrChunks::str_chunks_exact
36//! [`str_rchunks`]: ImplStrChunks::str_rchunks
37//! [`str_rchunks_exact`]: ImplStrChunks::str_rchunks_exact
38
39use core::{
40 iter::{once, Chain, Once},
41 marker::PhantomData,
42 num::NonZeroUsize,
43 str::CharIndices,
44};
45
46/// Trait implemented on &str to provide convenience methods.
47///
48/// Import this!
49///
50/// # Example
51/// ```
52/// use str_chunks::ImplStrChunks;
53/// let s = "lorem";
54/// let mut iter = s.str_chunks(2);
55/// assert_eq!(iter.next(), Some("lo"));
56/// assert_eq!(iter.next(), Some("re"));
57/// assert_eq!(iter.next(), Some("m"));
58/// assert_eq!(iter.next(), None);
59/// assert_eq!(iter.remaining(), "");
60/// ```
61pub trait ImplStrChunks {
62 /// Returns an iterator over `chunk_size` chars at a time,
63 /// starting at the beginning of the str.
64 /// The chunks are &str slices and do not overlap.
65 ///
66 /// If `chunk_size` does not divide the char-length of the str,
67 /// then the last chunk will not have char-length `chunk_size`.
68 ///
69 /// See [`str_chunks_exact`] for a variant of this iterator that returns chunks of always
70 /// exactly `chunk_size` elements, and [`str_rchunks`] for the same iterator but starting at
71 /// the end of the str.
72 ///
73 /// # Panics
74 /// Panics if `chunk_size` is 0.
75 ///
76 /// # Example
77 ///
78 /// ```
79 /// use str_chunks::*;
80 /// let s = "lorem";
81 /// let mut iter = s.str_chunks(2);
82 /// assert_eq!(iter.next(), Some("lo"));
83 /// assert_eq!(iter.next(), Some("re"));
84 /// assert_eq!(iter.next(), Some("m"));
85 /// assert_eq!(iter.next(), None);
86 /// assert_eq!(iter.remaining(), "");
87 /// ```
88 /// [`str_chunks`]: ImplStrChunks::str_chunks
89 /// [`str_chunks_exact`]: ImplStrChunks::str_chunks_exact
90 /// [`str_rchunks`]: ImplStrChunks::str_rchunks
91 /// [`str_rchunks_exact`]: ImplStrChunks::str_rchunks_exact
92 fn str_chunks(&self, chunk_size: usize) -> StrChunks;
93 /// Returns an iterator over `chunk_size` chars at a time,
94 /// starting at the beginning of the str.
95 /// The chunks are &str slices and do not overlap.
96 ///
97 /// If `chunk_size` does not divide the char-length of the str,
98 /// then the last up to `chunk_size-1` chars will be omitted and can be retrieved from the
99 /// [`remaining`] function of the iterator after the iterator has returned None.
100 ///
101 /// See [`str_chunks`] for a variant of this iterator that also returns the remainder as a
102 /// smaller chunk.
103 ///
104 /// # Panics
105 /// Panics if `chunk_size` is 0.
106 ///
107 /// # Example
108 ///
109 /// ```
110 /// use str_chunks::*;
111 /// let s = "lorem";
112 /// let mut iter = s.str_chunks_exact(2);
113 /// assert_eq!(iter.next(), Some("lo"));
114 /// assert_eq!(iter.next(), Some("re"));
115 /// assert_eq!(iter.next(), None);
116 /// assert_eq!(iter.remaining(), "m");
117 /// ```
118 /// [`str_chunks`]: ImplStrChunks::str_chunks
119 /// [`str_chunks_exact`]: ImplStrChunks::str_chunks_exact
120 /// [`str_rchunks`]: ImplStrChunks::str_rchunks
121 /// [`str_rchunks_exact`]: ImplStrChunks::str_rchunks_exact
122 /// [`remaining`]: StrChunksExact::remaining
123 fn str_chunks_exact(&self, chunk_size: usize) -> StrChunksExact;
124 /// Returns an iterator over `chunk_size` chars at a time,
125 /// starting at the end of the str.
126 /// The chunks are &str slices and do not overlap.
127 ///
128 /// If `chunk_size` does not divide the char-length of the str,
129 /// then the last chunk will not have char-length `chunk_size`.
130 ///
131 /// See [`str_rchunks_exact`] for a variant of this iterator that returns chunks of always
132 /// exactly `chunk_size` elements, and [`str_chunks`] for the same iterator but starting at the
133 /// beginning of the str.
134 ///
135 /// # Panics
136 /// Panics if `chunk_size` is 0.
137 ///
138 /// # Example
139 ///
140 /// ```
141 /// use str_chunks::*;
142 /// let s = "lorem";
143 /// let mut iter = s.str_rchunks(2);
144 /// assert_eq!(iter.next(), Some("em"));
145 /// assert_eq!(iter.next(), Some("or"));
146 /// assert_eq!(iter.next(), Some("l"));
147 /// assert_eq!(iter.next(), None);
148 /// assert_eq!(iter.remaining(), "");
149 /// ```
150 /// [`str_chunks`]: ImplStrChunks::str_chunks
151 /// [`str_chunks_exact`]: ImplStrChunks::str_chunks_exact
152 /// [`str_rchunks`]: ImplStrChunks::str_rchunks
153 /// [`str_rchunks_exact`]: ImplStrChunks::str_rchunks_exact
154 fn str_rchunks(&self, chunk_size: usize) -> StrRChunks;
155 /// Returns an iterator over `chunk_size` chars at a time,
156 /// starting at the end of the str.
157 /// The chunks are &str slices and do not overlap.
158 ///
159 /// If `chunk_size` does not divide the char-length of the str,
160 /// then the last up to `chunk_size-1` chars will be omitted and can be retrieved from the
161 /// [`remaining`] function of the iterator after the iterator has
162 /// returned None.
163 ///
164 /// See [`str_rchunks`] for a variant of this iterator that also returns the remainder as a
165 /// smaller chunk, and [`str_chunks_exact`] for the same iterator but starting at the beginning
166 /// of the str.
167 ///
168 /// # Panics
169 /// Panics if `chunk_size` is 0.
170 ///
171 /// # Example
172 ///
173 /// ```
174 /// use str_chunks::*;
175 /// let s = "lorem";
176 /// let mut iter = s.str_rchunks_exact(2);
177 /// assert_eq!(iter.next(), Some("em"));
178 /// assert_eq!(iter.next(), Some("or"));
179 /// assert_eq!(iter.next(), None);
180 /// assert_eq!(iter.remaining(), "l");
181 /// ```
182 /// [`str_chunks`]: ImplStrChunks::str_chunks
183 /// [`str_chunks_exact`]: ImplStrChunks::str_chunks_exact
184 /// [`str_rchunks`]: ImplStrChunks::str_rchunks
185 /// [`str_rchunks_exact`]: ImplStrChunks::str_rchunks_exact
186 /// [`remaining`]: StrChunksExact::remaining
187 fn str_rchunks_exact(&self, chunk_size: usize) -> StrRChunksExact;
188}
189impl ImplStrChunks for &str {
190 fn str_chunks(&self, chunk_size: usize) -> StrChunks {
191 assert!(chunk_size != 0, "chunk size must be non-zero");
192 // UNWRAP: already checking chunk_size is non-zero, unwrap will be optimized out
193 StrChunks::new(self, chunk_size.try_into().unwrap())
194 }
195 fn str_chunks_exact(&self, chunk_size: usize) -> StrChunksExact {
196 assert!(chunk_size != 0, "chunk size must be non-zero");
197 // UNWRAP: already checking chunk_size is non-zero, unwrap will be optimized out
198 StrChunksExact::new(self, chunk_size.try_into().unwrap())
199 }
200 fn str_rchunks(&self, chunk_size: usize) -> StrRChunks {
201 assert!(chunk_size != 0, "chunk size must be non-zero");
202 // UNWRAP: already checking chunk_size is non-zero, unwrap will be optimized out
203 StrRChunks::new(self, chunk_size.try_into().unwrap())
204 }
205 fn str_rchunks_exact(&self, chunk_size: usize) -> StrRChunksExact {
206 assert!(chunk_size != 0, "chunk size must be non-zero");
207 // UNWRAP: already checking chunk_size is non-zero, unwrap will be optimized out
208 StrRChunksExact::new(self, chunk_size.try_into().unwrap())
209 }
210}
211
212#[allow(clippy::doc_markdown)]
213/// str::char_indices does not include s.len() as an index.
214/// the length is, however, a valid index for str::split_at.
215/// this iterator adds the length of the str to the end of the char_indices
216fn valid_split_points(s: &str) -> Chain<CharIndices, Once<(usize, char)>> {
217 s.char_indices()
218 .chain(once((s.len(), char::REPLACEMENT_CHARACTER)))
219}
220
221/// An iterator over a str in (non-overlapping) chunks (`chunk_size` chars at a time) starting at
222/// the beginning of the str.
223///
224/// When the str len (in characters) is not evenly divided by the chunk size, the last str of
225/// the iteration will be the remainder.
226///
227/// This struct is created by the [`str_chunks`] method on `str`
228///
229/// # Example
230///
231/// ```
232/// use str_chunks::*;
233/// let s = "lorem";
234/// let mut iter = s.str_chunks(2);
235/// assert_eq!(iter.next(), Some("lo"));
236/// assert_eq!(iter.next(), Some("re"));
237/// assert_eq!(iter.next(), Some("m"));
238/// assert_eq!(iter.next(), None);
239/// assert_eq!(iter.remaining(), "");
240/// ```
241///
242/// [`str_chunks`]: ImplStrChunks::str_chunks
243pub struct StrChunks<'s> {
244 s: &'s str,
245 // 0 chunk_size makes no sense
246 chunk_size: NonZeroUsize,
247}
248
249impl<'s> StrChunks<'s> {
250 fn new(s: &'s str, chunk_size: NonZeroUsize) -> Self {
251 Self { s, chunk_size }
252 }
253 #[must_use]
254 /// Returns the remaining string slice that has not yet been iterated over.
255 pub fn remaining(&self) -> &'s str {
256 self.s
257 }
258 #[must_use]
259 /// Makes this iterator reversible.
260 ///
261 /// Reversing these iterators requires precomputing the number of characters in the string
262 /// so the remainder can be split off in advance. This is not a trivial operation, as the only
263 /// way to get the character is to iterate over them all.
264 ///
265 /// Keep in mind that because of remainders, reversing a [`StrChunks`] will not necessarily yield
266 /// the same items as a corresponding [`StrRChunks`].
267 pub fn reversable(self) -> DoubleEnded<'s, Self> {
268 DoubleEnded::<'s, Self>::new(self)
269 }
270}
271
272impl<'s> Iterator for StrChunks<'s> {
273 type Item = &'s str;
274 fn next(&mut self) -> Option<&'s str> {
275 let (index, _) = valid_split_points(self.s)
276 .nth(self.chunk_size.get())
277 // SAFETY: the end of a str is a valid byte offset, and is the boundary of a codepoint
278 // by definition of a str.
279 .unwrap_or((self.s.len(), char::REPLACEMENT_CHARACTER));
280 // SAFETY: indices from valid_split_points are valid byte offsets on the boundary of a codepoint
281 let (chunk, rest) = unsafe { self.s.split_at_checked(index).unwrap_unchecked() };
282 self.s = rest;
283 // chunk is empty if index is 0 and (no remainder or remainder was already returned)
284 if chunk.is_empty() {
285 None
286 } else {
287 Some(chunk)
288 }
289 }
290}
291
292/// An iterator over a str in (non-overlapping) chunks (`chunk_size` chars at a time) starting at
293/// the beginning of the str.
294///
295/// When the str len (in characters) is not evenly divided by the chunk size, the last up to
296/// `chunk_size-1` chars can will be omitted but can be retrieved with the [`remaining`] function
297/// after the iteration has returned None.
298///
299/// This struct is created by the [`str_chunks_exact`] method on `str`
300///
301/// # Example
302///
303/// ```
304/// use str_chunks::*;
305/// let s = "lorem";
306/// let mut iter = s.str_chunks_exact(2);
307/// assert_eq!(iter.next(), Some("lo"));
308/// assert_eq!(iter.next(), Some("re"));
309/// assert_eq!(iter.next(), None);
310/// assert_eq!(iter.remaining(), "m");
311/// ```
312///
313/// [`str_chunks_exact`]: ImplStrChunks::str_chunks_exact
314/// [`remaining`]: StrChunksExact::remaining
315pub struct StrChunksExact<'s> {
316 s: &'s str,
317 // 0 chunk_size makes no sense
318 chunk_size: NonZeroUsize,
319}
320
321impl<'s> StrChunksExact<'s> {
322 fn new(s: &'s str, chunk_size: NonZeroUsize) -> Self {
323 Self { s, chunk_size }
324 }
325 #[must_use]
326 /// Returns the remaining string slice that has not yet been iterated over.
327 /// If [`Self::next`] has returned None, this is the remainder, and is at most N-1 chars long.
328 pub fn remaining(&self) -> &'s str {
329 self.s
330 }
331 #[must_use]
332 /// Makes this iterator reversible.
333 ///
334 /// Reversing these iterators requires precomputing the number of characters in the string
335 /// so the remainder can be split off in advance. This is not a trivial operation, as the only
336 /// way to get the character is to iterate over them all.
337 ///
338 /// Keep in mind that because of remainders, reversing a [`StrChunks`] will not necessarily yield
339 /// the same items as a corresponding [`StrRChunks`].
340 pub fn reversable(self) -> DoubleEnded<'s, Self> {
341 DoubleEnded::<'s, Self>::new(self)
342 }
343}
344
345impl<'s> Iterator for StrChunksExact<'s> {
346 type Item = &'s str;
347 fn next(&mut self) -> Option<&'s str> {
348 // will short-circuit None if s.len() < chunk_size, leaving the remainder in s
349 let (index, _) = valid_split_points(self.s).nth(self.chunk_size.get())?;
350
351 // SAFETY: indices from valid_split_points are valid byte offsets on the boundary of a codepoint
352 let (chunk, rest) = unsafe { self.s.split_at_checked(index).unwrap_unchecked() };
353
354 self.s = rest;
355 Some(chunk)
356 }
357}
358
359/// An iterator over a str in (non-overlapping) chunks (`chunk_size` chars at a time) starting at
360/// the end of the str.
361///
362/// When the str len (in characters) is not evenly divided by the chunk size, the last str of
363/// the iteration will be the remainder.
364///
365/// This struct is created by the [`str_rchunks`] method on `str`
366///
367/// # Example
368///
369/// ```
370/// use str_chunks::*;
371/// let s = "lorem";
372/// let mut iter = s.str_rchunks(2);
373/// assert_eq!(iter.next(), Some("em"));
374/// assert_eq!(iter.next(), Some("or"));
375/// assert_eq!(iter.next(), Some("l"));
376/// assert_eq!(iter.next(), None);
377/// assert_eq!(iter.remaining(), "");
378/// ```
379///
380/// [`str_rchunks`]: ImplStrChunks::str_rchunks
381pub struct StrRChunks<'s> {
382 s: &'s str,
383 // 0 chunk_size makes no sense
384 chunk_size: NonZeroUsize,
385}
386
387impl<'s> StrRChunks<'s> {
388 fn new(s: &'s str, chunk_size: NonZeroUsize) -> Self {
389 Self { s, chunk_size }
390 }
391 #[must_use]
392 /// Returns the remaining string slice that has not yet been iterated over.
393 pub fn remaining(&self) -> &'s str {
394 self.s
395 }
396 #[must_use]
397 /// Makes this iterator reversible.
398 ///
399 /// Reversing these iterators requires precomputing the number of characters in the string
400 /// so the remainder can be split off in advance. This is not a trivial operation, as the only
401 /// way to get the character is to iterate over them all.
402 ///
403 /// Keep in mind that because of remainders, reversing a [`StrChunks`] will not necessarily yield
404 /// the same items as a corresponding [`StrRChunks`].
405 pub fn reversable(self) -> DoubleEnded<'s, Self> {
406 DoubleEnded::<'s, Self>::new(self)
407 }
408}
409
410impl<'s> Iterator for StrRChunks<'s> {
411 type Item = &'s str;
412 fn next(&mut self) -> Option<&'s str> {
413 let (index, _) = valid_split_points(self.s)
414 .nth_back(self.chunk_size.get())
415 // SAFETY: the start of a str is a valid byte offset, and is the boundary of a codepoint
416 // by definition of a str.
417 .unwrap_or((0, char::REPLACEMENT_CHARACTER));
418 // SAFETY: indices from valid_split_points are valid byte offsets on the boundary of a codepoint
419 let (rest, chunk) = unsafe { self.s.split_at_checked(index).unwrap_unchecked() };
420 self.s = rest;
421 // chunk is empty if index is 0 and (no remainder or remainder was already returned)
422 if chunk.is_empty() {
423 None
424 } else {
425 Some(chunk)
426 }
427 }
428}
429
430/// An iterator over a str in (non-overlapping) chunks (`chunk_size` chars at a time) starting at
431/// the end of the str.
432///
433/// When the str len (in characters) is not evenly divided by the chunk size, the last up to
434/// `chunk_size-1` chars can will be omitted but can be retrieved with the [`remaining`] function
435/// after the iteration has returned None.
436///
437/// This struct is created by the [`str_rchunks_exact`] method on `str`
438///
439/// # Example
440///
441/// ```
442/// use str_chunks::*;
443/// let s = "lorem";
444/// let mut iter = s.str_rchunks_exact(2);
445/// assert_eq!(iter.next(), Some("em"));
446/// assert_eq!(iter.next(), Some("or"));
447/// assert_eq!(iter.next(), None);
448/// assert_eq!(iter.remaining(), "l");
449/// ```
450///
451/// [`str_rchunks_exact`]: ImplStrChunks::str_rchunks_exact
452/// [`remaining`]: StrRChunksExact::remaining
453pub struct StrRChunksExact<'s> {
454 s: &'s str,
455 // 0 chunk_size makes no sense
456 chunk_size: NonZeroUsize,
457}
458
459impl<'s> StrRChunksExact<'s> {
460 fn new(s: &'s str, chunk_size: NonZeroUsize) -> Self {
461 Self { s, chunk_size }
462 }
463 #[must_use]
464 /// Returns the remaining string slice that has not yet been iterated over.
465 /// If [`Self::next`] has returned None, this is the remainder, and is at most N-1 chars long.
466 pub fn remaining(&self) -> &'s str {
467 self.s
468 }
469 #[must_use]
470 /// Makes this iterator reversible.
471 ///
472 /// Reversing these iterators requires precomputing the number of characters in the string
473 /// so the remainder can be split off in advance. This is not a trivial operation, as the only
474 /// way to get the character is to iterate over them all.
475 ///
476 /// Keep in mind that because of remainders, reversing a [`StrChunks`] will not necessarily yield
477 /// the same items as a corresponding [`StrRChunks`].
478 pub fn reversable(self) -> DoubleEnded<'s, Self> {
479 DoubleEnded::<'s, Self>::new(self)
480 }
481}
482
483impl<'s> Iterator for StrRChunksExact<'s> {
484 type Item = &'s str;
485 fn next(&mut self) -> Option<&'s str> {
486 // will short-circuit None if s.len() < chunk_size, leaving the remainder in s
487 let (index, _) = valid_split_points(self.s).nth_back(self.chunk_size.get())?;
488
489 // SAFETY: indices from valid_split_points are valid byte offsets on the boundary of a codepoint
490 let (rest, chunk) = unsafe { self.s.split_at_checked(index).unwrap_unchecked() };
491
492 self.s = rest;
493 Some(chunk)
494 }
495}
496
497/// A double-ended variant of a strchunks variant.
498/// This struct is created by the [`reversable`] method on any strchunks variant.
499///
500/// Behaves exactly the same as the variant it was produced from,
501/// but implements [`DoubleEndedIterator`], letting you iterate from both ends,
502/// as well as reverse the iterator.
503///
504/// When the str len (in characters) is not evenly divided by the chunk size, the last up to
505/// `chunk_size-1` chars is the "remainder".
506/// For Exact iterators, the remainder will be omitted, but can be retrieved with the [`remainder`] function
507/// at any time.
508/// For non-Exact iterators, the remainder is the last element to be yielded from the front,
509/// or the first to be yielded from the back.
510///
511///
512/// # Example
513///
514/// ```
515/// use str_chunks::*;
516/// let s = "lorem";
517/// let mut iter = s.str_chunks_exact(2).reversable();
518/// assert_eq!(iter.next_back(), Some("re"));
519/// assert_eq!(iter.next_back(), Some("lo"));
520/// assert_eq!(iter.next_back(), None);
521/// assert_eq!(iter.next(), None);
522/// assert_eq!(iter.remainder(), "m");
523///
524/// let mut iter = s.str_chunks(2).reversable();
525/// assert_eq!(iter.next_back(), Some("m"));
526/// assert_eq!(iter.next_back(), Some("re"));
527/// assert_eq!(iter.next_back(), Some("lo"));
528/// assert_eq!(iter.next_back(), None);
529/// assert_eq!(iter.next(), None);
530/// ```
531///
532/// [`reversable`]: StrChunks::reversable
533/// [`DoubleEndedIterator`]: core::iter::DoubleEndedIterator
534/// [`remainder`]: DoubleEnded::remainder
535pub struct DoubleEnded<'s, T> {
536 s: &'s str,
537 chunk_size: NonZeroUsize,
538 remainder: &'s str,
539 _inner: PhantomData<T>,
540}
541impl<'s, T> DoubleEnded<'s, T> {
542 #[must_use]
543 /// Returns the remaining string slice that has not yet been iterated over,
544 /// not including the remainder.
545 ///
546 /// Note this is NOT the same as the `remainder` function available on Exact variants.
547 pub fn remaining(&self) -> &'s str {
548 self.s
549 }
550}
551impl<'s> DoubleEnded<'s, StrChunks<'s>> {
552 #[allow(clippy::needless_pass_by_value)]
553 fn new(from: StrChunks<'s>) -> Self {
554 // rust stdlib includes some optimization for Chars.count(), but its still expensive
555 let char_length = from.s.chars().count();
556 let rem_size = char_length % from.chunk_size;
557 // SAFETY: length of valid_split_points is char_length+1, rem_size is at most char_length,
558 // char_length+1 > char_length >= rem_size
559 let rem_index = unsafe {
560 valid_split_points(from.s)
561 .nth_back(rem_size)
562 .unwrap_unchecked()
563 .0
564 };
565 let (s, remainder) = from.s.split_at(rem_index);
566 Self {
567 s,
568 chunk_size: from.chunk_size,
569 remainder,
570 _inner: PhantomData,
571 }
572 }
573}
574impl<'s> DoubleEnded<'s, StrRChunks<'s>> {
575 #[allow(clippy::needless_pass_by_value)]
576 fn new(from: StrRChunks<'s>) -> Self {
577 // rust stdlib includes some optimization for Chars.count(), but its still expensive
578 let char_length = from.s.chars().count();
579 let rem_size = char_length % from.chunk_size;
580 // SAFETY: length of valid_split_points is char_length+1, rem_size is at most char_length,
581 // char_length+1 > char_length >= rem_size
582 let rem_index = unsafe {
583 valid_split_points(from.s)
584 .nth(rem_size)
585 .unwrap_unchecked()
586 .0
587 };
588 let (remainder, s) = from.s.split_at(rem_index);
589 Self {
590 s,
591 chunk_size: from.chunk_size,
592 remainder,
593 _inner: PhantomData,
594 }
595 }
596}
597impl<'s> DoubleEnded<'s, StrChunksExact<'s>> {
598 #[allow(clippy::needless_pass_by_value)]
599 fn new(from: StrChunksExact<'s>) -> Self {
600 let n = DoubleEnded::<'s, StrChunks<'s>>::new(StrChunks::new(from.s, from.chunk_size));
601 Self {
602 s: n.s,
603 chunk_size: n.chunk_size,
604 remainder: n.remainder,
605 _inner: PhantomData,
606 }
607 }
608 #[must_use]
609 /// Returns the remainder of the original string that is not going to be returned by the
610 /// iterator. the returned string has at most `chunk_size-1` characters.
611 pub fn remainder(&self) -> &'s str {
612 self.remainder
613 }
614}
615impl<'s> DoubleEnded<'s, StrRChunksExact<'s>> {
616 #[allow(clippy::needless_pass_by_value)]
617 fn new(from: StrRChunksExact<'s>) -> Self {
618 let n = DoubleEnded::<'s, StrRChunks<'s>>::new(StrRChunks::new(from.s, from.chunk_size));
619 Self {
620 s: n.s,
621 chunk_size: n.chunk_size,
622 remainder: n.remainder,
623 _inner: PhantomData,
624 }
625 }
626 #[must_use]
627 /// Returns the remainder of the original string that is not going to be returned by the
628 /// iterator. the returned string has at most `chunk_size-1` characters.
629 pub fn remainder(&self) -> &'s str {
630 self.remainder
631 }
632}
633impl<'s> Iterator for DoubleEnded<'s, StrChunks<'s>> {
634 type Item = &'s str;
635 fn next(&mut self) -> Option<Self::Item> {
636 // StrChunks::new doesnt do any processing or anything, this should get optimized well
637 let mut inner = StrChunks::new(self.s, self.chunk_size);
638 let next = inner.next().or_else(|| {
639 if self.remainder.is_empty() {
640 None
641 } else {
642 // SAFETY: 0 is a valid code point boundary
643 let (empty, remainder) =
644 unsafe { self.remainder.split_at_checked(0).unwrap_unchecked() };
645 self.remainder = empty;
646 Some(remainder)
647 }
648 });
649 self.s = inner.s;
650 next
651 }
652}
653impl<'s> DoubleEndedIterator for DoubleEnded<'s, StrChunks<'s>> {
654 fn next_back(&mut self) -> Option<Self::Item> {
655 if self.remainder.is_empty() {
656 let mut inner = StrRChunks::new(self.s, self.chunk_size);
657 let next = inner.next();
658 self.s = inner.s;
659 next
660 } else {
661 // SAFETY: 0 is a valid code point boundary
662 let (empty, remainder) =
663 unsafe { self.remainder.split_at_checked(0).unwrap_unchecked() };
664 self.remainder = empty;
665 Some(remainder)
666 }
667 }
668}
669impl<'s> Iterator for DoubleEnded<'s, StrChunksExact<'s>> {
670 type Item = &'s str;
671 fn next(&mut self) -> Option<Self::Item> {
672 let mut inner = StrChunksExact::new(self.s, self.chunk_size);
673 let next = inner.next();
674 self.s = inner.s;
675 next
676 }
677}
678impl<'s> DoubleEndedIterator for DoubleEnded<'s, StrChunksExact<'s>> {
679 fn next_back(&mut self) -> Option<Self::Item> {
680 let mut inner = StrRChunks::new(self.s, self.chunk_size);
681 let next = inner.next();
682 self.s = inner.s;
683 next
684 }
685}
686impl<'s> Iterator for DoubleEnded<'s, StrRChunks<'s>> {
687 type Item = &'s str;
688 fn next(&mut self) -> Option<Self::Item> {
689 let mut inner = StrRChunks::new(self.s, self.chunk_size);
690 let next = inner.next().or_else(|| {
691 if self.remainder.is_empty() {
692 None
693 } else {
694 // SAFETY: 0 is a valid code point boundary
695 let (empty, remainder) =
696 unsafe { self.remainder.split_at_checked(0).unwrap_unchecked() };
697 self.remainder = empty;
698 Some(remainder)
699 }
700 });
701 self.s = inner.s;
702 next
703 }
704}
705impl<'s> DoubleEndedIterator for DoubleEnded<'s, StrRChunks<'s>> {
706 fn next_back(&mut self) -> Option<Self::Item> {
707 if self.remainder.is_empty() {
708 let mut inner = StrChunks::new(self.s, self.chunk_size);
709 let next = inner.next();
710 self.s = inner.s;
711 next
712 } else {
713 // SAFETY: 0 is a valid code point boundary
714 let (empty, remainder) =
715 unsafe { self.remainder.split_at_checked(0).unwrap_unchecked() };
716 self.remainder = empty;
717 Some(remainder)
718 }
719 }
720}
721impl<'s> Iterator for DoubleEnded<'s, StrRChunksExact<'s>> {
722 type Item = &'s str;
723 fn next(&mut self) -> Option<Self::Item> {
724 let mut inner = StrRChunks::new(self.s, self.chunk_size);
725 let next = inner.next();
726 self.s = inner.s;
727 next
728 }
729}
730impl<'s> DoubleEndedIterator for DoubleEnded<'s, StrRChunksExact<'s>> {
731 fn next_back(&mut self) -> Option<Self::Item> {
732 let mut inner = StrChunks::new(self.s, self.chunk_size);
733 let next = inner.next();
734 self.s = inner.s;
735 next
736 }
737}
738
739#[cfg(test)]
740mod tests {
741 use super::*;
742
743 macro_rules! assert_chunks_fore {
744 ($iter:expr, $slice:expr, $remainder:expr) => {{
745 let mut iter = $iter;
746 let mut slice_iter = $slice.into_iter();
747 loop {
748 let i1 = iter.next();
749 let i2 = slice_iter.next();
750 if i1.is_none() && i2.is_none() {
751 // done with iteration, check remainder
752 break;
753 } else {
754 assert_eq!(i1.as_ref(), i2);
755 }
756 }
757 assert_eq!(iter.remaining(), $remainder);
758 iter
759 }};
760 }
761 macro_rules! assert_chunks_back {
762 ($iter:expr, $slice:expr, $remainder:expr) => {{
763 let mut iter = $iter;
764 let mut slice_iter = $slice.into_iter();
765 loop {
766 let i1 = iter.next_back();
767 let i2 = slice_iter.next_back();
768 if i1.is_none() && i2.is_none() {
769 // done with iteration, check remainder
770 break;
771 } else {
772 assert_eq!(i1.as_ref(), i2);
773 }
774 }
775 assert_eq!(iter.remaining(), $remainder);
776 iter
777 }};
778 }
779 macro_rules! assert_chunks_reversable {
780 ($iter:expr, $slice:expr) => {
781 assert_chunks_fore!($iter.reversable(), $slice, "");
782 assert_chunks_back!($iter.reversable(), $slice, "");
783 };
784 ($iter:expr, $slice:expr, $remainder:expr) => {
785 let fore = assert_chunks_fore!($iter.reversable(), $slice, "");
786 let back = assert_chunks_back!($iter.reversable(), $slice, "");
787 assert_eq!(fore.remainder(), $remainder);
788 assert_eq!(back.remainder(), $remainder);
789 };
790 }
791
792 macro_rules! assert_chunks {
793 ($iter:expr, $slice:expr, $remainder:expr) => {
794 assert_chunks_fore!($iter, $slice, $remainder);
795 assert_chunks_reversable!($iter, $slice);
796 };
797 (exact $iter:expr, $slice:expr, $remainder:expr) => {
798 assert_chunks_fore!($iter, $slice, $remainder);
799 assert_chunks_reversable!($iter, $slice, $remainder);
800 };
801 }
802
803 #[test]
804 fn str_chunks() {
805 let straight_str = "012345";
806 assert_chunks!(
807 straight_str.str_chunks(1),
808 &["0", "1", "2", "3", "4", "5"],
809 ""
810 );
811 assert_chunks!(straight_str.str_chunks(2), &["01", "23", "45"], "");
812 assert_chunks!(straight_str.str_chunks(3), &["012", "345"], "");
813 assert_chunks!(straight_str.str_chunks(4), &["0123", "45"], "");
814 assert_chunks!(straight_str.str_chunks(5), &["01234", "5"], "");
815 assert_chunks!(straight_str.str_chunks(6), &["012345"], "");
816 }
817 #[test]
818 fn str_chunks_exact() {
819 let straight_str = "012345";
820 assert_chunks!(exact
821 straight_str.str_chunks_exact(1),
822 &["0", "1", "2", "3", "4", "5"],
823 ""
824 );
825 assert_chunks!(exact straight_str.str_chunks_exact(2), &["01", "23", "45"], "");
826 assert_chunks!(exact straight_str.str_chunks_exact(3), &["012", "345"], "");
827 assert_chunks!(exact straight_str.str_chunks_exact(4), &["0123"], "45");
828 assert_chunks!(exact straight_str.str_chunks_exact(5), &["01234"], "5");
829 assert_chunks!(exact straight_str.str_chunks_exact(6), &["012345"], "");
830 }
831 #[test]
832 fn str_rchunks() {
833 let straight_str = "012345";
834 assert_chunks!(
835 straight_str.str_rchunks(1),
836 &["5", "4", "3", "2", "1", "0"],
837 ""
838 );
839 assert_chunks!(straight_str.str_rchunks(2), &["45", "23", "01"], "");
840 assert_chunks!(straight_str.str_rchunks(3), &["345", "012"], "");
841 assert_chunks!(straight_str.str_rchunks(4), &["2345", "01"], "");
842 assert_chunks!(straight_str.str_rchunks(5), &["12345", "0"], "");
843 assert_chunks!(straight_str.str_rchunks(6), &["012345"], "");
844 }
845 #[test]
846 fn str_rchunks_exact() {
847 let straight_str = "012345";
848 assert_chunks!(exact
849 straight_str.str_rchunks_exact(1),
850 &["5", "4", "3", "2", "1", "0"],
851 ""
852 );
853 assert_chunks!(exact straight_str.str_rchunks_exact(2), &["45", "23", "01"], "");
854 assert_chunks!(exact straight_str.str_rchunks_exact(3), &["345", "012"], "");
855 assert_chunks!(exact straight_str.str_rchunks_exact(4), &["2345"], "01");
856 assert_chunks!(exact straight_str.str_rchunks_exact(5), &["12345"], "0");
857 assert_chunks!(exact straight_str.str_rchunks_exact(6), &["012345"], "");
858 }
859 #[test]
860 fn emoji() {
861 // these are multi-byte characters
862 let s = "π₯Ίππ΅";
863 assert_chunks!(s.str_chunks(2), &["π₯Ίπ", "π΅"], "");
864 assert_chunks!(exact s.str_chunks_exact(2), &["π₯Ίπ"], "π΅");
865 assert_chunks!(s.str_rchunks(2), &["ππ΅", "π₯Ί"], "");
866 assert_chunks!(exact s.str_rchunks_exact(2), &["ππ΅"], "π₯Ί");
867 }
868 #[test]
869 fn empty() {
870 let s = "";
871 assert_chunks!(s.str_chunks(2), &[], "");
872 assert_chunks!(exact s.str_chunks_exact(2), &[], "");
873 assert_chunks!(s.str_rchunks(2), &[], "");
874 assert_chunks!(exact s.str_rchunks_exact(2), &[], "");
875 }
876 #[test]
877 fn short() {
878 let s = "f";
879 assert_chunks!(s.str_chunks(2), &["f"], "");
880 assert_chunks!(exact s.str_chunks_exact(2), &[], "f");
881 assert_chunks!(s.str_rchunks(2), &["f"], "");
882 assert_chunks!(exact s.str_rchunks_exact(2), &[], "f");
883 }
884 #[test]
885 fn remainder() {
886 let s = "foo";
887 assert_chunks!(s.str_chunks(5), &["foo"], "");
888 assert_chunks!(exact s.str_chunks_exact(5), &[], "foo");
889 assert_chunks!(s.str_rchunks(5), &["foo"], "");
890 assert_chunks!(exact s.str_rchunks_exact(5), &[], "foo");
891 }
892 #[test]
893 fn reverse() {
894 let s = "0123456";
895 assert_chunks_reversable!(s.str_chunks(3), &["012", "345", "6"]);
896 assert_chunks_reversable!(s.str_rchunks(3), &["456", "123", "0"]);
897 assert_chunks_reversable!(s.str_chunks_exact(3), &["012", "345"], "6");
898 assert_chunks_reversable!(s.str_rchunks_exact(3), &["456", "123"], "0");
899 }
900}