s0rg/decompose

View on GitHub
internal/builder/dot.go

Summary

Maintainability
A
0 mins
Test Coverage
A
100%
package builder

import (
    "io"
    "slices"

    "github.com/emicklei/dot"

    "github.com/s0rg/decompose/internal/node"
)

type DOT struct {
    g     *dot.Graph
    edges map[string]map[string][]string
}

func NewDOT() *DOT {
    g := dot.NewGraph(dot.Directed)

    return &DOT{
        g:     g,
        edges: make(map[string]map[string][]string),
    }
}

func (d *DOT) Name() string {
    return "graphviz-dot"
}

func (d *DOT) AddNode(n *node.Node) error {
    label, color := renderNode(n)

    d.g.Node(n.ID).Attr(
        "color", color,
    ).Label(label)

    return nil
}

func (d *DOT) AddEdge(e *node.Edge) {
    if _, ok := d.g.FindNodeById(e.SrcID); !ok {
        return
    }

    if _, ok := d.g.FindNodeById(e.DstID); !ok {
        return
    }

    d.addEdge(e.SrcID, e.DstID, e.Port.Label())
}

func (d *DOT) addEdge(src, dst, label string) {
    dmap, ok := d.edges[src]
    if !ok {
        dmap = make(map[string][]string)
        d.edges[src] = dmap
    }

    dmap[dst] = append(dmap[dst], label)
}

func (d *DOT) buildEdges() {
    order := make([]string, 0, len(d.edges))

    for k := range d.edges {
        order = append(order, k)
    }

    slices.Sort(order)

    for _, srcID := range order {
        src, _ := d.g.FindNodeById(srcID)

        dmap := d.edges[srcID]
        dorder := make([]string, 0, len(dmap))

        for k := range dmap {
            dorder = append(dorder, k)
        }

        slices.Sort(dorder)

        for _, dstID := range dorder {
            dst, _ := d.g.FindNodeById(dstID)
            ports := dmap[dstID]

            if tmp, ok := d.edges[dstID]; ok {
                if dports, ok := tmp[srcID]; ok {
                    ports = append(ports, dports...)

                    delete(tmp, srcID)
                }
            }

            d.g.Edge(src, dst, ports...)
        }
    }
}

func (d *DOT) Write(w io.Writer) error {
    d.buildEdges()
    d.g.Write(w)

    return nil
}

func renderNode(n *node.Node) (label, color string) {
    label, color = n.Name, "black"

    if n.IsExternal() {
        color = "gray"
        label = "external: " + n.Name
    }

    return label, color
}