use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::LazyLock;
use std::sync::Mutex;
mod hold_serde {
use serde::{Deserialize, Deserializer, Serializer};
pub fn serialize<S: Serializer>(negated: &bool, ser: S) -> Result<S::Ok, S::Error> {
debug_assert!(*negated);
ser.serialize_str("never")
}
pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<bool, D::Error> {
let s: Option<String> = Option::deserialize(d)?;
Ok(s.as_deref() == Some("never"))
}
}
fn is_not_negated(negated: &bool) -> bool {
!*negated
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct SpytialDecorators {
pub constraints: Vec<Constraint>,
pub directives: Vec<Directive>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(untagged)]
pub enum Constraint {
Orientation(OrientationConstraint),
Align(AlignConstraint),
Cyclic(CyclicConstraint),
Group(GroupConstraint),
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(untagged)]
pub enum Directive {
AtomColor(AtomColorDirective),
Size(SizeDirective),
Icon(IconDirective),
EdgeStyle(EdgeStyleDirective),
Projection(ProjectionDirective),
Attribute(AttributeDirective),
HideField(HideFieldDirective),
HideAtom(HideAtomDirective),
InferredEdge(InferredEdgeDirective),
Tag(TagDirective),
Flag(FlagDirective),
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct OrientationConstraint {
pub orientation: OrientationParams,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct OrientationParams {
pub selector: String,
pub directions: Vec<String>,
#[serde(
rename = "hold",
default,
skip_serializing_if = "is_not_negated",
serialize_with = "hold_serde::serialize",
deserialize_with = "hold_serde::deserialize"
)]
pub negated: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct AlignConstraint {
pub align: AlignParams,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct AlignParams {
pub selector: String,
pub direction: String,
#[serde(
rename = "hold",
default,
skip_serializing_if = "is_not_negated",
serialize_with = "hold_serde::serialize",
deserialize_with = "hold_serde::deserialize"
)]
pub negated: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct CyclicConstraint {
pub cyclic: CyclicParams,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct CyclicParams {
pub selector: String,
pub direction: String,
#[serde(
rename = "hold",
default,
skip_serializing_if = "is_not_negated",
serialize_with = "hold_serde::serialize",
deserialize_with = "hold_serde::deserialize"
)]
pub negated: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct GroupConstraint {
pub group: GroupParams,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(untagged)]
pub enum GroupParams {
FieldBased {
field: String,
#[serde(rename = "groupOn")]
group_on: u32,
#[serde(rename = "addToGroup")]
add_to_group: u32,
#[serde(skip_serializing_if = "Option::is_none")]
selector: Option<String>,
#[serde(
rename = "hold",
default,
skip_serializing_if = "is_not_negated",
serialize_with = "hold_serde::serialize",
deserialize_with = "hold_serde::deserialize"
)]
negated: bool,
},
SelectorBased {
selector: String,
name: String,
#[serde(
rename = "hold",
default,
skip_serializing_if = "is_not_negated",
serialize_with = "hold_serde::serialize",
deserialize_with = "hold_serde::deserialize"
)]
negated: bool,
},
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct AtomColorDirective {
#[serde(rename = "atomColor")]
pub atom_color: AtomColorParams,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct AtomColorParams {
pub selector: String,
pub value: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct SizeDirective {
pub size: SizeParams,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct SizeParams {
pub selector: String,
pub height: u32,
pub width: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct IconDirective {
pub icon: IconParams,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct IconParams {
pub selector: String,
pub path: String,
#[serde(rename = "showLabels")]
pub show_labels: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct EdgeStyleDirective {
#[serde(rename = "edgeColor")]
pub edge_style: EdgeStyleParams,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct EdgeStyleParams {
pub field: String,
pub value: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub selector: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub filter: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub style: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub weight: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none", rename = "showLabel")]
pub show_label: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub hidden: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ProjectionDirective {
pub projection: ProjectionParams,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ProjectionParams {
pub sig: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct AttributeDirective {
pub attribute: AttributeParams,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct AttributeParams {
pub field: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub selector: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct HideFieldDirective {
#[serde(rename = "hideField")]
pub hide_field: HideFieldParams,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct HideFieldParams {
pub field: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub selector: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct HideAtomDirective {
#[serde(rename = "hideAtom")]
pub hide_atom: HideAtomParams,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct HideAtomParams {
pub selector: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct InferredEdgeDirective {
#[serde(rename = "inferredEdge")]
pub inferred_edge: InferredEdgeParams,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct InferredEdgeParams {
pub name: String,
pub selector: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct TagDirective {
pub tag: TagParams,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct TagParams {
#[serde(rename = "toTag")]
pub to_tag: String,
pub name: String,
pub value: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct FlagDirective {
pub flag: String,
}
pub trait HasSpytialDecorators {
fn decorators() -> SpytialDecorators;
}
impl<T: HasSpytialDecorators + ?Sized> HasSpytialDecorators for &T {
fn decorators() -> SpytialDecorators {
T::decorators()
}
}
pub struct DecoProbe<T>(
pub ::std::marker::PhantomData<T>,
);
impl<T: HasSpytialDecorators> DecoProbe<T> {
pub fn get(self) -> SpytialDecorators {
T::decorators()
}
}
pub trait DefaultDecorators {
fn get(self) -> SpytialDecorators;
}
impl<T> DefaultDecorators for DecoProbe<T> {
fn get(self) -> SpytialDecorators {
SpytialDecorators::default()
}
}
static TYPE_REGISTRY: LazyLock<Mutex<HashMap<String, SpytialDecorators>>> =
LazyLock::new(|| Mutex::new(HashMap::new()));
pub fn register_type_decorators(type_name: &str, decorators: SpytialDecorators) {
let mut registry = TYPE_REGISTRY
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
registry.insert(type_name.to_string(), decorators);
}
pub fn get_type_decorators(type_name: &str) -> Option<SpytialDecorators> {
let registry = TYPE_REGISTRY
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
registry.get(type_name).cloned()
}
pub fn to_yaml(decorators: &SpytialDecorators) -> Result<String, serde_yaml_ng::Error> {
serde_yaml_ng::to_string(decorators)
}
#[derive(Debug)]
pub struct SpytialDecoratorsBuilder {
constraints: Vec<Constraint>,
directives: Vec<Directive>,
}
impl Default for SpytialDecoratorsBuilder {
fn default() -> Self {
Self::new()
}
}
impl SpytialDecoratorsBuilder {
pub fn new() -> Self {
Self {
constraints: Vec::new(),
directives: Vec::new(),
}
}
pub fn orientation(mut self, selector: &str, directions: Vec<&str>, negated: bool) -> Self {
self.constraints
.push(Constraint::Orientation(OrientationConstraint {
orientation: OrientationParams {
selector: selector.to_string(),
directions: directions.iter().map(|s| s.to_string()).collect(),
negated,
},
}));
self
}
pub fn align(mut self, selector: &str, direction: &str, negated: bool) -> Self {
self.constraints.push(Constraint::Align(AlignConstraint {
align: AlignParams {
selector: selector.to_string(),
direction: direction.to_string(),
negated,
},
}));
self
}
pub fn cyclic(mut self, selector: &str, direction: &str, negated: bool) -> Self {
self.constraints.push(Constraint::Cyclic(CyclicConstraint {
cyclic: CyclicParams {
selector: selector.to_string(),
direction: direction.to_string(),
negated,
},
}));
self
}
pub fn group_field_based(
mut self,
field: &str,
group_on: u32,
add_to_group: u32,
selector: Option<&str>,
negated: bool,
) -> Self {
self.constraints.push(Constraint::Group(GroupConstraint {
group: GroupParams::FieldBased {
field: field.to_string(),
group_on,
add_to_group,
selector: selector.map(|s| s.to_string()),
negated,
},
}));
self
}
pub fn group_selector_based(mut self, selector: &str, name: &str, negated: bool) -> Self {
self.constraints.push(Constraint::Group(GroupConstraint {
group: GroupParams::SelectorBased {
selector: selector.to_string(),
name: name.to_string(),
negated,
},
}));
self
}
pub fn atom_color(mut self, selector: &str, value: &str) -> Self {
self.directives
.push(Directive::AtomColor(AtomColorDirective {
atom_color: AtomColorParams {
selector: selector.to_string(),
value: value.to_string(),
},
}));
self
}
pub fn size(mut self, selector: &str, height: u32, width: u32) -> Self {
self.directives.push(Directive::Size(SizeDirective {
size: SizeParams {
selector: selector.to_string(),
height,
width,
},
}));
self
}
pub fn icon(mut self, selector: &str, path: &str, show_labels: bool) -> Self {
self.directives.push(Directive::Icon(IconDirective {
icon: IconParams {
selector: selector.to_string(),
path: path.to_string(),
show_labels,
},
}));
self
}
#[allow(clippy::too_many_arguments)]
pub fn edge_style(
mut self,
field: &str,
value: &str,
selector: Option<&str>,
filter: Option<&str>,
style: Option<&str>,
weight: Option<f64>,
show_label: Option<bool>,
hidden: Option<bool>,
) -> Self {
self.directives
.push(Directive::EdgeStyle(EdgeStyleDirective {
edge_style: EdgeStyleParams {
field: field.to_string(),
value: value.to_string(),
selector: selector.map(|s| s.to_string()),
filter: filter.map(|s| s.to_string()),
style: style.map(|s| s.to_string()),
weight,
show_label,
hidden,
},
}));
self
}
pub fn projection(mut self, sig: &str) -> Self {
self.directives
.push(Directive::Projection(ProjectionDirective {
projection: ProjectionParams {
sig: sig.to_string(),
},
}));
self
}
pub fn attribute(mut self, field: &str, selector: Option<&str>) -> Self {
self.directives
.push(Directive::Attribute(AttributeDirective {
attribute: AttributeParams {
field: field.to_string(),
selector: selector.map(|s| s.to_string()),
},
}));
self
}
pub fn hide_field(mut self, field: &str, selector: Option<&str>) -> Self {
self.directives
.push(Directive::HideField(HideFieldDirective {
hide_field: HideFieldParams {
field: field.to_string(),
selector: selector.map(|s| s.to_string()),
},
}));
self
}
pub fn hide_atom(mut self, selector: &str) -> Self {
self.directives.push(Directive::HideAtom(HideAtomDirective {
hide_atom: HideAtomParams {
selector: selector.to_string(),
},
}));
self
}
pub fn inferred_edge(mut self, name: &str, selector: &str) -> Self {
self.directives
.push(Directive::InferredEdge(InferredEdgeDirective {
inferred_edge: InferredEdgeParams {
name: name.to_string(),
selector: selector.to_string(),
},
}));
self
}
pub fn flag(mut self, name: &str) -> Self {
self.directives.push(Directive::Flag(FlagDirective {
flag: name.to_string(),
}));
self
}
pub fn tag(mut self, to_tag: &str, name: &str, value: &str) -> Self {
self.directives.push(Directive::Tag(TagDirective {
tag: TagParams {
to_tag: to_tag.to_string(),
name: name.to_string(),
value: value.to_string(),
},
}));
self
}
pub fn include_decorators_from_type<T: HasSpytialDecorators>(mut self) -> Self {
let other_decorators = T::decorators();
self.constraints.extend(other_decorators.constraints);
self.directives.extend(other_decorators.directives);
self
}
pub fn extend_with(mut self, other: SpytialDecorators) -> Self {
self.constraints.extend(other.constraints);
self.directives.extend(other.directives);
self
}
pub fn build(self) -> SpytialDecorators {
SpytialDecorators {
constraints: self.constraints,
directives: self.directives,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_spytial_decorators_default() {
let decorators = SpytialDecorators::default();
assert!(decorators.constraints.is_empty());
assert!(decorators.directives.is_empty());
}
#[test]
fn test_yaml_serialization() {
let decorators = SpytialDecorators {
constraints: vec![
Constraint::Orientation(OrientationConstraint {
orientation: OrientationParams {
selector: "value".to_string(),
directions: vec!["above".to_string()],
negated: false,
},
}),
Constraint::Align(AlignConstraint {
align: AlignParams {
selector: "siblings".to_string(),
direction: "horizontal".to_string(),
negated: false,
},
}),
],
directives: vec![Directive::Flag(FlagDirective {
flag: "test_flag".to_string(),
})],
};
let yaml = to_yaml(&decorators).unwrap();
assert!(yaml.contains("orientation"));
assert!(yaml.contains("align"));
assert!(yaml.contains("flag"));
}
}