asciidoc_parser/document/
header.rs1use std::slice::Iter;
2
3use crate::{
4 document::Attribute,
5 span::MatchedItem,
6 warnings::{MatchAndWarnings, Warning, WarningType},
7 HasSpan, Parser, Span,
8};
9
10#[derive(Clone, Debug, Eq, PartialEq)]
14pub struct Header<'src> {
15 title: Option<Span<'src>>,
16 attributes: Vec<Attribute<'src>>,
17 source: Span<'src>,
18}
19
20impl<'src> Header<'src> {
21 pub(crate) fn parse(
22 source: Span<'src>,
23 _parser: &mut Parser,
24 ) -> MatchAndWarnings<'src, MatchedItem<'src, Self>> {
25 let original_src = source;
26
27 let mut attributes: Vec<Attribute> = vec![];
28 let mut warnings: Vec<Warning<'src>> = vec![];
29
30 let source = source.discard_empty_lines();
31
32 let (title, mut after) = if let Some(mi) = parse_title(source) {
33 (Some(mi.item), mi.after)
34 } else {
35 (None, source)
36 };
37
38 while let Some(attr) = Attribute::parse(after) {
39 attributes.push(attr.item);
40 after = attr.after;
41 }
42
43 let source = source.trim_remainder(after);
44
45 if title.is_none() && attributes.is_empty() {
47 return MatchAndWarnings {
48 item: MatchedItem {
49 item: Self {
50 title: None,
51 attributes,
52 source: original_src.into_parse_result(0).item,
53 },
54 after,
55 },
56 warnings,
57 };
58 }
59
60 after = match after.take_empty_line() {
62 Some(mi) => mi.after.discard_empty_lines(),
63 None => {
64 warnings.push(Warning {
65 source: after.take_line().item,
66 warning: WarningType::DocumentHeaderNotTerminated,
67 });
68 after
69 }
70 };
71
72 MatchAndWarnings {
73 item: MatchedItem {
74 item: Self {
75 title,
76 attributes,
77 source: source.trim_trailing_whitespace(),
78 },
79 after,
80 },
81 warnings,
82 }
83 }
84
85 pub fn title(&'src self) -> Option<Span<'src>> {
87 self.title
88 }
89
90 pub fn attributes(&'src self) -> Iter<'src, Attribute<'src>> {
92 self.attributes.iter()
93 }
94}
95
96impl<'src> HasSpan<'src> for Header<'src> {
97 fn span(&'src self) -> &'src Span<'src> {
98 &self.source
99 }
100}
101
102fn parse_title(source: Span<'_>) -> Option<MatchedItem<Span>> {
103 let line = source.take_non_empty_line()?;
104 let equal = line.item.take_prefix("=")?;
105 let ws = equal.after.take_required_whitespace()?;
106
107 Some(MatchedItem {
108 item: ws.after,
109 after: line.after,
110 })
111}