1use crate::{
2 lexer::{Token, TokenKind},
3 nodes::Heading,
4};
5
6use super::{anchor, Parser};
7
8pub(crate) fn heading<Callback>(p: &mut Parser<'_>, new_line_check: Callback) -> Option<Heading>
9where
10 Callback: Fn(&Token) -> bool,
11{
12 let start = p.pos();
13 let mut text_start: Option<usize> = None;
14 let mut end_modifier = 0;
15
16 let mut heading = Heading::new(
17 p.next_token()
18 .expect("to have token")
19 .slice
20 .len()
21 .try_into()
22 .expect("to be < 7"),
23 vec![],
24 );
25
26 if let Some(t) = p.next_token() {
27 if t.kind != TokenKind::Space {
28 p.backtrack(start);
29 p.flip_to_literal_at(start);
30 return None;
31 }
32 }
33
34 while let Some((t, pos)) = p.peek() {
35 match t.kind {
36 TokenKind::Terminator => break,
37 TokenKind::LeftSquareBracket => {
38 if let Some(a) = anchor(p) {
39 if let Some(start) = text_start.take() {
40 heading.body.push(p.range_to_string(start..pos).into());
41 }
42 heading.body.push(a.into());
43 } else {
44 text_start.get_or_insert(pos);
45 p.next_token();
46 }
47 }
48 _ if t.position.column == 0 && new_line_check(t) => {
49 end_modifier = 1;
50 text_start.take_if(|start| pos - *start < 2);
51 break;
52 }
53 _ => {
54 text_start.get_or_insert(pos);
55 p.next_token();
56 }
57 }
58 }
59
60 if let Some(start) = text_start.take() {
61 heading
62 .body
63 .push(p.range_to_string(start..p.pos() - end_modifier).into());
64 }
65
66 if !heading.body.is_empty() {
67 return Some(heading);
68 }
69
70 p.backtrack(start);
71 p.flip_to_literal_at(start);
72
73 None
74}
75
76#[cfg(test)]
77mod tests {
78 use pretty_assertions::assert_eq;
79
80 use crate::{
81 lexer::{Position, Token, TokenKind},
82 nodes::{Anchor, Heading},
83 parser::{heading, Parser},
84 };
85
86 #[test]
87 fn happy_path() {
88 let mut p = Parser::new("## heading [a](u) text");
89 assert_eq!(
90 heading(&mut p, |_| false),
91 Some(Heading::new(
92 2,
93 vec![
94 String::from("heading ").into(),
95 Anchor::new("a", "u").into(),
96 String::from(" text").into()
97 ]
98 ))
99 );
100 }
101
102 #[test]
103 fn start_with_anchor() {
104 let mut p = Parser::new("## [a](u) heading");
105 assert_eq!(
106 heading(&mut p, |_| false),
107 Some(Heading::new(
108 2,
109 vec![
110 Anchor::new("a", "u").into(),
111 String::from(" heading").into(),
112 ]
113 ))
114 );
115 }
116
117 #[test]
118 fn broken_anchor() {
119 let mut p = Parser::new("## heading [a](u text");
120 assert_eq!(
121 heading(&mut p, |_| false),
122 Some(Heading::new(
123 2,
124 vec.into(),]
125 ))
126 );
127 }
128
129 #[test]
130 fn with_terminator() {
131 let mut p = Parser::new("## heading\n\ntext");
132 assert_eq!(
133 heading(&mut p, |_| false),
134 Some(Heading::new(2, vec![String::from("heading").into()]))
135 );
136 }
137
138 #[test]
139 fn have_no_space_before_text() {
140 let mut p = Parser::new("##heading\n\ntext");
141 assert_eq!(heading(&mut p, |_| false), None);
142 assert_eq!(
143 p.peek(),
144 Some((
145 &Token::new(TokenKind::Literal, "##", Position::default()),
146 0
147 ))
148 );
149 }
150
151 #[test]
152 fn new_line_check() {
153 let mut p = Parser::new("## heading [a](u) text\n ");
154 assert_eq!(
155 heading(&mut p, |t| t.kind == TokenKind::Space),
156 Some(Heading::new(
157 2,
158 vec![
159 String::from("heading ").into(),
160 Anchor::new("a", "u").into(),
161 String::from(" text").into()
162 ]
163 ))
164 );
165 }
166
167 #[test]
168 fn only_one_token() {
169 let mut p = Parser::new("##");
170 assert_eq!(heading(&mut p, |_| false), None);
171 assert_eq!(
172 p.peek(),
173 Some((
174 &Token::new(TokenKind::Literal, "##", Position::default()),
175 0
176 ))
177 );
178 }
179}