asciidoc_parser/blocks/
section.rs1use std::slice::Iter;
2
3use crate::{
4 HasSpan, Parser, Span,
5 attributes::Attrlist,
6 blocks::{
7 Block, ContentModel, IsBlock, metadata::BlockMetadata, parse_utils::parse_blocks_until,
8 },
9 span::MatchedItem,
10 strings::CowStr,
11 warnings::MatchAndWarnings,
12};
13
14#[derive(Clone, Debug, Eq, PartialEq)]
22pub struct SectionBlock<'src> {
23 level: usize,
24 section_title: Span<'src>,
25 blocks: Vec<Block<'src>>,
26 source: Span<'src>,
27 title_source: Option<Span<'src>>,
28 title: Option<String>,
29 anchor: Option<Span<'src>>,
30 attrlist: Option<Attrlist<'src>>,
31}
32
33impl<'src> SectionBlock<'src> {
34 pub(crate) fn parse(
35 metadata: &BlockMetadata<'src>,
36 parser: &mut Parser,
37 ) -> Option<MatchAndWarnings<'src, MatchedItem<'src, Self>>> {
38 let source = metadata.block_start.discard_empty_lines();
39 let level = parse_title_line(source)?;
40
41 let maw_blocks = parse_blocks_until(
42 level.after,
43 |i| peer_or_ancestor_section(*i, level.item.0),
44 parser,
45 );
46
47 let blocks = maw_blocks.item;
48 let source = metadata.source.trim_remainder(blocks.after);
49
50 Some(MatchAndWarnings {
51 item: MatchedItem {
52 item: Self {
53 level: level.item.0,
54 section_title: level.item.1,
55 blocks: blocks.item,
56 source: source.trim_trailing_whitespace(),
57 title_source: metadata.title_source,
58 title: metadata.title.clone(),
59 anchor: metadata.anchor,
60 attrlist: metadata.attrlist.clone(),
61 },
62 after: blocks.after,
63 },
64 warnings: maw_blocks.warnings,
65 })
66 }
67
68 pub fn level(&self) -> usize {
78 self.level
79 }
80
81 pub fn section_title(&'src self) -> &'src Span<'src> {
83 &self.section_title
84 }
85}
86
87impl<'src> IsBlock<'src> for SectionBlock<'src> {
88 fn content_model(&self) -> ContentModel {
89 ContentModel::Compound
90 }
91
92 fn raw_context(&self) -> CowStr<'src> {
93 "section".into()
94 }
95
96 fn nested_blocks(&'src self) -> Iter<'src, Block<'src>> {
97 self.blocks.iter()
98 }
99
100 fn title_source(&'src self) -> Option<Span<'src>> {
101 self.title_source
102 }
103
104 fn title(&self) -> Option<&str> {
105 self.title.as_deref()
106 }
107
108 fn anchor(&'src self) -> Option<Span<'src>> {
109 self.anchor
110 }
111
112 fn attrlist(&'src self) -> Option<&'src Attrlist<'src>> {
113 self.attrlist.as_ref()
114 }
115}
116
117impl<'src> HasSpan<'src> for SectionBlock<'src> {
118 fn span(&self) -> Span<'src> {
119 self.source
120 }
121}
122
123fn parse_title_line(source: Span<'_>) -> Option<MatchedItem<'_, (usize, Span<'_>)>> {
124 let mi = source.take_non_empty_line()?;
125 let mut line = mi.item;
126
127 let mut count = 0;
132
133 while let Some(mi) = line.take_prefix("=") {
134 count += 1;
135 line = mi.after;
136 }
137
138 let title = line.take_required_whitespace()?;
139
140 Some(MatchedItem {
141 item: (count - 1, title.after),
142 after: mi.after,
143 })
144}
145
146fn peer_or_ancestor_section(source: Span<'_>, level: usize) -> bool {
147 if let Some(mi) = parse_title_line(source) {
148 mi.item.0 <= level
149 } else {
150 false
151 }
152}