letter-sequence 2.1.0

A method to create sequence displayed as uppercase or lower letters, or digits
Documentation
use super::*;
use crate::sequence::helper::*;
use std::convert::TryFrom;

/// Render as Upper, Lower, or Numeric
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum RenderDisplay {
	Upper,
	Lower,
	Numeric,
}

impl Default for RenderDisplay {
	fn default() -> Self { RenderDisplay::Numeric }
}


/// Provide any character to try_from to see what the render mode must have
/// been to generate it
///
/// ```rust
/// use letter_sequence::SequenceError;
/// use letter_sequence::sequence::renderer::RenderDisplay;
/// use std::convert::TryFrom;
/// assert_eq!( RenderDisplay::try_from('A').unwrap(),     RenderDisplay::Upper );
/// assert_eq!( RenderDisplay::try_from('5').unwrap(),     RenderDisplay::Numeric );
/// assert_eq!( RenderDisplay::try_from('a').unwrap(),     RenderDisplay::Lower );
/// assert!( matches!(RenderDisplay::try_from('!').unwrap_err(), SequenceError::InvalidCharacter('!')) );
/// ```
impl TryFrom<char> for RenderDisplay {
	type Error = SequenceError;
	fn try_from(c: char) -> Result<Self, Self::Error> {
		if c.is_ascii_digit() {
			Ok(RenderDisplay::Numeric)
		}
		else if c.is_ascii_lowercase() {
			Ok(RenderDisplay::Lower)
		}
		else if c.is_ascii_uppercase() {
			Ok(RenderDisplay::Upper)
		}
		else {
			Err(SequenceError::InvalidCharacter(c))
		}
	}
}

impl TryFrom<&str> for RenderDisplay {
	type Error = SequenceError;
	/// Assumes the input is a single rendered sequence
	fn try_from(str: &str) -> Result<Self, Self::Error> {
		let char = str.chars().next();
		match char {
			Some(char) => Ok(RenderDisplay::try_from(char)?),
			None => Err(SequenceError::EmptyString)
		}
	}
}

#[derive(Default, Clone, Copy, Debug, PartialEq, Eq)]
pub struct SeqRenderer {
	/// Rendering mode, Numeric, Uppercase, or Lowercase
	display: RenderDisplay,
	/// The maximum length of the rendered output (in chars)
	capacity: Option<u8>,
	/// The minimum length of the rendered output (in chars)
	length: Option<u8>,
}

impl SeqRenderer {
	pub fn display(self) -> RenderDisplay { self.display }

	/// ```rust
	/// use letter_sequence::SeqRendererBuilder;
	///
	/// let upper = SeqRendererBuilder::upper().build().unwrap();
	/// assert_eq!(upper.render(0).unwrap(), "A");
	///
	/// let lower = SeqRendererBuilder::lower().build().unwrap();
	/// assert_eq!(lower.render(0).unwrap(), "a");
	///
	/// let numeric = SeqRendererBuilder::numeric().build().unwrap();
	/// assert_eq!(numeric.render(0).unwrap(), "0");
	///
	/// let numeric = SeqRendererBuilder::numeric().length(5).build().unwrap();
	/// assert_eq!(numeric.render(7).unwrap(), "00007");
	/// ```
	pub fn render(&self, x: u64) -> Result<String, SequenceError> {
		let result = match self.display {
			RenderDisplay::Upper => int_to_string(x)?,
			RenderDisplay::Lower => {
				let mut display = int_to_string(x)?;
				display.make_ascii_lowercase();
				display.to_string()
			}
			RenderDisplay::Numeric => x.to_string()
		};

		if let Some(capacity) = self.capacity {
			if result.len() > capacity as usize {
				return Err(SequenceError::OutOfRange)
			}
		}

		match (self.length, self.display) {
			(None, _) => Ok(result),
			(Some(length), RenderDisplay::Numeric) => Ok(format!("{:0>len$}", result, len=length as usize)),
			(_, RenderDisplay::Upper | RenderDisplay::Lower) => Err(SequenceError::PaddingAlpha),
		}
	}
	pub fn max(&self) -> Option<u64> {
		if let Some(char_limit) = self.capacity {
			match self.display {
				RenderDisplay::Upper | RenderDisplay::Lower => Some(max_alpha(char_limit)),
				RenderDisplay::Numeric => Some(max_numeric(char_limit)),
			}
		}
		else {
			None
		}
	}
}

#[derive(Default, Debug)]
pub struct SeqRendererBuilder {
	display: RenderDisplay,
	capacity: Option<u8>,
	length: Option<u8>,
}

impl SeqRendererBuilder {
	pub fn upper()   -> Self { Self::new_with_display( RenderDisplay::Upper)   }
	pub fn lower()   -> Self { Self::new_with_display( RenderDisplay::Lower)   }
	pub fn numeric() -> Self { Self::new_with_display( RenderDisplay::Numeric) }
	pub fn set_upper(self)   -> Self { self.set_display(RenderDisplay::Upper)   }
	pub fn set_lower(self)   -> Self { self.set_display(RenderDisplay::Lower)   }
	pub fn set_numeric(self) -> Self { self.set_display(RenderDisplay::Numeric) }
	pub fn set_display(mut self, display: RenderDisplay) -> Self {
		self.display = display;
		self
	}
	fn new_with_display(display: RenderDisplay) -> Self { Self { display, ..Default::default() } }
	// The maximum length of the rendered output (in chars)
	pub fn capacity(mut self, val: u8) -> Self { self.capacity = Some(val); self }
	// The minimum length of the rendered output (in chars)
	pub fn length(mut self, val: u8)   -> Self { self.length = Some(val); self }
	// This should not permt building if self.display is upper|lower, or 
	// have length
	pub fn build(self) -> Result<SeqRenderer,SequenceError> {
		let sr = SeqRenderer {
			capacity: self.capacity,
			display: self.display,
			length: self.length,
			.. Default::default()
		};
		Ok(sr)
	}
}


/// Returns the maximum int that can be represented with `chars` alphabetic
/// characters
///
/// ```
/// use letter_sequence::sequence::renderer;
/// assert_eq!( renderer::max_alpha(1u8), 25 );
/// assert_eq!( renderer::max_alpha(2u8), 26u64.pow(2) + 25 );
/// ```
pub fn max_alpha(chars: u8) -> u64 {
	const BASE : u64 = 26;
	const Z: u32 = 25;

	let mut max: u64 = 0;
	for x in 0..chars {
		max += (1 + Z) as u64 * BASE.pow(x as u32)
	}
	max -= 1;
	max
}

/// Finds the maximum int that can be represented with `chars` digits
/// 
/// ```
/// use letter_sequence::sequence::renderer;
/// // Simple string comparison for digits.
/// # for i in 1..15 {
/// 	assert_eq!( renderer::max_numeric(i).to_string(), "9".repeat(i as usize) );
/// # }
/// ```
pub fn max_numeric(chars: u8) -> u64 {
	10u64.pow(chars as u32) - 1
}