panache_parser/parser/yaml/
profile.rs1use crate::options::Flavor;
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub enum YamlConsumer {
31 Libyaml,
35 Jsyaml,
38 RYaml,
43}
44
45#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
47pub struct ConsumerSet(u8);
48
49impl ConsumerSet {
50 const fn bit(consumer: YamlConsumer) -> u8 {
51 match consumer {
52 YamlConsumer::Libyaml => 0b001,
53 YamlConsumer::Jsyaml => 0b010,
54 YamlConsumer::RYaml => 0b100,
55 }
56 }
57
58 pub const fn all() -> Self {
60 ConsumerSet(0b111)
61 }
62
63 pub const fn empty() -> Self {
65 ConsumerSet(0)
66 }
67
68 pub const fn of(consumer: YamlConsumer) -> Self {
70 ConsumerSet(Self::bit(consumer))
71 }
72
73 pub const fn with(self, consumer: YamlConsumer) -> Self {
75 ConsumerSet(self.0 | Self::bit(consumer))
76 }
77
78 pub const fn contains(self, consumer: YamlConsumer) -> bool {
80 self.0 & Self::bit(consumer) != 0
81 }
82
83 pub const fn intersects(self, other: ConsumerSet) -> bool {
85 self.0 & other.0 != 0
86 }
87
88 pub const fn is_empty(self) -> bool {
90 self.0 == 0
91 }
92}
93
94#[derive(Debug, Clone, Copy, PartialEq, Eq)]
98pub enum YamlLocation {
99 Frontmatter,
100 Hashpipe,
101}
102
103#[derive(Debug, Clone, Copy, PartialEq, Eq)]
107pub struct YamlValidationContext {
108 consumers: ConsumerSet,
109 substrate: bool,
110}
111
112impl YamlValidationContext {
113 pub const fn substrate() -> Self {
117 Self {
118 consumers: ConsumerSet::empty(),
119 substrate: true,
120 }
121 }
122
123 pub fn new(flavor: Flavor, location: YamlLocation) -> Self {
125 let consumers = match location {
126 YamlLocation::Frontmatter => frontmatter_consumers(flavor),
127 YamlLocation::Hashpipe => hashpipe_consumers(flavor),
128 };
129 Self {
130 consumers,
131 substrate: false,
132 }
133 }
134
135 pub fn frontmatter(flavor: Flavor) -> Self {
137 Self::new(flavor, YamlLocation::Frontmatter)
138 }
139
140 pub fn hashpipe(flavor: Flavor) -> Self {
142 Self::new(flavor, YamlLocation::Hashpipe)
143 }
144
145 pub const fn is_substrate(&self) -> bool {
148 self.substrate
149 }
150
151 pub const fn consumers(&self) -> ConsumerSet {
153 self.consumers
154 }
155
156 pub const fn any_rejects(&self, rejecting: ConsumerSet) -> bool {
159 self.consumers.intersects(rejecting)
160 }
161}
162
163fn frontmatter_consumers(flavor: Flavor) -> ConsumerSet {
173 match flavor {
174 Flavor::Pandoc => ConsumerSet::of(YamlConsumer::Libyaml),
175 Flavor::Quarto => ConsumerSet::of(YamlConsumer::Libyaml).with(YamlConsumer::Jsyaml),
176 Flavor::RMarkdown => ConsumerSet::of(YamlConsumer::Libyaml).with(YamlConsumer::RYaml),
177 Flavor::Gfm | Flavor::CommonMark | Flavor::MultiMarkdown => ConsumerSet::empty(),
178 }
179}
180
181fn hashpipe_consumers(flavor: Flavor) -> ConsumerSet {
185 match flavor {
186 Flavor::Quarto => ConsumerSet::of(YamlConsumer::Jsyaml),
187 Flavor::RMarkdown => ConsumerSet::of(YamlConsumer::RYaml),
188 Flavor::Pandoc | Flavor::Gfm | Flavor::CommonMark | Flavor::MultiMarkdown => {
189 ConsumerSet::empty()
190 }
191 }
192}
193
194#[cfg(test)]
195mod tests {
196 use super::*;
197
198 #[test]
199 fn substrate_runs_no_consumer_checks() {
200 let ctx = YamlValidationContext::substrate();
201 assert!(ctx.is_substrate());
202 assert!(ctx.consumers().is_empty());
203 }
204
205 #[test]
206 fn pandoc_frontmatter_is_libyaml_only() {
207 let ctx = YamlValidationContext::frontmatter(Flavor::Pandoc);
208 assert!(!ctx.is_substrate());
209 assert!(ctx.consumers().contains(YamlConsumer::Libyaml));
210 assert!(!ctx.consumers().contains(YamlConsumer::Jsyaml));
211 }
212
213 #[test]
214 fn quarto_frontmatter_is_both() {
215 let ctx = YamlValidationContext::frontmatter(Flavor::Quarto);
216 assert!(ctx.consumers().contains(YamlConsumer::Libyaml));
217 assert!(ctx.consumers().contains(YamlConsumer::Jsyaml));
218 }
219
220 #[test]
221 fn quarto_hashpipe_is_jsyaml_only() {
222 let ctx = YamlValidationContext::hashpipe(Flavor::Quarto);
223 assert!(ctx.consumers().contains(YamlConsumer::Jsyaml));
224 assert!(!ctx.consumers().contains(YamlConsumer::Libyaml));
225 }
226
227 #[test]
228 fn rmarkdown_uses_pandoc_and_r_yaml() {
229 let fm = YamlValidationContext::frontmatter(Flavor::RMarkdown);
230 assert!(fm.consumers().contains(YamlConsumer::Libyaml)); assert!(fm.consumers().contains(YamlConsumer::RYaml)); assert!(!fm.consumers().contains(YamlConsumer::Jsyaml));
233
234 let hp = YamlValidationContext::hashpipe(Flavor::RMarkdown);
235 assert!(hp.consumers().contains(YamlConsumer::RYaml)); assert!(!hp.consumers().contains(YamlConsumer::Jsyaml));
237 assert!(!hp.consumers().contains(YamlConsumer::Libyaml));
238 }
239
240 #[test]
241 fn commonmark_frontmatter_is_lenient() {
242 let ctx = YamlValidationContext::frontmatter(Flavor::CommonMark);
243 assert!(ctx.consumers().is_empty());
244 assert!(!ctx.is_substrate());
245 }
246
247 #[test]
248 fn any_rejects_matches_intersection() {
249 let all = ConsumerSet::all();
251 assert!(YamlValidationContext::frontmatter(Flavor::Pandoc).any_rejects(all));
252 assert!(YamlValidationContext::frontmatter(Flavor::RMarkdown).any_rejects(all));
253
254 let dup = ConsumerSet::of(YamlConsumer::Jsyaml).with(YamlConsumer::RYaml);
257 assert!(!YamlValidationContext::frontmatter(Flavor::Pandoc).any_rejects(dup));
258 assert!(YamlValidationContext::frontmatter(Flavor::Quarto).any_rejects(dup));
259 assert!(YamlValidationContext::frontmatter(Flavor::RMarkdown).any_rejects(dup));
260 assert!(YamlValidationContext::hashpipe(Flavor::RMarkdown).any_rejects(dup));
261 }
262}