Skip to main content

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 nested_blocks_mut(&mut self) -> &mut [Block<'src>] {
52        &mut self.blocks
53    }
54
55    fn title_source(&'src self) -> Option<Span<'src>> {
56        None
57    }
58
59    fn title(&self) -> Option<&str> {
60        None
61    }
62
63    fn anchor(&'src self) -> Option<Span<'src>> {
64        None
65    }
66
67    fn anchor_reftext(&'src self) -> Option<Span<'src>> {
68        None
69    }
70
71    fn attrlist(&'src self) -> Option<&'src Attrlist<'src>> {
72        None
73    }
74}
75
76impl<'src> HasSpan<'src> for Preamble<'src> {
77    fn span(&self) -> Span<'src> {
78        self.source
79    }
80}
81
82impl std::fmt::Debug for Preamble<'_> {
83    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84        f.debug_struct("Preamble")
85            .field("blocks", &DebugSliceReference(&self.blocks))
86            .field("source", &self.source)
87            .finish()
88    }
89}
90
91#[cfg(test)]
92mod tests {
93    #![allow(clippy::panic)]
94    #![allow(clippy::unwrap_used)]
95
96    use crate::{blocks::ContentModel, tests::prelude::*};
97
98    fn doc_fixture() -> crate::Document<'static> {
99        Parser::default().parse("= Document Title\n\nSome early words go here.\n\n== First Section")
100    }
101
102    fn fixture_preamble<'src>(
103        doc: &'src crate::Document<'src>,
104    ) -> &'src crate::blocks::Block<'src> {
105        doc.nested_blocks().next().unwrap()
106    }
107
108    #[test]
109    fn impl_clone() {
110        // Silly test to mark the #[derive(...)] line as covered.
111        let doc = doc_fixture();
112
113        let b1 = fixture_preamble(&doc);
114        let b2 = b1.clone();
115
116        assert_eq!(b1, &b2);
117    }
118
119    #[test]
120    fn impl_debug() {
121        let doc = doc_fixture();
122        let preamble = fixture_preamble(&doc);
123
124        let crate::blocks::Block::Preamble(preamble) = preamble else {
125            panic!("Unexpected block: {preamble:#?}");
126        };
127
128        dbg!(&preamble);
129
130        assert_eq!(
131            format!("{preamble:#?}"),
132            r#"Preamble {
133    blocks: &[
134        Block::Simple(
135            SimpleBlock {
136                content: Content {
137                    original: Span {
138                        data: "Some early words go here.",
139                        line: 3,
140                        col: 1,
141                        offset: 18,
142                    },
143                    rendered: "Some early words go here.",
144                },
145                source: Span {
146                    data: "Some early words go here.",
147                    line: 3,
148                    col: 1,
149                    offset: 18,
150                },
151                style: SimpleBlockStyle::Paragraph,
152                title_source: None,
153                title: None,
154                caption: None,
155                number: 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                    style: SimpleBlockStyle::Paragraph,
197                    title_source: None,
198                    title: None,
199                    caption: None,
200                    number: None,
201                    anchor: None,
202                    anchor_reftext: None,
203                    attrlist: None,
204                },),],
205                source: Span {
206                    data: "Some early words go here.",
207                    line: 3,
208                    col: 1,
209                    offset: 18,
210                },
211            },)
212        );
213
214        assert_eq!(preamble.content_model(), ContentModel::Compound);
215        assert!(preamble.rendered_content().is_none());
216        assert_eq!(preamble.raw_context().as_ref(), "preamble");
217        assert_eq!(preamble.resolved_context().as_ref(), "preamble");
218        assert!(preamble.declared_style().is_none());
219
220        let mut blocks = preamble.nested_blocks();
221        assert_eq!(
222            blocks.next().unwrap(),
223            &Block::Simple(SimpleBlock {
224                content: Content {
225                    original: Span {
226                        data: "Some early words go here.",
227                        line: 3,
228                        col: 1,
229                        offset: 18,
230                    },
231                    rendered: "Some early words go here.",
232                },
233                source: Span {
234                    data: "Some early words go here.",
235                    line: 3,
236                    col: 1,
237                    offset: 18,
238                },
239                style: SimpleBlockStyle::Paragraph,
240                title_source: None,
241                title: None,
242                caption: None,
243                number: None,
244                anchor: None,
245                anchor_reftext: None,
246                attrlist: None,
247            })
248        );
249
250        assert!(blocks.next().is_none());
251
252        assert!(preamble.id().is_none());
253        assert!(preamble.roles().is_empty());
254        assert!(preamble.options().is_empty());
255        assert!(preamble.title_source().is_none());
256        assert!(preamble.title().is_none());
257        assert!(preamble.anchor().is_none());
258        assert!(preamble.anchor_reftext().is_none());
259        assert!(preamble.attrlist().is_none());
260        assert_eq!(preamble.substitution_group(), SubstitutionGroup::Normal);
261
262        assert_eq!(
263            format!("{preamble:#?}"),
264            "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                    caption: None,\n                    number: 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)"
265        );
266
267        assert_eq!(
268            preamble.span(),
269            Span {
270                data: "Some early words go here.",
271                line: 3,
272                col: 1,
273                offset: 18,
274            }
275        );
276    }
277}