cosmos-sdk/container/internal/graphviz/graph.go
Aaron Craelius 7a31a28e24
refactor(container)!: remove dependency on C graphviz (#11934)
## Description

Closes: #11925 

This replace the dependency on https://pkg.go.dev/github.com/goccy/go-graphviz which wraps the whole C Graphviz library and is causing ARM build problems in #11924 and generally probably shouldn't be used because it's a heavyweight dependency just used for debugging.

It adds:
* a custom `graphviz` package that does just what we need for `container`
* updates to graphviz rendering to make it nicer and a README with some examples
* golden tests for graphviz and log debugging
* a `StderrLogger` `DebugOption` which is now the default for `Debug`/`AutoDebug`



---

### Author Checklist

*All items are required. Please add a note to the item if the item is not applicable and
please add links to any relevant follow up issues.*

I have...

- [ ] included the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title
- [ ] added `!` to the type prefix if API or client breaking change
- [ ] targeted the correct branch (see [PR Targeting](https://github.com/cosmos/cosmos-sdk/blob/main/CONTRIBUTING.md#pr-targeting))
- [ ] provided a link to the relevant issue or specification
- [ ] followed the guidelines for [building modules](https://github.com/cosmos/cosmos-sdk/blob/main/docs/building-modules)
- [ ] included the necessary unit and integration [tests](https://github.com/cosmos/cosmos-sdk/blob/main/CONTRIBUTING.md#testing)
- [ ] added a changelog entry to `CHANGELOG.md`
- [ ] included comments for [documenting Go code](https://blog.golang.org/godoc)
- [ ] updated the relevant documentation or specification
- [ ] reviewed "Files changed" and left comments if necessary
- [ ] confirmed all CI checks have passed

### Reviewers Checklist

*All items are required. Please add a note if the item is not applicable and please add
your handle next to the items reviewed if you only reviewed selected items.*

I have...

- [ ] confirmed the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title
- [ ] confirmed `!` in the type prefix if API or client breaking change
- [ ] confirmed all author checklist items have been addressed 
- [ ] reviewed state machine logic
- [ ] reviewed API design and naming
- [ ] reviewed documentation is accurate
- [ ] reviewed tests and test coverage
- [ ] manually tested (if applicable)
2022-05-12 17:00:55 +00:00

157 lines
3.3 KiB
Go

// Package graphviz
package graphviz
import (
"bytes"
"fmt"
"io"
"github.com/cosmos/cosmos-sdk/container/internal/util"
)
// Graph represents a graphviz digraph.
type Graph struct {
*Attributes
// name is the optional name of this graph
name string
// parent is non-nil if this is a sub-graph
parent *Graph
// allNodes includes all nodes in the graph and its sub-graphs.
// It is set to the same map in parent and sub-graphs.
allNodes map[string]*Node
// myNodes are the nodes in this graph (whether it's a root or sub-graph)
myNodes map[string]*Node
subgraphs map[string]*Graph
edges []*Edge
}
// NewGraph creates a new Graph instance.
func NewGraph() *Graph {
return &Graph{
Attributes: NewAttributes(),
name: "",
parent: nil,
allNodes: map[string]*Node{},
myNodes: map[string]*Node{},
subgraphs: map[string]*Graph{},
edges: nil,
}
}
// FindOrCreateNode finds or creates the node with the provided name.
func (g *Graph) FindOrCreateNode(name string) (node *Node, found bool) {
if node, ok := g.allNodes[name]; ok {
return node, true
}
node = &Node{
Attributes: NewAttributes(),
name: name,
}
g.allNodes[name] = node
g.myNodes[name] = node
return node, false
}
// FindOrCreateSubGraph finds or creates the subgraph with the provided name.
func (g *Graph) FindOrCreateSubGraph(name string) (graph *Graph, found bool) {
if sub, ok := g.subgraphs[name]; ok {
return sub, true
}
n := &Graph{
Attributes: NewAttributes(),
name: name,
parent: g,
allNodes: g.allNodes,
myNodes: map[string]*Node{},
subgraphs: map[string]*Graph{},
edges: nil,
}
g.subgraphs[name] = n
return n, false
}
// CreateEdge creates a new graphviz edge.
func (g *Graph) CreateEdge(from, to *Node) *Edge {
edge := &Edge{
Attributes: NewAttributes(),
from: from,
to: to,
}
g.edges = append(g.edges, edge)
return edge
}
// RenderDOT renders the graph to DOT format.
func (g *Graph) RenderDOT(w io.Writer) error {
return g.render(w, "")
}
func (g *Graph) render(w io.Writer, indent string) error {
if g.parent == nil {
_, err := fmt.Fprintf(w, "%sdigraph %q {\n", indent, g.name)
if err != nil {
return err
}
} else {
_, err := fmt.Fprintf(w, "%ssubgraph %q {\n", indent, g.name)
if err != nil {
return err
}
}
{
subIndent := indent + " "
if attrStr := g.Attributes.String(); attrStr != "" {
_, err := fmt.Fprintf(w, "%sgraph %s;\n", subIndent, attrStr)
if err != nil {
return err
}
}
// we do map iteration in sorted order so that outputs are stable and
// can be used in tests
err := util.IterateMapOrdered(g.subgraphs, func(_ string, subgraph *Graph) error {
return subgraph.render(w, subIndent+" ")
})
if err != nil {
return err
}
err = util.IterateMapOrdered(g.myNodes, func(_ string, node *Node) error {
return node.render(w, subIndent)
})
if err != nil {
return err
}
for _, edge := range g.edges {
err := edge.render(w, subIndent)
if err != nil {
return err
}
}
}
_, err := fmt.Fprintf(w, "%s}\n\n", indent)
return err
}
// String returns the graph in DOT format.
func (g *Graph) String() string {
buf := &bytes.Buffer{}
err := g.RenderDOT(buf)
if err != nil {
panic(err)
}
return buf.String()
}