use std::cell::Cell;
use std::sync::{Arc, OnceLock};
use indexmap::IndexMap;
use crate::ast::Node;
use crate::renderable::{RenderableTreeNode, RenderableTreeNodes, Scalar, Tag};
use crate::transform::resolve::resolve;
use crate::validate::schema_types::{Class, Id};
use crate::validate::{Config, Schema, SchemaAttribute, ValidationType};
pub const MAX_TRANSFORM_DEPTH: usize = 512;
thread_local! {
static DEPTH: Cell<usize> = const { Cell::new(0) };
}
struct Depth;
impl Depth {
fn enter() -> Option<Depth> {
DEPTH.with(|depth| {
let current = depth.get();
if current >= MAX_TRANSFORM_DEPTH {
return None;
}
depth.set(current + 1);
Some(Depth)
})
}
}
impl Drop for Depth {
fn drop(&mut self) {
DEPTH.with(|depth| depth.set(depth.get().saturating_sub(1)));
}
}
#[must_use]
pub fn global_attributes() -> &'static [(&'static str, SchemaAttribute); 2] {
static GLOBALS: OnceLock<[(&'static str, SchemaAttribute); 2]> = OnceLock::new();
GLOBALS.get_or_init(|| {
let custom = |kind: ValidationType| SchemaAttribute {
attribute_type: Some(kind),
..SchemaAttribute::default()
};
[
("class", custom(ValidationType::Custom(Arc::new(Class)))),
("id", custom(ValidationType::Custom(Arc::new(Id)))),
]
})
}
#[must_use]
pub fn node<'a>(node: &'a Node<'a>, config: &Config<'a>) -> RenderableTreeNodes {
let Some(_depth) = Depth::enter() else {
return RenderableTreeNodes::Many(Vec::new());
};
let schema = config.find_schema(node);
if let Some(transform) = schema.and_then(|schema| schema.transform.as_ref()) {
return transform(node, config);
}
let children = children(node, config);
let Some(render) = schema.and_then(|schema| schema.render.as_deref()) else {
return RenderableTreeNodes::Many(children);
};
RenderableTreeNodes::One(RenderableTreeNode::tag(Tag::with(
render,
attributes(node, config),
children,
)))
}
#[must_use]
pub fn children<'a>(parent: &'a Node<'a>, config: &Config<'a>) -> Vec<RenderableTreeNode> {
let mut out = Vec::with_capacity(parent.children.len());
for child in &parent.children {
out.extend(node(child, config).into_vec());
}
out
}
#[must_use]
pub fn attributes<'a>(
node: &'a Node<'a>,
config: &Config<'a>,
) -> IndexMap<String, RenderableTreeNodes> {
let schema = config.find_schema(node);
let mut output: IndexMap<String, RenderableTreeNodes> = IndexMap::new();
for (key, global) in global_attributes() {
let declared = schema
.and_then(|schema| schema.attributes.get(*key))
.unwrap_or(global);
render_attribute(node, config, key, declared, &mut output);
}
if let Some(schema) = schema {
for (key, declared) in &schema.attributes {
if global_attributes().iter().any(|(global, _)| global == key) {
continue;
}
render_attribute(node, config, key, declared, &mut output);
}
}
let Some(schema) = schema else {
return output;
};
for (key, slot) in &schema.slots {
let Some(name) = slot.render.output_name(key) else {
continue;
};
if let Some(filled) = node.slots.get(key.as_str()) {
output.insert(name.to_string(), self::node(filled, config));
}
}
output
}
fn render_attribute<'a>(
node: &'a Node<'a>,
config: &Config<'a>,
key: &str,
declared: &SchemaAttribute,
output: &mut IndexMap<String, RenderableTreeNodes>,
) {
let Some(name) = declared.render.output_name(key) else {
return;
};
let resolved = node.get(key).and_then(|value| resolve(value, config));
let value = match &declared.attribute_type {
Some(ValidationType::Custom(custom)) => custom.transform(resolved.as_ref(), config),
_ => resolved.as_ref().and_then(Scalar::from_value),
};
let value = value.or_else(|| declared.default.as_ref().and_then(Scalar::from_value));
if let Some(value) = value {
output.insert(
name.to_string(),
RenderableTreeNodes::One(RenderableTreeNode::Scalar(value)),
);
}
}
#[must_use]
pub fn find_schema<'c>(node: &Node<'_>, config: &'c Config<'_>) -> Option<&'c Schema> {
config.find_schema(node)
}
#[must_use]
pub(crate) fn scalar(value: Option<Scalar>) -> RenderableTreeNodes {
match value {
Some(value) => RenderableTreeNodes::One(RenderableTreeNode::Scalar(value)),
None => RenderableTreeNodes::Many(Vec::new()),
}
}