我们知道通过go mod graph
命令可以输出当前工程的模块依赖图,但是这个输出是纯文本的并且只是简单的一对一的关系。
很难看出所有模块之间的依赖关系,如果能用图的形式来显示就清晰多了。
Graphviz工具可以画出我们需要的图表。
实现步骤
- 使用go mod graph命令得到各模块之间的依赖关系
- 将上述文本转换成dot文件格式(画图需要用到这个文件)
- 使用
dot example1.dot –Tsvg –o example1.svg
命令生成图表
当然Graphviz非常强大这里只使用了很小的一点功能。
演示
如下文本是开发本网站的模块依赖,将其粘贴到ningto.com/graph提交到后台就会返回生成的图表链接,
如:
ningtogo github.com/PuerkitoBio/goquery@v1.5.0
ningtogo github.com/gin-contrib/sessions@v0.0.0-20190512062852-3cb4c4f2d615
ningtogo github.com/gin-gonic/gin@v1.4.0
ningtogo github.com/globalsign/mgo@v0.0.0-20181015135952-eeefdecb41b8
ningtogo github.com/kardianos/osext@v0.0.0-20190222173326-2bc1f35cddc0
ningtogo github.com/kr/pretty@v0.1.0
ningtogo github.com/microcosm-cc/bluemonday@v1.0.2
ningtogo github.com/mojocn/base64Captcha@v0.0.0-20190716153509-e5e80f1b3816
ningtogo github.com/robfig/cron@v1.2.0
ningtogo github.com/sergi/go-diff@v1.0.0
ningtogo github.com/sevlyar/go-daemon@v0.1.5
ningtogo github.com/shurcooL/github_flavored_markdown@v0.0.0-20181002035957-2122de532470
ningtogo github.com/shurcooL/go@v0.0.0-20190704215121-7189cc372560
ningtogo github.com/shurcooL/go-goon@v0.0.0-20170922171312-37c2f522c041
ningtogo github.com/shurcooL/highlight_diff@v0.0.0-20181222201841-111da2e7d480
ningtogo github.com/shurcooL/highlight_go@v0.0.0-20181215221002-9d8641ddf2e1
ningtogo github.com/shurcooL/octicon@v0.0.0-20181222203144-9ff1a4cf27f4
ningtogo github.com/shurcooL/sanitized_anchor_name@v1.0.0
ningtogo github.com/sourcegraph/annotate@v0.0.0-20160123013949-f4cad6c6324d
ningtogo github.com/sourcegraph/syntaxhighlight@v0.0.0-20170531221838-bd320f5d308e
ningtogo github.com/streadway/amqp@v0.0.0-20190827072141-edfb9018d271
github.com/microcosm-cc/bluemonday@v1.0.2 golang.org/x/net@v0.0.0-20181220203305-927f97764cc3
github.com/kr/pretty@v0.1.0 github.com/kr/text@v0.1.0
github.com/shurcooL/github_flavored_markdown@v0.0.0-20181002035957-2122de532470 github.com/russross/blackfriday@v1.5.2
github.com/gin-gonic/gin@v1.4.0 github.com/gin-contrib/sse@v0.0.0-20190301062529-5545eab6dad3
github.com/gin-gonic/gin@v1.4.0 github.com/golang/protobuf@v1.3.1
github.com/gin-gonic/gin@v1.4.0 github.com/json-iterator/go@v1.1.6
github.com/gin-gonic/gin@v1.4.0 github.com/mattn/go-isatty@v0.0.7
github.com/gin-gonic/gin@v1.4.0 github.com/modern-go/concurrent@v0.0.0-20180306012644-bacd9c7ef1dd
github.com/gin-gonic/gin@v1.4.0 github.com/modern-go/reflect2@v1.0.1
github.com/gin-gonic/gin@v1.4.0 github.com/stretchr/testify@v1.3.0
github.com/gin-gonic/gin@v1.4.0 github.com/ugorji/go@v1.1.4
github.com/gin-gonic/gin@v1.4.0 golang.org/x/net@v0.0.0-20190503192946-f4e77d36d62c
github.com/gin-gonic/gin@v1.4.0 gopkg.in/go-playground/assert.v1@v1.2.1
github.com/gin-gonic/gin@v1.4.0 gopkg.in/go-playground/validator.v8@v8.18.2
github.com/gin-gonic/gin@v1.4.0 gopkg.in/yaml.v2@v2.2.2
golang.org/x/net@v0.0.0-20190503192946-f4e77d36d62c golang.org/x/crypto@v0.0.0-20190308221718-c2843e01d9a2
golang.org/x/net@v0.0.0-20190503192946-f4e77d36d62c golang.org/x/text@v0.3.0
golang.org/x/crypto@v0.0.0-20190308221718-c2843e01d9a2 golang.org/x/sys@v0.0.0-20190215142949-d0b11bdaac8a
github.com/mattn/go-isatty@v0.0.7 golang.org/x/sys@v0.0.0-20190222072716-a9d3bda3a223
github.com/mojocn/base64Captcha@v0.0.0-20190716153509-e5e80f1b3816 github.com/golang/freetype@v0.0.0-20170609003504-e2365dfdc4a0
github.com/mojocn/base64Captcha@v0.0.0-20190716153509-e5e80f1b3816 golang.org/x/image@v0.0.0-20190501045829-6d32002ffd75
github.com/gin-contrib/sessions@v0.0.0-20190512062852-3cb4c4f2d615 github.com/boj/redistore@v0.0.0-20180917114910-cd5dcc76aeff
github.com/gin-contrib/sessions@v0.0.0-20190512062852-3cb4c4f2d615 github.com/bradfitz/gomemcache@v0.0.0-20190329173943-551aad21a668
github.com/gin-contrib/sessions@v0.0.0-20190512062852-3cb4c4f2d615 github.com/bradleypeabody/gorilla-sessions-memcache@v0.0.0-20181103040241-
659414f458e1
github.com/gin-contrib/sessions@v0.0.0-20190512062852-3cb4c4f2d615 github.com/gin-gonic/gin@v1.4.0
github.com/gin-contrib/sessions@v0.0.0-20190512062852-3cb4c4f2d615 github.com/globalsign/mgo@v0.0.0-20181015135952-eeefdecb41b8
github.com/gin-contrib/sessions@v0.0.0-20190512062852-3cb4c4f2d615 github.com/gomodule/redigo@v2.0.0+incompatible
github.com/gin-contrib/sessions@v0.0.0-20190512062852-3cb4c4f2d615 github.com/gorilla/context@v1.1.1
github.com/gin-contrib/sessions@v0.0.0-20190512062852-3cb4c4f2d615 github.com/gorilla/sessions@v1.1.3
github.com/gin-contrib/sessions@v0.0.0-20190512062852-3cb4c4f2d615 github.com/kidstuff/mongostore@v0.0.0-20181113001930-e650cd85ee4b
github.com/gin-contrib/sessions@v0.0.0-20190512062852-3cb4c4f2d615 github.com/memcachier/mc@v2.0.1+incompatible
github.com/gin-contrib/sessions@v0.0.0-20190512062852-3cb4c4f2d615 github.com/quasoft/memstore@v0.0.0-20180925164028-84a050167438
github.com/PuerkitoBio/goquery@v1.5.0 github.com/andybalholm/cascadia@v1.0.0
github.com/PuerkitoBio/goquery@v1.5.0 golang.org/x/net@v0.0.0-20181114220301-adae6a3d119a
gopkg.in/yaml.v2@v2.2.2 gopkg.in/check.v1@v0.0.0-20161208181325-20d25e280405
golang.org/x/image@v0.0.0-20190501045829-6d32002ffd75 golang.org/x/text@v0.3.0
github.com/gorilla/sessions@v1.1.3 github.com/gorilla/context@v1.1.1
github.com/gorilla/sessions@v1.1.3 github.com/gorilla/securecookie@v1.1.1
github.com/boj/redistore@v0.0.0-20180917114910-cd5dcc76aeff github.com/gomodule/redigo@v2.0.0+incompatible
github.com/boj/redistore@v0.0.0-20180917114910-cd5dcc76aeff github.com/gorilla/securecookie@v1.1.1
github.com/boj/redistore@v0.0.0-20180917114910-cd5dcc76aeff github.com/gorilla/sessions@v1.1.1
github.com/gorilla/sessions@v1.1.1 github.com/gorilla/context@v1.1.1
github.com/gorilla/sessions@v1.1.1 github.com/gorilla/securecookie@v1.1.1
github.com/andybalholm/cascadia@v1.0.0 golang.org/x/net@v0.0.0-20180218175443-cbe0f9307d01
github.com/kr/text@v0.1.0 github.com/kr/pty@v1.1.1
github.com/stretchr/testify@v1.3.0 github.com/davecgh/go-spew@v1.1.0
github.com/stretchr/testify@v1.3.0 github.com/pmezard/go-difflib@v1.0.0
github.com/stretchr/testify@v1.3.0 github.com/stretchr/objx@v0.1.0
实现源码
package model
import (
"bytes"
"crypto/md5"
"errors"
"fmt"
"html/template"
"io/ioutil"
"ningtogo/configs"
"ningtogo/tools/util"
"os"
"os/exec"
"path"
"strings"
)
type ModGraph struct {
Horizontal bool
RemoveVersion bool
Maps map[string]int
Deps map[int][]int
Content string
Format string
}
const GraphHost = "https://ningto.com/upload/graph/"
var GraphDir = path.Join(configs.UploadDir(), "graph")
func init() {
os.MkdirAll(GraphDir, os.ModePerm)
}
func NewModGraph(content string, format string) *ModGraph {
m := new(ModGraph)
m.Horizontal = true
m.Maps = make(map[string]int)
m.Deps = make(map[int][]int)
m.Content = strings.ReplaceAll(content, "\r\n", "\n")
if format != "svg" && format != "png" {
m.Format = "svg"
} else {
m.Format = format
}
return m
}
func (m *ModGraph) FindLocal() (string, error) {
name := m.unique() + m.Format
f := path.Join(GraphDir, name)
if util.PathExist(f) {
return GraphHost + name, nil
}
return "", errors.New("local not found")
}
func (m *ModGraph) Parse() error {
rowList := strings.Split(m.Content, "\n")
index := 0
for _, row := range rowList {
colList := strings.Split(row, " ")
if len(colList) < 2 {
continue
}
a := colList[0]
b := colList[1]
if m.RemoveVersion {
f1 := strings.LastIndex(a, "@")
f2 := strings.LastIndex(b, "@")
if f1 > 0 {
a = a[:f1]
}
if f2 > 0 {
b = b[:f2]
}
}
if len(a) == 0 || len(b) == 0 {
continue
}
i, _ := m.Maps[a]
j, _ := m.Maps[b]
if i == 0 {
index += 1
i = index
m.Maps[a] = i
}
if j == 0 {
index += 1
j = index
m.Maps[b] = j
}
if _, ok := m.Deps[i]; !ok {
m.Deps[i] = []int{}
}
m.Deps[i] = append(m.Deps[i], j)
}
return nil
}
func (m *ModGraph) Render() (string, error) {
digraph := `digraph {
{{ if .Horizontal }}rankdir=LR;{{ end }}
node [shape=box];
{{range $key, $val := .Maps -}}
{{$val}} [label="{{$key}}"];
{{end -}}
{{range $key, $valList := .Deps}}
{{- range $valList -}}
{{$key}} -> {{.}};
{{end -}}
{{end -}}
}`
digraph = strings.ReplaceAll(digraph, "\r", "")
t := template.Must(template.New("digraph").Parse(digraph))
var buf bytes.Buffer
err := t.Execute(&buf, *m)
if err != nil {
return "", err
}
inputPath := path.Join(GraphDir, m.unique()+".dot")
err = ioutil.WriteFile(inputPath, buf.Bytes(), os.ModePerm)
if err != nil {
return "", err
}
outputName := m.unique() + "." + m.Format
outputPath := path.Join(GraphDir, outputName)
_, err = exec.Command("dot", inputPath, "-T"+m.Format, "-o", outputPath).Output()
if err != nil {
return "", err
}
return GraphHost + outputName, nil
}
func (m *ModGraph) unique() string {
return fmt.Sprintf("%x", md5.Sum([]byte(fmt.Sprintf("%v", *m))))
}