asciidoc_parser/document/
attribute.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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
use crate::{span::MatchedItem, strings::CowStr, HasSpan, Span};

/// Document attributes are effectively document-scoped variables for the
/// AsciiDoc language. The AsciiDoc language defines a set of built-in
/// attributes, and also allows the author (or extensions) to define additional
/// document attributes, which may replace built-in attributes when permitted.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Attribute<'src> {
    name: Span<'src>,
    value: RawAttributeValue<'src>,
    source: Span<'src>,
}

impl<'src> Attribute<'src> {
    pub(crate) fn parse(source: Span<'src>) -> Option<MatchedItem<'src, Self>> {
        let attr_line = source.take_line_with_continuation()?;
        let colon = attr_line.item.take_prefix(":")?;

        let mut unset = false;
        let line = if colon.after.starts_with('!') {
            unset = true;
            colon.after.slice_from(1..)
        } else {
            colon.after
        };

        let name = line.take_ident()?;

        let line = if name.after.starts_with('!') && !unset {
            unset = true;
            name.after.slice_from(1..)
        } else {
            name.after
        };

        let line = line.take_prefix(":")?;

        let value = if unset {
            // Ensure line is now empty except for comment.
            RawAttributeValue::Unset
        } else if line.after.is_empty() {
            RawAttributeValue::Set
        } else {
            let value = line.after.take_whitespace();
            RawAttributeValue::Value(value.after)
        };

        let source = source.trim_remainder(attr_line.after);
        Some(MatchedItem {
            item: Self {
                name: name.item,
                value,
                source,
            },
            after: attr_line.after,
        })
    }

    /// Return a [`Span`] describing the attribute name.
    pub fn name(&'src self) -> &'src Span<'src> {
        &self.name
    }

    /// Return the attribute's raw value.
    pub fn raw_value(&'src self) -> &'src RawAttributeValue<'src> {
        &self.value
    }

    /// Return the attribute's interpolated value.
    pub fn value(&'src self) -> AttributeValue<'src> {
        self.value.as_attribute_value()
    }
}

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

/// The raw value of an [`Attribute`].
///
/// If the value contains a textual value, this value will
/// contain continuation markers.
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum RawAttributeValue<'src> {
    /// A custom value, described by its accompanying [`Span`].
    Value(Span<'src>),

    /// No explicit value. This is typically interpreted as either
    /// boolean `true` or a default value for a built-in attribute.
    Set,

    /// Explicitly unset. This is typically interpreted as boolean 'false'.
    Unset,
}

impl<'src> RawAttributeValue<'src> {
    /// Convert this to an [`AttributeValue`], resolving any interpolation
    /// necessary if the value contains a textual value.
    pub fn as_attribute_value(&self) -> AttributeValue<'src> {
        match self {
            Self::Value(span) => {
                let data = span.data();
                if data.contains('\n') {
                    let value: Vec<&str> = (0..)
                        .zip(data.lines())
                        .map(|(count, line)| {
                            let line = if count > 0 {
                                line.trim_start_matches(' ')
                            } else {
                                line
                            };

                            line.trim_start_matches('\r')
                                .trim_end_matches(' ')
                                .trim_end_matches('\\')
                                .trim_end_matches(' ')
                        })
                        .collect();

                    let value = value.join(" ");
                    AttributeValue::Value(CowStr::from(value))
                } else {
                    AttributeValue::Value(CowStr::Borrowed(data))
                }
            }

            Self::Set => AttributeValue::Set,
            Self::Unset => AttributeValue::Unset,
        }
    }
}

/// The interpreted value of an [`Attribute`].
///
/// If the value contains a textual value, this value will
/// have any continuation markers resolved, but will no longer
/// contain a reference to the [`Span`] that contains the value.
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum AttributeValue<'src> {
    /// A custom value with all necessary interpolations applied.
    Value(CowStr<'src>),

    /// No explicit value. This is typically interpreted as either
    /// boolean `true` or a default value for a built-in attribute.
    Set,

    /// Explicitly unset. This is typically interpreted as boolean 'false'.
    Unset,
}