diff --git a/export/dot.go b/export/dot.go index 451ac16..b09e31a 100644 --- a/export/dot.go +++ b/export/dot.go @@ -10,71 +10,101 @@ import ( type dotExporter struct { } +const nodeIDLabel = "export/dot/id" + func NewDotExporter() Exporter { return &dotExporter{} } // Export returns the f-mesh represented as digraph in DOT language -func (d *dotExporter) Export(fm *fmesh.FMesh) []byte { - // Setup graph +func (d *dotExporter) Export(fm *fmesh.FMesh) ([]byte, error) { + // Setup main graph graph := dot.NewGraph(dot.Directed) graph. Attr("layout", "dot"). Attr("splines", "ortho") - for componentName, c := range fm.Components() { + for _, component := range fm.Components() { // Component subgraph (wrapper) componentSubgraph := graph.NewSubgraph() componentSubgraph. NodeBaseAttrs(). Attr("width", "1.0").Attr("height", "1.0") componentSubgraph. - Attr("label", componentName). + Attr("label", component.Name()). Attr("cluster", "true"). Attr("style", "rounded"). Attr("color", "black"). Attr("bgcolor", "lightgrey"). Attr("margin", "20") - // Component node + // Create component node and subgraph (cluster) componentNode := componentSubgraph.Node() componentNode.Attr("label", "𝑓") - if c.Description() != "" { - componentNode.Attr("label", c.Description()) + if component.Description() != "" { + componentNode.Attr("label", component.Description()) } componentNode. Attr("color", "blue"). Attr("shape", "rect"). - Attr("group", componentName) + Attr("group", component.Name()) + + // Create nodes for input ports + for _, port := range component.Inputs() { + portID := getPortID(component.Name(), "input", port.Name()) - // Input ports - for portName := range c.Inputs() { - portNode := componentSubgraph.NodeWithID(fmt.Sprintf("%s.inputs.%s", componentName, portName)) + //Mark input ports to be able to find their respective nodes later when adding pipes + port.AddLabel(nodeIDLabel, portID) + + portNode := componentSubgraph.NodeWithID(portID) portNode. - Attr("label", portName). + Attr("label", port.Name()). Attr("shape", "circle"). - Attr("group", componentName) + Attr("group", component.Name()) componentSubgraph.Edge(portNode, componentNode) } - // Output ports - for portName, port := range c.Outputs() { - portNode := componentSubgraph.NodeWithID(fmt.Sprintf("%s.inputs.%s", componentName, portName)) + // Create nodes for output ports + for _, port := range component.Outputs() { + portID := getPortID(component.Name(), "output", port.Name()) + portNode := componentSubgraph.NodeWithID(portID) portNode. - Attr("label", portName). + Attr("label", port.Name()). Attr("shape", "circle"). - Attr("group", componentName) + Attr("group", component.Name()) componentSubgraph.Edge(componentNode, portNode) + } + } + + // Create edges representing pipes (all ports must exist at this point) + for _, component := range fm.Components() { + for _, srcPort := range component.Outputs() { + for _, destPort := range srcPort.Pipes() { + // Any destination port in any pipe is input port, but we do not know in which component + // so we use the label we added earlier + destPortID, err := destPort.Label(nodeIDLabel) + if err != nil { + return nil, err + } + // Clean up and leave the f-mesh as it was before export + destPort.DeleteLabel(nodeIDLabel) - // Pipes - //@TODO + // Any source port in any pipe is always output port, so we can build its node ID + srcPortNode := graph.FindNodeByID(getPortID(component.Name(), "output", srcPort.Name())) + destPortNode := graph.FindNodeByID(destPortID) + graph.Edge(srcPortNode, destPortNode) + } } } buf := new(bytes.Buffer) graph.Write(buf) - return buf.Bytes() + return buf.Bytes(), nil +} + +func getPortID(componentName string, portKind string, portName string) string { + return fmt.Sprintf("component/%s/%s/%s", componentName, portKind, portName) } diff --git a/export/exporter.go b/export/exporter.go index 4ab7567..8888403 100644 --- a/export/exporter.go +++ b/export/exporter.go @@ -2,6 +2,7 @@ package export import "github.com/hovsep/fmesh" +// Exporter is the common interface for all formats type Exporter interface { - Export(fm *fmesh.FMesh) []byte + Export(fm *fmesh.FMesh) ([]byte, error) }