use super::attrs::Kind;
use super::contracts::{Contract, Module, Prop as Compiled, Render, Slot};
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct Prop {
name: String,
kind: Kind,
required: bool,
values: Vec<String>,
doc: String,
}
impl Prop {
fn new(name: &str, kind: Kind) -> Self {
Self {
name: name.to_owned(),
kind,
required: false,
values: Vec::new(),
doc: String::new(),
}
}
pub fn text(name: &str) -> Self {
Self::new(name, Kind::Text)
}
pub fn bool(name: &str) -> Self {
Self::new(name, Kind::Bool)
}
pub fn number(name: &str) -> Self {
Self::new(name, Kind::Number)
}
pub fn data(name: &str) -> Self {
Self::new(name, Kind::Data)
}
pub fn choice(name: &str, values: &[&str]) -> Self {
let mut prop = Self::new(name, Kind::Enum);
prop.values = values.iter().map(|v| (*v).to_owned()).collect();
prop
}
pub fn required(mut self) -> Self {
self.required = true;
self
}
pub fn doc(mut self, doc: &str) -> Self {
self.doc = doc.to_owned();
self
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct Component {
tag: String,
template: String,
macro_name: String,
doc: String,
props: Vec<Prop>,
content: bool,
attrs: bool,
}
impl Component {
pub fn macro_call(tag: &str, template: &str, macro_name: &str) -> Self {
Self {
tag: tag.to_owned(),
template: template.to_owned(),
macro_name: macro_name.to_owned(),
doc: String::new(),
props: Vec::new(),
content: true,
attrs: false,
}
}
pub fn doc(mut self, doc: &str) -> Self {
self.doc = doc.to_owned();
self
}
pub fn prop(mut self, prop: Prop) -> Self {
self.props.push(prop);
self
}
pub fn content(mut self, takes: bool) -> Self {
self.content = takes;
self
}
pub fn attrs(mut self, pass: bool) -> Self {
self.attrs = pass;
self
}
pub fn tag(&self) -> &str {
&self.tag
}
pub(crate) fn problem(&self) -> Option<String> {
if !self.tag.starts_with("rx-") || self.tag.len() <= 3 {
return Some(format!("the tag \"{}\" must start with \"rx-\"", self.tag));
}
if super::contracts::BUILTIN.iter().any(|c| c.tag == self.tag) {
return Some(format!("<{}> is a built-in component", self.tag));
}
None
}
pub(crate) fn contract(&self) -> Contract {
fn leak(s: &str) -> &'static str {
Box::leak(s.to_owned().into_boxed_str())
}
let props: Vec<Compiled> = self
.props
.iter()
.map(|p| Compiled {
name: leak(&p.name),
kind: p.kind,
required: p.required,
values: Box::leak(
p.values
.iter()
.map(|v| leak(v))
.collect::<Vec<_>>()
.into_boxed_slice(),
),
doc: leak(&p.doc),
})
.collect();
let alias: String = self
.template
.chars()
.map(|c| if c.is_ascii_alphanumeric() { c } else { '_' })
.collect();
let slots: &'static [Slot] = if self.content {
Box::leak(Box::new([Slot {
name: "",
into: None,
args: &[],
optional: true,
doc: "The content.",
}]))
} else {
&[]
};
Contract {
tag: leak(&self.tag),
doc: leak(&self.doc),
render: Render::Macro {
module: Module::Custom {
path: leak(&self.template),
alias: leak(&format!("__rx_p_{alias}")),
},
name: leak(&self.macro_name),
},
props: Box::leak(props.into_boxed_slice()),
slots,
events: &[],
route_prop: None,
attrs: self.attrs,
parent: None,
}
}
}