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},
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                title_source: None,
155                title: None,
156                anchor: None,
157                anchor_reftext: None,
158                attrlist: None,
159            },
160        ),
161    ],
162    source: Span {
163        data: "Some early words go here.",
164        line: 3,
165        col: 1,
166        offset: 18,
167    },
168}"#
169        );
170    }
171
172    #[test]
173    fn impl_is_block() {
174        let doc = doc_fixture();
175        let preamble = fixture_preamble(&doc);
176
177        assert_eq!(
178            preamble,
179            &Block::Preamble(Preamble {
180                blocks: &[Block::Simple(SimpleBlock {
181                    content: Content {
182                        original: Span {
183                            data: "Some early words go here.",
184                            line: 3,
185                            col: 1,
186                            offset: 18,
187                        },
188                        rendered: "Some early words go here.",
189                    },
190                    source: Span {
191                        data: "Some early words go here.",
192                        line: 3,
193                        col: 1,
194                        offset: 18,
195                    },
196                    title_source: None,
197                    title: None,
198                    anchor: None,
199                    anchor_reftext: None,
200                    attrlist: None,
201                },),],
202                source: Span {
203                    data: "Some early words go here.",
204                    line: 3,
205                    col: 1,
206                    offset: 18,
207                },
208            },)
209        );
210
211        assert_eq!(preamble.content_model(), ContentModel::Compound);
212        assert_eq!(preamble.raw_context().as_ref(), "preamble");
213        assert_eq!(preamble.resolved_context().as_ref(), "preamble");
214        assert!(preamble.declared_style().is_none());
215
216        let mut blocks = preamble.nested_blocks();
217        assert_eq!(
218            blocks.next().unwrap(),
219            &Block::Simple(SimpleBlock {
220                content: Content {
221                    original: Span {
222                        data: "Some early words go here.",
223                        line: 3,
224                        col: 1,
225                        offset: 18,
226                    },
227                    rendered: "Some early words go here.",
228                },
229                source: Span {
230                    data: "Some early words go here.",
231                    line: 3,
232                    col: 1,
233                    offset: 18,
234                },
235                title_source: None,
236                title: None,
237                anchor: None,
238                anchor_reftext: None,
239                attrlist: None,
240            })
241        );
242
243        assert!(blocks.next().is_none());
244
245        assert!(preamble.id().is_none());
246        assert!(preamble.roles().is_empty());
247        assert!(preamble.options().is_empty());
248        assert!(preamble.title_source().is_none());
249        assert!(preamble.title().is_none());
250        assert!(preamble.anchor().is_none());
251        assert!(preamble.anchor_reftext().is_none());
252        assert!(preamble.attrlist().is_none());
253        assert_eq!(preamble.substitution_group(), SubstitutionGroup::Normal);
254
255        assert_eq!(
256            format!("{preamble:#?}"),
257            "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                    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)"
258        );
259
260        assert_eq!(
261            preamble.span(),
262            Span {
263                data: "Some early words go here.",
264                line: 3,
265                col: 1,
266                offset: 18,
267            }
268        );
269    }
270}