Skip to content

Commit

Permalink
Merge 99a8a4b into 71bb0c0
Browse files Browse the repository at this point in the history
  • Loading branch information
reid-spencer committed Oct 31, 2023
2 parents 71bb0c0 + 99a8a4b commit 7a8d276
Show file tree
Hide file tree
Showing 34 changed files with 765 additions and 399 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ target
/.riddl-timestamp
doc/src/main/hugo/public/
*.lock
/hugo-input/
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
package com.reactific.riddl.commands

import com.reactific.riddl.language.AST.*
import com.reactific.riddl.utils.OutputFile
import com.reactific.riddl.utils.{OutputFile, Timer}

import java.nio.file.Path
import scala.collection.mutable
Expand Down Expand Up @@ -37,12 +37,15 @@ trait TranslatingState[OF <: OutputFile] {
path.resolve(nm)
}

def writeFiles: Seq[Path] = {
files.foreach(_.write())
files.map(_.filePath).toSeq
def writeFiles(timeEach: Boolean): Unit = {
files.foreach { (file: OF) =>
Timer.time(s"Writing file: ${file.filePath}", timeEach) {
file.write()
}
}
}

def addFile(file: OF): this.type = {files.append(file); this}
def addFile(file: OF): this.type = { files.append(file); this }

def makeDefPath(
definition: Definition,
Expand All @@ -63,16 +66,13 @@ object TranslationCommand {
}
}

/** An abstract base class for translation style commands. That is, they
* translate an input file into an output directory of files.
*
* @param name
* The name of the command to pass to [[CommandPlugin]]
* @tparam OPT
* The option type for the command
*/
abstract class TranslationCommand[OPT <: TranslationCommand.Options : ClassTag](name: String)
extends PassCommand[OPT](name) {


}
/** An abstract base class for translation style commands. That is, they translate an input file into an output
* directory of files.
*
* @param name
* The name of the command to pass to [[CommandPlugin]]
* @tparam OPT
* The option type for the command
*/
abstract class TranslationCommand[OPT <: TranslationCommand.Options: ClassTag](name: String)
extends PassCommand[OPT](name) {}
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"
}
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) {

}
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()
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -12,29 +12,27 @@ import com.reactific.riddl.utils.FileBuilder

import scala.reflect.ClassTag

/** A trait to be implemented by the user of SequenceDiagram that provides information that can only be provided from
* outside SequenceDiagram itself. Note that the PassesResult from running the standard passes is required.
/** A trait to be implemented by the user of UseCaseDiagram that provides information that can only be provided from
* outside UseCaseDiagram itself. Note that the PassesResult from running the standard passes is required.
*/
trait SequenceDiagramSupport {
trait UseCaseDiagramSupport {
def passesResult: PassesResult
def getDefinitionFor[T <: Definition: ClassTag](pathId: PathIdentifier, parent: Definition): Option[T] = {
passesResult.refMap.definitionOf[T](pathId, parent)
}
def makeDocLink(definition: Definition): String
}

/** A class to generate the sequence diagrams for an Epic
/** A class to generate the sequence diagrams for an Epic's Use Case
* @param sds
* The SequenceDiagramSupport implementation that provides information for the SequenceDiagram
* @param epic
* The epic from which to draw use cases instances and convert into sequence diagrams
* @param parents
* The parents of the epic within the model
* The UseCaseeDiagramSupport implementation that provides information for the UseCaseDiagram
* @param useCase
* The UseCase from the AST to which this diagram applies
*/
@SuppressWarnings(Array("org.wartremover.warts.OptionPartial"))
case class SequenceDiagram(sds: SequenceDiagramSupport, useCase: UseCase) extends FileBuilder {
case class UseCaseDiagram(sds: UseCaseDiagramSupport, useCase: UseCase) extends FileBuilder {

final val indent_per_level = 4
private final val indent_per_level = 4

def generate: Seq[String] = {
sb.append("sequenceDiagram"); nl
Expand All @@ -46,7 +44,7 @@ case class SequenceDiagram(sds: SequenceDiagramSupport, useCase: UseCase) extend
sb.toString().split('\n').toSeq
}

def actorsFirst(a: (String, Definition), b: (String, Definition)): Boolean = {
private def actorsFirst(a: (String, Definition), b: (String, Definition)): Boolean = {
a._2 match
case _: User if b._2.isInstanceOf[User] => a._1 < b._1
case _: User => true
Expand Down Expand Up @@ -76,7 +74,7 @@ case class SequenceDiagram(sds: SequenceDiagramSupport, useCase: UseCase) extend
.toMap
}

def ndnt(width: Int = indent_per_level): String = {
private def ndnt(width: Int = indent_per_level): String = {
" ".repeat(width)
}

Expand Down
10 changes: 10 additions & 0 deletions diagrams/src/test/input/context-relationships.riddl
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 { ??? }
}
Loading

0 comments on commit 7a8d276

Please sign in to comment.