asciidoc_parser/blocks/
preamble.rs

1use std::slice::Iter;
2
3use crate::{
4    HasSpan, Span,
5    attributes::Attrlist,
6    blocks::{Block, ContentModel, IsBlock},
7    internal::debug::DebugSliceReference,
8    strings::CowStr,
9};
10
11/// Content between the end of the document header and the first section title
12/// in the document body is called the preamble.
13#[derive(Clone, Eq, PartialEq)]
14pub struct Preamble<'src> {
15    blocks: Vec<Block<'src>>,
16    source: Span<'src>,
17}
18
19impl<'src> Preamble<'src> {
20    pub(crate) fn from_blocks(blocks: Vec<Block<'src>>, source: Span<'src>) -> Self {
21        let preamble_source = if let Some(last_block) = blocks.last() {
22            let after_last = last_block.span().discard_all();
23            source.trim_remainder(after_last)
24        } else {
25            // This clause is here as a fallback, but should not be reachable in practice. A
26            // Preamble should only be constructed if there are content-bearing blocks
27            // before the first section.
28            source.trim_remainder(source)
29        };
30
31        Self {
32            blocks,
33            source: preamble_source,
34        }
35    }
36}
37
38impl<'src> IsBlock<'src> for Preamble<'src> {
39    fn content_model(&self) -> ContentModel {
40        ContentModel::Compound
41    }
42
43    fn raw_context(&self) -> CowStr<'src> {
44        "preamble".into()
45    }
46
47    fn nested_blocks(&'src self) -> Iter<'src, Block<'src>> {
48        self.blocks.iter()
49    }
50
51    fn title_source(&'src self) -> Option<Span<'src>> {
52        None
53    }
54
55    fn title(&self) -> Option<&str> {
56        None
57    }
58
59    fn anchor(&'src self) -> Option<Span<'src>> {
60        None
61    }
62
63    fn anchor_reftext(&'src self) -> Option<Span<'src>> {
64        None
65    }
66
67    fn attrlist(&'src self) -> Option<&'src Attrlist<'src>> {
68        None
69    }
70}
71
72impl<'src> HasSpan<'src> for Preamble<'src> {
73    fn span(&self) -> Span<'src> {
74        self.source
75    }
76}
77
78impl std::fmt::Debug for Preamble<'_> {
79    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80        f.debug_struct("Preamble")
81            .field("blocks", &DebugSliceReference(&self.blocks))
82            .field("source", &self.source)
83            .finish()
84    }
85}
86
87#[cfg(test)]
88mod tests {
89    #![allow(clippy::panic)]
90    #![allow(clippy::unwrap_used)]
91
92    use pretty_assertions_sorted::assert_eq;
93
94    use crate::{
95        HasSpan, Parser,
96        blocks::{ContentModel, IsBlock, SimpleBlockStyle},
97        content::SubstitutionGroup,
98        tests::prelude::*,
99    };
100
101    fn doc_fixture() -> crate::Document<'static> {
102        Parser::default().parse("= Document Title\n\nSome early words go here.\n\n== First Section")
103    }
104
105    fn fixture_preamble<'src>(
106        doc: &'src crate::Document<'src>,
107    ) -> &'src crate::blocks::Block<'src> {
108        doc.nested_blocks().next().unwrap()
109    }
110
111    #[test]
112    fn impl_clone() {
113        // Silly test to mark the #[derive(...)] line as covered.
114        let doc = doc_fixture();
115
116        let b1 = fixture_preamble(&doc);
117        let b2 = b1.clone();
118
119        assert_eq!(b1, &b2);
120    }
121
122    #[test]
123    fn impl_debug() {
124        let doc = doc_fixture();
125        let preamble = fixture_preamble(&doc);
126
127        let crate::blocks::Block::Preamble(preamble) = preamble else {
128            panic!("Unexpected block: {preamble:#?}");
129        };
130
131        dbg!(&preamble);
132
133        assert_eq!(
134            format!("{preamble:#?}"),
135            r#"Preamble {
136    blocks: &[
137        Block::Simple(
138            SimpleBlock {
139                content: Content {
140                    original: Span {
141                        data: "Some early words go here.",
142                        line: 3,
143                        col: 1,
144                        offset: 18,
145                    },
146                    rendered: "Some early words go here.",
147                },
148                source: Span {
149                    data: "Some early words go here.",
150                    line: 3,
151                    col: 1,
152                    offset: 18,
153                },
154                style: SimpleBlockStyle::Paragraph,
155                title_source: None,
156                title: None,
157                anchor: None,
158                anchor_reftext: None,
159                attrlist: None,
160            },
161        ),
162    ],
163    source: Span {
164        data: "Some early words go here.",
165        line: 3,
166        col: 1,
167        offset: 18,
168    },
169}"#
170        );
171    }
172
173    #[test]
174    fn impl_is_block() {
175        let doc = doc_fixture();
176        let preamble = fixture_preamble(&doc);
177
178        assert_eq!(
179            preamble,
180            &Block::Preamble(Preamble {
181                blocks: &[Block::Simple(SimpleBlock {
182                    content: Content {
183                        original: Span {
184                            data: "Some early words go here.",
185                            line: 3,
186                            col: 1,
187                            offset: 18,
188                        },
189                        rendered: "Some early words go here.",
190                    },
191                    source: Span {
192                        data: "Some early words go here.",
193                        line: 3,
194                        col: 1,
195                        offset: 18,
196                    },
197                    style: SimpleBlockStyle::Paragraph,
198                    title_source: None,
199                    title: None,
200                    anchor: None,
201                    anchor_reftext: None,
202                    attrlist: None,
203                },),],
204                source: Span {
205                    data: "Some early words go here.",
206                    line: 3,
207                    col: 1,
208                    offset: 18,
209                },
210            },)
211        );
212
213        assert_eq!(preamble.content_model(), ContentModel::Compound);
214        assert_eq!(preamble.raw_context().as_ref(), "preamble");
215        assert_eq!(preamble.resolved_context().as_ref(), "preamble");
216        assert!(preamble.declared_style().is_none());
217
218        let mut blocks = preamble.nested_blocks();
219        assert_eq!(
220            blocks.next().unwrap(),
221            &Block::Simple(SimpleBlock {
222                content: Content {
223                    original: Span {
224                        data: "Some early words go here.",
225                        line: 3,
226                        col: 1,
227                        offset: 18,
228                    },
229                    rendered: "Some early words go here.",
230                },
231                source: Span {
232                    data: "Some early words go here.",
233                    line: 3,
234                    col: 1,
235                    offset: 18,
236                },
237                style: SimpleBlockStyle::Paragraph,
238                title_source: None,
239                title: None,
240                anchor: None,
241                anchor_reftext: None,
242                attrlist: None,
243            })
244        );
245
246        assert!(blocks.next().is_none());
247
248        assert!(preamble.id().is_none());
249        assert!(preamble.roles().is_empty());
250        assert!(preamble.options().is_empty());
251        assert!(preamble.title_source().is_none());
252        assert!(preamble.title().is_none());
253        assert!(preamble.anchor().is_none());
254        assert!(preamble.anchor_reftext().is_none());
255        assert!(preamble.attrlist().is_none());
256        assert_eq!(preamble.substitution_group(), SubstitutionGroup::Normal);
257
258        assert_eq!(
259            format!("{preamble:#?}"),
260            "Block::Preamble(\n    Preamble {\n        blocks: &[\n            Block::Simple(\n                SimpleBlock {\n                    content: Content {\n                        original: Span {\n                            data: \"Some early words go here.\",\n                            line: 3,\n                            col: 1,\n                            offset: 18,\n                        },\n                        rendered: \"Some early words go here.\",\n                    },\n                    source: Span {\n                        data: \"Some early words go here.\",\n                        line: 3,\n                        col: 1,\n                        offset: 18,\n                    },\n                    style: SimpleBlockStyle::Paragraph,\n                    title_source: None,\n                    title: None,\n                    anchor: None,\n                    anchor_reftext: None,\n                    attrlist: None,\n                },\n            ),\n        ],\n        source: Span {\n            data: \"Some early words go here.\",\n            line: 3,\n            col: 1,\n            offset: 18,\n        },\n    },\n)"
261        );
262
263        assert_eq!(
264            preamble.span(),
265            Span {
266                data: "Some early words go here.",
267                line: 3,
268                col: 1,
269                offset: 18,
270            }
271        );
272    }
273}