mirror of
https://github.com/project-gauntlet/gauntlet.git
synced 2025-12-23 10:35:53 +00:00
806 lines
41 KiB
Rust
806 lines
41 KiB
Rust
use std::env;
|
|
use std::fs::File;
|
|
use std::io::Write;
|
|
use std::ops::Deref;
|
|
use std::path::Path;
|
|
|
|
use convert_case::Case;
|
|
use convert_case::Casing;
|
|
use gauntlet_component_model::create_component_model;
|
|
use gauntlet_component_model::Arity;
|
|
use gauntlet_component_model::Children;
|
|
use gauntlet_component_model::Component;
|
|
use gauntlet_component_model::ComponentName;
|
|
use gauntlet_component_model::ComponentRef;
|
|
use gauntlet_component_model::Property;
|
|
use gauntlet_component_model::PropertyKind;
|
|
use gauntlet_component_model::PropertyType;
|
|
use gauntlet_component_model::SharedType;
|
|
use indexmap::IndexMap;
|
|
use itertools::Itertools;
|
|
|
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
tonic_build::configure()
|
|
.protoc_arg("--experimental_allow_proto3_optional")
|
|
.compile_protos(&["./../../schema/backend.proto"], &["./../../schema/"])?;
|
|
|
|
component_model_generator()?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn component_model_generator() -> Result<(), Box<dyn std::error::Error>> {
|
|
let out_dir = env::var("OUT_DIR")?;
|
|
let dest_path = Path::new(&out_dir).join("components.rs");
|
|
|
|
let mut output = String::new();
|
|
|
|
let components = create_component_model();
|
|
|
|
for component in &components {
|
|
match component {
|
|
Component::Standard {
|
|
name, props, children, ..
|
|
} => {
|
|
let props_has_content = props
|
|
.iter()
|
|
.any(|prop| matches!(prop.property_type.kind(), PropertyKind::Component));
|
|
|
|
let children_has_content = match children {
|
|
Children::Members {
|
|
ordered_members,
|
|
per_type_members,
|
|
..
|
|
}
|
|
| Children::StringOrMembers {
|
|
ordered_members,
|
|
per_type_members,
|
|
..
|
|
} => !ordered_members.is_empty() || !per_type_members.is_empty(),
|
|
_ => false,
|
|
};
|
|
|
|
let has_text = matches!(children, Children::StringOrMembers { .. } | Children::String { .. });
|
|
|
|
let has_content = children_has_content || props_has_content || has_text;
|
|
|
|
let default = IndexMap::new();
|
|
|
|
let (ordered_members, per_type_members) = match children {
|
|
Children::Members {
|
|
ordered_members,
|
|
per_type_members,
|
|
..
|
|
}
|
|
| Children::StringOrMembers {
|
|
ordered_members,
|
|
per_type_members,
|
|
..
|
|
} => (ordered_members, per_type_members),
|
|
_ => (&default, &default),
|
|
};
|
|
|
|
if !ordered_members.is_empty() {
|
|
output.push_str("#[derive(Debug, Encode, Decode)]\n");
|
|
output.push_str(&format!("pub enum {}WidgetOrderedMembers {{\n", name));
|
|
|
|
let unique_component_refs = ordered_members
|
|
.iter()
|
|
.map(|(_member_name, component_ref)| component_ref)
|
|
.unique_by(|component_ref| component_ref.component_name.clone())
|
|
.collect::<Vec<_>>();
|
|
|
|
for component_ref in &unique_component_refs {
|
|
output.push_str(&format!(
|
|
" {}({}Widget),\n",
|
|
component_ref.component_name, component_ref.component_name
|
|
));
|
|
}
|
|
|
|
output.push_str("}\n");
|
|
}
|
|
|
|
if has_content {
|
|
{
|
|
output.push_str("#[derive(Debug, Encode, Decode)]\n");
|
|
output.push_str(&format!("pub struct {}WidgetContent {{\n", name));
|
|
|
|
for prop in props {
|
|
if matches!(prop.property_type.kind(), PropertyKind::Component) {
|
|
let is_union = match &prop.property_type {
|
|
PropertyType::Union { .. } => true,
|
|
PropertyType::Array { item } => matches!(item.as_ref(), PropertyType::Union { .. }),
|
|
_ => false,
|
|
};
|
|
|
|
if is_union {
|
|
output.push_str(&format!(
|
|
" pub {}: {},\n",
|
|
prop.name.to_case(Case::Snake),
|
|
generate_required_type(
|
|
&prop.property_type,
|
|
Some(format!("{}{}", name, &prop.name.to_case(Case::Pascal)))
|
|
)
|
|
));
|
|
} else {
|
|
output.push_str(&format!(
|
|
" pub {}: {},\n",
|
|
prop.name.to_case(Case::Snake),
|
|
generate_type(&prop, name)
|
|
));
|
|
}
|
|
}
|
|
}
|
|
|
|
for (member_name, component_ref) in per_type_members {
|
|
match component_ref.arity {
|
|
Arity::ZeroOrOne => {
|
|
output.push_str(&format!(
|
|
" pub {}: Option<{}Widget>,\n",
|
|
member_name.to_case(Case::Snake),
|
|
component_ref.component_name
|
|
));
|
|
}
|
|
Arity::One => {
|
|
output.push_str(&format!(
|
|
" pub {}: {}Widget,\n",
|
|
member_name.to_case(Case::Snake),
|
|
component_ref.component_name
|
|
));
|
|
}
|
|
Arity::ZeroOrMore => {
|
|
todo!()
|
|
}
|
|
}
|
|
}
|
|
|
|
if !ordered_members.is_empty() {
|
|
output.push_str(&format!(
|
|
" pub ordered_members: Vec<{}WidgetOrderedMembers>,\n",
|
|
name
|
|
));
|
|
}
|
|
|
|
if has_text {
|
|
output.push_str(" pub text: Vec<String>,\n");
|
|
}
|
|
|
|
output.push_str("}\n");
|
|
}
|
|
|
|
{
|
|
let unique_ordered_component_refs = ordered_members
|
|
.iter()
|
|
.map(|(_member_name, component_ref)| component_ref)
|
|
.unique_by(|component_ref| component_ref.component_name.clone())
|
|
.collect::<Vec<_>>();
|
|
|
|
let per_type_component_refs = per_type_members
|
|
.iter()
|
|
.map(|(_member_name, component_ref)| component_ref)
|
|
.collect::<Vec<_>>();
|
|
|
|
let mut prop_union_component_refs = IndexMap::new();
|
|
let mut prop_other_component_refs = IndexMap::new();
|
|
|
|
for prop in props {
|
|
let is_union = match &prop.property_type {
|
|
PropertyType::Union { .. } => true,
|
|
PropertyType::Array { item } => matches!(item.as_ref(), PropertyType::Union { .. }),
|
|
_ => false,
|
|
};
|
|
|
|
let prop_name = prop.name.to_case(Case::Snake);
|
|
|
|
if is_union {
|
|
fn all_component_refs(property_type: &PropertyType) -> Vec<&ComponentRef> {
|
|
match property_type {
|
|
PropertyType::String => vec![],
|
|
PropertyType::Number => vec![],
|
|
PropertyType::Boolean => vec![],
|
|
PropertyType::Component { reference } => vec![reference],
|
|
PropertyType::Function { .. } => vec![],
|
|
PropertyType::SharedTypeRef { .. } => vec![],
|
|
PropertyType::Union { items } => {
|
|
items.iter().flat_map(|prop| all_component_refs(prop)).collect()
|
|
}
|
|
PropertyType::Array { item } => all_component_refs(item),
|
|
}
|
|
}
|
|
|
|
prop_union_component_refs.insert(prop_name, all_component_refs(&prop.property_type));
|
|
} else {
|
|
match &prop.property_type {
|
|
PropertyType::Component { reference } => {
|
|
prop_other_component_refs.insert(prop_name, reference);
|
|
}
|
|
PropertyType::Array { item, .. } => {
|
|
match item.as_ref() {
|
|
PropertyType::Component { reference } => {
|
|
prop_other_component_refs.insert(prop_name, reference);
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
}
|
|
|
|
let mut component_refs = vec![];
|
|
component_refs.extend(unique_ordered_component_refs.clone());
|
|
component_refs.extend(per_type_component_refs.clone());
|
|
component_refs.extend(prop_other_component_refs.values());
|
|
|
|
{
|
|
output.push_str(&format!("impl<'de> Deserialize<'de> for {}WidgetContent {{\n", name));
|
|
output.push_str(&format!(
|
|
" fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n"
|
|
));
|
|
output.push_str(&format!(" where\n"));
|
|
output.push_str(&format!(" D: Deserializer<'de>,\n"));
|
|
output.push_str(&format!(" {{\n"));
|
|
|
|
{
|
|
output.push_str(" #[derive(Debug, Deserialize)]\n");
|
|
output.push_str(" #[serde(tag = \"__type__\")]\n");
|
|
output.push_str(&format!(" enum {}WidgetMembersOwned {{\n", name));
|
|
|
|
for (_, prop_union_component_refs) in &prop_union_component_refs {
|
|
for prop_union_component_ref in prop_union_component_refs {
|
|
output.push_str(&format!(
|
|
" #[serde(rename = \"gauntlet:{}\")]\n",
|
|
prop_union_component_ref.component_internal_name
|
|
));
|
|
output.push_str(&format!(
|
|
" {}({}Widget),\n",
|
|
prop_union_component_ref.component_name,
|
|
prop_union_component_ref.component_name
|
|
));
|
|
}
|
|
}
|
|
|
|
for component_ref in &component_refs {
|
|
output.push_str(&format!(
|
|
" #[serde(rename = \"gauntlet:{}\")]\n",
|
|
component_ref.component_internal_name
|
|
));
|
|
output.push_str(&format!(
|
|
" {}({}Widget),\n",
|
|
component_ref.component_name, component_ref.component_name
|
|
));
|
|
}
|
|
|
|
if has_text {
|
|
output
|
|
.push_str(&format!(" #[serde(rename = \"gauntlet:text_part\")]\n"));
|
|
output.push_str(&format!(" Text {{\n"));
|
|
output.push_str(&format!(" value: String\n"));
|
|
output.push_str(&format!(" }},\n"));
|
|
}
|
|
|
|
output.push_str(" }\n");
|
|
}
|
|
|
|
output.push_str(&format!(
|
|
" let mut members = Vec::<{}WidgetMembersOwned>::deserialize(deserializer)?;\n",
|
|
name
|
|
));
|
|
output.push_str("\n");
|
|
|
|
for (prop_name, _) in &prop_other_component_refs {
|
|
output.push_str(&format!(" let mut {}: Option<_> = None;\n", prop_name));
|
|
}
|
|
|
|
for (prop_name, _) in &prop_union_component_refs {
|
|
output.push_str(&format!(" let mut {}: Vec<_> = vec![];\n", prop_name));
|
|
}
|
|
|
|
for per_type_component_ref in &per_type_component_refs {
|
|
output.push_str(&format!(
|
|
" let mut {}: Option<_> = None;\n",
|
|
per_type_component_ref.component_internal_name
|
|
));
|
|
}
|
|
|
|
if !ordered_members.is_empty() {
|
|
output.push_str(" let mut ordered_members = vec![];\n");
|
|
}
|
|
|
|
if has_text {
|
|
output.push_str(" let mut text = vec![];\n");
|
|
}
|
|
|
|
output.push_str("\n");
|
|
|
|
if has_content {
|
|
output.push_str(" while let Some(member) = members.pop() {\n");
|
|
output.push_str(" match member {\n");
|
|
|
|
for (prop_name, prop_union_component_refs) in &prop_union_component_refs {
|
|
for (index, prop_union_component_ref) in
|
|
prop_union_component_refs.iter().enumerate()
|
|
{
|
|
output.push_str(&format!(
|
|
" {}WidgetMembersOwned::{}(widget) => {{\n",
|
|
name, prop_union_component_ref.component_name
|
|
));
|
|
output.push_str(&format!(
|
|
" {}.push({}{}::_{}(widget));\n",
|
|
prop_name,
|
|
name,
|
|
prop_name.to_case(Case::Pascal),
|
|
index
|
|
));
|
|
output.push_str(&format!(" }}\n"));
|
|
}
|
|
}
|
|
|
|
for (prop_name, prop_other_component_refs) in &prop_other_component_refs {
|
|
output.push_str(&format!(
|
|
" {}WidgetMembersOwned::{}(widget) => {{\n",
|
|
name, prop_other_component_refs.component_name
|
|
));
|
|
output
|
|
.push_str(&format!(" if let Some(_) = {} {{\n", prop_name));
|
|
output.push_str(&format!(" return Err(Error::custom(\"Only one {} is allowed\"))\n", prop_other_component_refs.component_name));
|
|
output.push_str(&format!(" }}\n"));
|
|
output.push_str(&format!(" {} = Some(widget);\n", prop_name));
|
|
output.push_str(&format!(" }}\n"));
|
|
}
|
|
|
|
for per_type_component_ref in &per_type_component_refs {
|
|
output.push_str(&format!(
|
|
" {}WidgetMembersOwned::{}(widget) => {{\n",
|
|
name, per_type_component_ref.component_name
|
|
));
|
|
output.push_str(&format!(
|
|
" if let Some(_) = {} {{\n",
|
|
per_type_component_ref.component_internal_name
|
|
));
|
|
output.push_str(&format!(" return Err(Error::custom(\"Only one {} is allowed\"))\n", per_type_component_ref.component_name));
|
|
output.push_str(&format!(" }}\n"));
|
|
output.push_str(&format!(
|
|
" {} = Some(widget);\n",
|
|
per_type_component_ref.component_internal_name
|
|
));
|
|
output.push_str(&format!(" }}\n"));
|
|
}
|
|
|
|
for ordered_component_ref in &unique_ordered_component_refs {
|
|
output.push_str(&format!(
|
|
" {}WidgetMembersOwned::{}(widget) => {{\n",
|
|
name, ordered_component_ref.component_name
|
|
));
|
|
output.push_str(&format!(" ordered_members.insert(0, {}WidgetOrderedMembers::{}(widget));\n", name, ordered_component_ref.component_name));
|
|
output.push_str(&format!(" }}\n"));
|
|
}
|
|
|
|
if has_text {
|
|
output.push_str(&format!(
|
|
" {}WidgetMembersOwned::Text {{ value }} => {{\n",
|
|
name
|
|
));
|
|
output.push_str(&format!(" text.insert(0, value);\n"));
|
|
output.push_str(&format!(" }}\n"));
|
|
}
|
|
|
|
output.push_str(" }\n");
|
|
output.push_str(" }\n");
|
|
}
|
|
|
|
output.push_str("\n");
|
|
output.push_str(&format!(" Ok({}WidgetContent {{\n", name));
|
|
|
|
for (prop_name, _) in &prop_union_component_refs {
|
|
output.push_str(&format!(" {},\n", prop_name));
|
|
}
|
|
|
|
for per_type_component_ref in &per_type_component_refs {
|
|
match per_type_component_ref.arity {
|
|
Arity::ZeroOrOne => {
|
|
output.push_str(&format!(
|
|
" {},\n",
|
|
per_type_component_ref.component_internal_name
|
|
));
|
|
}
|
|
Arity::One => {
|
|
output.push_str(&format!(
|
|
" {}: {}.ok_or(Error::custom(\"{} is required\"))?,\n",
|
|
per_type_component_ref.component_internal_name,
|
|
per_type_component_ref.component_internal_name,
|
|
per_type_component_ref.component_name
|
|
));
|
|
}
|
|
Arity::ZeroOrMore => {
|
|
todo!()
|
|
}
|
|
}
|
|
}
|
|
|
|
for (prop_name, prop_other_component_ref) in &prop_other_component_refs {
|
|
match prop_other_component_ref.arity {
|
|
Arity::ZeroOrOne => {
|
|
output.push_str(&format!(" {},\n", prop_name));
|
|
}
|
|
Arity::One => {
|
|
output.push_str(&format!(
|
|
" {}: {}.ok_or(Error::custom(\"{} is required\"))?,\n",
|
|
prop_name,
|
|
prop_other_component_ref.component_internal_name,
|
|
prop_other_component_ref.component_name
|
|
));
|
|
}
|
|
Arity::ZeroOrMore => {
|
|
todo!()
|
|
}
|
|
}
|
|
}
|
|
|
|
if !ordered_members.is_empty() {
|
|
output.push_str(" ordered_members\n");
|
|
}
|
|
|
|
if has_text {
|
|
output.push_str(" text\n");
|
|
}
|
|
|
|
output.push_str(&format!(" }})\n"));
|
|
output.push_str(&format!(" }}\n"));
|
|
output.push_str(&format!("}}\n"));
|
|
}
|
|
|
|
{
|
|
output.push_str(&format!("impl Serialize for {}WidgetContent {{\n", name));
|
|
output.push_str(&format!(
|
|
" fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>\n"
|
|
));
|
|
output.push_str(&format!(" where\n"));
|
|
output.push_str(&format!(" S: Serializer\n"));
|
|
output.push_str(&format!(" {{\n"));
|
|
|
|
{
|
|
output.push_str(" #[derive(Debug, Serialize)]\n");
|
|
output.push_str(" #[serde(tag = \"__type__\")]\n");
|
|
output.push_str(&format!(" enum {}WidgetMembersRef<'a> {{\n", name));
|
|
|
|
for (_, prop_union_component_refs) in &prop_union_component_refs {
|
|
for prop_union_component_ref in prop_union_component_refs {
|
|
output.push_str(&format!(
|
|
" #[serde(rename = \"gauntlet:{}\")]\n",
|
|
prop_union_component_ref.component_internal_name
|
|
));
|
|
output.push_str(&format!(
|
|
" {}(&'a {}Widget),\n",
|
|
prop_union_component_ref.component_name,
|
|
prop_union_component_ref.component_name
|
|
));
|
|
}
|
|
}
|
|
|
|
for component_ref in &component_refs {
|
|
output.push_str(&format!(
|
|
" #[serde(rename = \"gauntlet:{}\")]\n",
|
|
component_ref.component_internal_name
|
|
));
|
|
output.push_str(&format!(
|
|
" {}(&'a {}Widget),\n",
|
|
component_ref.component_name, component_ref.component_name
|
|
));
|
|
}
|
|
|
|
if has_text {
|
|
output
|
|
.push_str(&format!(" #[serde(rename = \"gauntlet:text_part\")]\n"));
|
|
output.push_str(&format!(" Text {{\n"));
|
|
output.push_str(&format!(" value: &'a String\n"));
|
|
output.push_str(&format!(" }},\n"));
|
|
}
|
|
|
|
output.push_str(" }\n");
|
|
}
|
|
|
|
output.push_str(&format!(
|
|
" let mut members = Vec::<{}WidgetMembersRef>::new();\n",
|
|
name
|
|
));
|
|
output.push_str("\n");
|
|
|
|
for (prop_name, prop_union_component_refs) in &prop_union_component_refs {
|
|
output.push_str(&format!(" for item in &self.{} {{\n", prop_name));
|
|
output.push_str(&format!(" match item {{\n"));
|
|
|
|
for (index, prop_union_component_ref) in prop_union_component_refs.iter().enumerate() {
|
|
output.push_str(&format!(
|
|
" {}{}::_{}(widget) => {{\n",
|
|
name,
|
|
prop_name.to_case(Case::Pascal),
|
|
index
|
|
));
|
|
output.push_str(&format!(
|
|
" members.push({}WidgetMembersRef::{}(widget));\n",
|
|
name, prop_union_component_ref.component_name
|
|
));
|
|
output.push_str(&format!(" }}\n"));
|
|
}
|
|
|
|
output.push_str(&format!(" }}\n"));
|
|
output.push_str(&format!(" }}\n"));
|
|
}
|
|
|
|
for (prop_name, prop_other_component_refs) in &prop_other_component_refs {
|
|
match prop_other_component_refs.arity {
|
|
Arity::ZeroOrOne => {
|
|
output.push_str(&format!(
|
|
" if let Some({}) = &self.{} {{\n",
|
|
prop_name, prop_name
|
|
));
|
|
output.push_str(&format!(
|
|
" members.push({}WidgetMembersRef::{}({}))\n",
|
|
name, prop_other_component_refs.component_name, prop_name
|
|
));
|
|
output.push_str(&format!(" }}\n"));
|
|
}
|
|
Arity::One => {
|
|
output.push_str(&format!(
|
|
" members.push({}WidgetMembersRef::{}(&self.{}))\n",
|
|
name, prop_other_component_refs.component_name, prop_name
|
|
));
|
|
}
|
|
Arity::ZeroOrMore => {
|
|
todo!()
|
|
}
|
|
}
|
|
}
|
|
|
|
for per_type_component_ref in &per_type_component_refs {
|
|
match per_type_component_ref.arity {
|
|
Arity::ZeroOrOne => {
|
|
output.push_str(&format!(
|
|
" if let Some(item) = &self.{} {{\n",
|
|
per_type_component_ref.component_internal_name
|
|
));
|
|
output.push_str(&format!(
|
|
" members.push({}WidgetMembersRef::{}(item))\n",
|
|
name, per_type_component_ref.component_name
|
|
));
|
|
output.push_str(&format!(" }}\n"));
|
|
}
|
|
Arity::One => {
|
|
output.push_str(&format!(
|
|
" members.push({}WidgetMembersRef::{}(&self.{}));\n",
|
|
name,
|
|
per_type_component_ref.component_name,
|
|
per_type_component_ref.component_internal_name
|
|
));
|
|
}
|
|
Arity::ZeroOrMore => {
|
|
todo!()
|
|
}
|
|
}
|
|
}
|
|
|
|
if !unique_ordered_component_refs.is_empty() {
|
|
output.push_str(&format!(" for member in &self.ordered_members {{\n"));
|
|
output.push_str(&format!(" match member {{\n"));
|
|
|
|
for ordered_component_ref in &unique_ordered_component_refs {
|
|
output.push_str(&format!(
|
|
" {}WidgetOrderedMembers::{}(widget) => {{\n",
|
|
name, ordered_component_ref.component_name
|
|
));
|
|
output.push_str(&format!(
|
|
" members.push({}WidgetMembersRef::{}(widget))\n",
|
|
name, ordered_component_ref.component_name
|
|
));
|
|
output.push_str(&format!(" }}\n"));
|
|
}
|
|
|
|
output.push_str(&format!(" }}\n"));
|
|
output.push_str(&format!(" }}\n"));
|
|
}
|
|
|
|
if has_text {
|
|
output.push_str(&format!(" for value in &self.text {{\n"));
|
|
output.push_str(&format!(
|
|
" members.push({}WidgetMembersRef::Text {{ value }});\n",
|
|
name
|
|
));
|
|
output.push_str(&format!(" }}\n"));
|
|
}
|
|
|
|
output.push_str("\n");
|
|
output.push_str(&format!(
|
|
" Vec::<{}WidgetMembersRef>::serialize(&members, serializer)\n",
|
|
name
|
|
));
|
|
|
|
output.push_str(&format!(" }}\n"));
|
|
output.push_str(&format!("}}\n"));
|
|
}
|
|
}
|
|
}
|
|
|
|
output.push_str("#[derive(Debug, Serialize, Deserialize, Encode, Decode)]\n");
|
|
output.push_str(&format!("pub struct {}Widget {{\n", name));
|
|
output.push_str(" #[serde(rename = \"__id__\")]\n");
|
|
output.push_str(" pub __id__: UiWidgetId,\n");
|
|
|
|
for prop in props {
|
|
if matches!(prop.property_type.kind(), PropertyKind::Property) {
|
|
output.push_str(&format!(" #[serde(rename = \"{}\")]\n", prop.name));
|
|
output.push_str(&format!(
|
|
" pub {}: {},\n",
|
|
prop.name.to_case(Case::Snake),
|
|
generate_type(&prop, name)
|
|
));
|
|
}
|
|
}
|
|
|
|
if has_content {
|
|
output.push_str(&format!(" pub content: {}WidgetContent,\n", name));
|
|
}
|
|
|
|
output.push_str("}\n");
|
|
|
|
let generate_union = |output: &mut String, items: &Vec<PropertyType>, prop_name: &String| {
|
|
output.push_str("#[derive(Debug, Encode, Decode)]\n");
|
|
output.push_str(&format!("pub enum {}{} {{\n", name, prop_name.to_case(Case::Pascal)));
|
|
|
|
for (index, property_type) in items.iter().enumerate() {
|
|
output.push_str(&format!(
|
|
" _{}({}),\n",
|
|
index,
|
|
generate_required_type(&property_type, None)
|
|
));
|
|
}
|
|
|
|
output.push_str("}\n");
|
|
output.push_str("\n");
|
|
};
|
|
|
|
for prop in props {
|
|
match &prop.property_type {
|
|
PropertyType::Union { items } => generate_union(&mut output, items, &prop.name),
|
|
PropertyType::Array { item } => {
|
|
match item.deref() {
|
|
PropertyType::Union { items } => generate_union(&mut output, items, &prop.name),
|
|
_ => {}
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
}
|
|
Component::Root {
|
|
children, shared_types, ..
|
|
} => {
|
|
for (type_name, shared_type) in shared_types {
|
|
match shared_type {
|
|
SharedType::Enum { items } => {
|
|
output.push_str("#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode)]\n");
|
|
output.push_str(&format!("pub enum {} {{\n", type_name));
|
|
|
|
for item in items {
|
|
output.push_str(&format!(" #[serde(rename = \"{}\")]\n", &item));
|
|
output.push_str(&format!(" {},\n", &item));
|
|
}
|
|
|
|
output.push_str("}\n");
|
|
output.push_str("\n");
|
|
}
|
|
SharedType::Object { items } => {
|
|
output.push_str("#[derive(Debug, Serialize, Deserialize, Encode, Decode)]\n");
|
|
output.push_str(&format!("pub struct {} {{\n", type_name));
|
|
|
|
for (property_name, property_type) in items {
|
|
output.push_str(&format!(
|
|
" pub {}: {},\n",
|
|
&property_name,
|
|
generate_required_type(
|
|
&property_type,
|
|
Some(format!("{}{}", type_name, property_name))
|
|
)
|
|
));
|
|
}
|
|
|
|
output.push_str("}\n");
|
|
output.push_str("\n");
|
|
}
|
|
SharedType::Union { items } => {
|
|
output.push_str("#[derive(Debug, Serialize, Deserialize, Encode, Decode)]\n");
|
|
output.push_str("#[serde(untagged)]\n");
|
|
output.push_str(&format!("pub enum {} {{\n", type_name));
|
|
|
|
for property_type in items {
|
|
match property_type {
|
|
PropertyType::SharedTypeRef { name } => {
|
|
output.push_str(&format!(" {}({}),\n", &name, name));
|
|
}
|
|
_ => {
|
|
todo!()
|
|
}
|
|
}
|
|
}
|
|
|
|
output.push_str("}\n");
|
|
output.push_str("\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
output.push_str("#[derive(Debug, Serialize, Deserialize, Encode, Decode)]\n");
|
|
output.push_str("#[serde(tag = \"__type__\")]\n");
|
|
output.push_str("pub enum RootWidgetMembers {\n");
|
|
|
|
for component_ref in children {
|
|
output.push_str(&format!(
|
|
" #[serde(rename = \"gauntlet:{}\")]\n",
|
|
component_ref.component_internal_name
|
|
));
|
|
output.push_str(&format!(
|
|
" {}({}Widget),\n",
|
|
component_ref.component_name, component_ref.component_name
|
|
));
|
|
}
|
|
|
|
output.push_str("}\n");
|
|
|
|
output.push_str("#[derive(Debug, Serialize, Deserialize, Encode, Decode)]\n");
|
|
output.push_str("pub struct RootWidget {\n");
|
|
output.push_str(" #[serde(default, deserialize_with = \"array_to_option\", serialize_with = \"option_to_array\")]\n");
|
|
output.push_str(" pub content: Option<RootWidgetMembers>\n");
|
|
output.push_str("}\n");
|
|
}
|
|
Component::TextPart { .. } => {}
|
|
}
|
|
}
|
|
|
|
generate_file(&dest_path, &output)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn generate_file<P: AsRef<Path>>(path: P, text: &str) -> std::io::Result<()> {
|
|
let mut f = File::create(path)?;
|
|
f.write_all(text.as_bytes())
|
|
}
|
|
|
|
fn generate_type(property: &Property, name: &ComponentName) -> String {
|
|
match property.optional {
|
|
true => {
|
|
generate_optional_type(
|
|
&property.property_type,
|
|
format!("{}{}", name, &property.name.to_case(Case::Pascal)),
|
|
)
|
|
}
|
|
false => {
|
|
generate_required_type(
|
|
&property.property_type,
|
|
Some(format!("{}{}", name, &property.name.to_case(Case::Pascal))),
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
fn generate_optional_type(property_type: &PropertyType, union_name: String) -> String {
|
|
format!("Option<{}>", generate_required_type(property_type, Some(union_name)))
|
|
}
|
|
|
|
fn generate_required_type(property_type: &PropertyType, union_name: Option<String>) -> String {
|
|
match property_type {
|
|
PropertyType::String => "String".to_owned(),
|
|
PropertyType::Number => "f64".to_owned(),
|
|
PropertyType::Boolean => "bool".to_owned(),
|
|
PropertyType::Function { .. } => panic!("client doesn't know about functions in properties"),
|
|
PropertyType::Component { reference } => format!("{}Widget", reference.component_name.to_string()),
|
|
PropertyType::SharedTypeRef { name } => name.to_owned(),
|
|
PropertyType::Union { .. } => {
|
|
match union_name {
|
|
None => panic!("should not be used"),
|
|
Some(union_name) => union_name,
|
|
}
|
|
}
|
|
PropertyType::Array { item } => format!("Vec<{}>", generate_required_type(item, union_name)),
|
|
}
|
|
}
|