asciidork_parser/tasks/
parse_section.rs

1use 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      // we don't push out of sequence sections, shouldn't panic
115      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}