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