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