Skip to content

Commit

Permalink
converted chunk_str into a proper iter struct
Browse files Browse the repository at this point in the history
  • Loading branch information
JohnPeel committed Apr 19, 2023
1 parent b3eca56 commit 9f85833
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 30 deletions.
22 changes: 6 additions & 16 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
#![doc = include_str!("../README.md")]
#![cfg_attr(not(feature = "std"), no_std)]
#![cfg_attr(not(any(feature = "std", test)), no_std)]
#![warn(missing_docs)]

use core::{fmt, ops::Deref, str::FromStr};

use arrayvec::ArrayString;

mod util;
use util::{chunk_str, digits};
use util::{ChunksExt as _, IteratorExt as _, digits};

include!(concat!(env!("OUT_DIR"), "/countries.rs"));

Expand Down Expand Up @@ -60,31 +60,21 @@ impl fmt::Debug for Bban {
}

impl fmt::Display for Iban {
/// Spaced formatting of the `Iban`.
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut iter = chunk_str(self, 4).peekable();

while let Some(chunk) = iter.next() {
for chunk in self.as_ref().chunks::<4>().delimited(" ") {
write!(f, "{chunk}")?;

if iter.peek().is_some() {
write!(f, " ")?;
}
}

Ok(())
}
}

impl fmt::Display for Bban {
/// Spaced formatting of the `Bban`.
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut iter = chunk_str(self, 4).peekable();

while let Some(chunk) = iter.next() {
for chunk in self.as_ref().chunks::<4>().delimited(" ") {
write!(f, "{chunk}")?;

if iter.peek().is_some() {
write!(f, " ")?;
}
}

Ok(())
Expand Down
76 changes: 62 additions & 14 deletions src/util.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,35 @@
use core::iter::Peekable;

pub trait IteratorExt: Iterator + Sized {
fn ensure_one(self, value: Self::Item) -> EnsureOne<Self> {
EnsureOne {
iter: self,
value: Some(value),
}
}

fn delimited(self, value: Self::Item) -> Delimited<Self> {
Delimited {
separator: value,
iter: self.peekable(),
needs_separator: false
}
}
}

impl<I: Iterator> IteratorExt for I {}

pub trait ChunksExt {
fn chunks<const N: usize>(&self) -> Chunks<'_, N>;
}

impl ChunksExt for str {
#[inline]
fn chunks<const N: usize>(&self) -> Chunks<'_, N> {
Chunks(self)
}
}

pub struct EnsureOne<I: Iterator> {
iter: I,
value: Option<I::Item>,
Expand All @@ -28,6 +49,47 @@ impl<I: Iterator> Iterator for EnsureOne<I> {
}
}

pub struct Delimited<I: Iterator> {
separator: I::Item,
iter: Peekable<I>,
needs_separator: bool,
}

impl<I: Iterator> Iterator for Delimited<I>
where
I::Item: Clone,
{
type Item = I::Item;

#[inline]
fn next(&mut self) -> Option<Self::Item> {
if self.needs_separator && self.iter.peek().is_some() {
self.needs_separator = false;
Some(self.separator.clone())
} else {
self.needs_separator = true;
self.iter.next()
}
}
}

pub struct Chunks<'str, const N: usize>(&'str str);

impl<'str, const N: usize> Iterator for Chunks<'str, N> {
type Item = &'str str;

#[inline]
fn next(&mut self) -> Option<Self::Item> {
if self.0.is_empty() {
return None;
}

let (chunk, remaining) = self.0.split_at(self.0.len().min(N));
self.0 = remaining;
Some(chunk)
}
}

pub fn digits(mut value: u8) -> impl Iterator<Item = u8> {
let hundreds = value / 100;
value -= hundreds * 100;
Expand All @@ -42,17 +104,3 @@ pub fn digits(mut value: u8) -> impl Iterator<Item = u8> {
// Ensure at least one value (0) is provided by this iterator.
.ensure_one(0)
}

#[inline]
pub fn chunk_str(s: &str, size: usize) -> impl Iterator<Item = &str> {
let mut remaining = s;
core::iter::from_fn(move || {
if remaining.is_empty() {
return None;
}

let (item, rest) = remaining.split_at(remaining.len().min(size));
remaining = rest;
Some(item)
})
}

0 comments on commit 9f85833

Please sign in to comment.