acdc_parser/model/inlines/
macros.rs

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