en/src/syntax/content/parser/token/list.rs

189 lines
4.9 KiB
Rust

use crate::{
syntax::content::{Lexeme, Parseable, parser::token::item::Item},
};
#[derive(Default, Debug, Clone, Eq, PartialEq)]
pub struct List {
pub ordered: bool,
pub items: Vec<Item>,
}
impl Parseable for List {
fn probe(lexeme: &Lexeme) -> bool {
lexeme.match_either_char('-', '+') && lexeme.match_next_char(' ')
}
fn lex(_lexeme: &Lexeme) -> List {
panic!("Attempt to lex a List directly from a lexeme")
}
fn render(&self) -> String {
let tag = if self.ordered { "ol" } else { "ul" };
let mut output = String::new();
let scale = self.scale_indent();
let mut iterator = self.items.iter().peekable();
while let Some(item) = iterator.next() {
let level = item.depth.unwrap_or(0).strict_div(scale);
let next_level = iterator
.peek()
.and_then(|n| n.depth)
.unwrap_or(0)
.strict_div(scale);
output.push_str(&format!("<li>{}", item.text));
if next_level > level {
// open nested lists
for _ in 0..(next_level.saturating_sub(level)) {
output.push_str(&format!("<{tag}>\n"));
}
} else {
// close current item
output.push_str("</li>");
// close nested lists
for _ in 0..(level.saturating_sub(next_level)) {
output.push_str(&format!("</{tag}></li>"));
}
output.push('\n');
}
}
format!("\n<{tag}>\n{output}</{tag}>\n\n")
}
fn flatten(&self) -> String {
format!("[List: {} items]", self.items.len())
}
}
impl List {
pub fn new(ordered: bool) -> List {
List {
ordered,
items: vec![],
}
}
fn scale_indent(&self) -> u8 {
let width = self
.items
.windows(2)
.find_map(|pair| {
let outer = pair.first()?.depth?;
let inner = pair.get(1)?.depth?;
(inner > outer).then_some(inner.saturating_sub(outer))
})
.unwrap_or(1);
assert!(width != 0, "Width of zero can't be a divisor");
width
}
}
impl std::fmt::Display for List {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
"List [{} {} items]",
self.items.len(),
if self.ordered { "ordered" } else { "unordered" },
)
}
}
#[cfg(test)]
mod tests {
use crate::syntax::content::parser::token::Token;
use super::*;
#[test]
fn render_flat_list() {
let mut list = List::new(false);
list.items = vec![
Item::new("a", Some(0)),
Item::new("b", Some(0)),
Item::new("c", Some(0)),
];
assert_eq!(
list.render(),
"\n<ul>\n\
<li>a</li>\n\
<li>b</li>\n\
<li>c</li>\n\
</ul>\n\n"
);
}
#[test]
fn render_nested_list() {
let mut list = List::new(false);
list.items = vec![
Item::new("0Aa", Some(0)),
Item::new("4Ba", Some(4)),
Item::new("0Ca", Some(0)),
Item::new("4Da", Some(4)),
Item::new("4Db", Some(4)),
Item::new("0Ea", Some(0)),
Item::new("0Eb", Some(0)),
];
assert_eq!(
list.render(),
"\n<ul>\n\
<li>0Aa<ul>\n\
<li>4Ba</li></ul></li>\n\
<li>0Ca<ul>\n\
<li>4Da</li>\n\
<li>4Db</li></ul></li>\n\
<li>0Ea</li>\n\
<li>0Eb</li>\n\
</ul>\n\n"
);
}
#[test]
fn render_multilevel_depth_drop() {
let mut list = List::new(false);
list.items = vec![
Item::new("0Aa", Some(0)),
Item::new("4Ba", Some(4)),
Item::new("8Ca", Some(8)),
Item::new("12Da", Some(12)),
Item::new("16Ea", Some(16)),
Item::new("8Fa", Some(8)),
Item::new("0Ga", Some(0)),
];
assert_eq!(
list.render(),
"\n<ul>\n\
<li>0Aa<ul>\n\
<li>4Ba<ul>\n\
<li>8Ca<ul>\n\
<li>12Da<ul>\n\
<li>16Ea</li></ul></li></ul></li>\n\
<li>8Fa</li></ul></li></ul></li>\n\
<li>0Ga</li>\n\
</ul>\n\n"
);
}
#[test]
fn token_display() {
let list = List::new(false);
assert_eq!(
format!("{}", Token::List(list.clone())),
"Tk:List [0 unordered items]"
);
}
#[test]
#[should_panic(expected = "Attempt to lex a List directly from a lexeme")]
fn lex() {
let lexeme = Lexeme::new("SL6PX", "6xsNB", "oeAHa");
List::lex(&lexeme);
}
}