1use std::fmt::{self, Display, Formatter};
2
3#[cfg(feature = "ast-json")]
4use serde::{Deserialize, Serialize};
5use thiserror::Error;
6
7use crate::{Token, TokenKind};
8
9#[derive(Error, Clone, Debug, PartialOrd, Eq, Ord, PartialEq)]
11#[error("Unknown selector `{0}`")]
12pub struct UnknownSelector(pub Token);
13
14fn parse_bracket_selector(s: &str) -> Option<Selector> {
18 let inner = s.strip_prefix(".[")?;
19 let (first, rest) = inner.split_once(']')?;
20
21 if !first.is_empty() && !first.bytes().all(|b| b.is_ascii_digit()) {
22 return None;
23 }
24 let first_idx: Option<usize> = if first.is_empty() {
25 None
26 } else {
27 Some(first.parse().ok()?)
28 };
29
30 if rest.is_empty() {
31 return Some(Selector::List(first_idx, None));
33 }
34
35 let inner2 = rest.strip_prefix('[')?;
36 let (second, tail) = inner2.split_once(']')?;
37 if !tail.is_empty() {
38 return None;
39 }
40 if !second.is_empty() && !second.bytes().all(|b| b.is_ascii_digit()) {
41 return None;
42 }
43 let second_idx: Option<usize> = if second.is_empty() {
44 None
45 } else {
46 Some(second.parse().ok()?)
47 };
48 Some(Selector::Table(first_idx, second_idx))
50}
51
52impl UnknownSelector {
53 pub fn new(token: Token) -> Self {
55 Self(token)
56 }
57}
58
59#[cfg_attr(feature = "ast-json", derive(Serialize, Deserialize))]
64#[derive(PartialEq, PartialOrd, Debug, Eq, Clone)]
65pub enum Selector {
66 Blockquote,
68 Footnote,
70 List(Option<usize>, Option<bool>),
74 Toml,
76 Yaml,
78 Break,
80 InlineCode,
82 InlineMath,
84 Delete,
86 Emphasis,
88 FootnoteRef,
90 Html,
92 Image,
94 ImageRef,
96 MdxJsxTextElement,
98 Link,
100 LinkRef,
102 Strong,
104 Code,
106 Math,
108 Heading(Option<u8>),
112 Table(Option<usize>, Option<usize>),
116 TableAlign,
118 Text,
120 HorizontalRule,
122 Definition,
124 MdxFlowExpression,
126 MdxTextExpression,
128 MdxJsEsm,
130 MdxJsxFlowElement,
132 Recursive,
134 Attr(AttrKind),
136}
137
138#[cfg_attr(feature = "ast-json", derive(Serialize, Deserialize))]
143#[derive(PartialEq, PartialOrd, Debug, Eq, Clone)]
144pub enum AttrKind {
145 Value,
147 Values,
149 Children,
151
152 Lang,
154 Meta,
156 Fence,
158
159 Url,
161 Alt,
163 Title,
165
166 Ident,
168 Label,
170
171 Depth,
173 Level,
175
176 Index,
178 Ordered,
180 Checked,
182
183 Column,
185 Row,
187
188 Align,
190
191 Name,
193}
194
195impl Display for AttrKind {
196 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
197 match self {
198 AttrKind::Value => write!(f, ".value"),
199 AttrKind::Values => write!(f, ".values"),
200 AttrKind::Children => write!(f, ".children"),
201 AttrKind::Lang => write!(f, ".lang"),
202 AttrKind::Meta => write!(f, ".meta"),
203 AttrKind::Fence => write!(f, ".fence"),
204 AttrKind::Url => write!(f, ".url"),
205 AttrKind::Alt => write!(f, ".alt"),
206 AttrKind::Title => write!(f, ".title"),
207 AttrKind::Ident => write!(f, ".ident"),
208 AttrKind::Label => write!(f, ".label"),
209 AttrKind::Depth => write!(f, ".depth"),
210 AttrKind::Level => write!(f, ".level"),
211 AttrKind::Index => write!(f, ".index"),
212 AttrKind::Ordered => write!(f, ".ordered"),
213 AttrKind::Checked => write!(f, ".checked"),
214 AttrKind::Column => write!(f, ".column"),
215 AttrKind::Row => write!(f, ".row"),
216 AttrKind::Align => write!(f, ".align"),
217 AttrKind::Name => write!(f, ".name"),
218 }
219 }
220}
221
222impl TryFrom<&Token> for Selector {
223 type Error = UnknownSelector;
224
225 fn try_from(token: &Token) -> Result<Self, Self::Error> {
226 if let TokenKind::Selector(s) = &token.kind {
227 match s.as_str() {
228 ".h" | ".heading" => Ok(Selector::Heading(None)),
230 ".h1" => Ok(Selector::Heading(Some(1))),
231 ".h2" => Ok(Selector::Heading(Some(2))),
232 ".h3" => Ok(Selector::Heading(Some(3))),
233 ".h4" => Ok(Selector::Heading(Some(4))),
234 ".h5" => Ok(Selector::Heading(Some(5))),
235 ".h6" => Ok(Selector::Heading(Some(6))),
236
237 ".>" | ".blockquote" => Ok(Selector::Blockquote),
239
240 ".^" | ".footnote" => Ok(Selector::Footnote),
242
243 ".<" | ".mdx_jsx_flow_element" => Ok(Selector::MdxJsxFlowElement),
245
246 ".**" | ".emphasis" => Ok(Selector::Emphasis),
248
249 ".$$" | ".math" => Ok(Selector::Math),
251
252 ".horizontal_rule" | ".---" | ".***" | ".___" => Ok(Selector::HorizontalRule),
254
255 ".{}" | ".mdx_text_expression" => Ok(Selector::MdxTextExpression),
257
258 ".[^]" | ".footnote_ref" => Ok(Selector::FootnoteRef),
260
261 ".definition" => Ok(Selector::Definition),
263
264 ".break" => Ok(Selector::Break),
266
267 ".delete" => Ok(Selector::Delete),
269
270 ".<>" | ".html" => Ok(Selector::Html),
272
273 ".image" => Ok(Selector::Image),
275
276 ".image_ref" => Ok(Selector::ImageRef),
278
279 ".code_inline" => Ok(Selector::InlineCode),
281
282 ".math_inline" => Ok(Selector::InlineMath),
284
285 ".link" => Ok(Selector::Link),
287
288 ".link_ref" => Ok(Selector::LinkRef),
290
291 ".[]" | ".list" => Ok(Selector::List(None, None)),
293
294 ".toml" => Ok(Selector::Toml),
296
297 ".strong" => Ok(Selector::Strong),
299
300 ".yaml" => Ok(Selector::Yaml),
302
303 ".code" => Ok(Selector::Code),
305
306 ".mdx_js_esm" => Ok(Selector::MdxJsEsm),
308
309 ".mdx_jsx_text_element" => Ok(Selector::MdxJsxTextElement),
311
312 ".mdx_flow_expression" => Ok(Selector::MdxFlowExpression),
314
315 ".text" => Ok(Selector::Text),
317
318 ".[][]" | ".table" => Ok(Selector::Table(None, None)),
320
321 ".table_align" => Ok(Selector::TableAlign),
323
324 ".." => Ok(Selector::Recursive),
326
327 ".value" => Ok(Selector::Attr(AttrKind::Value)),
329 ".values" => Ok(Selector::Attr(AttrKind::Values)),
330 ".children" | ".cn" => Ok(Selector::Attr(AttrKind::Children)),
331
332 ".lang" => Ok(Selector::Attr(AttrKind::Lang)),
334 ".meta" => Ok(Selector::Attr(AttrKind::Meta)),
335 ".fence" => Ok(Selector::Attr(AttrKind::Fence)),
336
337 ".url" => Ok(Selector::Attr(AttrKind::Url)),
339 ".alt" => Ok(Selector::Attr(AttrKind::Alt)),
340 ".title" => Ok(Selector::Attr(AttrKind::Title)),
341
342 ".ident" => Ok(Selector::Attr(AttrKind::Ident)),
344 ".label" => Ok(Selector::Attr(AttrKind::Label)),
345
346 ".depth" => Ok(Selector::Attr(AttrKind::Depth)),
348 ".level" => Ok(Selector::Attr(AttrKind::Level)),
349
350 ".index" => Ok(Selector::Attr(AttrKind::Index)),
352 ".ordered" => Ok(Selector::Attr(AttrKind::Ordered)),
353 ".checked" => Ok(Selector::Attr(AttrKind::Checked)),
354
355 ".column" => Ok(Selector::Attr(AttrKind::Column)),
357 ".row" => Ok(Selector::Attr(AttrKind::Row)),
358
359 ".align" => Ok(Selector::Attr(AttrKind::Align)),
361
362 ".name" => Ok(Selector::Attr(AttrKind::Name)),
364
365 _ => parse_bracket_selector(s).ok_or_else(|| UnknownSelector(token.clone())),
366 }
367 } else {
368 Err(UnknownSelector(token.clone()))
369 }
370 }
371}
372
373impl Display for Selector {
374 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
375 match self {
376 Selector::Heading(None) => write!(f, ".h"),
377 Selector::Heading(Some(1)) => write!(f, ".h1"),
378 Selector::Heading(Some(2)) => write!(f, ".h2"),
379 Selector::Heading(Some(3)) => write!(f, ".h3"),
380 Selector::Heading(Some(4)) => write!(f, ".h4"),
381 Selector::Heading(Some(5)) => write!(f, ".h5"),
382 Selector::Heading(Some(6)) => write!(f, ".h6"),
383 Selector::Heading(Some(n)) => write!(f, ".h{}", n),
384 Selector::Blockquote => write!(f, ".blockquote"),
385 Selector::Footnote => write!(f, ".footnote"),
386 Selector::List(None, None) => write!(f, ".list"),
387 Selector::List(Some(idx), None) => write!(f, ".[{}]", idx),
388 Selector::List(Some(idx), _) => write!(f, ".[{}]", idx),
389 Selector::List(None, _) => write!(f, ".[]"),
390 Selector::Toml => write!(f, ".toml"),
391 Selector::Yaml => write!(f, ".yaml"),
392 Selector::Break => write!(f, ".break"),
393 Selector::InlineCode => write!(f, ".code_inline"),
394 Selector::InlineMath => write!(f, ".math_inline"),
395 Selector::Delete => write!(f, ".delete"),
396 Selector::Emphasis => write!(f, ".emphasis"),
397 Selector::FootnoteRef => write!(f, ".footnote_ref"),
398 Selector::Html => write!(f, ".html"),
399 Selector::Image => write!(f, ".image"),
400 Selector::ImageRef => write!(f, ".image_ref"),
401 Selector::MdxJsxTextElement => write!(f, ".mdx_jsx_text_element"),
402 Selector::Link => write!(f, ".link"),
403 Selector::LinkRef => write!(f, ".link_ref"),
404 Selector::Strong => write!(f, ".strong"),
405 Selector::Code => write!(f, ".code"),
406 Selector::Math => write!(f, ".math"),
407 Selector::Table(None, None) => write!(f, ".table"),
408 Selector::Table(Some(row), None) => write!(f, ".[{}][]", row),
409 Selector::Table(Some(row), Some(col)) => write!(f, ".[{}][{}]", row, col),
410 Selector::Table(None, Some(col)) => write!(f, ".[][{}]", col),
411 Selector::TableAlign => write!(f, ".table_align"),
412 Selector::Text => write!(f, ".text"),
413 Selector::HorizontalRule => write!(f, ".horizontal_rule"),
414 Selector::Definition => write!(f, ".definition"),
415 Selector::MdxFlowExpression => write!(f, ".mdx_flow_expression"),
416 Selector::MdxTextExpression => write!(f, ".mdx_text_expression"),
417 Selector::MdxJsEsm => write!(f, ".mdx_js_esm"),
418 Selector::MdxJsxFlowElement => write!(f, ".mdx_jsx_flow_element"),
419 Selector::Recursive => write!(f, ".."),
420 Selector::Attr(attr) => write!(f, "{}", attr),
421 }
422 }
423}
424
425impl Selector {
426 pub fn is_attribute_selector(&self) -> bool {
428 matches!(self, Selector::Attr(_))
429 }
430}
431
432#[cfg(test)]
433mod tests {
434 use crate::{
435 ArenaId, Position, Range, Token, TokenKind,
436 selector::{AttrKind, Selector, UnknownSelector},
437 };
438 use rstest::rstest;
439 use smol_str::SmolStr;
440
441 #[rstest]
442 #[case::heading(".h", Selector::Heading(None), ".h")]
444 #[case::heading_h1(".h1", Selector::Heading(Some(1)), ".h1")]
445 #[case::heading_h2(".h2", Selector::Heading(Some(2)), ".h2")]
446 #[case::heading_h3(".h3", Selector::Heading(Some(3)), ".h3")]
447 #[case::heading_h4(".h4", Selector::Heading(Some(4)), ".h4")]
448 #[case::heading_h5(".h5", Selector::Heading(Some(5)), ".h5")]
449 #[case::heading_h6(".h6", Selector::Heading(Some(6)), ".h6")]
450 #[case::blockquote(".blockquote", Selector::Blockquote, ".blockquote")]
452 #[case::blockquote_alias(".>", Selector::Blockquote, ".blockquote")]
453 #[case::footnote(".footnote", Selector::Footnote, ".footnote")]
455 #[case::footnote_alias(".^", Selector::Footnote, ".footnote")]
456 #[case::mdx_jsx_flow_element(".mdx_jsx_flow_element", Selector::MdxJsxFlowElement, ".mdx_jsx_flow_element")]
458 #[case::mdx_jsx_flow_element_alias(".<", Selector::MdxJsxFlowElement, ".mdx_jsx_flow_element")]
459 #[case::emphasis(".emphasis", Selector::Emphasis, ".emphasis")]
461 #[case::emphasis_alias(".**", Selector::Emphasis, ".emphasis")]
462 #[case::math(".math", Selector::Math, ".math")]
464 #[case::math_alias(".$$", Selector::Math, ".math")]
465 #[case::horizontal_rule(".horizontal_rule", Selector::HorizontalRule, ".horizontal_rule")]
467 #[case::horizontal_rule_alias_dash(".---", Selector::HorizontalRule, ".horizontal_rule")]
468 #[case::horizontal_rule_alias_star(".***", Selector::HorizontalRule, ".horizontal_rule")]
469 #[case::horizontal_rule_alias_underscore(".___", Selector::HorizontalRule, ".horizontal_rule")]
470 #[case::mdx_text_expression(".mdx_text_expression", Selector::MdxTextExpression, ".mdx_text_expression")]
472 #[case::mdx_text_expression_alias(".{}", Selector::MdxTextExpression, ".mdx_text_expression")]
473 #[case::footnote_ref(".footnote_ref", Selector::FootnoteRef, ".footnote_ref")]
475 #[case::footnote_ref_alias(".[^]", Selector::FootnoteRef, ".footnote_ref")]
476 #[case::definition(".definition", Selector::Definition, ".definition")]
478 #[case::break_selector(".break", Selector::Break, ".break")]
480 #[case::delete(".delete", Selector::Delete, ".delete")]
482 #[case::html(".html", Selector::Html, ".html")]
484 #[case::html_alias(".<>", Selector::Html, ".html")]
485 #[case::image(".image", Selector::Image, ".image")]
487 #[case::image_ref(".image_ref", Selector::ImageRef, ".image_ref")]
489 #[case::code_inline(".code_inline", Selector::InlineCode, ".code_inline")]
491 #[case::math_inline(".math_inline", Selector::InlineMath, ".math_inline")]
493 #[case::link(".link", Selector::Link, ".link")]
495 #[case::link_ref(".link_ref", Selector::LinkRef, ".link_ref")]
497 #[case::list(".list", Selector::List(None, None), ".list")]
499 #[case::list_bracket(".[]", Selector::List(None, None), ".list")]
500 #[case::list_with_index(".[1]", Selector::List(Some(1), None), ".[1]")]
501 #[case::toml(".toml", Selector::Toml, ".toml")]
503 #[case::strong(".strong", Selector::Strong, ".strong")]
505 #[case::yaml(".yaml", Selector::Yaml, ".yaml")]
507 #[case::code(".code", Selector::Code, ".code")]
509 #[case::mdx_js_esm(".mdx_js_esm", Selector::MdxJsEsm, ".mdx_js_esm")]
511 #[case::mdx_jsx_text_element(".mdx_jsx_text_element", Selector::MdxJsxTextElement, ".mdx_jsx_text_element")]
513 #[case::mdx_flow_expression(".mdx_flow_expression", Selector::MdxFlowExpression, ".mdx_flow_expression")]
515 #[case::text(".text", Selector::Text, ".text")]
517 #[case::table(".table", Selector::Table(None, None), ".table")]
519 #[case::table_bracket(".[][]", Selector::Table(None, None), ".table")]
520 #[case::table_row_any(".[1][]", Selector::Table(Some(1), None), ".[1][]")]
521 #[case::table_row_col(".[1][2]", Selector::Table(Some(1), Some(2)), ".[1][2]")]
522 #[case::table_any_col(".[][2]", Selector::Table(None, Some(2)), ".[][2]")]
523 #[case::table_align(".table_align", Selector::TableAlign, ".table_align")]
525 #[case::recursive("..", Selector::Recursive, "..")]
527 #[case::attr_value(".value", Selector::Attr(AttrKind::Value), ".value")]
529 #[case::attr_values(".values", Selector::Attr(AttrKind::Values), ".values")]
530 #[case::attr_children(".children", Selector::Attr(AttrKind::Children), ".children")]
531 #[case::attr_lang(".lang", Selector::Attr(AttrKind::Lang), ".lang")]
533 #[case::attr_meta(".meta", Selector::Attr(AttrKind::Meta), ".meta")]
534 #[case::attr_fence(".fence", Selector::Attr(AttrKind::Fence), ".fence")]
535 #[case::attr_url(".url", Selector::Attr(AttrKind::Url), ".url")]
537 #[case::attr_alt(".alt", Selector::Attr(AttrKind::Alt), ".alt")]
538 #[case::attr_title(".title", Selector::Attr(AttrKind::Title), ".title")]
539 #[case::attr_ident(".ident", Selector::Attr(AttrKind::Ident), ".ident")]
541 #[case::attr_label(".label", Selector::Attr(AttrKind::Label), ".label")]
542 #[case::attr_depth(".depth", Selector::Attr(AttrKind::Depth), ".depth")]
544 #[case::attr_level(".level", Selector::Attr(AttrKind::Level), ".level")]
545 #[case::attr_index(".index", Selector::Attr(AttrKind::Index), ".index")]
547 #[case::attr_ordered(".ordered", Selector::Attr(AttrKind::Ordered), ".ordered")]
548 #[case::attr_checked(".checked", Selector::Attr(AttrKind::Checked), ".checked")]
549 #[case::attr_column(".column", Selector::Attr(AttrKind::Column), ".column")]
551 #[case::attr_row(".row", Selector::Attr(AttrKind::Row), ".row")]
552 #[case::attr_align(".align", Selector::Attr(AttrKind::Align), ".align")]
554 #[case::attr_name(".name", Selector::Attr(AttrKind::Name), ".name")]
556 fn test_selector_try_from_and_display(
557 #[case] input: &str,
558 #[case] expected_selector: Selector,
559 #[case] expected_display: &str,
560 ) {
561 let selector = Selector::try_from(&Token {
563 kind: TokenKind::Selector(SmolStr::new(input)),
564 range: Range {
565 start: Position::new(0, 0),
566 end: Position::new(0, 0),
567 },
568 module_id: ArenaId::new(0),
569 })
570 .expect("Should parse valid selector");
571 assert_eq!(selector, expected_selector);
572
573 assert_eq!(selector.to_string(), expected_display);
575 }
576
577 #[test]
578 fn test_selector_try_from_unknown() {
579 let token = Token {
580 kind: TokenKind::Selector(SmolStr::new(".unknown")),
581 range: Range {
582 start: Position::new(0, 0),
583 end: Position::new(0, 0),
584 },
585 module_id: ArenaId::new(0),
586 };
587 let result = Selector::try_from(&token);
588 assert!(result.is_err());
589 if let Err(e) = result {
590 assert_eq!(e, UnknownSelector(token));
591 }
592 }
593}