Files
GSP/internal/gsp/graph.go

104 lines
2.6 KiB
Go

package gsp
import (
"fmt"
"regexp"
"sort"
"strings"
)
func (p *Project) Graph(id string, depth int) Graph {
if id != "" {
flattened := p.Flatten(id, depth, Filter{})
return p.graphForUnits(flattened.Units)
}
return p.graphForUnits(p.Units)
}
func (p *Project) graphForUnits(units []*Unit) Graph {
nodeMap := map[string]GraphNode{}
edgeMap := map[string]GraphEdge{}
include := map[string]bool{}
for _, unit := range units {
include[unit.ID] = true
nodeMap[unit.ID] = GraphNode{ID: unit.ID, Type: unit.Type, Resolution: unit.Resolution, File: unit.File}
}
for _, unit := range units {
if unit.Refines != "" {
addEdge(edgeMap, unit.ID, unit.Refines, "refines")
if !include[unit.Refines] {
nodeMap[unit.Refines] = GraphNode{ID: unit.Refines, Missing: p.ByID[unit.Refines] == nil}
}
}
for _, rel := range unit.With {
addEdge(edgeMap, unit.ID, rel.ID, "with")
if !include[rel.ID] {
nodeMap[rel.ID] = GraphNode{ID: rel.ID, Missing: p.ByID[rel.ID] == nil}
}
}
}
nodes := make([]GraphNode, 0, len(nodeMap))
for _, node := range nodeMap {
nodes = append(nodes, node)
}
sort.Slice(nodes, func(i, j int) bool {
return nodes[i].ID < nodes[j].ID
})
edges := make([]GraphEdge, 0, len(edgeMap))
for _, edge := range edgeMap {
edges = append(edges, edge)
}
sort.Slice(edges, func(i, j int) bool {
if edges[i].From == edges[j].From {
if edges[i].To == edges[j].To {
return edges[i].Kind < edges[j].Kind
}
return edges[i].To < edges[j].To
}
return edges[i].From < edges[j].From
})
return Graph{Nodes: nodes, Edges: edges}
}
func addEdge(edges map[string]GraphEdge, from, to, kind string) {
key := from + "\x00" + to + "\x00" + kind
edges[key] = GraphEdge{From: from, To: to, Kind: kind}
}
var mermaidIDPattern = regexp.MustCompile(`[^A-Za-z0-9_]`)
func (g Graph) Mermaid() string {
var builder strings.Builder
builder.WriteString("graph TD\n")
if len(g.Nodes) == 0 {
return builder.String()
}
for _, node := range g.Nodes {
label := node.ID
if node.Missing {
label += " (missing)"
}
builder.WriteString(fmt.Sprintf(" %s[\"%s\"]\n", mermaidID(node.ID), escapeMermaid(label)))
}
for _, edge := range g.Edges {
builder.WriteString(fmt.Sprintf(" %s -- %s --> %s\n", mermaidID(edge.From), edge.Kind, mermaidID(edge.To)))
}
return builder.String()
}
func mermaidID(id string) string {
value := mermaidIDPattern.ReplaceAllString(id, "_")
if value == "" {
return "node"
}
if value[0] >= '0' && value[0] <= '9' {
value = "n_" + value
}
return value
}
func escapeMermaid(value string) string {
value = strings.ReplaceAll(value, `"`, `\"`)
return value
}