Skip to main content

acdc_parser/model/
metadata.rs

1//! Block metadata types for `AsciiDoc` documents.
2
3use serde::Serialize;
4
5use super::anchor::Anchor;
6use super::attributes::{AttributeValue, ElementAttributes};
7use super::substitution::SubstitutionSpec;
8
9pub type Role = String;
10
11/// A `BlockMetadata` represents the metadata of a block in a document.
12#[derive(Clone, Debug, Default, PartialEq, Serialize)]
13#[non_exhaustive]
14pub struct BlockMetadata {
15    #[serde(default, skip_serializing_if = "ElementAttributes::is_empty")]
16    pub attributes: ElementAttributes,
17    #[serde(default, skip_serializing)]
18    pub positional_attributes: Vec<String>,
19    #[serde(default, skip_serializing_if = "Vec::is_empty")]
20    pub roles: Vec<Role>,
21    #[serde(default, skip_serializing_if = "Vec::is_empty")]
22    pub options: Vec<String>,
23    #[serde(default, skip_serializing_if = "Option::is_none")]
24    pub style: Option<String>,
25    #[serde(default, skip_serializing_if = "Option::is_none")]
26    pub id: Option<Anchor>,
27    #[serde(default, skip_serializing_if = "Vec::is_empty")]
28    pub anchors: Vec<Anchor>,
29    /// Substitutions to apply to block content.
30    ///
31    /// - `None`: Use block-type defaults (VERBATIM for listing/literal, NORMAL for paragraphs)
32    /// - `Some(Explicit([]))`: No substitutions (equivalent to `subs=none`)
33    /// - `Some(Explicit(list))`: Use the explicit list of substitutions
34    /// - `Some(Modifiers(ops))`: Apply modifier operations to block-type defaults
35    #[serde(default, skip_serializing_if = "Option::is_none")]
36    pub substitutions: Option<SubstitutionSpec>,
37}
38
39impl BlockMetadata {
40    /// Create a new block metadata with default values.
41    #[must_use]
42    pub fn new() -> Self {
43        Self::default()
44    }
45
46    /// Set the attributes.
47    #[must_use]
48    pub fn with_attributes(mut self, attributes: ElementAttributes) -> Self {
49        self.attributes = attributes;
50        self
51    }
52
53    /// Set the options.
54    #[must_use]
55    pub fn with_options(mut self, options: Vec<String>) -> Self {
56        self.options = options;
57        self
58    }
59
60    /// Set the roles.
61    #[must_use]
62    pub fn with_roles(mut self, roles: Vec<Role>) -> Self {
63        self.roles = roles;
64        self
65    }
66
67    /// Set the style.
68    #[must_use]
69    pub fn with_style(mut self, style: Option<String>) -> Self {
70        self.style = style;
71        self
72    }
73
74    /// Set the ID.
75    #[must_use]
76    pub fn with_id(mut self, id: Option<Anchor>) -> Self {
77        self.id = id;
78        self
79    }
80
81    pub fn move_positional_attributes_to_attributes(&mut self) {
82        for positional_attribute in self.positional_attributes.drain(..) {
83            self.attributes
84                .insert(positional_attribute, AttributeValue::None);
85        }
86    }
87
88    pub fn set_attributes(&mut self, attributes: ElementAttributes) {
89        self.attributes = attributes;
90    }
91
92    #[must_use]
93    pub fn is_default(&self) -> bool {
94        self.roles.is_empty()
95            && self.options.is_empty()
96            && self.style.is_none()
97            && self.id.is_none()
98            && self.anchors.is_empty()
99            && self.attributes.is_empty()
100            && self.positional_attributes.is_empty()
101            && self.substitutions.is_none()
102    }
103
104    #[tracing::instrument(level = "debug")]
105    pub fn merge(&mut self, other: &BlockMetadata) {
106        self.attributes.merge(other.attributes.clone());
107        self.positional_attributes
108            .extend(other.positional_attributes.clone());
109        self.roles.extend(other.roles.clone());
110        self.options.extend(other.options.clone());
111        if self.style.is_none() {
112            self.style.clone_from(&other.style);
113        }
114        if self.id.is_none() {
115            self.id.clone_from(&other.id);
116        }
117        self.anchors.extend(other.anchors.clone());
118        if self.substitutions.is_none() {
119            self.substitutions.clone_from(&other.substitutions);
120        }
121    }
122}