-
-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
34 changed files
with
765 additions
and
399 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,3 +17,4 @@ target | |
/.riddl-timestamp | ||
doc/src/main/hugo/public/ | ||
*.lock | ||
/hugo-input/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
127 changes: 127 additions & 0 deletions
127
diagrams/src/main/scala/com/reactific/riddl/diagrams/DiagramsPass.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
/* | ||
* Copyright 2023 Ossum, Inc. | ||
* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package com.reactific.riddl.diagrams | ||
|
||
import com.reactific.riddl.passes.{Pass, PassInfo, PassInput, PassOutput, PassesOutput} | ||
import com.reactific.riddl.language.AST.* | ||
import com.reactific.riddl.language.{AST, Messages} | ||
|
||
import scala.collection.mutable | ||
|
||
/** The information needed to generate a Data Flow Diagram. DFDs are generated | ||
* for each [[Context]] and consist of the streaming components that that | ||
* are connected. | ||
* | ||
*/ | ||
case class DataFlowDiagramData() | ||
|
||
/** The information needed to generate a Use Case Diagram. The diagram for a | ||
* use case is very similar to a Sequence Diagram showing the interactions | ||
* between involved components of the model. | ||
*/ | ||
case class UseCaseDiagramData() | ||
|
||
/** The information needed to generate a Context Diagram showing the relationships | ||
* between bounded contexts | ||
*/ | ||
case class ContextDiagramData( | ||
aggregates: Seq[Entity] = Seq.empty, | ||
relationships: Seq[(Context, String)] | ||
) | ||
|
||
case class DiagramsPassOutput( | ||
messages: Messages.Messages = Messages.empty, | ||
dataFlowDiagrams: Map[Context, DataFlowDiagramData] = Map.empty, | ||
userCaseDiagrams: Map[Epic, Seq[UseCaseDiagramData]] = Map.empty, | ||
contextDiagrams: Map[Context, ContextDiagramData] = Map.empty | ||
) extends PassOutput | ||
|
||
class DiagramsPass(input: PassInput, outputs: PassesOutput) extends Pass(input, outputs) { | ||
|
||
def name: String = DiagramsPass.name | ||
|
||
private val refMap = outputs.refMap | ||
private val symTab = outputs.symbols | ||
|
||
private val dataFlowDiagrams: mutable.HashMap[Context, DataFlowDiagramData] = mutable.HashMap.empty | ||
private val useCaseDiagrams: mutable.HashMap[Epic, Seq[UseCaseDiagramData]] = mutable.HashMap.empty | ||
private val contextDiagrams: mutable.HashMap[Context, ContextDiagramData] = mutable.HashMap.empty | ||
|
||
protected def process(definition: Definition, parents: mutable.Stack[Definition]): Unit = { | ||
definition match | ||
case c: Context => | ||
val aggregates = c.entities.filter(_.hasOption[EntityIsAggregate]) | ||
val relationships = findRelationships(c) | ||
contextDiagrams.put(c, ContextDiagramData(aggregates, relationships)) | ||
case _ => () | ||
} | ||
|
||
def findRelationships(context: AST.Context): Seq[(Context, String)] = { | ||
val statements: Seq[Statement] = pullStatements(context) ++ | ||
context.entities.flatMap(pullStatements) ++ | ||
context.adaptors.flatMap(pullStatements) ++ | ||
context.repositories.flatMap(pullStatements) ++ | ||
context.streamlets.flatMap(pullStatements) | ||
|
||
for { | ||
s <- statements | ||
ref <- getStatementReferences(s) | ||
definition <- refMap.definitionOf(ref, context) | ||
relationship <- inferRelationship(context, definition) | ||
} yield { | ||
context -> relationship | ||
} | ||
} | ||
|
||
def getStatementReferences(statement: Statement): Seq[Reference[Definition]] = { | ||
statement match | ||
case SendStatement(_, msg, portlet) => Seq(msg, portlet) | ||
case TellStatement(_, msg, processor) => Seq(msg, processor) | ||
case SetStatement(_, field, _) => Seq(field) | ||
case ReplyStatement(_, message) => Seq(message) | ||
case _ => Seq.empty | ||
} | ||
|
||
def inferRelationship(context: Context, definition: Definition): Option[String] = { | ||
definition match | ||
case m: Type if m.typ.isContainer && m.typ.hasDefinitions => | ||
symTab.contextOf(m).map(c => s"Uses ${m.identify} in ${c.identify}") | ||
case f: Field => | ||
symTab.contextOf(f).map(c => s"Sets ${f.identify} in ${c.identify}") | ||
case p: Portlet => | ||
symTab.contextOf(p).map(c => s"Sends to ${p.identify} in ${c.identify}") | ||
case p: Processor[?, ?] => | ||
symTab.contextOf(p).map(c => s"Tells to ${p.identify} in ${c.identify}") | ||
case _ => None | ||
} | ||
|
||
def pullStatements(processor: Processor[?, ?]): Seq[Statement] = { | ||
val s1 = processor.functions.flatMap(_.statements) | ||
val s2 = for { | ||
h <- processor.handlers | ||
omc <- h.clauses if omc.isInstanceOf[OnMessageClause] | ||
s <- omc.statements | ||
} yield {s} | ||
s1 ++ s2 | ||
} | ||
|
||
|
||
def postProcess(root: com.reactific.riddl.language.AST.RootContainer): Unit = {} | ||
|
||
def result: DiagramsPassOutput = { | ||
DiagramsPassOutput( | ||
messages.toMessages, | ||
dataFlowDiagrams.toMap, | ||
useCaseDiagrams.toMap, | ||
contextDiagrams.toMap | ||
) | ||
} | ||
} | ||
|
||
object DiagramsPass extends PassInfo { | ||
val name: String = "Diagrams" | ||
} |
6 changes: 6 additions & 0 deletions
6
diagrams/src/main/scala/com/reactific/riddl/diagrams/mermaid/C4Diagram.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package com.reactific.riddl.diagrams.mermaid | ||
import com.reactific.riddl.passes.PassesResult | ||
|
||
class C4Diagram (passesResult: PassesResult) { | ||
|
||
} |
99 changes: 99 additions & 0 deletions
99
diagrams/src/main/scala/com/reactific/riddl/diagrams/mermaid/DataFlowDiagram.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
package com.reactific.riddl.diagrams.mermaid | ||
import com.reactific.riddl.language.AST._ | ||
import com.reactific.riddl.passes.PassesResult | ||
|
||
/** Generate a data flow diagram Like this: | ||
* {{{ | ||
* flowchart TD | ||
* A[Christmas] -->|Get money| B(Go shopping) | ||
* B --> C{Let me think} | ||
* C -->|One| D[Laptop] | ||
* C -->|Two| E[iPhone] | ||
* C -->|Three| F[fa:fa-car Car] | ||
* }}} | ||
* @param pr | ||
* The PassesResult from running the standard passes to obtain all the collected ideas. | ||
*/ | ||
case class DataFlowDiagram(pr: PassesResult) { | ||
|
||
final private val newline: String = System.getProperty("line.separator") | ||
private val sb: StringBuilder = StringBuilder(1000) | ||
private val spacesPerIndent = 2 | ||
def indent(str: String, level: Int = 1): Unit = { | ||
sb.append(" ".repeat(level * spacesPerIndent)) | ||
sb.append(str) | ||
sb.append(newline) | ||
} | ||
|
||
private def makeNodeLabel(definition: Definition): Unit = { | ||
pr.symbols.parentOf(definition) match { | ||
case Some(parent) => | ||
val name = parent.id.value + "." + definition.id.value | ||
val id = definition match { | ||
case _: Outlet => s"Outlet $name" | ||
case _: Inlet => s"Inlet $name" | ||
case s: Streamlet => s"${s.kind} $name" | ||
case s: Connector => s"Connector $name" | ||
case d: Definition => s"${d.kind} $name" | ||
} | ||
val (left, right) = definition match { | ||
case _: Outlet => "[\\" -> "\\]" | ||
case _: Inlet => "[/" -> "/]" | ||
case _: Streamlet => "[[" -> "]]" | ||
case _: Processor[?, ?] => "[{" -> "}]" | ||
case _: Definition => "[" -> "]" | ||
} | ||
indent(s"${definition.id.value}$left\"$id\"$right") | ||
case _ => | ||
indent(s"${definition.id.value}") | ||
} | ||
} | ||
|
||
private[mermaid] def makeConnection(from: Outlet, to: Inlet, thick: Boolean, how: String): Unit = { | ||
val fromName = from.id.value | ||
val toName = to.id.value | ||
if thick then indent(s"$fromName == $how ==> $toName") | ||
else indent(s"$fromName -- $how --> $toName") | ||
} | ||
|
||
private[mermaid] def participants(connector: Connector): Seq[Definition] = { | ||
for { | ||
flows <- connector.flows | ||
to <- connector.to | ||
from <- connector.from | ||
typeDef <- pr.refMap.definitionOf[Type](flows, connector) | ||
toDef <- pr.refMap.definitionOf[Inlet](to, connector) | ||
fromDef <- pr.refMap.definitionOf[Outlet](from, connector) | ||
} yield { | ||
val to_users: Seq[Definition] = pr.usage.getUsers(toDef).flatMap { | ||
case oc: OnClause => pr.symbols.parentOf(oc).flatMap(pr.symbols.parentOf) | ||
case e: Entity => Seq.empty | ||
case _ => Seq.empty // FIXME: unfinished cases here | ||
} | ||
val from_users = pr.usage.getUsers(fromDef) | ||
(Seq(fromDef, toDef) ++ to_users ++ from_users).distinct.filterNot(_.isInstanceOf[Connector]) | ||
} | ||
}.getOrElse(Seq.empty) | ||
|
||
def generate(context: Context): String = { | ||
sb.append("flowchart LR").append(newline) | ||
val parts = for | ||
connector <- context.connections | ||
participants <- this.participants(connector) | ||
yield participants | ||
for part <- parts.distinct do makeNodeLabel(part) | ||
for { | ||
conn <- context.connections | ||
from <- conn.from | ||
to <- conn.to | ||
flows <- conn.flows | ||
typeDef <- pr.refMap.definitionOf[Type](flows, conn) | ||
toDef <- pr.refMap.definitionOf[Inlet](to, conn) | ||
fromDef <- pr.refMap.definitionOf[Outlet](from, conn) | ||
} do { | ||
makeConnection(fromDef, toDef, false, typeDef.identify) | ||
} | ||
sb.result() | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
domain foo { | ||
context A { ??? } | ||
context B { ??? } | ||
} | ||
|
||
domain bar { | ||
context C { ??? } | ||
context D { ??? } | ||
context E { ??? } | ||
} |
Oops, something went wrong.