1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
use super::format_text;
use nom::{
branch::alt,
bytes::complete::{tag, take},
combinator::{verify, recognize, opt, not, map},
multi::{many1, count},
sequence::{pair, terminated, preceded},
character::complete::line_ending,
};
use nom::IResult;
pub use super::token::{ListToken, TokenEnumerator, TokenWrapper};
use super::token::parse_item_token;
#[derive(Debug, Clone)]
pub struct List {
pub vec: Vec<ListItem>,
pub token: ListToken
}
#[derive(Debug, Clone)]
pub struct ListItem {
pub text: String,
pub list: Option<List>
}
fn parse_item_start(indent: usize) -> impl Fn(&str) -> IResult<&str, ListToken> {
move |input: &str|
map(
pair(
count(tag("\t"), indent),
parse_item_token,
),
|(_, token)| token
)(input)
}
fn parse_item_start_and_enforce(indent: usize, enforced: &'_ ListToken) -> impl Fn(&str) -> IResult<&str, ListToken> + '_ {
move |input: &str|
verify(
parse_item_start(indent),
|token| {
if token == enforced {
return true;
}
// this enables roman numerals to also qualify
// as alphabetic tokens
if let Some(a) = &enforced.enumerator {
if let Some(b) = &token.enumerator {
use TokenEnumerator::*;
if let Alphabetical(alpha_upper) = &a {
if let Roman(roman_upper) = &b {
// this part checks if they have the same case
return *alpha_upper == *roman_upper;
}
}
}
}
false
}
)(input)
}
fn parse_item(indent: usize, token: &ListToken) -> impl Fn(&str) -> IResult<&str, ListItem> + '_ {
let item_start = parse_item_start_and_enforce(indent, token);
let next_item_start = parse_item_start(indent + 1);
move |input: &str|
map(
terminated(
pair(
&item_start,
map(
many1(
preceded(
not(pair(
line_ending,
alt((
recognize(line_ending),
recognize(&item_start),
))
)),
take(1u8)
)
),
|s: Vec<&str>| s.join("").trim().to_string()
),
),
opt(line_ending)
),
|(_, content)| {
let mut list = None;
let text;
// checks for sublists
for (i, _) in content.char_indices() {
let s = &content[i..];
if let Ok(_) = next_item_start(s) {
if let Ok(result) = parse_list(indent + 1)(s) {
let consumed = content[..i].trim();
list = Some((consumed, result.1));
break;
}
}
}
if let Some((consumed, _)) = list {
// -1 because of the newline
text = format_text(&consumed);
} else {
text = format_text(&content.trim());
}
ListItem {
text,
list: list.map(|l| l.1)
}
}
)(input)
}
pub fn parse_list(indent: usize) -> impl Fn(&str) -> IResult<&str, List> + 'static {
move |input: &str| {
// this works, but there must be a better way
let mut token = ListToken::bullet();
if let Ok((_, result)) = parse_item_start(indent)(input) {
token = result;
}
let clone = token.clone();
map(
terminated(
many1(
parse_item(indent, &token.clone())
),
opt(line_ending)
),
move |items| {
List {
vec: items,
token: clone.clone()
}
}
)(input)
}
}