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 Task,
136 Todo,
138 Done,
140 Attr(AttrKind),
142}
143
144#[cfg_attr(feature = "ast-json", derive(Serialize, Deserialize))]
149#[derive(PartialEq, PartialOrd, Debug, Eq, Clone)]
150pub enum AttrKind {
151 Value,
153 Values,
155 Children,
157
158 Lang,
160 Meta,
162 Fence,
164
165 Url,
167 Alt,
169 Title,
171
172 Ident,
174 Label,
176
177 Depth,
179 Level,
181
182 Index,
184 Ordered,
186 Checked,
188
189 Column,
191 Row,
193
194 Align,
196
197 Name,
199}
200
201impl Display for AttrKind {
202 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
203 match self {
204 AttrKind::Value => write!(f, ".value"),
205 AttrKind::Values => write!(f, ".values"),
206 AttrKind::Children => write!(f, ".children"),
207 AttrKind::Lang => write!(f, ".lang"),
208 AttrKind::Meta => write!(f, ".meta"),
209 AttrKind::Fence => write!(f, ".fence"),
210 AttrKind::Url => write!(f, ".url"),
211 AttrKind::Alt => write!(f, ".alt"),
212 AttrKind::Title => write!(f, ".title"),
213 AttrKind::Ident => write!(f, ".ident"),
214 AttrKind::Label => write!(f, ".label"),
215 AttrKind::Depth => write!(f, ".depth"),
216 AttrKind::Level => write!(f, ".level"),
217 AttrKind::Index => write!(f, ".index"),
218 AttrKind::Ordered => write!(f, ".ordered"),
219 AttrKind::Checked => write!(f, ".checked"),
220 AttrKind::Column => write!(f, ".column"),
221 AttrKind::Row => write!(f, ".row"),
222 AttrKind::Align => write!(f, ".align"),
223 AttrKind::Name => write!(f, ".name"),
224 }
225 }
226}
227
228impl TryFrom<&Token> for Selector {
229 type Error = UnknownSelector;
230
231 fn try_from(token: &Token) -> Result<Self, Self::Error> {
232 if let TokenKind::Selector(s) = &token.kind {
233 match s.as_str() {
234 ".h" | ".heading" => Ok(Selector::Heading(None)),
236 ".h1" => Ok(Selector::Heading(Some(1))),
237 ".h2" => Ok(Selector::Heading(Some(2))),
238 ".h3" => Ok(Selector::Heading(Some(3))),
239 ".h4" => Ok(Selector::Heading(Some(4))),
240 ".h5" => Ok(Selector::Heading(Some(5))),
241 ".h6" => Ok(Selector::Heading(Some(6))),
242
243 ".>" | ".blockquote" => Ok(Selector::Blockquote),
245
246 ".^" | ".footnote" => Ok(Selector::Footnote),
248
249 ".<" | ".mdx_jsx_flow_element" => Ok(Selector::MdxJsxFlowElement),
251
252 ".**" | ".emphasis" => Ok(Selector::Emphasis),
254
255 ".$$" | ".math" => Ok(Selector::Math),
257
258 ".horizontal_rule" | ".---" | ".***" | ".___" => Ok(Selector::HorizontalRule),
260
261 ".{}" | ".mdx_text_expression" => Ok(Selector::MdxTextExpression),
263
264 ".[^]" | ".footnote_ref" => Ok(Selector::FootnoteRef),
266
267 ".definition" => Ok(Selector::Definition),
269
270 ".break" => Ok(Selector::Break),
272
273 ".delete" => Ok(Selector::Delete),
275
276 ".<>" | ".html" => Ok(Selector::Html),
278
279 ".image" => Ok(Selector::Image),
281
282 ".image_ref" => Ok(Selector::ImageRef),
284
285 ".code_inline" => Ok(Selector::InlineCode),
287
288 ".math_inline" => Ok(Selector::InlineMath),
290
291 ".link" => Ok(Selector::Link),
293
294 ".link_ref" => Ok(Selector::LinkRef),
296
297 ".[]" | ".list" => Ok(Selector::List(None, None)),
299
300 ".task" => Ok(Selector::Task),
302
303 ".todo" => Ok(Selector::Todo),
305
306 ".done" => Ok(Selector::Done),
308
309 ".toml" => Ok(Selector::Toml),
311
312 ".strong" => Ok(Selector::Strong),
314
315 ".yaml" => Ok(Selector::Yaml),
317
318 ".code" => Ok(Selector::Code),
320
321 ".mdx_js_esm" => Ok(Selector::MdxJsEsm),
323
324 ".mdx_jsx_text_element" => Ok(Selector::MdxJsxTextElement),
326
327 ".mdx_flow_expression" => Ok(Selector::MdxFlowExpression),
329
330 ".text" => Ok(Selector::Text),
332
333 ".[][]" | ".table" => Ok(Selector::Table(None, None)),
335
336 ".table_align" => Ok(Selector::TableAlign),
338
339 ".." => Ok(Selector::Recursive),
341
342 ".value" => Ok(Selector::Attr(AttrKind::Value)),
344 ".values" => Ok(Selector::Attr(AttrKind::Values)),
345 ".children" | ".cn" => Ok(Selector::Attr(AttrKind::Children)),
346
347 ".lang" => Ok(Selector::Attr(AttrKind::Lang)),
349 ".meta" => Ok(Selector::Attr(AttrKind::Meta)),
350 ".fence" => Ok(Selector::Attr(AttrKind::Fence)),
351
352 ".url" => Ok(Selector::Attr(AttrKind::Url)),
354 ".alt" => Ok(Selector::Attr(AttrKind::Alt)),
355 ".title" => Ok(Selector::Attr(AttrKind::Title)),
356
357 ".ident" => Ok(Selector::Attr(AttrKind::Ident)),
359 ".label" => Ok(Selector::Attr(AttrKind::Label)),
360
361 ".depth" => Ok(Selector::Attr(AttrKind::Depth)),
363 ".level" => Ok(Selector::Attr(AttrKind::Level)),
364
365 ".index" => Ok(Selector::Attr(AttrKind::Index)),
367 ".ordered" => Ok(Selector::Attr(AttrKind::Ordered)),
368 ".checked" => Ok(Selector::Attr(AttrKind::Checked)),
369
370 ".column" => Ok(Selector::Attr(AttrKind::Column)),
372 ".row" => Ok(Selector::Attr(AttrKind::Row)),
373
374 ".align" => Ok(Selector::Attr(AttrKind::Align)),
376
377 ".name" => Ok(Selector::Attr(AttrKind::Name)),
379
380 _ => parse_bracket_selector(s).ok_or_else(|| UnknownSelector(token.clone())),
381 }
382 } else {
383 Err(UnknownSelector(token.clone()))
384 }
385 }
386}
387
388impl Display for Selector {
389 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
390 match self {
391 Selector::Heading(None) => write!(f, ".h"),
392 Selector::Heading(Some(1)) => write!(f, ".h1"),
393 Selector::Heading(Some(2)) => write!(f, ".h2"),
394 Selector::Heading(Some(3)) => write!(f, ".h3"),
395 Selector::Heading(Some(4)) => write!(f, ".h4"),
396 Selector::Heading(Some(5)) => write!(f, ".h5"),
397 Selector::Heading(Some(6)) => write!(f, ".h6"),
398 Selector::Heading(Some(n)) => write!(f, ".h{}", n),
399 Selector::Blockquote => write!(f, ".blockquote"),
400 Selector::Footnote => write!(f, ".footnote"),
401 Selector::List(None, None) => write!(f, ".list"),
402 Selector::List(Some(idx), None) => write!(f, ".[{}]", idx),
403 Selector::List(Some(idx), _) => write!(f, ".[{}]", idx),
404 Selector::List(None, _) => write!(f, ".[]"),
405 Selector::Toml => write!(f, ".toml"),
406 Selector::Yaml => write!(f, ".yaml"),
407 Selector::Break => write!(f, ".break"),
408 Selector::InlineCode => write!(f, ".code_inline"),
409 Selector::InlineMath => write!(f, ".math_inline"),
410 Selector::Delete => write!(f, ".delete"),
411 Selector::Emphasis => write!(f, ".emphasis"),
412 Selector::FootnoteRef => write!(f, ".footnote_ref"),
413 Selector::Html => write!(f, ".html"),
414 Selector::Image => write!(f, ".image"),
415 Selector::ImageRef => write!(f, ".image_ref"),
416 Selector::MdxJsxTextElement => write!(f, ".mdx_jsx_text_element"),
417 Selector::Link => write!(f, ".link"),
418 Selector::LinkRef => write!(f, ".link_ref"),
419 Selector::Strong => write!(f, ".strong"),
420 Selector::Code => write!(f, ".code"),
421 Selector::Math => write!(f, ".math"),
422 Selector::Table(None, None) => write!(f, ".table"),
423 Selector::Table(Some(row), None) => write!(f, ".[{}][]", row),
424 Selector::Table(Some(row), Some(col)) => write!(f, ".[{}][{}]", row, col),
425 Selector::Table(None, Some(col)) => write!(f, ".[][{}]", col),
426 Selector::TableAlign => write!(f, ".table_align"),
427 Selector::Text => write!(f, ".text"),
428 Selector::HorizontalRule => write!(f, ".horizontal_rule"),
429 Selector::Definition => write!(f, ".definition"),
430 Selector::MdxFlowExpression => write!(f, ".mdx_flow_expression"),
431 Selector::MdxTextExpression => write!(f, ".mdx_text_expression"),
432 Selector::MdxJsEsm => write!(f, ".mdx_js_esm"),
433 Selector::MdxJsxFlowElement => write!(f, ".mdx_jsx_flow_element"),
434 Selector::Recursive => write!(f, ".."),
435 Selector::Task => write!(f, ".task"),
436 Selector::Todo => write!(f, ".todo"),
437 Selector::Done => write!(f, ".done"),
438 Selector::Attr(attr) => write!(f, "{}", attr),
439 }
440 }
441}
442
443impl Selector {
444 pub fn is_attribute_selector(&self) -> bool {
446 matches!(self, Selector::Attr(_))
447 }
448}
449
450#[cfg(test)]
451mod tests {
452 use crate::{
453 ArenaId, Position, Range, Token, TokenKind,
454 selector::{AttrKind, Selector, UnknownSelector},
455 };
456 use rstest::rstest;
457 use smol_str::SmolStr;
458
459 #[rstest]
460 #[case::heading(".h", Selector::Heading(None), ".h")]
462 #[case::heading_h1(".h1", Selector::Heading(Some(1)), ".h1")]
463 #[case::heading_h2(".h2", Selector::Heading(Some(2)), ".h2")]
464 #[case::heading_h3(".h3", Selector::Heading(Some(3)), ".h3")]
465 #[case::heading_h4(".h4", Selector::Heading(Some(4)), ".h4")]
466 #[case::heading_h5(".h5", Selector::Heading(Some(5)), ".h5")]
467 #[case::heading_h6(".h6", Selector::Heading(Some(6)), ".h6")]
468 #[case::blockquote(".blockquote", Selector::Blockquote, ".blockquote")]
470 #[case::blockquote_alias(".>", Selector::Blockquote, ".blockquote")]
471 #[case::footnote(".footnote", Selector::Footnote, ".footnote")]
473 #[case::footnote_alias(".^", Selector::Footnote, ".footnote")]
474 #[case::mdx_jsx_flow_element(".mdx_jsx_flow_element", Selector::MdxJsxFlowElement, ".mdx_jsx_flow_element")]
476 #[case::mdx_jsx_flow_element_alias(".<", Selector::MdxJsxFlowElement, ".mdx_jsx_flow_element")]
477 #[case::emphasis(".emphasis", Selector::Emphasis, ".emphasis")]
479 #[case::emphasis_alias(".**", Selector::Emphasis, ".emphasis")]
480 #[case::math(".math", Selector::Math, ".math")]
482 #[case::math_alias(".$$", Selector::Math, ".math")]
483 #[case::horizontal_rule(".horizontal_rule", Selector::HorizontalRule, ".horizontal_rule")]
485 #[case::horizontal_rule_alias_dash(".---", Selector::HorizontalRule, ".horizontal_rule")]
486 #[case::horizontal_rule_alias_star(".***", Selector::HorizontalRule, ".horizontal_rule")]
487 #[case::horizontal_rule_alias_underscore(".___", Selector::HorizontalRule, ".horizontal_rule")]
488 #[case::mdx_text_expression(".mdx_text_expression", Selector::MdxTextExpression, ".mdx_text_expression")]
490 #[case::mdx_text_expression_alias(".{}", Selector::MdxTextExpression, ".mdx_text_expression")]
491 #[case::footnote_ref(".footnote_ref", Selector::FootnoteRef, ".footnote_ref")]
493 #[case::footnote_ref_alias(".[^]", Selector::FootnoteRef, ".footnote_ref")]
494 #[case::definition(".definition", Selector::Definition, ".definition")]
496 #[case::break_selector(".break", Selector::Break, ".break")]
498 #[case::delete(".delete", Selector::Delete, ".delete")]
500 #[case::html(".html", Selector::Html, ".html")]
502 #[case::html_alias(".<>", Selector::Html, ".html")]
503 #[case::image(".image", Selector::Image, ".image")]
505 #[case::image_ref(".image_ref", Selector::ImageRef, ".image_ref")]
507 #[case::code_inline(".code_inline", Selector::InlineCode, ".code_inline")]
509 #[case::math_inline(".math_inline", Selector::InlineMath, ".math_inline")]
511 #[case::link(".link", Selector::Link, ".link")]
513 #[case::link_ref(".link_ref", Selector::LinkRef, ".link_ref")]
515 #[case::list(".list", Selector::List(None, None), ".list")]
517 #[case::list_bracket(".[]", Selector::List(None, None), ".list")]
518 #[case::list_with_index(".[1]", Selector::List(Some(1), None), ".[1]")]
519 #[case::task(".task", Selector::Task, ".task")]
521 #[case::task(".todo", Selector::Todo, ".todo")]
522 #[case::task(".done", Selector::Done, ".done")]
523 #[case::toml(".toml", Selector::Toml, ".toml")]
525 #[case::strong(".strong", Selector::Strong, ".strong")]
527 #[case::yaml(".yaml", Selector::Yaml, ".yaml")]
529 #[case::code(".code", Selector::Code, ".code")]
531 #[case::mdx_js_esm(".mdx_js_esm", Selector::MdxJsEsm, ".mdx_js_esm")]
533 #[case::mdx_jsx_text_element(".mdx_jsx_text_element", Selector::MdxJsxTextElement, ".mdx_jsx_text_element")]
535 #[case::mdx_flow_expression(".mdx_flow_expression", Selector::MdxFlowExpression, ".mdx_flow_expression")]
537 #[case::text(".text", Selector::Text, ".text")]
539 #[case::table(".table", Selector::Table(None, None), ".table")]
541 #[case::table_bracket(".[][]", Selector::Table(None, None), ".table")]
542 #[case::table_row_any(".[1][]", Selector::Table(Some(1), None), ".[1][]")]
543 #[case::table_row_col(".[1][2]", Selector::Table(Some(1), Some(2)), ".[1][2]")]
544 #[case::table_any_col(".[][2]", Selector::Table(None, Some(2)), ".[][2]")]
545 #[case::table_align(".table_align", Selector::TableAlign, ".table_align")]
547 #[case::recursive("..", Selector::Recursive, "..")]
549 #[case::attr_value(".value", Selector::Attr(AttrKind::Value), ".value")]
551 #[case::attr_values(".values", Selector::Attr(AttrKind::Values), ".values")]
552 #[case::attr_children(".children", Selector::Attr(AttrKind::Children), ".children")]
553 #[case::attr_lang(".lang", Selector::Attr(AttrKind::Lang), ".lang")]
555 #[case::attr_meta(".meta", Selector::Attr(AttrKind::Meta), ".meta")]
556 #[case::attr_fence(".fence", Selector::Attr(AttrKind::Fence), ".fence")]
557 #[case::attr_url(".url", Selector::Attr(AttrKind::Url), ".url")]
559 #[case::attr_alt(".alt", Selector::Attr(AttrKind::Alt), ".alt")]
560 #[case::attr_title(".title", Selector::Attr(AttrKind::Title), ".title")]
561 #[case::attr_ident(".ident", Selector::Attr(AttrKind::Ident), ".ident")]
563 #[case::attr_label(".label", Selector::Attr(AttrKind::Label), ".label")]
564 #[case::attr_depth(".depth", Selector::Attr(AttrKind::Depth), ".depth")]
566 #[case::attr_level(".level", Selector::Attr(AttrKind::Level), ".level")]
567 #[case::attr_index(".index", Selector::Attr(AttrKind::Index), ".index")]
569 #[case::attr_ordered(".ordered", Selector::Attr(AttrKind::Ordered), ".ordered")]
570 #[case::attr_checked(".checked", Selector::Attr(AttrKind::Checked), ".checked")]
571 #[case::attr_column(".column", Selector::Attr(AttrKind::Column), ".column")]
573 #[case::attr_row(".row", Selector::Attr(AttrKind::Row), ".row")]
574 #[case::attr_align(".align", Selector::Attr(AttrKind::Align), ".align")]
576 #[case::attr_name(".name", Selector::Attr(AttrKind::Name), ".name")]
578 fn test_selector_try_from_and_display(
579 #[case] input: &str,
580 #[case] expected_selector: Selector,
581 #[case] expected_display: &str,
582 ) {
583 let selector = Selector::try_from(&Token {
585 kind: TokenKind::Selector(SmolStr::new(input)),
586 range: Range {
587 start: Position::new(0, 0),
588 end: Position::new(0, 0),
589 },
590 module_id: ArenaId::new(0),
591 })
592 .expect("Should parse valid selector");
593 assert_eq!(selector, expected_selector);
594
595 assert_eq!(selector.to_string(), expected_display);
597 }
598
599 #[test]
600 fn test_selector_try_from_unknown() {
601 let token = Token {
602 kind: TokenKind::Selector(SmolStr::new(".unknown")),
603 range: Range {
604 start: Position::new(0, 0),
605 end: Position::new(0, 0),
606 },
607 module_id: ArenaId::new(0),
608 };
609 let result = Selector::try_from(&token);
610 assert!(result.is_err());
611 if let Err(e) = result {
612 assert_eq!(e, UnknownSelector(token));
613 }
614 }
615}