use crate::options::Flavor;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum YamlConsumer {
Libyaml,
Jsyaml,
RYaml,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct ConsumerSet(u8);
impl ConsumerSet {
const fn bit(consumer: YamlConsumer) -> u8 {
match consumer {
YamlConsumer::Libyaml => 0b001,
YamlConsumer::Jsyaml => 0b010,
YamlConsumer::RYaml => 0b100,
}
}
pub const fn all() -> Self {
ConsumerSet(0b111)
}
pub const fn empty() -> Self {
ConsumerSet(0)
}
pub const fn of(consumer: YamlConsumer) -> Self {
ConsumerSet(Self::bit(consumer))
}
pub const fn with(self, consumer: YamlConsumer) -> Self {
ConsumerSet(self.0 | Self::bit(consumer))
}
pub const fn contains(self, consumer: YamlConsumer) -> bool {
self.0 & Self::bit(consumer) != 0
}
pub const fn intersects(self, other: ConsumerSet) -> bool {
self.0 & other.0 != 0
}
pub const fn is_empty(self) -> bool {
self.0 == 0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum YamlLocation {
Frontmatter,
Hashpipe,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct YamlValidationContext {
consumers: ConsumerSet,
substrate: bool,
}
impl YamlValidationContext {
pub const fn substrate() -> Self {
Self {
consumers: ConsumerSet::empty(),
substrate: true,
}
}
pub fn new(flavor: Flavor, location: YamlLocation) -> Self {
let consumers = match location {
YamlLocation::Frontmatter => frontmatter_consumers(flavor),
YamlLocation::Hashpipe => hashpipe_consumers(flavor),
};
Self {
consumers,
substrate: false,
}
}
pub fn frontmatter(flavor: Flavor) -> Self {
Self::new(flavor, YamlLocation::Frontmatter)
}
pub fn hashpipe(flavor: Flavor) -> Self {
Self::new(flavor, YamlLocation::Hashpipe)
}
pub const fn is_substrate(&self) -> bool {
self.substrate
}
pub const fn consumers(&self) -> ConsumerSet {
self.consumers
}
pub const fn any_rejects(&self, rejecting: ConsumerSet) -> bool {
self.consumers.intersects(rejecting)
}
}
fn frontmatter_consumers(flavor: Flavor) -> ConsumerSet {
match flavor {
Flavor::Pandoc => ConsumerSet::of(YamlConsumer::Libyaml),
Flavor::Quarto => ConsumerSet::of(YamlConsumer::Libyaml).with(YamlConsumer::Jsyaml),
Flavor::RMarkdown => ConsumerSet::of(YamlConsumer::Libyaml).with(YamlConsumer::RYaml),
Flavor::Gfm | Flavor::CommonMark | Flavor::MultiMarkdown => ConsumerSet::empty(),
}
}
fn hashpipe_consumers(flavor: Flavor) -> ConsumerSet {
match flavor {
Flavor::Quarto => ConsumerSet::of(YamlConsumer::Jsyaml),
Flavor::RMarkdown => ConsumerSet::of(YamlConsumer::RYaml),
Flavor::Pandoc | Flavor::Gfm | Flavor::CommonMark | Flavor::MultiMarkdown => {
ConsumerSet::empty()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn substrate_runs_no_consumer_checks() {
let ctx = YamlValidationContext::substrate();
assert!(ctx.is_substrate());
assert!(ctx.consumers().is_empty());
}
#[test]
fn pandoc_frontmatter_is_libyaml_only() {
let ctx = YamlValidationContext::frontmatter(Flavor::Pandoc);
assert!(!ctx.is_substrate());
assert!(ctx.consumers().contains(YamlConsumer::Libyaml));
assert!(!ctx.consumers().contains(YamlConsumer::Jsyaml));
}
#[test]
fn quarto_frontmatter_is_both() {
let ctx = YamlValidationContext::frontmatter(Flavor::Quarto);
assert!(ctx.consumers().contains(YamlConsumer::Libyaml));
assert!(ctx.consumers().contains(YamlConsumer::Jsyaml));
}
#[test]
fn quarto_hashpipe_is_jsyaml_only() {
let ctx = YamlValidationContext::hashpipe(Flavor::Quarto);
assert!(ctx.consumers().contains(YamlConsumer::Jsyaml));
assert!(!ctx.consumers().contains(YamlConsumer::Libyaml));
}
#[test]
fn rmarkdown_uses_pandoc_and_r_yaml() {
let fm = YamlValidationContext::frontmatter(Flavor::RMarkdown);
assert!(fm.consumers().contains(YamlConsumer::Libyaml)); assert!(fm.consumers().contains(YamlConsumer::RYaml)); assert!(!fm.consumers().contains(YamlConsumer::Jsyaml));
let hp = YamlValidationContext::hashpipe(Flavor::RMarkdown);
assert!(hp.consumers().contains(YamlConsumer::RYaml)); assert!(!hp.consumers().contains(YamlConsumer::Jsyaml));
assert!(!hp.consumers().contains(YamlConsumer::Libyaml));
}
#[test]
fn commonmark_frontmatter_is_lenient() {
let ctx = YamlValidationContext::frontmatter(Flavor::CommonMark);
assert!(ctx.consumers().is_empty());
assert!(!ctx.is_substrate());
}
#[test]
fn any_rejects_matches_intersection() {
let all = ConsumerSet::all();
assert!(YamlValidationContext::frontmatter(Flavor::Pandoc).any_rejects(all));
assert!(YamlValidationContext::frontmatter(Flavor::RMarkdown).any_rejects(all));
let dup = ConsumerSet::of(YamlConsumer::Jsyaml).with(YamlConsumer::RYaml);
assert!(!YamlValidationContext::frontmatter(Flavor::Pandoc).any_rejects(dup));
assert!(YamlValidationContext::frontmatter(Flavor::Quarto).any_rejects(dup));
assert!(YamlValidationContext::frontmatter(Flavor::RMarkdown).any_rejects(dup));
assert!(YamlValidationContext::hashpipe(Flavor::RMarkdown).any_rejects(dup));
}
}