Skip to content

go mod graph 图表

Published: at 11:43 AM | 4 min read

我们知道通过go mod graph命令可以输出当前工程的模块依赖图,但是这个输出是纯文本的并且只是简单的一对一的关系。 很难看出所有模块之间的依赖关系,如果能用图的形式来显示就清晰多了。

Graphviz工具可以画出我们需要的图表。

实现步骤

当然Graphviz非常强大这里只使用了很小的一点功能。

演示

如下文本是开发本网站的模块依赖,将其粘贴到ningto.com/graph提交到后台就会返回生成的图表链接, 如:https://ningto.com/upload/graph/c25e61f17241380e226493670092182c.svg

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))))
}