letter_sequence/sequence/
renderer.rs

1use super::*;
2use crate::sequence::helper::*;
3use std::convert::TryFrom;
4
5/// Render as Upper, Lower, or Numeric
6#[derive(Clone, Copy, Debug, PartialEq, Eq)]
7pub enum RenderDisplay {
8	Upper,
9	Lower,
10	Numeric,
11}
12
13impl Default for RenderDisplay {
14	fn default() -> Self { RenderDisplay::Numeric }
15}
16
17
18/// Provide any character to try_from to see what the render mode must have
19/// been to generate it
20///
21/// ```rust
22/// use letter_sequence::SequenceError;
23/// use letter_sequence::sequence::renderer::RenderDisplay;
24/// use std::convert::TryFrom;
25/// assert_eq!( RenderDisplay::try_from('A').unwrap(),     RenderDisplay::Upper );
26/// assert_eq!( RenderDisplay::try_from('5').unwrap(),     RenderDisplay::Numeric );
27/// assert_eq!( RenderDisplay::try_from('a').unwrap(),     RenderDisplay::Lower );
28/// assert!( matches!(RenderDisplay::try_from('!').unwrap_err(), SequenceError::InvalidCharacter('!')) );
29/// ```
30impl TryFrom<char> for RenderDisplay {
31	type Error = SequenceError;
32	fn try_from(c: char) -> Result<Self, Self::Error> {
33		if c.is_ascii_digit() {
34			Ok(RenderDisplay::Numeric)
35		}
36		else if c.is_ascii_lowercase() {
37			Ok(RenderDisplay::Lower)
38		}
39		else if c.is_ascii_uppercase() {
40			Ok(RenderDisplay::Upper)
41		}
42		else {
43			Err(SequenceError::InvalidCharacter(c))
44		}
45	}
46}
47
48impl TryFrom<&str> for RenderDisplay {
49	type Error = SequenceError;
50	/// Assumes the input is a single rendered sequence
51	fn try_from(str: &str) -> Result<Self, Self::Error> {
52		let char = str.chars().next();
53		match char {
54			Some(char) => Ok(RenderDisplay::try_from(char)?),
55			None => Err(SequenceError::EmptyString)
56		}
57	}
58}
59
60#[derive(Default, Clone, Copy, Debug, PartialEq, Eq)]
61pub struct SeqRenderer {
62	/// Rendering mode, Numeric, Uppercase, or Lowercase
63	display: RenderDisplay,
64	/// The maximum length of the rendered output (in chars)
65	capacity: Option<u8>,
66	/// The minimum length of the rendered output (in chars)
67	length: Option<u8>,
68}
69
70impl SeqRenderer {
71	pub fn display(self) -> RenderDisplay { self.display }
72
73	/// ```rust
74	/// use letter_sequence::SeqRendererBuilder;
75	///
76	/// let upper = SeqRendererBuilder::upper().build().unwrap();
77	/// assert_eq!(upper.render(0).unwrap(), "A");
78	///
79	/// let lower = SeqRendererBuilder::lower().build().unwrap();
80	/// assert_eq!(lower.render(0).unwrap(), "a");
81	///
82	/// let numeric = SeqRendererBuilder::numeric().build().unwrap();
83	/// assert_eq!(numeric.render(0).unwrap(), "0");
84	///
85	/// let numeric = SeqRendererBuilder::numeric().length(5).build().unwrap();
86	/// assert_eq!(numeric.render(7).unwrap(), "00007");
87	/// ```
88	pub fn render(&self, x: u64) -> Result<String, SequenceError> {
89		let result = match self.display {
90			RenderDisplay::Upper => int_to_string(x)?,
91			RenderDisplay::Lower => {
92				let mut display = int_to_string(x)?;
93				display.make_ascii_lowercase();
94				display.to_string()
95			}
96			RenderDisplay::Numeric => x.to_string()
97		};
98
99		if let Some(capacity) = self.capacity {
100			if result.len() > capacity as usize {
101				return Err(SequenceError::OutOfRange)
102			}
103		}
104
105		match (self.length, self.display) {
106			(None, _) => Ok(result),
107			(Some(length), RenderDisplay::Numeric) => Ok(format!("{:0>len$}", result, len=length as usize)),
108			(_, RenderDisplay::Upper | RenderDisplay::Lower) => Err(SequenceError::PaddingAlpha),
109		}
110	}
111	pub fn max(&self) -> Option<u64> {
112		if let Some(char_limit) = self.capacity {
113			match self.display {
114				RenderDisplay::Upper | RenderDisplay::Lower => Some(max_alpha(char_limit)),
115				RenderDisplay::Numeric => Some(max_numeric(char_limit)),
116			}
117		}
118		else {
119			None
120		}
121	}
122}
123
124#[derive(Default, Debug)]
125pub struct SeqRendererBuilder {
126	display: RenderDisplay,
127	capacity: Option<u8>,
128	length: Option<u8>,
129}
130
131impl SeqRendererBuilder {
132	pub fn upper()   -> Self { Self::new_with_display( RenderDisplay::Upper)   }
133	pub fn lower()   -> Self { Self::new_with_display( RenderDisplay::Lower)   }
134	pub fn numeric() -> Self { Self::new_with_display( RenderDisplay::Numeric) }
135	pub fn set_upper(self)   -> Self { self.set_display(RenderDisplay::Upper)   }
136	pub fn set_lower(self)   -> Self { self.set_display(RenderDisplay::Lower)   }
137	pub fn set_numeric(self) -> Self { self.set_display(RenderDisplay::Numeric) }
138	pub fn set_display(mut self, display: RenderDisplay) -> Self {
139		self.display = display;
140		self
141	}
142	fn new_with_display(display: RenderDisplay) -> Self { Self { display, ..Default::default() } }
143	// The maximum length of the rendered output (in chars)
144	pub fn capacity(mut self, val: u8) -> Self { self.capacity = Some(val); self }
145	// The minimum length of the rendered output (in chars)
146	pub fn length(mut self, val: u8)   -> Self { self.length = Some(val); self }
147	// This should not permt building if self.display is upper|lower, or 
148	// have length
149	pub fn build(self) -> Result<SeqRenderer,SequenceError> {
150		let sr = SeqRenderer {
151			capacity: self.capacity,
152			display: self.display,
153			length: self.length,
154			.. Default::default()
155		};
156		Ok(sr)
157	}
158}
159
160
161/// Returns the maximum int that can be represented with `chars` alphabetic
162/// characters
163///
164/// ```
165/// use letter_sequence::sequence::renderer;
166/// assert_eq!( renderer::max_alpha(1u8), 25 );
167/// assert_eq!( renderer::max_alpha(2u8), 26u64.pow(2) + 25 );
168/// ```
169pub fn max_alpha(chars: u8) -> u64 {
170	const BASE : u64 = 26;
171	const Z: u32 = 25;
172
173	let mut max: u64 = 0;
174	for x in 0..chars {
175		max += (1 + Z) as u64 * BASE.pow(x as u32)
176	}
177	max -= 1;
178	max
179}
180
181/// Finds the maximum int that can be represented with `chars` digits
182/// 
183/// ```
184/// use letter_sequence::sequence::renderer;
185/// // Simple string comparison for digits.
186/// # for i in 1..15 {
187/// 	assert_eq!( renderer::max_numeric(i).to_string(), "9".repeat(i as usize) );
188/// # }
189/// ```
190pub fn max_numeric(chars: u8) -> u64 {
191	10u64.pow(chars as u32) - 1
192}