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