asciidoc_parser/document/
header.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
use std::slice::Iter;

use crate::{
    document::Attribute,
    span::MatchedItem,
    warnings::{MatchAndWarnings, Warning, WarningType},
    HasSpan, Span,
};

/// An AsciiDoc document may begin with a document header. The document header
/// encapsulates the document title, author and revision information,
/// document-wide attributes, and other document metadata.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Header<'src> {
    title: Option<Span<'src>>,
    attributes: Vec<Attribute<'src>>,
    source: Span<'src>,
}

impl<'src> Header<'src> {
    pub(crate) fn parse(source: Span<'src>) -> MatchAndWarnings<'src, MatchedItem<'src, Self>> {
        let original_src = source;

        let mut attributes: Vec<Attribute> = vec![];
        let mut warnings: Vec<Warning<'src>> = vec![];

        let source = source.discard_empty_lines();

        let (title, mut after) = if let Some(mi) = parse_title(source) {
            (Some(mi.item), mi.after)
        } else {
            (None, source)
        };

        while let Some(attr) = Attribute::parse(after) {
            attributes.push(attr.item);
            after = attr.after;
        }

        let source = source.trim_remainder(after);

        // Nothing resembling a header so far? Don't look for empty line.
        if title.is_none() && attributes.is_empty() {
            return MatchAndWarnings {
                item: MatchedItem {
                    item: Self {
                        title: None,
                        attributes,
                        source: original_src.into_parse_result(0).item,
                    },
                    after,
                },
                warnings,
            };
        }

        // Header is valid so far. Warn if not followed by empty line or EOF.
        after = match after.take_empty_line() {
            Some(mi) => mi.after.discard_empty_lines(),
            None => {
                warnings.push(Warning {
                    source: after.take_line().item,
                    warning: WarningType::DocumentHeaderNotTerminated,
                });
                after
            }
        };

        MatchAndWarnings {
            item: MatchedItem {
                item: Self {
                    title,
                    attributes,
                    source,
                },
                after,
            },
            warnings,
        }
    }

    /// Return a [`Span`] describing the document title, if there was one.
    pub fn title(&'src self) -> Option<Span<'src>> {
        self.title
    }

    /// Return an iterator over the attributes in this header.
    pub fn attributes(&'src self) -> Iter<'src, Attribute<'src>> {
        self.attributes.iter()
    }
}

impl<'src> HasSpan<'src> for Header<'src> {
    fn span(&'src self) -> &'src Span<'src> {
        &self.source
    }
}

fn parse_title(source: Span<'_>) -> Option<MatchedItem<Span>> {
    let line = source.take_non_empty_line()?;
    let equal = line.item.take_prefix("=")?;
    let ws = equal.after.take_required_whitespace()?;

    Some(MatchedItem {
        item: ws.after,
        after: line.after,
    })
}