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,
}