letter-sequence 2.1.0

A method to create sequence displayed as uppercase or lower letters, or digits
Documentation
//! This file models a combined sequences.

use super::*;
use crate::sequence::renderer::RenderDisplay;
use std::convert::From;
use std::convert::TryFrom;
use std::fmt::Display;
use std::iter::Iterator;

/// A `CombinedSequence` models any sequence which is not entirely either
/// numbers, uppercase, or lowercase. It does this by combining sequences which
/// are. Each internal sequence it contains has its own state. A
/// `CombinedSequence` gives you a method to increment and display a set of
/// sequences.
/// 
/// ```rust
/// use letter_sequence::{ CombinedSequence, TryFrom };
/// 
/// // When created the default is for the last element in the sequence to
/// // grow, here the first "Z" from the left turns into "AA"
/// let mut seq = CombinedSequence::try_from("Z9Z").unwrap();
/// let elem = seq.next().unwrap();
/// assert_eq!( elem.to_string(), "Z9Z" );
/// let elem = seq.next().unwrap();
/// assert_eq!( elem.to_string(), "AA0A" );
///
/// // here we the right has two characters "ZZ", the number will only be
/// // incremeneted after this point.
/// let mut seq = CombinedSequence::try_from("9ZZ").unwrap();
/// let elem = seq.next().unwrap();
/// assert_eq!( elem.to_string(), "9ZZ" );
/// let elem = seq.next().unwrap();
/// assert_eq!( elem.to_string(), "10A" );
///
/// // let elem = seq.nth( 26usize.pow(2) - 1 + 25 ).unwrap(); // 0 based- 26 letters in the alphabet
/// // assert_eq!( elem.to_string(), "10ZZ" );
/// ```
#[derive(Default, Debug, Clone)]
pub struct CombinedSequence {
	inner: Vec<Sequence>,
	done: bool,
	initialized: bool,
}


impl CombinedSequence {
	fn new() -> Self {
		Self::default()
	}
	pub fn push(&mut self, seq: Sequence) {
		self.inner.push(seq)
	}
	
	/// Returns an iterator over the internal sequences
	pub fn sequences(&self) -> std::slice::Iter<Sequence> {
		self.inner.iter()
	}
	/// Returns a mutable iterator over the internal seqeunces
	pub fn sequences_mut(&mut self) -> std::slice::IterMut<Sequence> {
		self.inner.iter_mut()
	}

	/// Reset a CombinedSequence to the initial state.
	/// ```
	/// use letter_sequence::{ CombinedSequence, TryFrom };
	///
	/// let mut seq = CombinedSequence::try_from("9ZZ").unwrap();
	/// let elem = seq.next().unwrap();
	/// assert_eq!( elem.to_string(), "9ZZ" );
	/// let elem = seq.next().unwrap();
	/// assert_eq!( elem.to_string(), "10A" );
	/// seq.reset();
	/// let elem = seq.next().unwrap();
	/// assert_eq!( elem.to_string(), "9ZZ" );
	/// ```
	pub fn reset(&mut self) {
		self.initialized = false;
		for seq in &mut self.sequences_mut() {
			seq.reset()
		}
	}
	
	/// Zero out a CombinedSequence. This ignores the initial state.
	/// ```
	/// use letter_sequence::{ CombinedSequence, TryFrom };
	///
	/// let mut seq = CombinedSequence::try_from("9ZZ").unwrap();
	/// seq.zero();
	/// let elem = seq.next().unwrap();
	/// assert_eq!( elem.to_string(), "0A" );
	/// ```
	pub fn zero(&mut self) {
		self.initialized = false;
		for seq in &mut self.sequences_mut() {
			seq.zero()
		}
	}
	
	fn generate_element(&self) -> CombinedElement {
		let mut ce = CombinedElement::new();
		for s in self.sequences() {
			ce.push(s.generate_element())
		}
		ce
	}

}

impl Iterator for CombinedSequence {
	type Item = CombinedElement;

	/// Combined sequences will roll over when calling `.next()`.
	/// ```
	/// use letter_sequence::{ CombinedSequence, TryFrom };
	/// 
	/// let mut seq = CombinedSequence::try_from("A9Z").unwrap();
	/// let elem = seq.next().unwrap();
	/// assert_eq!( elem.to_string(), "A9Z" );
	/// let elem = seq.next().unwrap();
	/// assert_eq!( elem.to_string(), "B0A" );
	/// ```
	fn next(&mut self) -> Option<Self::Item> {
		match (self.done, self.initialized) {
			(_, false) => {
				for s in &mut self.sequences_mut() {
					s.next();
				}
				self.initialized = true;
				Some(self.generate_element())
			}
			(true, _) => { dbg!("done = true"); None },
			(false, true) => {
				for s in &mut self.sequences_mut() {
					match s.next() {
						None => s.rollover(),
						Some(_) => break
					};
				}
				Some(self.generate_element())
			},
		}
	}
}

impl From<Vec<Sequence>> for CombinedSequence {
	fn from(vec: Vec<Sequence>) -> Self {
		Self { inner: vec, .. Default::default() }
	}
}

impl TryFrom<&str> for CombinedSequence {
	type Error = SequenceError;

	fn try_from(input: &str) -> Result<CombinedSequence, Self::Error> {
		let mut input : &mut str = &mut input.to_owned();
		if ! input.is_ascii() {
			return Err(SequenceError::InvalidString);
		}

		let mut splits: Vec<(usize, RenderDisplay)> = Vec::new();
		let mut iter = input.char_indices();
		while let Some((i, val)) = iter.next() {
			let val = RenderDisplay::try_from(val)?;
			match splits.last() {
				// current character is rendered differently from last character
				Some((_, lastval)) if *lastval != val => splits.push( (i,val) ),
				// first character seen
				None => splits.push( (i,val) ),
				_ => ()
			}
		}

		let mut cs = CombinedSequence::new();
		while let Some((idx, _)) = splits.pop() {
			let (init, last) = input.split_at_mut(idx);
			input = &mut(*init);
			let mut seq = SequenceBuilder::try_from(&*last).unwrap();
			if ! splits.is_empty() {
				let length = last.len();
				seq = seq.max_render_capacity(length as u8);
			}
			cs.push( seq.build()? );
		}
		Ok(cs)
	}
}

#[derive(Debug, Default, Clone)]
pub struct CombinedElement {
	inner: Vec<Element>
}

impl CombinedElement {
	fn new() -> Self {
		Self::default()
	}
	pub fn push(&mut self, elem: Element) {
		self.inner.push(elem)
	}
	/// Returns an iterator over the internal sequences
	pub fn elements(&self) -> std::slice::Iter<Element> {
		self.inner.iter()
	}
}

impl Display for CombinedElement {
	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
		for i in self.elements().rev() {
			write!(f, "{}", i )?
		}
		Ok(())
	}
}


#[cfg(test)]
mod test {
	use super::*;


	/// Ensure parity between a CombinedSequence and a Sequence
	#[test]
	fn one_char() {
		let mut seq = SequenceBuilder::try_from("A").unwrap().build().unwrap();
		let mut cs = CombinedSequence::try_from("A").unwrap();
		let elem = seq.next().unwrap();
		let celem = cs.next().unwrap();
		assert_eq!( elem.to_string(), "A" );
		assert_eq!( celem.to_string(), "A" );

		let elem = seq.next().unwrap();
		let celem = cs.next().unwrap();
		assert_eq!( elem.to_string(), "B" );
		assert_eq!( celem.to_string(), "B" );
	}
	
	#[test]
	fn two_char() {
		let mut seq = CombinedSequence::try_from("A0").unwrap();
		let elem = seq.next().unwrap();
		assert_eq!( elem.to_string(), "A0" );
		let elem = seq.next().unwrap();
		assert_eq!( elem.to_string(), "A1" );
	}

	#[test]
	fn segment_access() {
		let mut seq = CombinedSequence::try_from("A9").unwrap();
		let segment1 = seq.inner[0].next().unwrap();
		assert_eq!( segment1.to_string(), "9" );
		let segment1 = seq.inner[0].next();
		assert_eq!( segment1, None );
	}


	#[test]
	fn one_char_with_rollover() {
		let mut seq = CombinedSequence::try_from("9").unwrap();
		let elem = seq.next().unwrap();
		assert_eq!( elem.to_string(), "9" );
		let elem = seq.next().unwrap();
		assert_eq!( elem.to_string(), "10" );
		
		let mut seq = CombinedSequence::try_from("Z").unwrap();
		let elem = seq.next().unwrap();
		assert_eq!( elem.to_string(), "Z" );
		let elem = seq.next().unwrap();
		assert_eq!( elem.to_string(), "AA" );
	}
	
	#[test]
	fn two_char_with_rollover1() {
		let mut seq = CombinedSequence::try_from("A9").unwrap();
		let elem = seq.next().unwrap();
		assert_eq!( elem.to_string(), "A9" );
		let elem = seq.next().unwrap();
		assert_eq!( elem.to_string(), "B0" );
		let elem = seq.next().unwrap();
		assert_eq!( elem.to_string(), "B1" );
		
		let mut seq = CombinedSequence::try_from("9A").unwrap();
		let elem = seq.next().unwrap();
		assert_eq!( elem.to_string(), "9A" );
		let elem = seq.next().unwrap();
		assert_eq!( elem.to_string(), "9B" );
		let elem = seq.next().unwrap();
		assert_eq!( elem.to_string(), "9C" );
		
		let mut seq = CombinedSequence::try_from("Z9ZZ").unwrap();
		let elem = seq.next().unwrap();
		assert_eq!( elem.to_string(), "Z9ZZ" );
		let elem = seq.next().unwrap();
		assert_eq!( elem.to_string(), "AA0A" );
	}
}