mod layout;
mod node;
mod reconcile;
mod theme;
pub use layout::measure_er_diagram;
pub use node::ErDiagramNode;
pub use reconcile::reconcile_er_diagram;
pub use theme::ErDiagramTheme;
use crate::core::element::{Element, ElementKind};
use crate::style::{BorderStyle, Length, Padding, Style};
use std::sync::Arc;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum ErCardinality {
ZeroOrOne,
ExactlyOne,
ZeroOrMore,
#[default]
OneOrMore,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct ErAttribute {
pub ty: Arc<str>,
pub name: Arc<str>,
pub pk: bool,
pub fk: bool,
pub uk: bool,
}
impl ErAttribute {
pub fn new(ty: impl Into<Arc<str>>, name: impl Into<Arc<str>>) -> Self {
Self {
ty: ty.into(),
name: name.into(),
pk: false,
fk: false,
uk: false,
}
}
pub fn pk(mut self) -> Self {
self.pk = true;
self
}
pub fn fk(mut self) -> Self {
self.fk = true;
self
}
pub fn uk(mut self) -> Self {
self.uk = true;
self
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct ErEntity {
pub name: Arc<str>,
pub attributes: Vec<ErAttribute>,
}
impl ErEntity {
pub fn new(name: impl Into<Arc<str>>) -> Self {
Self {
name: name.into(),
attributes: Vec::new(),
}
}
pub fn attribute(mut self, attribute: ErAttribute) -> Self {
self.attributes.push(attribute);
self
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct ErRelation {
pub left: Arc<str>,
pub right: Arc<str>,
pub left_cardinality: ErCardinality,
pub right_cardinality: ErCardinality,
pub label: Option<Arc<str>>,
}
#[derive(Clone)]
pub struct ErDiagram {
pub(crate) entities: Arc<[ErEntity]>,
pub(crate) relations: Arc<[ErRelation]>,
pub(crate) style: Style,
pub(crate) entity_style: Style,
pub(crate) edge_style: Style,
pub(crate) label_style: Style,
pub(crate) border_style: BorderStyle,
pub(crate) padding: Padding,
pub(crate) node_padding: Padding,
pub(crate) layer_gap: u16,
pub(crate) node_gap: u16,
pub(crate) max_node_width: u16,
pub(crate) theme: ErDiagramTheme,
pub(crate) width: Length,
pub(crate) height: Length,
}
impl Default for ErDiagram {
fn default() -> Self {
Self {
entities: Arc::new([]),
relations: Arc::new([]),
style: Style::default(),
entity_style: Style::default(),
edge_style: Style::default(),
label_style: Style::default(),
border_style: BorderStyle::Plain,
padding: Padding::default(),
node_padding: (0, 1).into(),
layer_gap: 4,
node_gap: 4,
max_node_width: 32,
theme: ErDiagramTheme::default(),
width: Length::Auto,
height: Length::Auto,
}
}
}
impl ErDiagram {
pub fn new() -> Self {
Self::default()
}
pub fn entities(mut self, entities: impl IntoIterator<Item = ErEntity>) -> Self {
self.entities = entities.into_iter().collect::<Vec<_>>().into();
self
}
pub fn relations(mut self, relations: impl IntoIterator<Item = ErRelation>) -> Self {
self.relations = relations.into_iter().collect::<Vec<_>>().into();
self
}
pub fn entity(mut self, name: impl Into<Arc<str>>) -> Self {
let mut v = self.entities.to_vec();
v.push(ErEntity::new(name));
self.entities = v.into();
self
}
pub fn attribute(
mut self,
entity: impl AsRef<str>,
ty: impl Into<Arc<str>>,
name: impl Into<Arc<str>>,
) -> Self {
self.update_entity(entity.as_ref(), |e| {
e.attributes.push(ErAttribute::new(ty, name))
});
self
}
pub fn relation(
mut self,
left: impl Into<Arc<str>>,
right: impl Into<Arc<str>>,
left_cardinality: ErCardinality,
right_cardinality: ErCardinality,
label: impl Into<Option<Arc<str>>>,
) -> Self {
let mut v = self.relations.to_vec();
v.push(ErRelation {
left: left.into(),
right: right.into(),
left_cardinality,
right_cardinality,
label: label.into(),
});
self.relations = v.into();
self
}
pub fn style(mut self, style: Style) -> Self {
self.style = style;
self
}
pub fn entity_style(mut self, style: Style) -> Self {
self.entity_style = style;
self
}
pub fn edge_style(mut self, style: Style) -> Self {
self.edge_style = style;
self
}
pub fn label_style(mut self, style: Style) -> Self {
self.label_style = style;
self
}
pub fn border_style(mut self, style: BorderStyle) -> Self {
self.border_style = style;
self
}
pub fn padding(mut self, padding: impl Into<Padding>) -> Self {
self.padding = padding.into();
self
}
pub fn node_padding(mut self, padding: impl Into<Padding>) -> Self {
self.node_padding = padding.into();
self
}
pub fn max_node_width(mut self, width: u16) -> Self {
self.max_node_width = width.max(1);
self
}
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
pub fn height(mut self, height: Length) -> Self {
self.height = height;
self
}
fn update_entity(&mut self, name: &str, f: impl FnOnce(&mut ErEntity)) {
let mut entities = self.entities.to_vec();
let index = entities
.iter()
.position(|e| e.name.as_ref() == name)
.unwrap_or_else(|| {
entities.push(ErEntity::new(name.to_owned()));
entities.len() - 1
});
f(&mut entities[index]);
self.entities = entities.into();
}
}
impl From<ErDiagram> for Element {
fn from(value: ErDiagram) -> Self {
Element::new(ElementKind::ErDiagram(Box::new(value)))
}
}