Skip to main content

acdc_parser/model/inlines/
macros.rs

1use serde::Serialize;
2
3use crate::{ElementAttributes, InlineNode, Location, Source, StemNotation, Substitution};
4
5pub const ICON_SIZES: &[&str] = &["1x", "2x", "3x", "4x", "5x", "lg", "fw"];
6
7/// A `Pass` represents a passthrough macro in a document.
8#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
9#[non_exhaustive]
10pub struct Pass {
11    #[serde(default, skip_serializing_if = "Option::is_none")]
12    pub text: Option<String>,
13    #[serde(default, skip_serializing_if = "Vec::is_empty")]
14    pub substitutions: Vec<Substitution>,
15    pub location: Location,
16    #[serde(skip)]
17    pub kind: PassthroughKind,
18}
19
20#[derive(Clone, Debug, Eq, PartialEq, Serialize, Default)]
21pub enum PassthroughKind {
22    #[default]
23    Single,
24    Double,
25    Triple,
26    Macro,
27}
28
29/// A `Footnote` represents an inline footnote in a document.
30#[derive(Clone, Debug, PartialEq, Serialize)]
31#[non_exhaustive]
32pub struct Footnote {
33    #[serde(default, skip_serializing_if = "Option::is_none")]
34    pub id: Option<String>,
35    #[serde(default, skip_serializing_if = "Vec::is_empty")]
36    pub content: Vec<InlineNode>,
37    #[serde(skip)]
38    pub number: u32,
39    pub location: Location,
40}
41
42/// An `Icon` represents an inline icon in a document.
43#[derive(Clone, Debug, PartialEq, Serialize)]
44#[non_exhaustive]
45pub struct Icon {
46    pub target: Source,
47    pub attributes: ElementAttributes,
48    pub location: Location,
49}
50
51/// A `Link` represents an inline link in a document.
52#[derive(Clone, Debug, PartialEq, Serialize)]
53#[non_exhaustive]
54pub struct Link {
55    // We don't serialize the text here because it's already serialized in the attributes
56    // (that's how it's represented in the ASG)
57    #[serde(skip_serializing)]
58    pub text: Option<String>,
59    pub target: Source,
60    pub attributes: ElementAttributes,
61    pub location: Location,
62}
63
64impl Link {
65    /// Creates a new `Link` with the given target.
66    #[must_use]
67    pub fn new(target: Source, location: Location) -> Self {
68        Self {
69            text: None,
70            target,
71            attributes: ElementAttributes::default(),
72            location,
73        }
74    }
75
76    /// Sets the link text.
77    #[must_use]
78    pub fn with_text(mut self, text: Option<String>) -> Self {
79        self.text = text;
80        self
81    }
82
83    /// Sets the link attributes.
84    #[must_use]
85    pub fn with_attributes(mut self, attributes: ElementAttributes) -> Self {
86        self.attributes = attributes;
87        self
88    }
89}
90
91/// An `Url` represents an inline URL in a document.
92#[derive(Clone, Debug, PartialEq, Serialize)]
93#[non_exhaustive]
94pub struct Url {
95    // We don't serialize the text here because it's already serialized in the attributes
96    // (that's how it's represented in the ASG)
97    #[serde(skip_serializing)]
98    pub text: Vec<InlineNode>,
99    pub target: Source,
100    pub attributes: ElementAttributes,
101    pub location: Location,
102}
103
104/// An `Mailto` represents an inline `mailto:` in a document.
105#[derive(Clone, Debug, PartialEq, Serialize)]
106#[non_exhaustive]
107pub struct Mailto {
108    // We don't serialize the text here because it's already serialized in the attributes
109    // (that's how it's represented in the ASG)
110    #[serde(skip_serializing)]
111    pub text: Vec<InlineNode>,
112    pub target: Source,
113    pub attributes: ElementAttributes,
114    pub location: Location,
115}
116
117/// A `Button` represents an inline button in a document.
118#[derive(Clone, Debug, PartialEq, Serialize)]
119#[non_exhaustive]
120pub struct Button {
121    pub label: String,
122    pub location: Location,
123}
124
125/// A `Menu` represents an inline menu in a document.
126#[derive(Clone, Debug, PartialEq, Serialize)]
127#[non_exhaustive]
128pub struct Menu {
129    pub target: String,
130    #[serde(default, skip_serializing_if = "Vec::is_empty")]
131    pub items: Vec<String>,
132    pub location: Location,
133}
134
135/// A `Keyboard` represents an inline keyboard shortcut in a document.
136#[derive(Clone, Debug, PartialEq, Serialize)]
137#[non_exhaustive]
138pub struct Keyboard {
139    pub keys: Vec<Key>,
140    pub location: Location,
141}
142
143impl Keyboard {
144    /// Creates a new `Keyboard` with the given keys.
145    #[must_use]
146    pub fn new(keys: Vec<Key>, location: Location) -> Self {
147        Self { keys, location }
148    }
149}
150
151// TODO(nlopes): this could perhaps be an enum instead with the allowed keys
152pub type Key = String;
153
154/// A `CrossReference` represents an inline cross-reference (xref) in a document.
155#[derive(Clone, Debug, PartialEq, Serialize)]
156#[non_exhaustive]
157pub struct CrossReference {
158    pub target: String,
159    // We don't serialize the text here because it's serialized as "inlines" in the ASG
160    #[serde(skip_serializing)]
161    pub text: Vec<InlineNode>,
162    pub location: Location,
163}
164
165impl CrossReference {
166    /// Creates a new `CrossReference` with the given target.
167    #[must_use]
168    pub fn new(target: impl Into<String>, location: Location) -> Self {
169        Self {
170            target: target.into(),
171            text: Vec::new(),
172            location,
173        }
174    }
175
176    /// Sets the cross-reference display text as inline nodes.
177    #[must_use]
178    pub fn with_text(mut self, text: Vec<InlineNode>) -> Self {
179        self.text = text;
180        self
181    }
182}
183
184/// An `Autolink` represents an inline autolink in a document.
185#[derive(Clone, Debug, PartialEq, Serialize)]
186#[non_exhaustive]
187pub struct Autolink {
188    pub url: Source,
189    /// Whether the autolink was written with angle brackets (e.g., `<user@example.com>`).
190    /// When true, the renderer should preserve the brackets in the output.
191    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
192    pub bracketed: bool,
193    pub location: Location,
194}
195
196/// A `Stem` represents an inline mathematical expression.
197#[derive(Clone, Debug, PartialEq, Serialize)]
198#[non_exhaustive]
199pub struct Stem {
200    pub content: String,
201    pub notation: StemNotation,
202    pub location: Location,
203}
204
205/// The kind of index term, encoding both visibility and structure.
206///
207/// This enum makes invalid states unrepresentable: flow terms can only have
208/// a single term (no hierarchy), while concealed terms support up to three
209/// hierarchical levels.
210#[derive(Clone, Debug, PartialEq, Serialize)]
211#[non_exhaustive]
212pub enum IndexTermKind {
213    /// Visible in output, single term only.
214    ///
215    /// Created by `((term))` or `indexterm2:[term]`.
216    Flow(String),
217    /// Hidden from output, supports hierarchical entries.
218    ///
219    /// Created by `(((term,secondary,tertiary)))` or `indexterm:[term,secondary,tertiary]`.
220    Concealed {
221        term: String,
222        #[serde(default, skip_serializing_if = "Option::is_none")]
223        secondary: Option<String>,
224        #[serde(default, skip_serializing_if = "Option::is_none")]
225        tertiary: Option<String>,
226    },
227}
228
229/// An `IndexTerm` represents an index term in a document.
230///
231/// Index terms can be either:
232/// - **Flow terms** (visible): `((term))` or `indexterm2:[term]` - the term appears in the text
233/// - **Concealed terms** (hidden): `(((term,secondary,tertiary)))` or `indexterm:[term,secondary,tertiary]`
234///   - only appears in the index
235///
236/// Concealed terms support hierarchical entries with primary, secondary, and tertiary levels.
237#[derive(Clone, Debug, PartialEq, Serialize)]
238#[non_exhaustive]
239pub struct IndexTerm {
240    /// The kind and content of this index term.
241    pub kind: IndexTermKind,
242    pub location: Location,
243}
244
245impl IndexTerm {
246    /// Returns the primary term.
247    #[must_use]
248    pub fn term(&self) -> &str {
249        match &self.kind {
250            IndexTermKind::Flow(term) | IndexTermKind::Concealed { term, .. } => term,
251        }
252    }
253
254    /// Returns the secondary term, if any.
255    #[must_use]
256    pub fn secondary(&self) -> Option<&str> {
257        match &self.kind {
258            IndexTermKind::Flow(_) => None,
259            IndexTermKind::Concealed { secondary, .. } => secondary.as_deref(),
260        }
261    }
262
263    /// Returns the tertiary term, if any.
264    #[must_use]
265    pub fn tertiary(&self) -> Option<&str> {
266        match &self.kind {
267            IndexTermKind::Flow(_) => None,
268            IndexTermKind::Concealed { tertiary, .. } => tertiary.as_deref(),
269        }
270    }
271
272    /// Returns whether this term is visible in the output.
273    #[must_use]
274    pub fn is_visible(&self) -> bool {
275        matches!(self.kind, IndexTermKind::Flow(_))
276    }
277}