1use super::*;
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25#[non_exhaustive]
26pub enum RdInlineSpanKind {
27 Emph,
28 Strong,
29 Bold,
30 Code,
31 Special,
32 Verb,
33 Url,
34 Email,
35 File,
36 Pkg,
37 Samp,
38 SQuote,
39 DQuote,
40 Kbd,
41 Var,
42 Env,
43 Command,
44 Option,
45 Acronym,
46 Abbr,
47 Cite,
48 Dfn,
49}
50
51#[derive(Debug, Clone, PartialEq)]
53pub struct RdInlineSpan<'a> {
54 path: RdPath,
55 kind: RdInlineSpanKind,
56 body: &'a [RdNode],
57}
58
59impl<'a> RdInlineSpan<'a> {
60 pub fn path(&self) -> &RdPath {
61 &self.path
62 }
63 pub fn kind(&self) -> RdInlineSpanKind {
64 self.kind
65 }
66 pub fn body(&self) -> &'a [RdNode] {
68 self.body
69 }
70}
71
72fn inline_span_kind(tag: &RdTag) -> Option<RdInlineSpanKind> {
73 Some(match tag {
74 RdTag::Emph => RdInlineSpanKind::Emph,
75 RdTag::Strong => RdInlineSpanKind::Strong,
76 RdTag::Bold => RdInlineSpanKind::Bold,
77 RdTag::Code => RdInlineSpanKind::Code,
78 RdTag::Special => RdInlineSpanKind::Special,
79 RdTag::Verb => RdInlineSpanKind::Verb,
80 RdTag::Url => RdInlineSpanKind::Url,
81 RdTag::Email => RdInlineSpanKind::Email,
82 RdTag::File => RdInlineSpanKind::File,
83 RdTag::Pkg => RdInlineSpanKind::Pkg,
84 RdTag::Samp => RdInlineSpanKind::Samp,
85 RdTag::SQuote => RdInlineSpanKind::SQuote,
86 RdTag::DQuote => RdInlineSpanKind::DQuote,
87 RdTag::Kbd => RdInlineSpanKind::Kbd,
88 RdTag::Var => RdInlineSpanKind::Var,
89 RdTag::Env => RdInlineSpanKind::Env,
90 RdTag::Command => RdInlineSpanKind::Command,
91 RdTag::Option => RdInlineSpanKind::Option,
92 RdTag::Acronym => RdInlineSpanKind::Acronym,
93 RdTag::Abbr => RdInlineSpanKind::Abbr,
94 RdTag::Cite => RdInlineSpanKind::Cite,
95 RdTag::Dfn => RdInlineSpanKind::Dfn,
96 _ => return None,
97 })
98}
99
100impl RdNode {
101 pub fn inline_span(&self, base_path: &RdPath) -> Option<RdInlineSpan<'_>> {
102 let tagged = self.as_tagged()?;
103 let kind = inline_span_kind(tagged.tag())?;
104 tagged.option().is_none().then(|| RdInlineSpan {
105 path: base_path.clone(),
106 kind,
107 body: tagged.children(),
108 })
109 }
110
111 pub fn inspect_inline_span(
112 &self,
113 base_path: &RdPath,
114 ) -> Result<Option<RdInlineSpan<'_>>, RdShapeError> {
115 match self {
116 RdNode::Tagged(tagged) => {
117 let Some(kind) = inline_span_kind(tagged.tag()) else {
118 return Ok(None);
119 };
120 if tagged.option().is_some() {
121 return Err(shape(
122 base_path.clone(),
123 Some(tagged.tag().clone()),
124 RdShapeErrorKind::UnexpectedOption,
125 ));
126 }
127 Ok(Some(RdInlineSpan {
128 path: base_path.clone(),
129 kind,
130 body: tagged.children(),
131 }))
132 }
133 RdNode::Raw(raw) => {
134 let Some(tag) = raw.tag().map(RdTag::from_rd_tag) else {
135 return Ok(None);
136 };
137 if inline_span_kind(&tag).is_none() {
138 return Ok(None);
139 }
140 Err(shape(
141 base_path.clone(),
142 Some(tag),
143 RdShapeErrorKind::UnexpectedNode {
144 expected: RdExpectedNode::Tagged,
145 actual: RdNodeKind::Raw,
146 },
147 ))
148 }
149 _ => Ok(None),
150 }
151 }
152}
153
154#[derive(Debug, Clone, Copy, PartialEq, Eq)]
156#[non_exhaustive]
157pub enum RdTextSymbolKind {
158 R,
159 Dots,
160 LDots,
161}
162
163impl RdTextSymbolKind {
164 pub const fn fallback_text(self) -> &'static str {
167 match self {
168 Self::R => "R",
169 Self::Dots | Self::LDots => "...",
170 }
171 }
172}
173
174#[derive(Debug, Clone, PartialEq)]
176pub struct RdTextSymbol {
177 path: RdPath,
178 kind: RdTextSymbolKind,
179}
180
181impl RdTextSymbol {
182 pub fn path(&self) -> &RdPath {
183 &self.path
184 }
185 pub fn kind(&self) -> RdTextSymbolKind {
186 self.kind
187 }
188 pub const fn fallback_text(&self) -> &'static str {
189 self.kind.fallback_text()
190 }
191}
192
193fn text_symbol_kind(tag: &RdTag) -> Option<RdTextSymbolKind> {
194 match tag {
195 RdTag::R => Some(RdTextSymbolKind::R),
196 RdTag::Dots => Some(RdTextSymbolKind::Dots),
197 RdTag::LDots => Some(RdTextSymbolKind::LDots),
198 _ => None,
199 }
200}
201
202impl RdNode {
203 pub fn text_symbol(&self, base_path: &RdPath) -> Option<RdTextSymbol> {
204 let tagged = self.as_tagged()?;
205 let kind = text_symbol_kind(tagged.tag())?;
206 (tagged.option().is_none() && tagged.children().is_empty()).then(|| RdTextSymbol {
207 path: base_path.clone(),
208 kind,
209 })
210 }
211
212 pub fn inspect_text_symbol(
213 &self,
214 base_path: &RdPath,
215 ) -> Result<Option<RdTextSymbol>, RdShapeError> {
216 match self {
217 RdNode::Tagged(tagged) => {
218 let Some(kind) = text_symbol_kind(tagged.tag()) else {
219 return Ok(None);
220 };
221 if tagged.option().is_some() {
222 return Err(shape(
223 base_path.clone(),
224 Some(tagged.tag().clone()),
225 RdShapeErrorKind::UnexpectedOption,
226 ));
227 }
228 if !tagged.children().is_empty() {
229 return Err(shape(
230 base_path.clone(),
231 Some(tagged.tag().clone()),
232 RdShapeErrorKind::WrongArity {
233 expected: RdArity::Exactly(0),
234 actual: tagged.children().len(),
235 },
236 ));
237 }
238 Ok(Some(RdTextSymbol {
239 path: base_path.clone(),
240 kind,
241 }))
242 }
243 RdNode::Raw(raw) => {
244 let Some(tag) = raw.tag().map(RdTag::from_rd_tag) else {
245 return Ok(None);
246 };
247 if text_symbol_kind(&tag).is_none() {
248 return Ok(None);
249 }
250 Err(shape(
251 base_path.clone(),
252 Some(tag),
253 RdShapeErrorKind::UnexpectedNode {
254 expected: RdExpectedNode::Tagged,
255 actual: RdNodeKind::Raw,
256 },
257 ))
258 }
259 _ => Ok(None),
260 }
261 }
262}