From e0ccbf010e7bcd8f0bcd04192bf046bb485839db Mon Sep 17 00:00:00 2001 From: Phodal Huang Date: Sat, 28 Dec 2019 10:37:25 +0800 Subject: [PATCH] feat: add first version arch --- .gitignore | 1 - README.md | 7 +- cmd/arch.go | 21 +- core/domain/arch/arch_app.go | 30 ++- core/domain/arch/tequila/LICENSE | 21 ++ core/domain/arch/tequila/incl_viz.go | 334 +++++++++++++++++++++++++++ go.mod | 4 +- go.sum | 10 +- 8 files changed, 414 insertions(+), 14 deletions(-) create mode 100644 core/domain/arch/tequila/LICENSE create mode 100644 core/domain/arch/tequila/incl_viz.go diff --git a/.gitignore b/.gitignore index 233c846..c2bb0b7 100644 --- a/.gitignore +++ b/.gitignore @@ -17,7 +17,6 @@ .idea/ html/ -tequila *.gch diff --git a/README.md b/README.md index 2c6a72c..cb6038f 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,8 @@ Todo: - [ ] Testable? - [ ] Test badsmell - [ ] Test bad smell list [https://testsmells.github.io/pages/testsmells.html] - + - Arch + ## Usage install @@ -444,6 +445,10 @@ go get github.com/onsi/gomega License --- +Arch based on [Tequila](https://github.com/newlee/tequila) + +Git Analysis inspired by [Code Maat](https://github.com/adamtornhill/code-maat) + [![Phodal's Idea](http://brand.phodal.com/shields/idea-small.svg)](http://ideas.phodal.com/) @ 2019 A [Phodal Huang](https://www.phodal.com)'s [Idea](http://github.com/phodal/ideas). This code is distributed under the MPL license. See `LICENSE` in this directory. diff --git a/cmd/arch.go b/cmd/arch.go index ea26da3..12f7861 100644 --- a/cmd/arch.go +++ b/cmd/arch.go @@ -4,8 +4,10 @@ import ( "fmt" "github.com/phodal/coca/cmd/cmd_util" "github.com/phodal/coca/config" + "github.com/phodal/coca/core/adapter" "github.com/phodal/coca/core/domain/arch" "github.com/spf13/cobra" + "strings" ) type ArchCmdConfig struct { @@ -21,12 +23,27 @@ var archCmd = &cobra.Command{ Short: "generate arch", Long: ``, Run: func(cmd *cobra.Command, args []string) { + identifiers = adapter.LoadIdentify(apiCmdConfig.DependencePath) + identifiersMap = adapter.BuildIdentifierMap(identifiers) + parsedDeps := cmd_util.GetDepsFromJson(archCmdConfig.DependencePath) archApp := arch.NewArchApp() - dotContent := archApp.Analysis(parsedDeps) + dotContent := archApp.Analysis(parsedDeps, identifiersMap) fmt.Println(dotContent) - //ConvertToSvg(dotContent) + + ignores := strings.Split("", ",") + var nodeFilter = func(key string) bool { + for _, f := range ignores { + if key == f { + return true + } + } + return false + } + + dotContent.ToDot("coca_reporter/arch.dot", ".", nodeFilter) + cmd_util.ConvertToSvg("arch") }, } diff --git a/core/domain/arch/arch_app.go b/core/domain/arch/arch_app.go index 46aaa77..9efd876 100644 --- a/core/domain/arch/arch_app.go +++ b/core/domain/arch/arch_app.go @@ -1,6 +1,9 @@ package arch -import "github.com/phodal/coca/core/models" +import ( + "github.com/phodal/coca/core/domain/arch/tequila" + "github.com/phodal/coca/core/models" +) type ArchApp struct { } @@ -9,6 +12,27 @@ func NewArchApp() ArchApp { return *&ArchApp{} } -func (a ArchApp) Analysis(deps []models.JClassNode) string { - return "" +func (a ArchApp) Analysis(deps []models.JClassNode, identifiersMap map[string]models.JIdentifier) tequila.FullGraph { + fullGraph := tequila.FullGraph{ + NodeList: make(map[string]string), + RelationList: make(map[string]*tequila.Relation), + } + + for _, clz := range deps { + src := clz.Package + "." + clz.Class + fullGraph.NodeList[src] = src + + for _, call := range clz.MethodCalls { + dst := call.Package + "." + call.Class + if _, ok := identifiersMap[dst]; ok { + relation := &tequila.Relation{ + From: dst, + To: src, + Style: "\"solid\"", + } + fullGraph.RelationList[relation.From+"->"+relation.To] = relation } + } + } + + return *&fullGraph } diff --git a/core/domain/arch/tequila/LICENSE b/core/domain/arch/tequila/LICENSE new file mode 100644 index 0000000..f97b529 --- /dev/null +++ b/core/domain/arch/tequila/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Li Xin + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/core/domain/arch/tequila/incl_viz.go b/core/domain/arch/tequila/incl_viz.go new file mode 100644 index 0000000..f2e1873 --- /dev/null +++ b/core/domain/arch/tequila/incl_viz.go @@ -0,0 +1,334 @@ +package tequila + +import ( + "bufio" + "fmt" + "github.com/awalterschulze/gographviz" + "io/ioutil" + "os" + "path/filepath" + "sort" + "strconv" + "strings" +) + +type Relation struct { + From string + To string + Style string +} + +type FullGraph struct { + NodeList map[string]string + RelationList map[string]*Relation +} + +type Fan struct { + Name string + FanIn int + FanOut int +} + +func (f *FullGraph) FindCrossRef(merge func(string) string) []string { + mergedRelationMap := make(map[string]string) + result := make([]string, 0) + for key := range f.RelationList { + relation := f.RelationList[key] + mergedFrom := merge(relation.From) + mergedTo := merge(relation.To) + if mergedFrom == mergedTo { + continue + } + if _, ok := mergedRelationMap[mergedTo+mergedFrom]; ok { + result = append(result, mergedFrom+" <-> "+mergedTo) + } + mergedRelationMap[mergedFrom+mergedTo] = "" + } + return result +} + +func (f *FullGraph) MergeHeaderFile(merge func(string) string) *FullGraph { + result := &FullGraph{ + NodeList: make(map[string]string), + RelationList: make(map[string]*Relation), + } + nodes := make(map[string]string) + + for key := range f.NodeList { + mergedKey := merge(key) + nodes[key] = mergedKey + result.NodeList[mergedKey] = mergedKey + } + for key := range f.RelationList { + relation := f.RelationList[key] + mergedFrom := merge(relation.From) + mergedTo := merge(relation.To) + if mergedFrom == mergedTo { + continue + } + + mergedRelation := &Relation{ + From: mergedFrom, + To: mergedTo, + Style: "\"solid\"", + } + + result.RelationList[mergedRelation.From+mergedRelation.To] = mergedRelation + } + return result +} + +func (f *FullGraph) EntryPoints(merge func(string) string) []string { + mergedGraph := f.MergeHeaderFile(merge) + fromMap := make(map[string]bool) + toMap := make(map[string]bool) + for key := range mergedGraph.RelationList { + relation := mergedGraph.RelationList[key] + if relation.From == "main" { + continue + } + fromMap[relation.From] = true + toMap[relation.To] = true + } + result := make([]string, 0) + for key := range fromMap { + if _, ok := toMap[key]; !ok { + result = append(result, key) + } + } + return result +} + +func (f *FullGraph) SortedByFan(merge func(string) string) []*Fan { + mergedGraph := f.MergeHeaderFile(merge) + result := make([]*Fan, len(mergedGraph.NodeList)) + index := 0 + fanMap := make(map[string]*Fan) + for key := range mergedGraph.NodeList { + fan := &Fan{Name: key} + result[index] = fan + fanMap[key] = fan + index++ + } + for key := range mergedGraph.RelationList { + relation := mergedGraph.RelationList[key] + fanMap[relation.From].FanOut++ + fanMap[relation.To].FanIn++ + } + sort.Slice(result, func(i, j int) bool { + return (result[i].FanIn + result[i].FanOut) > (result[j].FanIn + result[j].FanOut) + }) + return result +} + +var fullGraph *FullGraph + +func parseRelation(edge *gographviz.Edge, nodes map[string]string) { + if _, ok := nodes[edge.Src]; ok { + if _, ok := nodes[edge.Dst]; ok { + dst := nodes[edge.Dst] + src := nodes[edge.Src] + dst = strings.ToLower(dst) + src = strings.ToLower(src) + relation := &Relation{ + From: dst, + To: src, + Style: "\"solid\"", + } + fullGraph.RelationList[relation.From+"->"+relation.To] = relation + } + } +} + +func filterDirectory(fullMethodName string) bool { + if strings.Contains(fullMethodName, "_test") { + return true + } + + if strings.Contains(fullMethodName, "Test") { + return true + } + + if strings.Contains(fullMethodName, "/Library/") { + return true + } + return false +} + +func parseDotFile(codeDotfile string) { + fbuf, _ := ioutil.ReadFile(codeDotfile) + parseFromBuffer(fbuf) +} +func parseFromBuffer(fbuf []byte) { + g, err := gographviz.Read(fbuf) + if err != nil { + fmt.Println(string(fbuf)) + } + nodes := make(map[string]string) + for _, node := range g.Nodes.Nodes { + fullMethodName := strings.Replace(node.Attrs["label"], "\"", "", 2) + if strings.Contains(fullMethodName, " ") { + tmp := strings.Split(fullMethodName, " ") + fullMethodName = tmp[len(tmp)-1] + } + if filterDirectory(fullMethodName) { + continue + } + + methodName := formatMethodName(fullMethodName) + fullGraph.NodeList[methodName] = methodName + nodes[node.Name] = methodName + } + for key := range g.Edges.DstToSrcs { + for edgesKey := range g.Edges.DstToSrcs[key] { + for _, edge := range g.Edges.DstToSrcs[key][edgesKey] { + parseRelation(edge, nodes) + } + } + } +} +func formatMethodName(fullMethodName string) string { + methodName := strings.Replace(fullMethodName, "\\l", "", -1) + methodName = strings.Replace(methodName, "src/", "", -1) + methodName = strings.Replace(methodName, "include/", "", -1) + methodName = strings.ToLower(methodName) + return methodName +} + +func codeDotFiles(codeDir string, fileFilter func(string) bool) []string { + codeDotFiles := make([]string, 0) + filepath.Walk(codeDir, func(path string, fi os.FileInfo, err error) error { + if strings.HasSuffix(path, ".dot") { + if fileFilter(path) { + //return nil + if strings.Contains(path, "_test_") { + return nil + } + + codeDotFiles = append(codeDotFiles, path) + } + } + + return nil + }) + + return codeDotFiles +} + +func ParseInclude(codeDir string) *FullGraph { + fullGraph = &FullGraph{ + NodeList: make(map[string]string), + RelationList: make(map[string]*Relation), + } + codeDotFiles := codeDotFiles(codeDir, func(path string) bool { + + return strings.HasSuffix(path, "_dep__incl.dot") + }) + + for _, codeDotfile := range codeDotFiles { + parseDotFile(codeDotfile) + } + + return fullGraph +} + +func (fullGraph *FullGraph) ToDot(fileName string, split string, filter func(string) bool) { + graph := gographviz.NewGraph() + graph.SetName("G") + + nodeIndex := 1 + layerIndex := 1 + nodes := make(map[string]string) + + layerMap := make(map[string][]string) + + for nodeKey := range fullGraph.NodeList { + if filter(nodeKey) { + continue + } + + tmp := strings.Split(nodeKey, split) + packageName := tmp[0] + if packageName == nodeKey { + packageName = "main" + } + if len(tmp) > 2 { + packageName = strings.Join(tmp[0:len(tmp)-1], split) + } + + if _, ok := layerMap[packageName]; !ok { + layerMap[packageName] = make([]string, 0) + } + layerMap[packageName] = append(layerMap[packageName], nodeKey) + } + + for layer := range layerMap { + layerAttr := make(map[string]string) + layerAttr["label"] = "\"" + layer + "\"" + layerName := "cluster" + strconv.Itoa(layerIndex) + graph.AddSubGraph("G", layerName, layerAttr) + layerIndex++ + for _, node := range layerMap[layer] { + attrs := make(map[string]string) + fileName := strings.Replace(node, layer+split, "", -1) + attrs["label"] = "\"" + fileName + "\"" + attrs["shape"] = "box" + graph.AddNode(layerName, "node"+strconv.Itoa(nodeIndex), attrs) + nodes[node] = "node" + strconv.Itoa(nodeIndex) + nodeIndex++ + } + } + + cross := make(map[string]bool) // mapping from strings to ints + for key := range fullGraph.RelationList { + relation := fullGraph.RelationList[key] + if nodes[relation.From] != "" && nodes[relation.To] != "" { + fromNode := nodes[relation.From] + toNode := nodes[relation.To] + + cross[fromNode+toNode] = true + attrs := make(map[string]string) + attrs["style"] = relation.Style + + graph.AddEdge(fromNode, toNode, true, attrs) + } + } + + f, _ := os.Create(fileName) + w := bufio.NewWriter(f) + w.WriteString("di" + graph.String()) + w.Flush() +} + +var Foo = func() string { + return "" +} + +func (fullGraph *FullGraph) ToDataSet(fileName string, split string, filter func(string) bool) { + nodes := make(map[string]string) + + for nodeKey := range fullGraph.NodeList { + if filter(nodeKey) { + continue + } + + nodes[nodeKey] = nodeKey + } + + relMap := make(map[string][]string) + for key := range fullGraph.RelationList { + relation := fullGraph.RelationList[key] + + if nodes[relation.From] == "" && nodes[relation.To] != "" { + if _, ok := relMap[relation.From]; !ok { + relMap[relation.From] = make([]string, 0) + } + relMap[relation.From] = append(relMap[relation.From], relation.To) + } + } + + for key := range relMap { + tos := relMap[key] + fmt.Print("['" + strings.Join(tos, "','") + "'],") + } +} diff --git a/go.mod b/go.mod index a81adde..39d1f2e 100644 --- a/go.mod +++ b/go.mod @@ -4,18 +4,18 @@ go 1.13 require ( github.com/antlr/antlr4 v0.0.0-20191031194250-3fcb6da1f690 + github.com/awalterschulze/gographviz v0.0.0-20190522210029-fa59802746ab github.com/boyter/scc v2.10.1+incompatible github.com/dbaggerman/cuba v0.3.2 // indirect github.com/iancoleman/strcase v0.0.0-20191112232945-16388991a334 github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 // indirect github.com/monochromegane/go-gitignore v0.0.0-20160105113617-38717d0a108c // indirect github.com/olekukonko/tablewriter v0.0.4 - github.com/onsi/ginkgo v1.10.3 + github.com/onsi/ginkgo v1.10.3 // indirect github.com/onsi/gomega v1.7.1 github.com/pkg/profile v1.3.0 github.com/sabhiram/go-gitignore v0.0.0-20180611051255-d3107576ba94 github.com/spf13/cobra v0.0.5 golang.org/x/exp v0.0.0-20191224044220-1fea468a75e9 // indirect - golang.org/x/tools v0.0.0-20191226230302-065ed046f11a // indirect gonum.org/v1/gonum v0.6.2 ) diff --git a/go.sum b/go.sum index df0e3e2..6573292 100644 --- a/go.sum +++ b/go.sum @@ -5,6 +5,8 @@ github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3 github.com/antlr/antlr4 v0.0.0-20191031194250-3fcb6da1f690 h1:ZxuHyCRVyeoXhu/PK93BvZP66NoNIsSZzCykW5MezKc= github.com/antlr/antlr4 v0.0.0-20191031194250-3fcb6da1f690/go.mod h1:T7PbCXFs94rrTttyxjbyT5+/1V8T2TYDejxUfHJjw1Y= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/awalterschulze/gographviz v0.0.0-20190522210029-fa59802746ab h1:+cdNqtOJWjvepyhxy23G7z7vmpYCoC65AP0nqi1f53s= +github.com/awalterschulze/gographviz v0.0.0-20190522210029-fa59802746ab/go.mod h1:GEV5wmg4YquNw7v1kkyoX9etIk8yVmXj+AkDHuuETHs= github.com/boyter/scc v2.10.1+incompatible h1:0bg2TmmduZjvoSqz0dfF3W3jfibgEFv2XOsKItTjarU= github.com/boyter/scc v2.10.1+incompatible/go.mod h1:VB5w4e0dahmIiKnpZ7LRh/sjauoY0BmCWjIzZcShNY0= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= @@ -73,7 +75,6 @@ github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1: golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2 h1:y102fOLFqhV41b+4GPiJoa0k/x+pJcEi2/HB1Y5T6fU= @@ -86,7 +87,6 @@ golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMx golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -94,12 +94,14 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwL golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a h1:1n5lsVfiQW3yfsRGu98756EH1YthsFqr/5mxHduZW2A= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -107,13 +109,11 @@ golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e h1:Io7mpb+aUAGF0MKxbyQ7HQl1VgB+cL6ZJZUFaFNqVV4= golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191226230302-065ed046f11a h1:dzKmLFHGw+XKWQ9gr1JrQ0lVhDehzY+5Am/i7wxZlMs= -golang.org/x/tools v0.0.0-20191226230302-065ed046f11a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= gonum.org/v1/gonum v0.6.2 h1:4r+yNT0+8SWcOkXP+63H2zQbN+USnC73cjGUxnDF94Q= gonum.org/v1/gonum v0.6.2/go.mod h1:9mxDZsDKxgMAuccQkewq682L+0eCu4dCN2yonUJTCLU= +gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0 h1:OE9mWmgKkjJyEmDAAtGMPjXu+YNeGvK9VTSHY6+Qihc= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= -- GitLab