Skip to content

Commit

Permalink
Add relation attributes & use double lines for required instead of ch…
Browse files Browse the repository at this point in the history
…anging arrow lines
  • Loading branch information
Jesse Hoobergs committed Sep 19, 2021
1 parent 05080a1 commit dcd8259
Show file tree
Hide file tree
Showing 6 changed files with 102 additions and 31 deletions.
4 changes: 2 additions & 2 deletions erd/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,6 @@ impl std::convert::From<(String, String, String)> for RelationMember {
pub enum Expr {
/// Matches an entity with attributes
Entity(Ident, Vec<Attribute>),
/// Matches a relation with an optional name and members
Relation(Ident, Option<String>, Vec<RelationMember>),
/// Matches a relation with an optional name, members and attributes
Relation(Ident, Option<String>, Vec<RelationMember>, Vec<Attribute>),
}
4 changes: 2 additions & 2 deletions erd/src/erd.pest
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ attribute = { attribute_prefix ~ ident }
entity = { "entity" ~ ident ~ (!"\n\n" ~ "\n" ~ attribute)* }

relation_name = { (!")" ~ ANY)* }
relation = { "relation" ~ ident ~ ("(" ~ relation_name ~ ")")? ~ ((!"\n\n" ~ "\n") ~ relation_part)+ }
relation = { "relation" ~ ident ~ ("(" ~ relation_name ~ ")")? ~ ((!"\n\n" ~ "\n") ~ (member | attribute))+ }

relation_part = { cardinality ~ optionality ~ ident }
member = { cardinality ~ optionality ~ ident }
cardinality = { "multiple" | "one" | ("exactly(" ~ ASCII_DIGIT+ ~ ")" ) }
optionality = { "optional" | "required" }

Expand Down
48 changes: 35 additions & 13 deletions erd/src/erd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,14 +125,14 @@ impl ToDot for Entity {
statements.push(dot::Statement::Edge(dot::EdgeStatement {
left: self.name.clone().into(),
right: dot::EdgeRHS {
r#type: dot::EdgeType::Directional,
r#type: dot::EdgeType::Normal,
id: format!("{}_{}", entity_name, attribute_name),
right: Box::new(None),
},
attributes: Some(dot::AttributeList {
content: dot::AList(vec![dot::AListItem {
key: "dir".into(),
value: "none".into(),
key: "len".into(),
value: "1.00".to_string(),
}]),
tail: Box::new(None),
}),
Expand Down Expand Up @@ -186,14 +186,14 @@ impl ToDot for Relation {
statements.push(dot::Statement::Edge(dot::EdgeStatement {
left: self.name.clone().into(),
right: dot::EdgeRHS {
r#type: dot::EdgeType::Directional,
r#type: dot::EdgeType::Normal,
id: format!("{}_{}", relation_name, attribute_name),
right: Box::new(None),
},
attributes: Some(dot::AttributeList {
content: dot::AList(vec![dot::AListItem {
key: "dir".into(),
value: "none".into(),
key: "len".into(),
value: "1.00".to_string(),
}]),
tail: Box::new(None),
}),
Expand All @@ -209,17 +209,17 @@ impl ToDot for Relation {
statements.push(dot::Statement::Edge(dot::EdgeStatement {
left: self.name.clone().into(),
right: dot::EdgeRHS {
r#type: dot::EdgeType::Directional,
r#type: dot::EdgeType::Normal,
id: member.entity.clone().into(),
right: Box::new(None),
},
attributes: Some(dot::AttributeList {
content: dot::AList(vec![
dot::AListItem {
key: "arrowhead".into(),
key: "color".into(),
value: match member.optionality {
RelationOptionality::Optional => "odot",
RelationOptionality::Required => "tee",
RelationOptionality::Optional => "black",
RelationOptionality::Required => "\"black:invis:black\"",
}
.into(),
},
Expand Down Expand Up @@ -264,11 +264,11 @@ impl std::convert::TryFrom<Vec<Expr>> for ERD {
let relations = v
.iter()
.filter_map(|expr| match expr {
Expr::Relation(name, label, members) => Some(Relation {
Expr::Relation(name, label, members, attributes) => Some(Relation {
name: name.clone(),
label: label.clone(),
members: members.clone(),
attributes: Vec::new(), // TODO
attributes: attributes.clone(), // TODO
}),
_ => None,
})
Expand Down Expand Up @@ -302,6 +302,28 @@ impl ToDot for ERD {

statements.push(dot::Statement::ID("layout".into(), "neato".into()));
statements.push(dot::Statement::ID("forcelabels".into(), "true".into()));
statements.push(dot::Statement::ID("overlap".into(), "scale".into()));

statements.push(dot::Statement::Attribute(dot::AttributeStatement {
r#type: dot::AttributeStatementType::Graph,
attributes: dot::AttributeList {
content: dot::AList(vec![
dot::AListItem {
key: "pad".into(),
value: "0.5".to_string(),
},
dot::AListItem {
key: "nodesep".into(),
value: "1".to_string(),
},
dot::AListItem {
key: "ranksep".into(),
value: "2".to_string(),
},
]),
tail: Box::new(None),
},
}));

statements.extend(self.entities.to_dot_statements());
statements.extend(self.relations.to_dot_statements());
Expand All @@ -314,7 +336,7 @@ impl ERD {
pub fn to_dot(&self) -> dot::Graph {
dot::Graph {
strict: false,
r#type: dot::GraphType::Directional,
r#type: dot::GraphType::Normal,
id: None, // TODO
statements: self.to_dot_statements(),
}
Expand Down
6 changes: 3 additions & 3 deletions erd/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ mod test {
let path = path.unwrap().path();
let expr = parse_file(&path)?;
let erd: Result<ERD, _> = expr.try_into();
println!("{:?}", erd);
std::fs::write("../examples/tmp.dot", erd.unwrap().to_dot().to_string())
.expect("failed writing");
let dot = erd.unwrap().to_dot().to_string();
println!("{}", dot);
std::fs::write("../examples/tmp.dot", dot).expect("failed writing");
let output = std::process::Command::new("dot")
.arg("-Tsvg")
.arg("../examples/tmp.dot")
Expand Down
41 changes: 30 additions & 11 deletions erd/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,13 @@ pub struct ParserNode<'i> {
pub enum ParserExpr {
/// (Name, Vec<("id" | "attribute", Name)> )
Entity(String, Vec<(String, String)>),
/// (Name, Optional label, Vec<RelationMembers>)
Relation(String, Option<String>, Vec<(String, String, String)>),
/// (Name, Optional label, Vec<RelationMembers>, Vec<("id" | "attribute", Name)>)
Relation(
String,
Option<String>,
Vec<(String, String, String)>,
Vec<(String, String)>,
),
}

impl<'i> std::convert::From<ParserNode<'i>> for ast::Expr {
Expand All @@ -36,10 +41,11 @@ impl<'i> std::convert::From<ParserExpr> for ast::Expr {
name.into(),
attributes.into_iter().map(|a| a.into()).collect(),
),
ParserExpr::Relation(name, label_option, members) => ast::Expr::Relation(
ParserExpr::Relation(name, label_option, members, attributes) => ast::Expr::Relation(
name.into(),
label_option.map(|l| l.into()),
members.into_iter().map(|m| m.into()).collect(),
attributes.into_iter().map(|m| m.into()).collect(),
),
}
}
Expand Down Expand Up @@ -120,6 +126,7 @@ fn consume_expression(expression: Pair<Rule>) -> Result<ParserNode, Vec<Error<Ru
let pair = pairs.next().unwrap();
let name = pair.as_str().trim().to_string();
let mut members = Vec::new();
let mut attributes = Vec::new();
let mut label: Option<String> = None;
let peek = pairs.peek();
if let Some(pair) = peek {
Expand All @@ -128,17 +135,29 @@ fn consume_expression(expression: Pair<Rule>) -> Result<ParserNode, Vec<Error<Ru
label = Some(pair.as_str().to_string());
}
}
for member in pairs {
let mut pairs = member.into_inner();
members.push((
pairs.next().unwrap().as_str().to_string(),
pairs.next().unwrap().as_str().to_string(),
pairs.next().unwrap().as_str().to_string(),
))
for item in pairs {
match item.as_rule() {
Rule::member => {
let mut pairs = item.into_inner();
members.push((
pairs.next().unwrap().as_str().to_string(),
pairs.next().unwrap().as_str().to_string(),
pairs.next().unwrap().as_str().to_string(),
))
}
Rule::attribute => {
let mut pairs = item.into_inner();
attributes.push((
pairs.next().unwrap().as_str().to_string(),
pairs.next().unwrap().as_str().to_string(),
))
}
_ => unreachable!(),
}
}

Ok(ParserNode {
expr: ParserExpr::Relation(name, label, members),
expr: ParserExpr::Relation(name, label, members, attributes),
span: pair.as_span(),
})
}
Expand Down
30 changes: 30 additions & 0 deletions examples/circus.erd
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
entity Elephant
attribute name
attribute age
attribute gender

entity Food
attribute name

entity Vendor
attribute name

entity Vaccination
attribute name

relation Sells
multiple required Food
multiple optional Vendor

relation Mother(Is mother of)
one required Elephant
multiple optional Elephant

relation Eats
multiple required Elephant
one required Food

relation Shot(Got vaccin)
multiple optional Elephant
multiple optional Vaccination
attribute date

0 comments on commit dcd8259

Please sign in to comment.