Skip to content

Commit

Permalink
feat: add aliases for custom layout of notification banner (#91)
Browse files Browse the repository at this point in the history
* feat: add way to derive GBuilder structs & add `Clone`s for all GBuilders

* feat: add alias syntax for custom layouts

* fix: missing safe stop during conversion from paragraph into lines

* feat: implement associated GenericBuilders

* fix(macros): use correct expression when set_member is empty
  • Loading branch information
JarKz authored Dec 24, 2024
1 parent f36f64c commit 2a75528
Show file tree
Hide file tree
Showing 16 changed files with 389 additions and 208 deletions.
4 changes: 2 additions & 2 deletions crates/config/src/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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))]
Expand Down Expand Up @@ -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))]
Expand Down
2 changes: 1 addition & 1 deletion crates/config/src/spacing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions crates/config/src/text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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))]
Expand Down
207 changes: 106 additions & 101 deletions crates/filetype/src/converter.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand All @@ -27,11 +28,57 @@ pub(super) fn convert_into_widgets(mut pairs: Pairs<Rule>) -> anyhow::Result<Wid
"In input should be a parsed Layout"
);

let node_type = pair.into_inner().next().unwrap();
convert_node_type(node_type)
let mut inner_pairs = pair.into_inner();
let mut alias_storage = HashMap::new();

let maybe_aliases = inner_pairs.next().unwrap();
let node_type = if maybe_aliases.as_rule() == Rule::AliasDefinitions {
convert_aliases(maybe_aliases, &mut alias_storage)?;
inner_pairs.next().unwrap()
} else {
maybe_aliases
};

convert_node_type(node_type, &alias_storage)
}

fn convert_aliases<'a>(
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<Rule>) -> anyhow::Result<Widget> {
fn convert_node_type<'a>(
node_type: Pair<'a, Rule>,
alias_storage: &'a HashMap<&'a str, GBuilder>,
) -> anyhow::Result<Widget> {
assert_eq!(
node_type.as_rule(),
Rule::NodeType,
Expand All @@ -41,26 +88,29 @@ fn convert_node_type(node_type: Pair<Rule>) -> anyhow::Result<Widget> {
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.");
}
}

Ok(widget_gbuilder.try_build()?.try_downcast()?)
}

fn convert_properties(node_type_pairs: &mut Pairs<Rule>) -> Vec<Property> {
fn convert_properties<'a>(
node_type_pairs: &mut Pairs<'a, Rule>,
alias_storage: &'a HashMap<&'a str, GBuilder>,
) -> Vec<Property> {
let _open_paren = node_type_pairs.next();

let properties_or_close_paren = node_type_pairs.next().unwrap();
Expand All @@ -81,7 +131,7 @@ fn convert_properties(node_type_pairs: &mut Pairs<Rule>) -> Vec<Property> {
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}"),
}
Expand All @@ -91,7 +141,10 @@ fn convert_properties(node_type_pairs: &mut Pairs<Rule>) -> Vec<Property> {
converted_properties
}

fn convert_property(property: Pair<Rule>) -> anyhow::Result<Property> {
fn convert_property<'a>(
property: Pair<'a, Rule>,
alias_storage: &'a HashMap<&'a str, GBuilder>,
) -> anyhow::Result<Property> {
assert_eq!(
property.as_rule(),
Rule::Property,
Expand All @@ -101,12 +154,15 @@ fn convert_property(property: Pair<Rule>) -> anyhow::Result<Property> {
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<Rule>) -> anyhow::Result<Value> {
fn convert_property_value<'a>(
property_value: Pair<'a, Rule>,
alias_storage: &'a HashMap<&'a str, GBuilder>,
) -> anyhow::Result<Value> {
assert_eq!(
property_value.as_rule(),
Rule::PropertyValue,
Expand All @@ -116,14 +172,19 @@ fn convert_property_value(property_value: Pair<Rule>) -> anyhow::Result<Value> {
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<Rule>) -> anyhow::Result<Vec<Widget>> {
fn convert_children<'a>(
node_type_pairs: &mut Pairs<'a, Rule>,
alias_storage: &'a HashMap<&'a str, GBuilder>,
) -> anyhow::Result<Vec<Widget>> {
let open_brace = node_type_pairs.next();

let mut children = None;
Expand All @@ -148,11 +209,14 @@ fn convert_children(node_type_pairs: &mut Pairs<Rule>) -> anyhow::Result<Vec<Wid

children
.into_inner()
.map(convert_node_type)
.map(|child| convert_node_type(child, alias_storage))
.collect::<anyhow::Result<Vec<Widget>>>()
}

fn convert_type_value(type_value: Pair<Rule>) -> anyhow::Result<Value> {
fn convert_type_value<'a>(
type_value: Pair<'a, Rule>,
alias_storage: &'a HashMap<&'a str, GBuilder>,
) -> anyhow::Result<GBuilder> {
assert_eq!(
type_value.as_rule(),
Rule::TypeValue,
Expand All @@ -162,91 +226,36 @@ fn convert_type_value(type_value: Pair<Rule>) -> anyhow::Result<Value> {
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<GBuilder> {
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<Property>) {
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> {
Expand All @@ -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<Box<dyn std::any::Any>> {
fn try_build(self) -> anyhow::Result<Box<dyn std::any::Any + Send + Sync>> {
macro_rules! implement_variants {
($($variant:ident into $dest_type:path),*) => {
match self {
Expand All @@ -293,28 +293,33 @@ impl GBuilder {
WText into Widget,
FlexContainer into Widget,

TextProperty into TextProperty,
ImageProperty into ImageProperty,

Spacing into Spacing,
Alignment into Alignment,
Border into Border
))
}
}

impl TryFrom<&str> for GBuilder {
impl TryFrom<(&str, &HashMap<&str, GBuilder>)> for GBuilder {
type Error = anyhow::Error;

fn try_from(value: &str) -> Result<Self, Self::Error> {
Ok(match value {
fn try_from(
(identifier, alias_storage): (&str, &HashMap<&str, GBuilder>),
) -> Result<Self, Self::Error> {
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}!")
}
}
})
}
}
Expand Down
Loading

0 comments on commit 2a75528

Please sign in to comment.