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