package dig
import (
"errors"
"fmt"
"io"
"strconv"
"go.uber.org/dig/internal/dot"
)
type VisualizeOption interface {
applyVisualizeOption(*visualizeOptions )
}
type visualizeOptions struct {
VisualizeError error
}
func VisualizeError (err error ) VisualizeOption {
return visualizeErrorOption {err }
}
type visualizeErrorOption struct { err error }
func (o visualizeErrorOption ) String () string {
return fmt .Sprintf ("VisualizeError(%v)" , o .err )
}
func (o visualizeErrorOption ) applyVisualizeOption (opt *visualizeOptions ) {
opt .VisualizeError = o .err
}
func updateGraph(dg *dot .Graph , err error ) error {
var errs []errVisualizer
for {
if ev , ok := err .(errVisualizer ); ok {
errs = append (errs , ev )
}
e := errors .Unwrap (err )
if e == nil {
break
}
err = e
}
if len (errs ) == 0 {
return nil
}
for i := len (errs ) - 1 ; i >= 0 ; i -- {
errs [i ].updateGraph (dg )
}
dg .PruneSuccess ()
return nil
}
func Visualize (c *Container , w io .Writer , opts ...VisualizeOption ) error {
dg := c .createGraph ()
var options visualizeOptions
for _ , o := range opts {
o .applyVisualizeOption (&options )
}
if options .VisualizeError != nil {
if err := updateGraph (dg , options .VisualizeError ); err != nil {
return err
}
}
visualizeGraph (w , dg )
return nil
}
func visualizeGraph(w io .Writer , dg *dot .Graph ) {
w .Write ([]byte ("digraph {\n\trankdir=RL;\n\tgraph [compound=true];\n" ))
for _ , g := range dg .Groups {
visualizeGroup (w , g )
}
for idx , c := range dg .Ctors {
visualizeCtor (w , idx , c )
}
for _ , f := range dg .Failed .TransitiveFailures {
fmt .Fprintf (w , "\t%s [color=orange];\n" , strconv .Quote (f .String ()))
}
for _ , f := range dg .Failed .RootCauses {
fmt .Fprintf (w , "\t%s [color=red];\n" , strconv .Quote (f .String ()))
}
w .Write ([]byte ("}" ))
}
func visualizeGroup(w io .Writer , g *dot .Group ) {
fmt .Fprintf (w , "\t%s [%s];\n" , strconv .Quote (g .String ()), g .Attributes ())
for _ , r := range g .Results {
fmt .Fprintf (w , "\t%s -> %s;\n" , strconv .Quote (g .String ()), strconv .Quote (r .String ()))
}
}
func visualizeCtor(w io .Writer , index int , c *dot .Ctor ) {
fmt .Fprintf (w , "\tsubgraph cluster_%d {\n" , index )
if c .Package != "" {
fmt .Fprintf (w , "\t\tlabel = %s;\n" , strconv .Quote (c .Package ))
}
fmt .Fprintf (w , "\t\tconstructor_%d [shape=plaintext label=%s];\n" , index , strconv .Quote (c .Name ))
if c .ErrorType != 0 {
fmt .Fprintf (w , "\t\tcolor=%s;\n" , c .ErrorType .Color ())
}
for _ , r := range c .Results {
fmt .Fprintf (w , "\t\t%s [%s];\n" , strconv .Quote (r .String ()), r .Attributes ())
}
fmt .Fprintf (w , "\t}\n" )
for _ , p := range c .Params {
var optionalStyle string
if p .Optional {
optionalStyle = " style=dashed"
}
fmt .Fprintf (w , "\tconstructor_%d -> %s [ltail=cluster_%d%s];\n" , index , strconv .Quote (p .String ()), index , optionalStyle )
}
for _ , p := range c .GroupParams {
fmt .Fprintf (w , "\tconstructor_%d -> %s [ltail=cluster_%d];\n" , index , strconv .Quote (p .String ()), index )
}
}
func CanVisualizeError (err error ) bool {
for {
if _ , ok := err .(errVisualizer ); ok {
return true
}
e := errors .Unwrap (err )
if e == nil {
break
}
err = e
}
return false
}
func (c *Container ) createGraph () *dot .Graph {
return c .scope .createGraph ()
}
func (s *Scope ) createGraph () *dot .Graph {
dg := dot .NewGraph ()
s .addNodes (dg )
return dg
}
func (s *Scope ) addNodes (dg *dot .Graph ) {
for _ , n := range s .nodes {
dg .AddCtor (newDotCtor (n ), n .paramList .DotParam (), n .resultList .DotResult ())
}
for _ , cs := range s .childScopes {
cs .addNodes (dg )
}
}
func newDotCtor(n *constructorNode ) *dot .Ctor {
return &dot .Ctor {
ID : n .id ,
Name : n .location .Name ,
Package : n .location .Package ,
File : n .location .File ,
Line : n .location .Line ,
}
}
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .