asciidork_parser/tasks/
parse_section.rs1use crate::internal::*;
2
3impl<'arena> Parser<'arena> {
4 pub(crate) fn parse_section(&mut self) -> Result<Option<Section<'arena>>> {
5 let Some(mut lines) = self.read_lines()? else {
6 return Ok(None);
7 };
8
9 let meta = self.parse_chunk_meta(&mut lines)?;
10
11 let Some(line) = lines.current() else {
12 if !meta.is_empty() {
13 self.err_line_starting("Unattached block metadata", meta.start_loc)?;
14 }
15 self.restore_peeked_meta(meta);
16 return Ok(None);
17 };
18
19 let Some(level) = self.line_heading_level(line) else {
20 self.restore_peeked(lines, meta);
21 return Ok(None);
22 };
23
24 if level == 0 && self.document.meta.get_doctype() != DocType::Book {
25 self.err_line("Level 0 section allowed only in doctype=book", line)?;
26 }
27
28 if meta.attrs.has_str_positional("discrete") || meta.attrs.has_str_positional("float") {
29 self.restore_peeked(lines, meta);
30 return Ok(None);
31 }
32
33 let last_level = self.ctx.section_level;
34 self.ctx.section_level = level;
35 let mut heading_line = lines.consume_current().unwrap();
36 let mut loc: MultiSourceLocation = heading_line.loc().unwrap().into();
37 let equals = heading_line.consume_current().unwrap();
38 heading_line.discard_assert(TokenKind::Whitespace);
39 let id = self.section_id(&heading_line, &meta.attrs);
40
41 let out_of_sequence = level > last_level && level - last_level > 1;
42 if out_of_sequence {
43 self.err_token_full(
44 format!(
45 "Section title out of sequence: expected level {} `{}`",
46 last_level + 1,
47 "=".repeat((last_level + 2) as usize)
48 ),
49 &equals,
50 )?;
51 }
52
53 let heading = self.parse_inlines(&mut heading_line.into_lines())?;
54 if !out_of_sequence {
55 self.push_toc_node(level, &heading, id.as_ref());
56 }
57
58 if let Some(id) = &id {
59 let reftext = meta
60 .attrs
61 .iter()
62 .find_map(|a| a.named.get("reftext"))
63 .cloned();
64 self.document.anchors.borrow_mut().insert(
65 id.clone(),
66 Anchor {
67 reftext,
68 title: heading.clone(),
69 source_loc: None,
70 source_idx: self.lexer.source_idx(),
71 is_biblio: false,
72 },
73 );
74 }
75
76 if meta.attrs.str_positional_at(0) == Some("bibliography") {
77 self.ctx.bibliography_ctx = BiblioContext::Section;
78 }
79
80 self.restore_lines(lines);
81 let mut blocks = BumpVec::new_in(self.bump);
82 while let Some(inner) = self.parse_block()? {
83 loc.extend_end(&inner.loc);
84 blocks.push(inner);
85 }
86
87 self.ctx.bibliography_ctx = BiblioContext::None;
88 self.ctx.section_level = last_level;
89 Ok(Some(Section {
90 meta,
91 level,
92 id,
93 heading,
94 blocks,
95 loc,
96 }))
97 }
98
99 pub fn push_toc_node(
100 &mut self,
101 level: u8,
102 heading: &InlineNodes<'arena>,
103 as_ref: Option<&BumpString<'arena>>,
104 ) {
105 let Some(toc) = self.document.toc.as_mut() else {
106 return;
107 };
108 if level > self.document.meta.u8_or("toclevels", 2) {
109 return;
110 }
111 let mut depth = level;
112 let mut nodes: &mut BumpVec<'_, TocNode<'_>> = toc.nodes.as_mut();
113 while depth > 1 {
114 nodes = nodes.last_mut().unwrap().children.as_mut();
116 depth -= 1;
117 }
118 nodes.push(TocNode {
119 level,
120 title: heading.clone(),
121 id: as_ref.cloned(),
122 children: BumpVec::new_in(self.bump),
123 });
124 }
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130 use pretty_assertions::assert_eq;
131 use test_utils::*;
132
133 #[test]
134 fn test_parse_2_sections() {
135 let input = adoc! {"
136 == one
137
138 foo
139
140 == two
141
142 bar
143 "};
144 let mut parser = test_parser!(input);
145 let section = parser.parse_section().unwrap().unwrap();
146 assert_eq!(
147 section,
148 Section {
149 meta: chunk_meta!(0),
150 level: 1,
151 id: Some(bstr!("_one")),
152 heading: nodes![node!("one"; 3..6)],
153 blocks: vecb![Block {
154 context: BlockContext::Paragraph,
155 content: BlockContent::Simple(nodes![node!("foo"; 8..11)]),
156 loc: (8..11).into(),
157 ..empty_block!(8)
158 }],
159 loc: (0..11).into()
160 }
161 );
162 let section = parser.parse_section().unwrap().unwrap();
163 assert_eq!(
164 section,
165 Section {
166 meta: chunk_meta!(13),
167 level: 1,
168 id: Some(bstr!("_two")),
169 heading: nodes![node!("two"; 16..19)],
170 blocks: vecb![Block {
171 context: BlockContext::Paragraph,
172 content: BlockContent::Simple(nodes![node!("bar"; 21..24)]),
173 loc: (21..24).into(),
174 ..empty_block!(21)
175 }],
176 loc: (13..24).into()
177 }
178 );
179 }
180}