1use super::*;
10
11#[derive(Debug, Clone, PartialEq)]
12#[non_exhaustive]
13pub enum RdFigureSecondArgument<'a> {
14 AltText {
15 nodes: &'a [RdNode],
16 text: String,
17 },
18 Options {
19 nodes: &'a [RdNode],
20 attributes: String,
21 },
22}
23
24impl<'a> RdFigureSecondArgument<'a> {
25 pub fn nodes(&self) -> &'a [RdNode] {
26 match self {
27 Self::AltText { nodes, .. } | Self::Options { nodes, .. } => nodes,
28 }
29 }
30 pub fn alt_text(&self) -> Option<&str> {
31 match self {
32 Self::AltText { text, .. } => Some(text),
33 Self::Options { .. } => None,
34 }
35 }
36 pub fn option_attributes(&self) -> Option<&str> {
37 match self {
38 Self::AltText { .. } => None,
39 Self::Options { attributes, .. } => Some(attributes),
40 }
41 }
42}
43
44#[derive(Debug, Clone, PartialEq)]
45pub struct RdFigure<'a> {
46 path: RdPath,
47 file_nodes: &'a [RdNode],
48 file: String,
49 second: Option<RdFigureSecondArgument<'a>>,
50}
51
52impl<'a> RdFigure<'a> {
53 pub fn path(&self) -> &RdPath {
54 &self.path
55 }
56 pub fn file_nodes(&self) -> &'a [RdNode] {
57 self.file_nodes
58 }
59 pub fn file(&self) -> &str {
60 &self.file
61 }
62 pub fn second(&self) -> Option<&RdFigureSecondArgument<'a>> {
63 self.second.as_ref()
64 }
65}
66
67impl RdNode {
68 pub fn figure(&self, base_path: &RdPath) -> Option<RdFigure<'_>> {
69 self.inspect_figure(base_path).ok().flatten()
70 }
71 pub fn inspect_figure(&self, base_path: &RdPath) -> Result<Option<RdFigure<'_>>, RdShapeError> {
72 let tagged = match self {
73 RdNode::Tagged(tagged) => tagged,
74 RdNode::Raw(raw) => {
75 let Some(tag) = raw.tag().map(RdTag::from_rd_tag) else {
76 return Ok(None);
77 };
78 if tag == RdTag::Figure {
79 return Err(shape(
80 base_path.clone(),
81 Some(tag),
82 RdShapeErrorKind::UnexpectedNode {
83 expected: RdExpectedNode::Tagged,
84 actual: RdNodeKind::Raw,
85 },
86 ));
87 }
88 return Ok(None);
89 }
90 _ => return Ok(None),
91 };
92 if tagged.tag() != &RdTag::Figure {
93 return Ok(None);
94 }
95 if tagged.option().is_some() {
96 return Err(shape(
97 base_path.clone(),
98 Some(RdTag::Figure),
99 RdShapeErrorKind::UnexpectedOption,
100 ));
101 }
102 let children = tagged.children();
103 if !(1..=2).contains(&children.len()) {
104 return Err(shape(
105 base_path.clone(),
106 Some(RdTag::Figure),
107 RdShapeErrorKind::WrongArity {
108 expected: RdArity::Range { min: 1, max: 2 },
109 actual: children.len(),
110 },
111 ));
112 }
113 let mut groups = Vec::with_capacity(children.len());
114 for (index, child) in children.iter().enumerate() {
115 let Some(group) = child.as_group() else {
116 return Err(shape(
117 base_path.with_child(index),
118 Some(RdTag::Figure),
119 RdShapeErrorKind::UnexpectedNode {
120 expected: RdExpectedNode::Group,
121 actual: RdNodeKind::of(child),
122 },
123 ));
124 };
125 groups.push(group.children());
126 }
127 let file_nodes = groups[0];
128 let file = concat_exact(
129 file_nodes,
130 RdNodeKind::Verb,
131 &base_path.with_child(0),
132 &RdTag::Figure,
133 )?;
134 let second = groups
135 .get(1)
136 .map(|nodes| {
137 let text = concat_exact(
138 nodes,
139 RdNodeKind::Verb,
140 &base_path.with_child(1),
141 &RdTag::Figure,
142 )?;
143 Ok(
144 if let Some(attributes) = text
145 .strip_prefix("options:")
146 .filter(|rest| rest.chars().next().is_some_and(char::is_whitespace))
147 {
148 RdFigureSecondArgument::Options {
149 nodes,
150 attributes: attributes.trim_start().to_owned(),
151 }
152 } else {
153 RdFigureSecondArgument::AltText { nodes, text }
154 },
155 )
156 })
157 .transpose()?;
158 Ok(Some(RdFigure {
159 path: base_path.clone(),
160 file_nodes,
161 file,
162 second,
163 }))
164 }
165}