From d9b7b595922dcaf82d4dd44868c0323faf42b8a2 Mon Sep 17 00:00:00 2001 From: Ondrej Fabry Date: Wed, 5 Aug 2020 04:00:38 +0200 Subject: [PATCH] Render images with Go library instead of Graphviz's dot (#70) --- README.md | 23 ++++++++++++++++------- dot.go | 36 ++++++++++++++++++++++++++++++++++-- go.mod | 1 + go.sum | 16 ++++++++++++++++ handler.go | 3 ++- main.go | 8 +++++--- version.go | 2 +- 7 files changed, 75 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 17f18d2..6faa0e2 100644 --- a/README.md +++ b/README.md @@ -21,10 +21,11 @@ the code much higher or when you are just simply trying to understand code of so ### Features - 🆕 **support for Go modules!** :boom: -- interactive view allowing quick switching between focused packages in web browser - focus specific package in the program +- click on package to quickly switch the focus using [interactive viewer](#interactive-viewer) - group functions by package and/or methods by type - filter packages to specific import path prefixes +- ignore funcs from standard library - omit various types of function calls ### Output preview @@ -74,8 +75,8 @@ Here you can find descriptions for various types of output. #### Requirements -- [Go](https://golang.org/dl/) 1.12+ -- [Graphviz](http://www.graphviz.org/download/) +- [Go](https://golang.org/dl/) 1.13+ +- [Graphviz](http://www.graphviz.org/download/) (optional, required only with `-graphviz` flag) ### Installation @@ -88,13 +89,19 @@ cd go-callvis && make install ### Usage +#### Interactive viewer + To use the interactive view provided by a web server that serves SVG images of focused packages, you can simply run: -`go-callvis [OPTIONS]
` +`go-callvis ` + +HTTP server is listening on [http://localhost:7878/](http://localhost:7878/) by default, use option `-http="ADDR:PORT"` to change HTTP server address. + +#### Render static output -> HTTP server is listening on [http://localhost:7878/](http://localhost:7878/) by default. +To generate a single output file use option `-file=` to choose output file destination. -To generate a single output file use option `-file=` to choose output file destination. The output format defaults to `svg`, use option `-format=` to pick a different output format. +The output format defaults to `svg`, use option `-format=` to pick a different output format. #### Options @@ -108,8 +115,10 @@ Usage of go-callvis: Focus specific package using name or import path. (default "main") -format string output file format [svg | png | jpg | ...] (default "svg") + -graphviz + Use Graphviz's dot program to render images. -group string - Grouping functions by packages and/or types [pkg, type] (separated by comma) + Grouping functions by packages and/or types [pkg, type] (separated by comma) (default "pkg") -http string HTTP service address. (default ":7878") -ignore string diff --git a/dot.go b/dot.go index f9fa88a..69335af 100644 --- a/dot.go +++ b/dot.go @@ -10,6 +10,8 @@ import ( "path/filepath" "strings" "text/template" + + "github.com/goccy/go-graphviz" ) var ( @@ -21,8 +23,8 @@ var ( // it's usually at: /usr/bin/dot var dotExe string -// dotToImage generates a SVG using the 'dot' utility, returning the filepath -func dotToImage(outfname string, format string, dot []byte) (string, error) { +// dotToImageGraphviz generates a SVG using the 'dot' utility, returning the filepath +func dotToImageGraphviz(outfname string, format string, dot []byte) (string, error) { if dotExe == "" { dot, err := exec.LookPath("dot") if err != nil { @@ -39,7 +41,37 @@ func dotToImage(outfname string, format string, dot []byte) (string, error) { } cmd := exec.Command(dotExe, fmt.Sprintf("-T%s", format), "-o", img) cmd.Stdin = bytes.NewReader(dot) + var stderr bytes.Buffer + cmd.Stderr = &stderr if err := cmd.Run(); err != nil { + return "", fmt.Errorf("command '%v': %v\n%v", cmd, err, stderr.String()) + } + return img, nil +} + +func dotToImage(outfname string, format string, dot []byte) (string, error) { + if *graphvizFlag { + return dotToImageGraphviz(outfname, format, dot) + } + + g := graphviz.New() + graph, err := graphviz.ParseBytes(dot) + if err != nil { + return "", err + } + defer func() { + if err := graph.Close(); err != nil { + log.Fatal(err) + } + g.Close() + }() + var img string + if outfname == "" { + img = filepath.Join(os.TempDir(), fmt.Sprintf("go-callvis_export.%s", format)) + } else { + img = fmt.Sprintf("%s.%s", outfname, format) + } + if err := g.RenderFilename(graph, graphviz.Format(format), img); err != nil { return "", err } return img, nil diff --git a/go.mod b/go.mod index f5cca6f..81cd529 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/ofabry/go-callvis go 1.12 require ( + github.com/goccy/go-graphviz v0.0.6 github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4 golang.org/x/tools v0.0.0-20200305224536-de023d59a5d1 ) diff --git a/go.sum b/go.sum index 1f4768f..94115d5 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,23 @@ +github.com/corona10/goimagehash v1.0.2 h1:pUfB0LnsJASMPGEZLj7tGY251vF+qLGqOgEP4rUs6kA= +github.com/corona10/goimagehash v1.0.2/go.mod h1:/l9umBhvcHQXVtQO1V6Gp1yD20STawkhRnnX0D1bvVI= +github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8= +github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/goccy/go-graphviz v0.0.6 h1:sCT69fmH2KKsObVfsozYyKXxrqmIfo3SyHZs72xkgxs= +github.com/goccy/go-graphviz v0.0.6/go.mod h1:wXVsXxmyMQU6TN3zGRttjNn3h+iCAS7xQFC6TlNvLhk= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5 h1:BvoENQQU+fZ9uukda/RzCAL/191HHwJA5b13R6diVlY= +github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4 h1:49lOXmGaUpV9Fz3gd7TFZY106KVlPVa5jcYD1gaQf98= github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200204104054-c9f3fb736b72/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/image v0.0.0-20200119044424-58c23975cae1 h1:5h3ngYt7+vXCDZCup/HkCQgW5XwmSvR/nA2JmJ0RErg= +golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= diff --git a/handler.go b/handler.go index 6548ffd..e97bfbf 100644 --- a/handler.go +++ b/handler.go @@ -106,7 +106,7 @@ func handler(w http.ResponseWriter, r *http.Request) { // Convert list-style args to []string if e := processListArgs(&opts); e != nil { - http.Error(w, "invalid group option", http.StatusInternalServerError) + http.Error(w, "invalid parameters", http.StatusBadRequest) return } @@ -123,6 +123,7 @@ func handler(w http.ResponseWriter, r *http.Request) { } log.Printf("converting dot to %s..\n", *outputFormat) + img, err := dotToImage("", *outputFormat, output) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) diff --git a/main.go b/main.go index b00b9ba..80aafa9 100644 --- a/main.go +++ b/main.go @@ -20,19 +20,21 @@ import ( var ( focusFlag = flag.String("focus", "main", "Focus specific package using name or import path.") - groupFlag = flag.String("group", "", "Grouping functions by packages and/or types [pkg, type] (separated by comma)") + groupFlag = flag.String("group", "pkg", "Grouping functions by packages and/or types [pkg, type] (separated by comma)") limitFlag = flag.String("limit", "", "Limit package paths to given prefixes (separated by comma)") ignoreFlag = flag.String("ignore", "", "Ignore package paths containing given prefixes (separated by comma)") includeFlag = flag.String("include", "", "Include package paths with given prefixes (separated by comma)") nostdFlag = flag.Bool("nostd", false, "Omit calls to/from packages in standard library.") nointerFlag = flag.Bool("nointer", false, "Omit calls to unexported functions.") testFlag = flag.Bool("tests", false, "Include test code.") - debugFlag = flag.Bool("debug", false, "Enable verbose log.") - versionFlag = flag.Bool("version", false, "Show version and exit.") + graphvizFlag = flag.Bool("graphviz", false, "Use Graphviz's dot program to render images.") httpFlag = flag.String("http", ":7878", "HTTP service address.") skipBrowser = flag.Bool("skipbrowser", false, "Skip opening browser.") outputFile = flag.String("file", "", "output filename - omit to use server mode") outputFormat = flag.String("format", "svg", "output file format [svg | png | jpg | ...]") + + debugFlag = flag.Bool("debug", false, "Enable verbose log.") + versionFlag = flag.Bool("version", false, "Show version and exit.") ) func init() { diff --git a/version.go b/version.go index bfb63f0..0ff398a 100644 --- a/version.go +++ b/version.go @@ -5,7 +5,7 @@ import ( ) var ( - version = "v0.6.0" + version = "v0.6.1" commit = "(unknown)" )