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 {
175 match flavor {
176 Flavor::Pandoc => ConsumerSet::of(YamlConsumer::Libyaml),
177 Flavor::Quarto => ConsumerSet::of(YamlConsumer::Libyaml).with(YamlConsumer::Jsyaml),
178 Flavor::RMarkdown => ConsumerSet::of(YamlConsumer::Libyaml).with(YamlConsumer::RYaml),
179 Flavor::Gfm
180 | Flavor::CommonMark
181 | Flavor::MultiMarkdown
182 | Flavor::Mdsvex
183 | Flavor::Myst => ConsumerSet::empty(),
184 }
185}
186
187fn hashpipe_consumers(flavor: Flavor) -> ConsumerSet {
191 match flavor {
192 Flavor::Quarto => ConsumerSet::of(YamlConsumer::Jsyaml),
193 Flavor::RMarkdown => ConsumerSet::of(YamlConsumer::RYaml),
194 Flavor::Pandoc
195 | Flavor::Gfm
196 | Flavor::CommonMark
197 | Flavor::MultiMarkdown
198 | Flavor::Mdsvex
199 | Flavor::Myst => ConsumerSet::empty(),
200 }
201}
202
203#[cfg(test)]
204mod tests {
205 use super::*;
206
207 #[test]
208 fn substrate_runs_no_consumer_checks() {
209 let ctx = YamlValidationContext::substrate();
210 assert!(ctx.is_substrate());
211 assert!(ctx.consumers().is_empty());
212 }
213
214 #[test]
215 fn pandoc_frontmatter_is_libyaml_only() {
216 let ctx = YamlValidationContext::frontmatter(Flavor::Pandoc);
217 assert!(!ctx.is_substrate());
218 assert!(ctx.consumers().contains(YamlConsumer::Libyaml));
219 assert!(!ctx.consumers().contains(YamlConsumer::Jsyaml));
220 }
221
222 #[test]
223 fn quarto_frontmatter_is_both() {
224 let ctx = YamlValidationContext::frontmatter(Flavor::Quarto);
225 assert!(ctx.consumers().contains(YamlConsumer::Libyaml));
226 assert!(ctx.consumers().contains(YamlConsumer::Jsyaml));
227 }
228
229 #[test]
230 fn quarto_hashpipe_is_jsyaml_only() {
231 let ctx = YamlValidationContext::hashpipe(Flavor::Quarto);
232 assert!(ctx.consumers().contains(YamlConsumer::Jsyaml));
233 assert!(!ctx.consumers().contains(YamlConsumer::Libyaml));
234 }
235
236 #[test]
237 fn rmarkdown_uses_pandoc_and_r_yaml() {
238 let fm = YamlValidationContext::frontmatter(Flavor::RMarkdown);
239 assert!(fm.consumers().contains(YamlConsumer::Libyaml)); assert!(fm.consumers().contains(YamlConsumer::RYaml)); assert!(!fm.consumers().contains(YamlConsumer::Jsyaml));
242
243 let hp = YamlValidationContext::hashpipe(Flavor::RMarkdown);
244 assert!(hp.consumers().contains(YamlConsumer::RYaml)); assert!(!hp.consumers().contains(YamlConsumer::Jsyaml));
246 assert!(!hp.consumers().contains(YamlConsumer::Libyaml));
247 }
248
249 #[test]
250 fn commonmark_frontmatter_is_lenient() {
251 let ctx = YamlValidationContext::frontmatter(Flavor::CommonMark);
252 assert!(ctx.consumers().is_empty());
253 assert!(!ctx.is_substrate());
254 }
255
256 #[test]
257 fn any_rejects_matches_intersection() {
258 let all = ConsumerSet::all();
260 assert!(YamlValidationContext::frontmatter(Flavor::Pandoc).any_rejects(all));
261 assert!(YamlValidationContext::frontmatter(Flavor::RMarkdown).any_rejects(all));
262
263 let dup = ConsumerSet::of(YamlConsumer::Jsyaml).with(YamlConsumer::RYaml);
266 assert!(!YamlValidationContext::frontmatter(Flavor::Pandoc).any_rejects(dup));
267 assert!(YamlValidationContext::frontmatter(Flavor::Quarto).any_rejects(dup));
268 assert!(YamlValidationContext::frontmatter(Flavor::RMarkdown).any_rejects(dup));
269 assert!(YamlValidationContext::hashpipe(Flavor::RMarkdown).any_rejects(dup));
270 }
271}