Skip to main content

asciidoc_parser/blocks/
preamble.rs

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