asciidoc_parser/document/
attribute.rs

1use crate::{span::MatchedItem, strings::CowStr, HasSpan, Span};
2
3/// Document attributes are effectively document-scoped variables for the
4/// AsciiDoc language. The AsciiDoc language defines a set of built-in
5/// attributes, and also allows the author (or extensions) to define additional
6/// document attributes, which may replace built-in attributes when permitted.
7#[derive(Clone, Debug, Eq, PartialEq)]
8pub struct Attribute<'src> {
9    name: Span<'src>,
10    value: RawAttributeValue<'src>,
11    source: Span<'src>,
12}
13
14impl<'src> Attribute<'src> {
15    pub(crate) fn parse(source: Span<'src>) -> Option<MatchedItem<'src, Self>> {
16        let attr_line = source.take_line_with_continuation()?;
17        let colon = attr_line.item.take_prefix(":")?;
18
19        let mut unset = false;
20        let line = if colon.after.starts_with('!') {
21            unset = true;
22            colon.after.slice_from(1..)
23        } else {
24            colon.after
25        };
26
27        let name = line.take_user_attr_name()?;
28
29        let line = if name.after.starts_with('!') && !unset {
30            unset = true;
31            name.after.slice_from(1..)
32        } else {
33            name.after
34        };
35
36        let line = line.take_prefix(":")?;
37
38        let value = if unset {
39            // Ensure line is now empty except for comment.
40            RawAttributeValue::Unset
41        } else if line.after.is_empty() {
42            RawAttributeValue::Set
43        } else {
44            let value = line.after.take_whitespace();
45            RawAttributeValue::Value(value.after)
46        };
47
48        let source = source.trim_remainder(attr_line.after);
49        Some(MatchedItem {
50            item: Self {
51                name: name.item,
52                value,
53                source: source.trim_trailing_whitespace(),
54            },
55            after: attr_line.after,
56        })
57    }
58
59    /// Return a [`Span`] describing the attribute name.
60    pub fn name(&'src self) -> &'src Span<'src> {
61        &self.name
62    }
63
64    /// Return the attribute's raw value.
65    pub fn raw_value(&'src self) -> &'src RawAttributeValue<'src> {
66        &self.value
67    }
68
69    /// Return the attribute's interpolated value.
70    pub fn value(&'src self) -> InterpretedValue<'src> {
71        self.value.as_interpreted_value()
72    }
73}
74
75impl<'src> HasSpan<'src> for Attribute<'src> {
76    fn span(&'src self) -> &'src Span<'src> {
77        &self.source
78    }
79}
80
81/// The raw value of an [`Attribute`].
82///
83/// If the value contains a textual value, this value will
84/// contain continuation markers.
85#[derive(Clone, Debug, Eq, PartialEq)]
86pub enum RawAttributeValue<'src> {
87    /// A custom value, described by its accompanying [`Span`].
88    Value(Span<'src>),
89
90    /// No explicit value. This is typically interpreted as either
91    /// boolean `true` or a default value for a built-in attribute.
92    Set,
93
94    /// Explicitly unset. This is typically interpreted as boolean `false`.
95    Unset,
96}
97
98impl<'src> RawAttributeValue<'src> {
99    /// Convert this to an [`InterpretedValue`], resolving any interpolation
100    /// necessary if the value contains a textual value.
101    pub fn as_interpreted_value(&self) -> InterpretedValue<'src> {
102        match self {
103            Self::Value(span) => {
104                let data = span.data();
105                if data.contains('\n') {
106                    let lines: Vec<&str> = data.lines().collect();
107                    let last_count = lines.len() - 1;
108
109                    let value: Vec<String> = lines
110                        .iter()
111                        .enumerate()
112                        .map(|(count, line)| {
113                            let line = if count > 0 {
114                                line.trim_start_matches(' ')
115                            } else {
116                                line
117                            };
118
119                            let line = line
120                                .trim_start_matches('\r')
121                                .trim_end_matches(' ')
122                                .trim_end_matches('\\')
123                                .trim_end_matches(' ');
124
125                            if line.ends_with('+') {
126                                format!("{}\n", line.trim_end_matches('+').trim_end_matches(' '))
127                            } else if count < last_count {
128                                format!("{line} ")
129                            } else {
130                                line.to_string()
131                            }
132                        })
133                        .collect();
134
135                    let value = value.join("");
136                    InterpretedValue::Value(CowStr::from(value))
137                } else {
138                    InterpretedValue::Value(CowStr::Borrowed(data))
139                }
140            }
141
142            Self::Set => InterpretedValue::Set,
143            Self::Unset => InterpretedValue::Unset,
144        }
145    }
146}
147
148/// The interpreted value of an [`Attribute`].
149///
150/// If the value contains a textual value, this value will
151/// have any continuation markers resolved, but will no longer
152/// contain a reference to the [`Span`] that contains the value.
153#[derive(Clone, Debug, Eq, PartialEq)]
154pub enum InterpretedValue<'src> {
155    /// A custom value with all necessary interpolations applied.
156    Value(CowStr<'src>),
157
158    /// No explicit value. This is typically interpreted as either
159    /// boolean `true` or a default value for a built-in attribute.
160    Set,
161
162    /// Explicitly unset. This is typically interpreted as boolean `false`.
163    Unset,
164}