use crate::parse::{
attributes::attributes,
block::{end_of_block, indent_align},
phrase::phrase,
};
use crate::structs::{
BlockHeader, BlockTag, InlineTag, List, ListItem, ListKind,
};
use nom::{
branch::alt,
character::complete::{char, line_ending},
combinator::{fail, map, opt, value},
multi::many1_count,
sequence::{delimited, terminated, tuple},
IResult,
};
pub fn list(input: &str) -> IResult<&str, BlockTag> {
let (rest, (kind, attributes, opt_indent_align)) = terminated(
tuple((
alt((
value(ListKind::Numeric, char('#')),
value(ListKind::Bulleted, char('*')),
)),
opt(attributes),
opt(indent_align),
)),
char(' '),
)(input)?;
let (indent, align) = opt_indent_align.unwrap_or((None, None));
let mut all_items: Vec<(ListKind, usize, InlineTag)> = Vec::new();
let (rest, first_content) = list_item_content(rest)?;
all_items.push((kind, 1, first_content));
let mut input = rest;
while let Err(_) = end_of_block(input) {
let (rest, (item_kind, depth)) = list_item_head(input)?;
let (rest, content) = list_item_content(rest)?;
all_items.push((item_kind, depth, content));
input = rest;
}
match list_items_to_nested_list(&mut all_items) {
Ok(l) => Ok((
input,
BlockTag::List {
header: BlockHeader {
attributes,
indent,
align,
},
content: l,
},
)),
Err(_) => fail(input),
}
}
fn list_item_head(input: &str) -> IResult<&str, (ListKind, usize)> {
delimited(
line_ending,
alt((
map(many1_count(char('#')), |n| (ListKind::Numeric, n)),
map(many1_count(char('*')), |n| (ListKind::Bulleted, n)),
)),
char(' '),
)(input)
}
fn list_item_content(input: &str) -> IResult<&str, InlineTag> {
let mut i = 0;
while let Err(_) = end_list_item(&input[i..]) {
i += 1;
while !input.is_char_boundary(i) {
i += 1;
}
}
let (_, content) = phrase(&input[..i])?;
Ok((&input[i..], content))
}
fn end_list_item(input: &str) -> IResult<&str, &str> {
alt((value("", list_item_head), end_of_block))(input)
}
fn list_items_to_nested_list(
list: &mut Vec<(ListKind, usize, InlineTag)>,
) -> Result<List, String> {
let mut items: Vec<ListItem> = Vec::new();
let kind = list[0].0;
let depth = list[0].1;
while !list.is_empty() {
if list[0].1 > depth {
items.last_mut().unwrap().sublist =
Some(list_items_to_nested_list(list)?);
} else if list[0].1 < depth {
break;
} else if list[0].0 != kind {
return Err(format!(
"List uses mixed markers on same level: {:?}",
list[0].2
));
} else {
let (_, _, content) = list.remove(0);
items.push(ListItem {
content,
sublist: None,
})
}
}
Ok(List { kind, items })
}