104 lines
2.6 KiB
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
|
|
}
|