Skip to content

Commit 7ca8afe

Browse files
burtonageoThomas Bahn
authored and
Thomas Bahn
committed
Add Chars, CharsMut and Lines iterators (#32)
1 parent ba9a639 commit 7ca8afe

File tree

2 files changed

+149
-1
lines changed

2 files changed

+149
-1
lines changed

src/ascii_str.rs

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ extern crate core;
22

33
use self::core::{fmt, mem};
44
use self::core::ops::{Index, IndexMut, Range, RangeTo, RangeFrom, RangeFull};
5+
use self::core::slice::{Iter, IterMut};
56
#[cfg(feature = "std")]
67
use std::error::Error;
78
#[cfg(feature = "std")]
@@ -139,6 +140,32 @@ impl AsciiStr {
139140
self.len() == 0
140141
}
141142

143+
/// Returns an iterator over the characters of the `AsciiStr`.
144+
#[inline]
145+
pub fn chars(&self) -> Chars {
146+
self.slice.iter()
147+
}
148+
149+
/// Returns an iterator over the characters of the `AsciiStr` which allows you to modify the
150+
/// value of each `AsciiChar`.
151+
#[inline]
152+
pub fn chars_mut(&mut self) -> CharsMut {
153+
self.slice.iter_mut()
154+
}
155+
156+
/// Returns an iterator over the lines of the `AsciiStr`, which are themselves `AsciiStr`s.
157+
///
158+
/// Lines are ended with either `LineFeed` (`\n`), or `CarriageReturn` then `LineFeed` (`\r\n`).
159+
///
160+
/// The final line ending is optional.
161+
#[inline]
162+
pub fn lines(&self) -> Lines {
163+
Lines {
164+
current_index: 0,
165+
string: self
166+
}
167+
}
168+
142169
/// Returns an ASCII string slice with leading and trailing whitespace removed.
143170
///
144171
/// # Examples
@@ -388,6 +415,82 @@ impl AsciiExt for AsciiStr {
388415
}
389416
}
390417

418+
impl<'a> IntoIterator for &'a AsciiStr {
419+
type Item = &'a AsciiChar;
420+
type IntoIter = Chars<'a>;
421+
#[inline]
422+
fn into_iter(self) -> Self::IntoIter {
423+
self.chars()
424+
}
425+
}
426+
427+
impl<'a> IntoIterator for &'a mut AsciiStr {
428+
type Item = &'a mut AsciiChar;
429+
type IntoIter = CharsMut<'a>;
430+
#[inline]
431+
fn into_iter(self) -> Self::IntoIter {
432+
self.chars_mut()
433+
}
434+
}
435+
436+
/// An immutable iterator over the characters of an `AsciiStr`.
437+
pub type Chars<'a> = Iter<'a, AsciiChar>;
438+
439+
/// A mutable iterator over the characters of an `AsciiStr`.
440+
pub type CharsMut<'a> = IterMut<'a, AsciiChar>;
441+
442+
/// An iterator over the lines of the internal character array.
443+
#[derive(Clone, Debug)]
444+
pub struct Lines<'a> {
445+
current_index: usize,
446+
string: &'a AsciiStr
447+
}
448+
449+
impl<'a> Iterator for Lines<'a> {
450+
type Item = &'a AsciiStr;
451+
452+
fn next(&mut self) -> Option<Self::Item> {
453+
let curr_idx = self.current_index;
454+
let len = self.string.len();
455+
if curr_idx >= len {
456+
return None;
457+
}
458+
459+
let mut next_idx = None;
460+
let mut linebreak_skip = 0;
461+
462+
for i in curr_idx..(len-1) {
463+
match (self.string[i], self.string[i + 1]) {
464+
(AsciiChar::CarriageReturn, AsciiChar::LineFeed) => {
465+
next_idx = Some(i);
466+
linebreak_skip = 2;
467+
break;
468+
}
469+
(AsciiChar::LineFeed, _) => {
470+
next_idx = Some(i);
471+
linebreak_skip = 1;
472+
break;
473+
}
474+
_ => {}
475+
}
476+
}
477+
478+
let next_idx = match next_idx {
479+
Some(i) => i,
480+
None => return None
481+
};
482+
let line = &self.string[curr_idx..next_idx];
483+
484+
self.current_index = next_idx + linebreak_skip;
485+
486+
if line.is_empty() && self.current_index == self.string.len() {
487+
// This is a trailing line break
488+
None
489+
} else {
490+
Some(line)
491+
}
492+
}
493+
}
391494

392495
/// Error that is returned when a sequence of `u8` are not all ASCII.
393496
///
@@ -604,6 +707,51 @@ mod tests {
604707
assert_eq!(b, "A@A");
605708
}
606709

710+
#[test]
711+
fn chars_iter() {
712+
let chars = &[b'h', b'e', b'l', b'l', b'o', b' ', b'w', b'o', b'r', b'l', b'd', b'\0'];
713+
let ascii = AsciiStr::from_ascii(chars).unwrap();
714+
for (achar, byte) in ascii.chars().zip(chars.iter()) {
715+
assert_eq!(achar, byte);
716+
}
717+
}
718+
719+
#[test]
720+
fn chars_iter_mut() {
721+
let mut chars = &mut [b'h', b'e', b'l', b'l', b'o', b' ', b'w', b'o', b'r', b'l', b'd', b'\0'];
722+
let mut ascii = chars.as_mut_ascii_str().unwrap();
723+
724+
*ascii.chars_mut().next().unwrap() = AsciiChar::H;
725+
726+
assert_eq!(ascii[0], b'H');
727+
}
728+
729+
#[test]
730+
fn lines_iter() {
731+
use super::core::iter::Iterator;
732+
let lines: [&str; 3] = ["great work", "cool beans", "awesome stuff"];
733+
let joined = "great work\ncool beans\r\nawesome stuff\n";
734+
let ascii = AsciiStr::from_ascii(joined.as_bytes()).unwrap();
735+
for (asciiline, line) in ascii.lines().zip(&lines) {
736+
assert_eq!(asciiline, *line);
737+
}
738+
739+
let trailing_line_break = b"\n";
740+
let ascii = AsciiStr::from_ascii(&trailing_line_break).unwrap();
741+
for _ in ascii.lines() {
742+
unreachable!();
743+
}
744+
745+
let empty_lines = b"\n\r\n\n\r\n";
746+
let mut ensure_iterated = false;
747+
let ascii = AsciiStr::from_ascii(&empty_lines).unwrap();
748+
for line in ascii.lines() {
749+
ensure_iterated = true;
750+
assert!(line.is_empty());
751+
}
752+
assert!(ensure_iterated);
753+
}
754+
607755
#[test]
608756
#[cfg(feature = "std")]
609757
fn fmt_ascii_str() {

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,6 @@ mod ascii_string;
3737

3838
pub use free_functions::{caret_encode, caret_decode};
3939
pub use ascii_char::{AsciiChar, ToAsciiChar, ToAsciiCharError};
40-
pub use ascii_str::{AsciiStr, AsAsciiStr, AsMutAsciiStr, AsAsciiStrError};
40+
pub use ascii_str::{AsciiStr, AsAsciiStr, AsMutAsciiStr, AsAsciiStrError, Chars, CharsMut, Lines};
4141
#[cfg(feature = "std")]
4242
pub use ascii_string::{AsciiString, IntoAsciiString, FromAsciiError};

0 commit comments

Comments
 (0)