use std::collections::BTreeMap;
use super::{FormatRegistry, IdTemplate, Shape};
use crate::{Severity, Spanned};
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Scope {
Document,
Host(String),
}
impl Scope {
pub fn parse(s: Option<&str>) -> Scope {
match s {
None | Some("document") => Scope::Document,
Some(host) => Scope::Host(host.to_string()),
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Only(pub BTreeMap<String, Vec<String>>);
impl Only {
pub fn admits(&self, states: &States) -> bool {
self.0.iter().all(|(dim, admitted)| {
states
.0
.get(dim)
.is_some_and(|have| have.iter().any(|s| admitted.contains(s)))
})
}
pub fn overlaps(&self, other: &Only) -> bool {
self.0.iter().all(|(dim, admitted)| {
other
.0
.get(dim)
.is_none_or(|theirs| theirs.iter().any(|s| admitted.contains(s)))
})
}
pub fn is_gated(&self) -> bool {
!self.0.is_empty()
}
pub fn render(&self) -> String {
self.0
.iter()
.map(|(k, v)| format!("{k}={}", v.join(",")))
.collect::<Vec<_>>()
.join(" ")
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct States(pub BTreeMap<String, Vec<String>>);
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct RuleCommon {
pub severity: Severity,
pub only: Only,
pub line: usize,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Text {
Literal(String),
From(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct KeyDerivation {
pub source: DerivationSource,
pub separator: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DerivationSource {
Rule(String),
From(String),
}
impl KeyDerivation {
pub fn source_label(&self) -> String {
match &self.source {
DerivationSource::Rule(r) => format!("rule `{r}`"),
DerivationSource::From(k) => format!("the frontmatter `{k}`"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FmFormat {
Url,
Slug,
Host(String),
}
impl FmFormat {
pub fn name(&self) -> &str {
match self {
FmFormat::Url => "url",
FmFormat::Slug => "slug",
FmFormat::Host(name) => name,
}
}
}
#[derive(Debug, Clone)]
pub struct FrontmatterConstraint {
pub common: RuleCommon,
pub const_: Option<String>,
pub shape: Option<Shape>,
pub enum_: Option<Vec<String>>,
pub format: Option<FmFormat>,
pub id_template: Option<IdTemplate>,
pub list: bool,
pub min: Option<usize>,
pub max: Option<usize>,
pub optional: bool,
pub example: Option<String>,
pub guidance: Option<String>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct LifecycleDimension {
pub key: String,
pub states: Vec<String>,
pub guidance: Option<String>,
pub line: usize,
pub extensions: BTreeMap<String, Spanned>,
}
#[derive(Debug, Clone)]
pub struct Example {
pub document: String,
pub line: usize,
}
#[derive(Debug, Clone)]
pub struct CoreSchema {
pub line_limit: i64,
pub lifecycle: Vec<LifecycleDimension>,
pub frontmatter: Vec<(String, FrontmatterConstraint)>,
pub frontmatter_open: bool,
pub prohibited_patterns: Vec<super::Prohibition>,
pub formats: FormatRegistry,
pub guidance: Option<String>,
pub examples: Vec<Example>,
pub line: usize,
pub key_lines: BTreeMap<String, usize>,
}
impl CoreSchema {
pub fn demand_nothing() -> CoreSchema {
CoreSchema {
line_limit: 0,
lifecycle: vec![],
frontmatter: vec![],
frontmatter_open: true,
prohibited_patterns: vec![],
formats: FormatRegistry::default(),
guidance: None,
examples: vec![],
line: 0,
key_lines: BTreeMap::new(),
}
}
pub fn states(&self, frontmatter: Option<&Spanned>) -> States {
let mut states = States::default();
for dim in &self.lifecycle {
if let Some(value) = frontmatter
.and_then(|f| f.get(&dim.key))
.and_then(|v| v.scalar_text())
{
states.0.insert(dim.key.clone(), vec![value]);
}
}
states
}
pub fn type_const(&self) -> Option<&str> {
self.constraint("type")?.const_.as_deref()
}
pub fn constraint(&self, key: &str) -> Option<&FrontmatterConstraint> {
self.frontmatter
.iter()
.find(|(k, _)| k == key)
.map(|(_, c)| c)
}
pub fn id_template(&self) -> Option<&IdTemplate> {
self.constraint("id")?.id_template.as_ref()
}
pub fn dimension(&self, key: &str) -> Option<&LifecycleDimension> {
self.lifecycle.iter().find(|d| d.key == key)
}
}