diff --git a/crates/config/src/display.rs b/crates/config/src/display.rs index 7f7575b..18daf9b 100644 --- a/crates/config/src/display.rs +++ b/crates/config/src/display.rs @@ -111,7 +111,7 @@ public! { public! { #[derive(ConfigProperty, GenericBuilder, Debug, Clone)] #[cfg_prop(name(TomlImageProperty), derive(Debug, Clone, Default, Deserialize))] - #[gbuilder(name(GBuilderImageProperty))] + #[gbuilder(name(GBuilderImageProperty), derive(Clone))] struct ImageProperty { #[cfg_prop(default(64))] #[gbuilder(default(64))] @@ -171,7 +171,7 @@ impl TryFromValue for ResizingMethod { public! { #[derive(ConfigProperty, GenericBuilder, Debug, Default, Clone)] #[cfg_prop(name(TomlBorder), derive(Debug, Clone, Default, Deserialize))] - #[gbuilder(name(GBuilderBorder))] + #[gbuilder(name(GBuilderBorder), derive(Clone))] struct Border { #[cfg_prop(default(0))] #[gbuilder(default(0))] diff --git a/crates/config/src/spacing.rs b/crates/config/src/spacing.rs index f278c91..9effc79 100644 --- a/crates/config/src/spacing.rs +++ b/crates/config/src/spacing.rs @@ -9,7 +9,7 @@ use serde::{de::Visitor, Deserialize}; use shared::value::TryFromValue; #[derive(GenericBuilder, Debug, Default, Clone)] -#[gbuilder(name(GBuilderSpacing))] +#[gbuilder(name(GBuilderSpacing), derive(Clone))] pub struct Spacing { #[gbuilder(default(0))] top: u8, diff --git a/crates/config/src/text.rs b/crates/config/src/text.rs index 40bf513..d208abd 100644 --- a/crates/config/src/text.rs +++ b/crates/config/src/text.rs @@ -6,8 +6,8 @@ use super::{public, Spacing}; public! { #[derive(ConfigProperty, GenericBuilder, Debug, Clone)] - #[cfg_prop(name(TomlTextProperty),derive(Debug, Clone, Default, Deserialize))] - #[gbuilder(name(GBuilderTextProperty))] + #[cfg_prop(name(TomlTextProperty), derive(Debug, Clone, Default, Deserialize))] + #[gbuilder(name(GBuilderTextProperty), derive(Clone))] struct TextProperty { #[cfg_prop(default(true))] #[gbuilder(default(true))] diff --git a/crates/filetype/src/converter.rs b/crates/filetype/src/converter.rs index f36f08d..c855954 100644 --- a/crates/filetype/src/converter.rs +++ b/crates/filetype/src/converter.rs @@ -1,8 +1,9 @@ +use std::collections::HashMap; + use anyhow::bail; use config::{ - display::{Border, GBuilderBorder, GBuilderImageProperty, ImageProperty}, + display::{Border, GBuilderBorder}, spacing::{GBuilderSpacing, Spacing}, - text::{GBuilderTextProperty, TextProperty}, }; use log::warn; use pest::iterators::{Pair, Pairs}; @@ -27,11 +28,57 @@ pub(super) fn convert_into_widgets(mut pairs: Pairs) -> anyhow::Result( + alias_definitions: Pair<'a, Rule>, + alias_storage: &mut HashMap<&'a str, GBuilder>, +) -> anyhow::Result<()> { + assert_eq!( + alias_definitions.as_rule(), + Rule::AliasDefinitions, + "In input should be an AliasDefinitions" + ); + + for alias_definition in alias_definitions.into_inner() { + debug_assert_eq!( + alias_definition.as_rule(), + Rule::AliasDefinition, + "In input should be an AliasDefinition" + ); + + let mut alias_definition_pairs = alias_definition.into_inner(); + + let _alias_keyword = alias_definition_pairs.next().unwrap(); + let alias_identifier = alias_definition_pairs.next().unwrap().as_str(); + let _eq_token = alias_definition_pairs.next().unwrap(); + let type_value_definition = alias_definition_pairs.next().unwrap(); + + alias_storage.insert( + alias_identifier, + convert_type_value(type_value_definition, alias_storage)?, + ); + } + + Ok(()) } -fn convert_node_type(node_type: Pair) -> anyhow::Result { +fn convert_node_type<'a>( + node_type: Pair<'a, Rule>, + alias_storage: &'a HashMap<&'a str, GBuilder>, +) -> anyhow::Result { assert_eq!( node_type.as_rule(), Rule::NodeType, @@ -41,18 +88,18 @@ fn convert_node_type(node_type: Pair) -> anyhow::Result { let mut node_type_pairs = node_type.into_inner(); let widget_name = node_type_pairs.next().unwrap().as_str(); - let mut widget_gbuilder: GBuilder = widget_name.try_into()?; + let mut widget_gbuilder: GBuilder = (widget_name, alias_storage).try_into()?; - let properties = convert_properties(&mut node_type_pairs); + let properties = convert_properties(&mut node_type_pairs, alias_storage); - let children = convert_children(&mut node_type_pairs)?; + let children = convert_children(&mut node_type_pairs, alias_storage)?; widget_gbuilder.set_properties(widget_name, properties); if !children.is_empty() { let assignment_result = widget_gbuilder.set_value("children", Value::Any(Box::new(children))); - if let Err(ConversionError::UnknownField { field_name }) = assignment_result { + if let Err(ConversionError::UnknownField { field_name, .. }) = assignment_result { warn!("The {widget_name} doesn't contain the '{field_name}' field! Skipped."); } } @@ -60,7 +107,10 @@ fn convert_node_type(node_type: Pair) -> anyhow::Result { Ok(widget_gbuilder.try_build()?.try_downcast()?) } -fn convert_properties(node_type_pairs: &mut Pairs) -> Vec { +fn convert_properties<'a>( + node_type_pairs: &mut Pairs<'a, Rule>, + alias_storage: &'a HashMap<&'a str, GBuilder>, +) -> Vec { let _open_paren = node_type_pairs.next(); let properties_or_close_paren = node_type_pairs.next().unwrap(); @@ -81,7 +131,7 @@ fn convert_properties(node_type_pairs: &mut Pairs) -> Vec { let mut converted_properties = vec![]; let mut properties_pairs = properties.into_inner(); while let Some(property) = properties_pairs.next() { - match convert_property(property) { + match convert_property(property, alias_storage) { Ok(property) => converted_properties.push(property), Err(err) => warn!("Failed to parse property and skipped. Error: {err}"), } @@ -91,7 +141,10 @@ fn convert_properties(node_type_pairs: &mut Pairs) -> Vec { converted_properties } -fn convert_property(property: Pair) -> anyhow::Result { +fn convert_property<'a>( + property: Pair<'a, Rule>, + alias_storage: &'a HashMap<&'a str, GBuilder>, +) -> anyhow::Result { assert_eq!( property.as_rule(), Rule::Property, @@ -101,12 +154,15 @@ fn convert_property(property: Pair) -> anyhow::Result { let mut property_pairs = property.into_inner(); let name = property_pairs.next().unwrap().as_str().to_string(); let _eq_token = property_pairs.next(); - let value = convert_property_value(property_pairs.next().unwrap())?; + let value = convert_property_value(property_pairs.next().unwrap(), alias_storage)?; Ok(Property { name, value }) } -fn convert_property_value(property_value: Pair) -> anyhow::Result { +fn convert_property_value<'a>( + property_value: Pair<'a, Rule>, + alias_storage: &'a HashMap<&'a str, GBuilder>, +) -> anyhow::Result { assert_eq!( property_value.as_rule(), Rule::PropertyValue, @@ -116,14 +172,19 @@ fn convert_property_value(property_value: Pair) -> anyhow::Result { let value = property_value.into_inner().next().unwrap(); Ok(match value.as_rule() { - Rule::TypeValue => convert_type_value(value)?, + Rule::TypeValue => convert_type_value(value, alias_storage) + .and_then(GBuilder::try_build) + .map(Value::Any)?, Rule::Literal => Value::String(value.as_str().to_string()), Rule::UInt => Value::UInt(value.as_str().parse().unwrap()), _ => unreachable!(), }) } -fn convert_children(node_type_pairs: &mut Pairs) -> anyhow::Result> { +fn convert_children<'a>( + node_type_pairs: &mut Pairs<'a, Rule>, + alias_storage: &'a HashMap<&'a str, GBuilder>, +) -> anyhow::Result> { let open_brace = node_type_pairs.next(); let mut children = None; @@ -148,11 +209,14 @@ fn convert_children(node_type_pairs: &mut Pairs) -> anyhow::Result>>() } -fn convert_type_value(type_value: Pair) -> anyhow::Result { +fn convert_type_value<'a>( + type_value: Pair<'a, Rule>, + alias_storage: &'a HashMap<&'a str, GBuilder>, +) -> anyhow::Result { assert_eq!( type_value.as_rule(), Rule::TypeValue, @@ -162,91 +226,36 @@ fn convert_type_value(type_value: Pair) -> anyhow::Result { let mut type_value_pairs = type_value.into_inner(); let type_name = type_value_pairs.next().unwrap().as_str(); - let mut type_gbuilder: GBuilder = type_name.try_into()?; + let mut type_gbuilder: GBuilder = (type_name, alias_storage).try_into()?; + + type_gbuilder.set_properties( + type_name, + convert_properties(&mut type_value_pairs, alias_storage), + ); - type_gbuilder.set_properties(type_name, convert_properties(&mut type_value_pairs)); - type_gbuilder.try_build().map(Value::Any).map(Ok)? + Ok(type_gbuilder) } +#[derive(Clone)] enum GBuilder { FlexContainer(GBuilderFlexContainer), WImage(GBuilderWImage), WText(GBuilderWText), - TextProperty(GBuilderTextProperty), - ImageProperty(GBuilderImageProperty), - Spacing(GBuilderSpacing), Alignment(GBuilderAlignment), Border(GBuilderBorder), } impl GBuilder { - fn get_associated_property(&self) -> Option { - match self { - GBuilder::WImage(_) => Some(GBuilder::ImageProperty(GBuilderImageProperty::new())), - GBuilder::WText(_) => Some(GBuilder::TextProperty(GBuilderTextProperty::new())), - _ => None, - } - } - fn set_properties(&mut self, self_name: &str, properties: Vec) { - let mut associated_property = self.get_associated_property(); - for Property { name, value } in properties { - let this = if self.contains_field(&name) { - &mut *self - } else if let Some(property) = associated_property.as_mut() { - property - } else { - warn!("The '{name}' field for the {self_name} widget is unknown. Skipped."); - continue; - }; - - if let Err(err) = this.set_value(&name, value) { + if let Err(err) = self.set_value(&name, value) { warn!( - "Cannot set value for the '{name}' field due error and skipped. Error: {err}" + "Cannot set value for the '{name}' field in {self_name} due error and skipped. Error: {err}" ); } } - - if let Some(property) = associated_property { - let property = match property.try_build() { - Ok(property) => Value::Any(property), - Err(err) => { - println!("{err}"); - warn!("Failed to analyze properties of the '{self_name}' type and skipped. Error: {err}"); - return; - } - }; - - if let Err(err) = self.set_value("property", property) { - warn!("Failed to apply properties to the '{self_name}' wiget and skipped. Error: {err}"); - } - } - } - - fn contains_field(&self, field_name: &str) -> bool { - macro_rules! implement_variants { - ($($variant:ident),*) => { - match self { - $( - Self::$variant(val) => val.contains_field(field_name), - )* - } - }; - } - - implement_variants!( - FlexContainer, - WImage, - WText, - TextProperty, - ImageProperty, - Spacing, - Alignment, - Border - ) } fn set_value(&mut self, field_name: &str, value: Value) -> Result<&mut Self, ConversionError> { @@ -262,20 +271,11 @@ impl GBuilder { }; } - implement_variants!( - FlexContainer, - WImage, - WText, - TextProperty, - ImageProperty, - Spacing, - Alignment, - Border - ); + implement_variants!(FlexContainer, WImage, WText, Spacing, Alignment, Border); Ok(self) } - fn try_build(self) -> anyhow::Result> { + fn try_build(self) -> anyhow::Result> { macro_rules! implement_variants { ($($variant:ident into $dest_type:path),*) => { match self { @@ -293,9 +293,6 @@ impl GBuilder { WText into Widget, FlexContainer into Widget, - TextProperty into TextProperty, - ImageProperty into ImageProperty, - Spacing into Spacing, Alignment into Alignment, Border into Border @@ -303,18 +300,26 @@ impl GBuilder { } } -impl TryFrom<&str> for GBuilder { +impl TryFrom<(&str, &HashMap<&str, GBuilder>)> for GBuilder { type Error = anyhow::Error; - fn try_from(value: &str) -> Result { - Ok(match value { + fn try_from( + (identifier, alias_storage): (&str, &HashMap<&str, GBuilder>), + ) -> Result { + Ok(match identifier { "FlexContainer" => GBuilder::FlexContainer(GBuilderFlexContainer::new()), "Image" => GBuilder::WImage(GBuilderWImage::new()), "Text" => GBuilder::WText(GBuilderWText::new()), "Spacing" => GBuilder::Spacing(GBuilderSpacing::new()), "Alignment" => GBuilder::Alignment(GBuilderAlignment::new()), "Border" => GBuilder::Border(GBuilderBorder::new()), - _ => bail!("Unknown type: {value}!"), + other => { + if let Some(aliased_gbuilder) = alias_storage.get(other).cloned() { + aliased_gbuilder + } else { + bail!("Unknown type: {other}!") + } + } }) } } diff --git a/crates/filetype/src/layout.pest b/crates/filetype/src/layout.pest index 1bbfec5..82270e7 100644 --- a/crates/filetype/src/layout.pest +++ b/crates/filetype/src/layout.pest @@ -11,6 +11,8 @@ // They will be here until the Pest parser gets better token // analyzing. +Alias = { "alias" } + OpeningBrace = { "{" } ClosingBrace = { "}" } @@ -24,7 +26,10 @@ Hashtag = { "#" } // END BASE TOKENS -Layout = { SOI ~ NodeType ~ EOI } +Layout = { SOI ~ AliasDefinitions? ~ NodeType ~ EOI } + +AliasDefinitions = { AliasDefinition+ } +AliasDefinition = { Alias ~ Identifier ~ Equal ~ TypeValue } NodeType = { Identifier ~ OpeningParenthesis diff --git a/crates/filetype/src/lib.rs b/crates/filetype/src/lib.rs index 1e000f4..e0e0a87 100644 --- a/crates/filetype/src/lib.rs +++ b/crates/filetype/src/lib.rs @@ -22,21 +22,25 @@ fn minimal_type() { // WText(kind = title) - FlexContainer( + alias SizedText = Text(font_size = 20) + alias DefaultAlignment = Alignment( + horizontal = start, + vertical = space_between, + ) + + alias Title = SizedText(kind = title) + alias Row = FlexContainer(direction = horizontal) + + Row( max_width = 400, max_height = 120, - direction = horizontal, - alignment = Alignment( - horizontal = start, - vertical = space_between, - ) + alignment = DefaultAlignment(), ) { Image( max_size = 86, ) - Text( - kind = title, + Title( wrap = false, line_spacing = 10, ) diff --git a/crates/filetype/src/parser.rs b/crates/filetype/src/parser.rs index 5cfeb86..47ae0c6 100644 --- a/crates/filetype/src/parser.rs +++ b/crates/filetype/src/parser.rs @@ -91,3 +91,83 @@ fn test_redundant_semicolon_in_children() { ) .unwrap(); } + +#[test] +#[should_panic] +fn test_invalid_alias_definition() { + LayoutParser::parse( + Rule::Layout, + r#" + alas Test = Title() + + FlexContainer( + min_width = 3, + max_width = 4 + ) { + Text() + Image() + } + "#, + ) + .unwrap(); +} + +#[test] +#[should_panic] +fn test_invalid_alias_definition2() { + LayoutParser::parse( + Rule::Layout, + r#" + alias Test = 3 + + FlexContainer( + min_width = 3, + max_width = 4 + ) { + Text() + Image() + } + "#, + ) + .unwrap(); +} + +#[test] +#[should_panic] +fn test_invalid_alias_definition3() { + LayoutParser::parse( + Rule::Layout, + r#" + alias Test = literal + + FlexContainer( + min_width = 3, + max_width = 4 + ) { + Text() + Image() + } + "#, + ) + .unwrap(); +} + +#[test] +#[should_panic] +fn test_invalid_alias_definition4() { + LayoutParser::parse( + Rule::Layout, + r#" + alias _ = Text() + + FlexContainer( + min_width = 3, + max_width = 4 + ) { + Text() + Image() + } + "#, + ) + .unwrap(); +} diff --git a/crates/macros/src/config_property.rs b/crates/macros/src/config_property.rs index f3b3a61..55f8802 100644 --- a/crates/macros/src/config_property.rs +++ b/crates/macros/src/config_property.rs @@ -2,12 +2,13 @@ use proc_macro::TokenStream; use proc_macro2::Ident; use quote::{quote, ToTokens}; use syn::{ - ext::IdentExt, parenthesized, parse::Parse, parse_macro_input, punctuated::Punctuated, - spanned::Spanned, Token, + parenthesized, parse::Parse, parse_macro_input, punctuated::Punctuated, spanned::Spanned, Token, }; use crate::{ - general::{field_name, wrap_by_option, AttributeInfo, DefaultAssignment, Structure}, + general::{ + field_name, wrap_by_option, AttributeInfo, DefaultAssignment, DeriveInfo, Structure, + }, propagate_err, }; @@ -383,14 +384,7 @@ impl Parse for StructInfo { let _paren = parenthesized!(content in input); name = Some(content.parse()?); } - "derive" => { - let content; - derive_info = Some(DeriveInfo { - ident, - paren: parenthesized!(content in input), - traits: content.parse_terminated(syn::Ident::parse_any, Token![,])?, - }); - } + "derive" => derive_info = Some(DeriveInfo::from_ident_and_input(ident, &input)?), _ => return Err(syn::Error::new(ident.span(), "Unknown attribute")), } @@ -412,23 +406,6 @@ impl Parse for StructInfo { } } -struct DeriveInfo { - ident: syn::Ident, - paren: syn::token::Paren, - traits: Punctuated, -} - -impl ToTokens for DeriveInfo { - fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { - proc_macro2::Punct::new('#', proc_macro2::Spacing::Joint).to_tokens(tokens); - syn::token::Bracket::default().surround(tokens, |tokens| { - self.ident.to_tokens(tokens); - self.paren - .surround(tokens, |tokens| self.traits.to_tokens(tokens)); - }); - } -} - struct FieldInfo { mergeable: bool, default: DefaultAssignment, diff --git a/crates/macros/src/general.rs b/crates/macros/src/general.rs index 5db0861..d2285ad 100644 --- a/crates/macros/src/general.rs +++ b/crates/macros/src/general.rs @@ -1,5 +1,6 @@ use std::collections::HashMap; +use quote::ToTokens; use syn::{ext::IdentExt, parse::Parse, spanned::Spanned, Ident, Token}; #[derive(Clone)] @@ -152,6 +153,37 @@ impl Parse for DefaultAssignment { } } +pub struct DeriveInfo { + ident: syn::Ident, + paren: syn::token::Paren, + traits: syn::punctuated::Punctuated, +} + +impl DeriveInfo { + pub fn from_ident_and_input( + ident: syn::Ident, + input: &syn::parse::ParseStream, + ) -> syn::Result { + let content; + Ok(Self { + ident, + paren: syn::parenthesized!(content in input), + traits: content.parse_terminated(syn::Ident::parse_any, Token![,])?, + }) + } +} + +impl ToTokens for DeriveInfo { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + proc_macro2::Punct::new('#', proc_macro2::Spacing::Joint).to_tokens(tokens); + syn::token::Bracket::default().surround(tokens, |tokens| { + self.ident.to_tokens(tokens); + self.paren + .surround(tokens, |tokens| self.traits.to_tokens(tokens)); + }); + } +} + pub(crate) fn wrap_by_option(ty: syn::Type) -> syn::Type { use proc_macro2::Span; use syn::PathSegment; diff --git a/crates/macros/src/generic_builder.rs b/crates/macros/src/generic_builder.rs index 14a2d11..2ef6818 100644 --- a/crates/macros/src/generic_builder.rs +++ b/crates/macros/src/generic_builder.rs @@ -3,7 +3,9 @@ use quote::{quote, ToTokens}; use syn::{parenthesized, parse::Parse, parse_macro_input, punctuated::Punctuated, Token}; use crate::{ - general::{field_name, wrap_by_option, AttributeInfo, DefaultAssignment, Structure}, + general::{ + field_name, wrap_by_option, AttributeInfo, DefaultAssignment, DeriveInfo, Structure, + }, propagate_err, }; @@ -14,7 +16,7 @@ pub(super) fn make_derive(item: TokenStream) -> TokenStream { let generic_builder = structure.create_generic_builder(&attribute_info.struct_info); let mut tokens = proc_macro2::TokenStream::new(); - generic_builder.build_gbuilder_struct(&mut tokens); + generic_builder.build_gbuilder_struct(&mut tokens, &attribute_info); propagate_err!(generic_builder.build_gbuilder_impl(&mut tokens, &attribute_info, &structure)); @@ -28,7 +30,11 @@ impl Structure { generic_builder } - fn build_gbuilder_struct(&self, tokens: &mut proc_macro2::TokenStream) { + fn build_gbuilder_struct( + &self, + tokens: &mut proc_macro2::TokenStream, + attribute_info: &AttributeInfo, + ) { let Structure { ref visibility, ref struct_token, @@ -38,7 +44,14 @@ impl Structure { .. } = self; + let derive_info = attribute_info + .struct_info + .derive_info + .as_ref() + .map(ToTokens::to_token_stream) + .unwrap_or_default(); quote! { + #derive_info #visibility #struct_token #name } .to_tokens(tokens); @@ -47,7 +60,18 @@ impl Structure { let mut fields = fields.clone(); fields.iter_mut().for_each(|field| { field.attrs.clear(); - field.ty = wrap_by_option(field.ty.clone()) + field.ty = if let Some(use_gbuilder) = attribute_info + .fields_info + .get(&field_name(field)) + .and_then(|field_info| field_info.use_gbuilder.as_ref()) + { + syn::Type::Path(syn::TypePath { + qself: None, + path: use_gbuilder.clone(), + }) + } else { + wrap_by_option(field.ty.clone()) + } }); fields.to_tokens(tokens) }); @@ -65,16 +89,14 @@ impl Structure { .filter(|field| !attribute_info.is_hidden_field(field)) .collect::>(); - let fn_new = self.build_fn_new(); - let fn_contains_field = self.build_fn_contains_field(&unhidden_fields)?; - let fn_set_value = self.build_fn_set_value(&unhidden_fields); + let fn_new = self.build_fn_new(attribute_info); + let fn_set_value = self.build_fn_set_value(&unhidden_fields, attribute_info); let fn_try_build = self.build_fn_try_build(target_struct, attribute_info); let gbuilder_ident = &self.name; quote! { impl #gbuilder_ident { #fn_new - #fn_contains_field #fn_set_value #fn_try_build } @@ -90,12 +112,25 @@ impl Structure { Ok(()) } - fn build_fn_new(&self) -> proc_macro2::TokenStream { + fn build_fn_new( + &self, + attribute_info: &AttributeInfo, + ) -> proc_macro2::TokenStream { let init_members: Punctuated = self .fields .iter() .map(|field| field.ident.clone().expect("Fields should be named")) - .map(|field_ident| quote! { #field_ident: None }) + .map(|field_ident| { + if let Some(use_gbuilder) = attribute_info + .fields_info + .get(&field_ident.to_string()) + .and_then(|field_info| field_info.use_gbuilder.as_ref()) + { + quote! { #field_ident: #use_gbuilder::new() } + } else { + quote! { #field_ident: None } + } + }) .collect(); let visibility = &self.visibility; @@ -108,41 +143,20 @@ impl Structure { } } - fn build_fn_contains_field( - &self, - unhidden_fields: &Punctuated<&syn::Field, Token![,]>, - ) -> syn::Result { - let quoted_field_names: Punctuated = unhidden_fields - .iter() - .map(|field| { - field - .ident - .clone() - .expect("Fields should be named") - .to_string() - }) - .collect(); - let field_count = quoted_field_names.len(); - - let visibility = &self.visibility; - Ok(quote! { - const FIELD_NAMES: [&'static str; #field_count] = [ - #quoted_field_names - ]; - - #visibility fn contains_field(&self, field_name: &str) -> bool { - Self::FIELD_NAMES.contains(&field_name) - } - }) - } - fn build_fn_set_value( &self, unhidden_fields: &Punctuated<&syn::Field, Token![,]>, + attribute_info: &AttributeInfo, ) -> proc_macro2::TokenStream { - let set_members: Punctuated = unhidden_fields - .clone() - .into_iter() + let (simple_fields, associated_gbuilders): (Vec<&syn::Field>, Vec<&syn::Field>) = + unhidden_fields.clone().into_iter().partition(|field| { + attribute_info + .fields_info + .get(&field_name(field)) + .is_none_or(|field_info| field_info.use_gbuilder.is_none()) + }); + + let set_members: Punctuated = simple_fields.into_iter() .map(|field| { let field_ident = field.ident.clone().expect("Fields should be named"); let field_name = field_ident.to_string(); @@ -150,16 +164,37 @@ impl Structure { }) .collect(); - let err_expression = quote! { - Err(shared::error::ConversionError::UnknownField { field_name: field_name.to_string() }) + let associated_gbuilder_assignments: Vec = associated_gbuilders + .into_iter() + .map(|field| { + let field_ident = field.ident.as_ref().expect("Fields should be named"); + + quote! { + match self.#field_ident.set_value(field_name, value) { + Ok(_) => return Ok(self), + Err(shared::error::ConversionError::UnknownField{ value: returned_value,.. }) => value = returned_value, + Err(err) => return Err(err), + } + } + }) + .collect(); + + let alternative_expression = quote! { + #(#associated_gbuilder_assignments)* + + return Err(shared::error::ConversionError::UnknownField { field_name: field_name.to_string(), value }) }; let function_body = if set_members.is_empty() { - err_expression + quote! { + #alternative_expression + } } else { quote! { match field_name { #set_members, - _ => return #err_expression + _ => { + #alternative_expression + } } Ok(self) } @@ -170,7 +205,7 @@ impl Structure { #visibility fn set_value( &mut self, field_name: &str, - value: shared::value::Value + mut value: shared::value::Value ) -> std::result::Result<&mut Self, shared::error::ConversionError> { #function_body } @@ -193,6 +228,15 @@ impl Structure { let field_name = field_ident.to_string(); let mut line = quote! { #field_ident: self.#field_ident }; + let is_associated_gbuilder = attribute_info + .fields_info + .get(&field_name) + .and_then(|field_info| field_info.use_gbuilder.as_ref()) + .is_some(); + if is_associated_gbuilder { + line = quote! { #line.try_build() } + }; + if let Some(default_assignment) = attribute_info .fields_info .get(&field_name) @@ -200,18 +244,20 @@ impl Structure { { match default_assignment { DefaultAssignment::Expression(expr) => { - quote! { .unwrap_or_else(|| #expr) }.to_tokens(&mut line) + line = quote! { #line.unwrap_or_else(|| #expr) } } DefaultAssignment::FunctionCall(function_path) => { - quote! { .unwrap_or_else(#function_path) }.to_tokens(&mut line) + line = quote! { #line.unwrap_or_else(#function_path) } } DefaultAssignment::DefaultCall => { - quote! { .unwrap_or_default() }.to_tokens(&mut line); + line = quote! { #line.unwrap_or_default() } } } + } else if is_associated_gbuilder { + line = quote! { #line? } } else { - quote! { .ok_or("The field '".to_string() + #field_name + "' should be set")? } - .to_tokens(&mut line); + let err_msg = format!("The field '{field_name}' should be set"); + line = quote! { #line.ok_or(#err_msg)? } } line @@ -240,12 +286,14 @@ impl AttributeInfo { struct StructInfo { name: syn::Ident, + derive_info: Option, } impl Parse for StructInfo { fn parse(input: syn::parse::ParseStream) -> syn::Result { let beginning_span = input.span(); - let mut name; + let mut name = None; + let mut derive_info = None; loop { let ident = input.parse::()?; @@ -256,6 +304,9 @@ impl Parse for StructInfo { let _paren = parenthesized!(content in input); name = Some(content.parse()?); } + "derive" => { + derive_info = Some(DeriveInfo::from_ident_and_input(ident, &input)?); + } _ => return Err(syn::Error::new(ident.span(), "Unknown attribute")), } @@ -273,19 +324,21 @@ impl Parse for StructInfo { )); }; - Ok(Self { name }) + Ok(Self { name, derive_info }) } } struct FieldInfo { hidden: bool, default: Option, + use_gbuilder: Option, } impl Parse for FieldInfo { fn parse(input: syn::parse::ParseStream) -> syn::Result { let mut hidden = false; let mut default = None; + let mut use_gbuilder = None; loop { let ident = input.parse::()?; @@ -301,6 +354,11 @@ impl Parse for FieldInfo { default = Some(DefaultAssignment::DefaultCall) } } + "use_gbuilder" => { + let content; + let _paren = parenthesized!(content in input); + use_gbuilder = Some(content.parse()?); + } _ => return Err(syn::Error::new(ident.span(), "Unknown attribute")), } @@ -311,6 +369,10 @@ impl Parse for FieldInfo { } } - Ok(Self { default, hidden }) + Ok(Self { + default, + hidden, + use_gbuilder, + }) } } diff --git a/crates/render/src/text.rs b/crates/render/src/text.rs index ad7ba7c..cde0da1 100644 --- a/crates/render/src/text.rs +++ b/crates/render/src/text.rs @@ -221,6 +221,10 @@ impl TextRect { if self.current_paragraph.is_empty() { self.current_paragraph = self.paragraphs.pop_front().unwrap_or_default(); paragraph_num += 1; + + if self.current_paragraph.is_empty() { + break; + } } } diff --git a/crates/render/src/widget/flex_container.rs b/crates/render/src/widget/flex_container.rs index 4f88d59..9d930d6 100644 --- a/crates/render/src/widget/flex_container.rs +++ b/crates/render/src/widget/flex_container.rs @@ -13,7 +13,7 @@ use super::{CompileState, Draw, Widget, WidgetConfiguration}; #[derive(macros::GenericBuilder, derive_builder::Builder, Clone)] #[builder(pattern = "owned")] -#[gbuilder(name(GBuilderFlexContainer))] +#[gbuilder(name(GBuilderFlexContainer), derive(Clone))] pub struct FlexContainer { #[builder(private, setter(skip))] #[gbuilder(hidden, default(None))] @@ -252,7 +252,7 @@ impl Draw for FlexContainer { } #[derive(macros::GenericBuilder, Debug, Default, Clone)] -#[gbuilder(name(GBuilderAlignment))] +#[gbuilder(name(GBuilderAlignment), derive(Clone))] pub struct Alignment { pub horizontal: Position, pub vertical: Position, diff --git a/crates/render/src/widget/image.rs b/crates/render/src/widget/image.rs index cc7e4ca..82e198c 100644 --- a/crates/render/src/widget/image.rs +++ b/crates/render/src/widget/image.rs @@ -1,4 +1,4 @@ -use config::display::ImageProperty; +use config::display::{GBuilderImageProperty, ImageProperty}; use log::warn; use crate::{ @@ -10,7 +10,7 @@ use crate::{ use super::{CompileState, Draw, WidgetConfiguration}; #[derive(macros::GenericBuilder, Clone)] -#[gbuilder(name(GBuilderWImage))] +#[gbuilder(name(GBuilderWImage), derive(Clone))] pub struct WImage { #[gbuilder(hidden, default(Image::Unknown))] content: Image, @@ -20,7 +20,7 @@ pub struct WImage { #[gbuilder(hidden, default(0))] height: usize, - #[gbuilder(default)] + #[gbuilder(use_gbuilder(GBuilderImageProperty), default)] property: ImageProperty, } diff --git a/crates/render/src/widget/text.rs b/crates/render/src/widget/text.rs index ed943ac..119d5f8 100644 --- a/crates/render/src/widget/text.rs +++ b/crates/render/src/widget/text.rs @@ -1,4 +1,4 @@ -use config::text::TextProperty; +use config::text::{GBuilderTextProperty, TextProperty}; use dbus::text::Text; use log::warn; use shared::{error::ConversionError, value::TryFromValue}; @@ -19,7 +19,7 @@ pub struct WText { #[gbuilder(hidden, default(None))] content: Option, - #[gbuilder(default)] + #[gbuilder(use_gbuilder(GBuilderTextProperty), default)] property: TextProperty, } @@ -34,6 +34,16 @@ impl Clone for WText { } } +impl Clone for GBuilderWText { + fn clone(&self) -> Self { + Self { + kind: self.kind.as_ref().cloned(), + content: None, + property: self.property.clone(), + } + } +} + #[derive(Clone, derive_more::Display)] pub enum WTextKind { #[display("title")] diff --git a/crates/shared/src/error.rs b/crates/shared/src/error.rs index 5e96d71..5a160c0 100644 --- a/crates/shared/src/error.rs +++ b/crates/shared/src/error.rs @@ -1,7 +1,9 @@ +use crate::value::Value; + #[derive(Debug, derive_more::Display)] pub enum ConversionError { #[display("The '{field_name}' field is unknown")] - UnknownField { field_name: String }, + UnknownField { field_name: String, value: Value }, #[display("Provided invalid value. Expected [{expected}], but given [{actual}]")] InvalidValue { expected: &'static str, diff --git a/crates/shared/src/value.rs b/crates/shared/src/value.rs index a87b38a..1cbc4e8 100644 --- a/crates/shared/src/value.rs +++ b/crates/shared/src/value.rs @@ -4,7 +4,7 @@ use crate::error::ConversionError; pub enum Value { UInt(usize), String(String), - Any(Box), + Any(Box), } pub trait TryFromValue: Sized + 'static { @@ -29,7 +29,7 @@ pub trait TryDowncast { fn try_downcast(self) -> Result; } -impl TryDowncast for Box { +impl TryDowncast for Box { fn try_downcast(self) -> Result { Ok(*self .downcast() @@ -51,7 +51,7 @@ macro_rules! impl_from_for_value { impl_from_for_value!(usize => Value::UInt); impl_from_for_value!(String => Value::String); -impl_from_for_value!(Box => Value::Any); +impl_from_for_value!(Box => Value::Any); impl TryFromValue for usize { fn try_from_uint(value: usize) -> Result {