asciidoc_parser/blocks/
simple.rs

1use crate::{
2    HasSpan, Parser, Span,
3    attributes::Attrlist,
4    blocks::{ContentModel, IsBlock, metadata::BlockMetadata},
5    content::{Content, SubstitutionGroup},
6    span::MatchedItem,
7    strings::CowStr,
8};
9
10/// A block that's treated as contiguous lines of paragraph text (and subject to
11/// normal substitutions) (e.g., a paragraph block).
12#[derive(Clone, Debug, Eq, PartialEq)]
13pub struct SimpleBlock<'src> {
14    content: Content<'src>,
15    source: Span<'src>,
16    title_source: Option<Span<'src>>,
17    title: Option<String>,
18    anchor: Option<Span<'src>>,
19    anchor_reftext: Option<Span<'src>>,
20    attrlist: Option<Attrlist<'src>>,
21}
22
23impl<'src> SimpleBlock<'src> {
24    pub(crate) fn parse(
25        metadata: &BlockMetadata<'src>,
26        parser: &mut Parser,
27    ) -> Option<MatchedItem<'src, Self>> {
28        let source = metadata.block_start.take_non_empty_lines()?;
29
30        let mut content: Content<'src> = source.item.into();
31
32        SubstitutionGroup::Normal
33            .override_via_attrlist(metadata.attrlist.as_ref())
34            .apply(&mut content, parser, metadata.attrlist.as_ref());
35
36        Some(MatchedItem {
37            item: Self {
38                content,
39                source: metadata
40                    .source
41                    .trim_remainder(source.after)
42                    .trim_trailing_whitespace(),
43                title_source: metadata.title_source,
44                title: metadata.title.clone(),
45                anchor: metadata.anchor,
46                anchor_reftext: metadata.anchor_reftext,
47                attrlist: metadata.attrlist.clone(),
48            },
49            after: source.after.discard_empty_lines(),
50        })
51    }
52
53    pub(crate) fn parse_fast(
54        source: Span<'src>,
55        parser: &mut Parser,
56    ) -> Option<MatchedItem<'src, Self>> {
57        let source = source.take_non_empty_lines()?;
58
59        let mut content: Content<'src> = source.item.into();
60        SubstitutionGroup::Normal.apply(&mut content, parser, None);
61
62        Some(MatchedItem {
63            item: Self {
64                content,
65                source: source.item,
66                title_source: None,
67                title: None,
68                anchor: None,
69                anchor_reftext: None,
70                attrlist: None,
71            },
72            after: source.after.discard_empty_lines(),
73        })
74    }
75
76    /// Return the interpreted content of this block.
77    pub fn content(&self) -> &Content<'src> {
78        &self.content
79    }
80}
81
82impl<'src> IsBlock<'src> for SimpleBlock<'src> {
83    fn content_model(&self) -> ContentModel {
84        ContentModel::Simple
85    }
86
87    fn raw_context(&self) -> CowStr<'src> {
88        "paragraph".into()
89    }
90
91    fn title_source(&'src self) -> Option<Span<'src>> {
92        self.title_source
93    }
94
95    fn title(&self) -> Option<&str> {
96        self.title.as_deref()
97    }
98
99    fn anchor(&'src self) -> Option<Span<'src>> {
100        self.anchor
101    }
102
103    fn anchor_reftext(&'src self) -> Option<Span<'src>> {
104        self.anchor_reftext
105    }
106
107    fn attrlist(&'src self) -> Option<&'src Attrlist<'src>> {
108        self.attrlist.as_ref()
109    }
110}
111
112impl<'src> HasSpan<'src> for SimpleBlock<'src> {
113    fn span(&self) -> Span<'src> {
114        self.source
115    }
116}
117
118#[cfg(test)]
119mod tests {
120    #![allow(clippy::unwrap_used)]
121
122    use std::ops::Deref;
123
124    use pretty_assertions_sorted::assert_eq;
125
126    use crate::{
127        Parser,
128        blocks::{ContentModel, IsBlock, metadata::BlockMetadata},
129        content::SubstitutionGroup,
130        tests::prelude::*,
131    };
132
133    #[test]
134    fn impl_clone() {
135        // Silly test to mark the #[derive(...)] line as covered.
136        let mut parser = Parser::default();
137
138        let b1 =
139            crate::blocks::SimpleBlock::parse(&BlockMetadata::new("abc"), &mut parser).unwrap();
140
141        let b2 = b1.item.clone();
142        assert_eq!(b1.item, b2);
143    }
144
145    #[test]
146    fn empty_source() {
147        let mut parser = Parser::default();
148        assert!(crate::blocks::SimpleBlock::parse(&BlockMetadata::new(""), &mut parser).is_none());
149    }
150
151    #[test]
152    fn only_spaces() {
153        let mut parser = Parser::default();
154        assert!(
155            crate::blocks::SimpleBlock::parse(&BlockMetadata::new("    "), &mut parser).is_none()
156        );
157    }
158
159    #[test]
160    fn single_line() {
161        let mut parser = Parser::default();
162        let mi =
163            crate::blocks::SimpleBlock::parse(&BlockMetadata::new("abc"), &mut parser).unwrap();
164
165        assert_eq!(
166            mi.item,
167            SimpleBlock {
168                content: Content {
169                    original: Span {
170                        data: "abc",
171                        line: 1,
172                        col: 1,
173                        offset: 0,
174                    },
175                    rendered: "abc",
176                },
177                source: Span {
178                    data: "abc",
179                    line: 1,
180                    col: 1,
181                    offset: 0,
182                },
183                title_source: None,
184                title: None,
185                anchor: None,
186                anchor_reftext: None,
187                attrlist: None,
188            },
189        );
190
191        assert_eq!(mi.item.content_model(), ContentModel::Simple);
192        assert_eq!(mi.item.raw_context().deref(), "paragraph");
193        assert_eq!(mi.item.resolved_context().deref(), "paragraph");
194        assert!(mi.item.declared_style().is_none());
195        assert!(mi.item.id().is_none());
196        assert!(mi.item.roles().is_empty());
197        assert!(mi.item.options().is_empty());
198        assert!(mi.item.title_source().is_none());
199        assert!(mi.item.title().is_none());
200        assert!(mi.item.anchor().is_none());
201        assert!(mi.item.anchor_reftext().is_none());
202        assert!(mi.item.attrlist().is_none());
203        assert_eq!(mi.item.substitution_group(), SubstitutionGroup::Normal);
204
205        assert_eq!(
206            mi.after,
207            Span {
208                data: "",
209                line: 1,
210                col: 4,
211                offset: 3
212            }
213        );
214    }
215
216    #[test]
217    fn multiple_lines() {
218        let mut parser = Parser::default();
219        let mi = crate::blocks::SimpleBlock::parse(&BlockMetadata::new("abc\ndef"), &mut parser)
220            .unwrap();
221
222        assert_eq!(
223            mi.item,
224            SimpleBlock {
225                content: Content {
226                    original: Span {
227                        data: "abc\ndef",
228                        line: 1,
229                        col: 1,
230                        offset: 0,
231                    },
232                    rendered: "abc\ndef",
233                },
234                source: Span {
235                    data: "abc\ndef",
236                    line: 1,
237                    col: 1,
238                    offset: 0,
239                },
240                title_source: None,
241                title: None,
242                anchor: None,
243                anchor_reftext: None,
244                attrlist: None,
245            }
246        );
247
248        assert_eq!(
249            mi.after,
250            Span {
251                data: "",
252                line: 2,
253                col: 4,
254                offset: 7
255            }
256        );
257    }
258
259    #[test]
260    fn consumes_blank_lines_after() {
261        let mut parser = Parser::default();
262        let mi = crate::blocks::SimpleBlock::parse(&BlockMetadata::new("abc\n\ndef"), &mut parser)
263            .unwrap();
264
265        assert_eq!(
266            mi.item,
267            SimpleBlock {
268                content: Content {
269                    original: Span {
270                        data: "abc",
271                        line: 1,
272                        col: 1,
273                        offset: 0,
274                    },
275                    rendered: "abc",
276                },
277                source: Span {
278                    data: "abc",
279                    line: 1,
280                    col: 1,
281                    offset: 0,
282                },
283                title_source: None,
284                title: None,
285                anchor: None,
286                anchor_reftext: None,
287                attrlist: None,
288            }
289        );
290
291        assert_eq!(
292            mi.after,
293            Span {
294                data: "def",
295                line: 3,
296                col: 1,
297                offset: 5
298            }
299        );
300    }
301
302    #[test]
303    fn overrides_sub_group_via_subs_attribute() {
304        let mut parser = Parser::default();
305        let mi = crate::blocks::SimpleBlock::parse(
306            &BlockMetadata::new("[subs=quotes]\na<b>c *bold*\n\ndef"),
307            &mut parser,
308        )
309        .unwrap();
310
311        assert_eq!(
312            mi.item,
313            SimpleBlock {
314                content: Content {
315                    original: Span {
316                        data: "a<b>c *bold*",
317                        line: 2,
318                        col: 1,
319                        offset: 14,
320                    },
321                    rendered: "a<b>c <strong>bold</strong>",
322                },
323                source: Span {
324                    data: "[subs=quotes]\na<b>c *bold*",
325                    line: 1,
326                    col: 1,
327                    offset: 0,
328                },
329                title_source: None,
330                title: None,
331                anchor: None,
332                anchor_reftext: None,
333                attrlist: Some(Attrlist {
334                    attributes: &[ElementAttribute {
335                        name: Some("subs"),
336                        value: "quotes",
337                        shorthand_items: &[],
338                    },],
339                    anchor: None,
340                    source: Span {
341                        data: "subs=quotes",
342                        line: 1,
343                        col: 2,
344                        offset: 1,
345                    },
346                },),
347            }
348        );
349
350        assert_eq!(
351            mi.after,
352            Span {
353                data: "def",
354                line: 4,
355                col: 1,
356                offset: 28
357            }
358        );
359    }
360}