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#[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 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 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}