175 lines
5.1 KiB
Rust
175 lines
5.1 KiB
Rust
|
use crate::component::Component;
|
||
|
|
||
|
// (line (centered (limited (padded "hello world"))))
|
||
|
#[derive(Debug, Clone)]
|
||
|
pub enum Token {
|
||
|
Centered(Box<Token>),
|
||
|
Limited(Box<Token>, usize),
|
||
|
CharPad(Box<Token>, char, usize),
|
||
|
String(String),
|
||
|
}
|
||
|
|
||
|
impl From<String> for Token {
|
||
|
fn from(value: String) -> Self {
|
||
|
Token::String(value)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
impl Token {
|
||
|
pub fn centered(self) -> Self {
|
||
|
Self::Centered(Box::new(self))
|
||
|
}
|
||
|
|
||
|
pub fn limited(self, lim: usize) -> Self {
|
||
|
Self::Limited(Box::new(self), lim)
|
||
|
}
|
||
|
|
||
|
pub fn padded(self, pad_to: usize) -> Self {
|
||
|
Self::CharPad(Box::new(self), ' ', pad_to)
|
||
|
}
|
||
|
|
||
|
pub fn str_len(&self) -> usize {
|
||
|
self.walk_to_end().len()
|
||
|
}
|
||
|
|
||
|
pub fn pad_char(self, c: char, pad_to: usize) -> Self {
|
||
|
Self::CharPad(Box::new(self), c, pad_to)
|
||
|
}
|
||
|
|
||
|
fn walk_to_end(&self) -> &String {
|
||
|
match self {
|
||
|
Token::String(s) => s,
|
||
|
Token::Centered(t) => t.walk_to_end(),
|
||
|
Token::Limited(t, _) => t.walk_to_end(),
|
||
|
Token::CharPad(t, _, _) => t.walk_to_end(),
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fn with_width(self, width: usize) -> Vec<Component> {
|
||
|
match self {
|
||
|
Token::String(s) => vec![Component::String(s)],
|
||
|
Token::Centered(cnt) => cnt
|
||
|
.with_width(width)
|
||
|
.into_iter()
|
||
|
.map(|comp| {
|
||
|
if let Component::String(s) = comp {
|
||
|
let str_len = s.len();
|
||
|
let x = if str_len > width {
|
||
|
0
|
||
|
} else {
|
||
|
(width - str_len) / 2
|
||
|
};
|
||
|
vec![Component::X(x), Component::String(s)]
|
||
|
} else {
|
||
|
vec![comp]
|
||
|
}
|
||
|
})
|
||
|
.flatten()
|
||
|
.collect(),
|
||
|
Token::Limited(s, lim) => s
|
||
|
.with_width(width)
|
||
|
.into_iter()
|
||
|
.map(|cmp| {
|
||
|
if let Component::String(mut s) = cmp {
|
||
|
s.truncate(lim);
|
||
|
Component::String(s)
|
||
|
} else {
|
||
|
cmp
|
||
|
}
|
||
|
})
|
||
|
.collect(),
|
||
|
Token::CharPad(s, c, pad_to) => s
|
||
|
.with_width(width)
|
||
|
.into_iter()
|
||
|
.map(|comp| {
|
||
|
if let Component::String(s) = comp {
|
||
|
if s.len() >= pad_to {
|
||
|
return Component::String(s);
|
||
|
}
|
||
|
Component::String(format!(
|
||
|
"{}{}",
|
||
|
s,
|
||
|
c.to_string().repeat(pad_to - s.len())
|
||
|
))
|
||
|
} else {
|
||
|
comp
|
||
|
}
|
||
|
})
|
||
|
.collect(),
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#[cfg(test)]
|
||
|
mod tests {
|
||
|
use super::*;
|
||
|
|
||
|
#[test]
|
||
|
fn test_token_gen() {
|
||
|
const WIDTH: usize = 20;
|
||
|
vec![
|
||
|
(
|
||
|
"string -> string",
|
||
|
Token::String("hello".into()),
|
||
|
vec![Component::String("hello".into())],
|
||
|
),
|
||
|
(
|
||
|
"string -> limited",
|
||
|
Token::String(
|
||
|
"This string is too long for this!".into(),
|
||
|
)
|
||
|
.limited(4),
|
||
|
vec![Component::String("This".into())],
|
||
|
),
|
||
|
(
|
||
|
"string -> limit -> pad_to",
|
||
|
Token::String(
|
||
|
"This string is too long, but some".into(),
|
||
|
)
|
||
|
.limited(10)
|
||
|
.padded(15),
|
||
|
vec![Component::String("This strin ".into())],
|
||
|
),
|
||
|
(
|
||
|
"center limited string",
|
||
|
Token::String("Ahhh this won't go far".into())
|
||
|
.limited(10)
|
||
|
.centered(),
|
||
|
vec![
|
||
|
Component::X((WIDTH - 10) / 2),
|
||
|
Component::String("Ahhh this ".into()),
|
||
|
],
|
||
|
),
|
||
|
(
|
||
|
"padded string with underscores is centered",
|
||
|
Token::String("It was...".into())
|
||
|
.pad_char('_', 15)
|
||
|
.centered(),
|
||
|
vec![
|
||
|
Component::X(WIDTH - 3),
|
||
|
Component::String("It was...______".into()),
|
||
|
],
|
||
|
),
|
||
|
]
|
||
|
.into_iter()
|
||
|
.for_each(|test| {
|
||
|
let (name, token, expected) = test;
|
||
|
eprintln!("token: {:#?}", &token);
|
||
|
let actual = token.with_width(WIDTH);
|
||
|
let actual_fmt = format!("{:#?}", &actual);
|
||
|
assert!(
|
||
|
expected
|
||
|
.iter()
|
||
|
.zip(actual.iter())
|
||
|
.filter(|&(l, r)| l != r)
|
||
|
.count()
|
||
|
== 0,
|
||
|
"{} expected: {:#?} actual: {}",
|
||
|
name,
|
||
|
expected,
|
||
|
actual_fmt,
|
||
|
)
|
||
|
})
|
||
|
}
|
||
|
}
|