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<'a> {
11    #[serde(default, skip_serializing_if = "Option::is_none")]
12    pub text: Option<&'a str>,
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<'a> {
36    #[serde(default, skip_serializing_if = "Option::is_none")]
37    pub id: Option<&'a str>,
38    #[serde(default, skip_serializing_if = "Vec::is_empty")]
39    pub content: Vec<InlineNode<'a>>,
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<'a> {
49    pub target: Source<'a>,
50    pub attributes: ElementAttributes<'a>,
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<'a> {
58    #[serde(skip_serializing)]
59    pub text: Vec<InlineNode<'a>>,
60    pub target: Source<'a>,
61    pub attributes: ElementAttributes<'a>,
62    pub location: Location,
63}
64
65impl<'a> Link<'a> {
66    /// Creates a new `Link` with the given target.
67    #[must_use]
68    pub fn new(target: Source<'a>, location: Location) -> Self {
69        Self {
70            text: Vec::new(),
71            target,
72            attributes: ElementAttributes::default(),
73            location,
74        }
75    }
76
77    /// Sets the link text as inline nodes.
78    #[must_use]
79    pub fn with_text(mut self, text: Vec<InlineNode<'a>>) -> Self {
80        self.text = text;
81        self
82    }
83
84    /// Sets the link attributes.
85    #[must_use]
86    pub fn with_attributes(mut self, attributes: ElementAttributes<'a>) -> Self {
87        self.attributes = attributes;
88        self
89    }
90}
91
92/// An `Url` represents an inline URL in a document.
93#[derive(Clone, Debug, PartialEq, Serialize)]
94#[non_exhaustive]
95pub struct Url<'a> {
96    #[serde(skip_serializing)]
97    pub text: Vec<InlineNode<'a>>,
98    pub target: Source<'a>,
99    pub attributes: ElementAttributes<'a>,
100    pub location: Location,
101}
102
103/// An `Mailto` represents an inline `mailto:` in a document.
104#[derive(Clone, Debug, PartialEq, Serialize)]
105#[non_exhaustive]
106pub struct Mailto<'a> {
107    #[serde(skip_serializing)]
108    pub text: Vec<InlineNode<'a>>,
109    pub target: Source<'a>,
110    pub attributes: ElementAttributes<'a>,
111    pub location: Location,
112}
113
114/// A `Button` represents an inline button in a document.
115#[derive(Clone, Debug, PartialEq, Serialize)]
116#[non_exhaustive]
117pub struct Button<'a> {
118    pub label: &'a str,
119    pub location: Location,
120}
121
122/// A `Menu` represents an inline menu in a document.
123#[derive(Clone, Debug, PartialEq, Serialize)]
124#[non_exhaustive]
125pub struct Menu<'a> {
126    pub target: &'a str,
127    #[serde(default, skip_serializing_if = "Vec::is_empty")]
128    pub items: Vec<&'a str>,
129    pub location: Location,
130}
131
132/// A `Keyboard` represents an inline keyboard shortcut in a document.
133#[derive(Clone, Debug, PartialEq, Serialize)]
134#[non_exhaustive]
135pub struct Keyboard<'a> {
136    pub keys: Vec<Key<'a>>,
137    pub location: Location,
138}
139
140impl<'a> Keyboard<'a> {
141    /// Creates a new `Keyboard` with the given keys.
142    #[must_use]
143    pub fn new(keys: Vec<Key<'a>>, location: Location) -> Self {
144        Self { keys, location }
145    }
146}
147
148// TODO(nlopes): this could perhaps be an enum instead with the allowed keys
149pub type Key<'a> = &'a str;
150
151/// A `CrossReference` represents an inline cross-reference (xref) in a document.
152#[derive(Clone, Debug, PartialEq, Serialize)]
153#[non_exhaustive]
154pub struct CrossReference<'a> {
155    pub target: &'a str,
156    #[serde(skip_serializing)]
157    pub text: Vec<InlineNode<'a>>,
158    pub location: Location,
159}
160
161impl<'a> CrossReference<'a> {
162    /// Creates a new `CrossReference` with the given target.
163    #[must_use]
164    pub fn new(target: &'a str, location: Location) -> Self {
165        Self {
166            target,
167            text: Vec::new(),
168            location,
169        }
170    }
171
172    /// Sets the cross-reference display text as inline nodes.
173    #[must_use]
174    pub fn with_text(mut self, text: Vec<InlineNode<'a>>) -> Self {
175        self.text = text;
176        self
177    }
178}
179
180/// An `Autolink` represents an inline autolink in a document.
181#[derive(Clone, Debug, PartialEq, Serialize)]
182#[non_exhaustive]
183pub struct Autolink<'a> {
184    pub url: Source<'a>,
185    /// Whether the autolink was written with angle brackets (e.g., `<user@example.com>`).
186    /// When true, the renderer should preserve the brackets in the output.
187    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
188    pub bracketed: bool,
189    pub location: Location,
190}
191
192/// A `Stem` represents an inline mathematical expression.
193#[derive(Clone, Debug, PartialEq, Serialize)]
194#[non_exhaustive]
195pub struct Stem<'a> {
196    pub content: &'a str,
197    pub notation: StemNotation,
198    pub location: Location,
199}
200
201/// The kind of index term, encoding both visibility and structure.
202#[derive(Clone, Debug, PartialEq, Serialize)]
203#[non_exhaustive]
204pub enum IndexTermKind<'a> {
205    /// Visible in output, single term only.
206    Flow(&'a str),
207    /// Hidden from output, supports hierarchical entries.
208    Concealed {
209        term: &'a str,
210        #[serde(default, skip_serializing_if = "Option::is_none")]
211        secondary: Option<&'a str>,
212        #[serde(default, skip_serializing_if = "Option::is_none")]
213        tertiary: Option<&'a str>,
214    },
215}
216
217/// An `IndexTerm` represents an index term in a document.
218#[derive(Clone, Debug, PartialEq, Serialize)]
219#[non_exhaustive]
220pub struct IndexTerm<'a> {
221    /// The kind and content of this index term.
222    pub kind: IndexTermKind<'a>,
223    pub location: Location,
224}
225
226impl IndexTerm<'_> {
227    /// Returns the primary term.
228    #[must_use]
229    pub fn term(&self) -> &str {
230        match &self.kind {
231            IndexTermKind::Flow(term) | IndexTermKind::Concealed { term, .. } => term,
232        }
233    }
234
235    /// Returns the secondary term, if any.
236    #[must_use]
237    pub fn secondary(&self) -> Option<&str> {
238        match &self.kind {
239            IndexTermKind::Flow(_) => None,
240            IndexTermKind::Concealed { secondary, .. } => *secondary,
241        }
242    }
243
244    /// Returns the tertiary term, if any.
245    #[must_use]
246    pub fn tertiary(&self) -> Option<&str> {
247        match &self.kind {
248            IndexTermKind::Flow(_) => None,
249            IndexTermKind::Concealed { tertiary, .. } => *tertiary,
250        }
251    }
252
253    /// Returns whether this term is visible in the output.
254    #[must_use]
255    pub fn is_visible(&self) -> bool {
256        matches!(self.kind, IndexTermKind::Flow(_))
257    }
258}