Skip to content

Commit

Permalink
Initial commit.
Browse files Browse the repository at this point in the history
  • Loading branch information
yuanyou committed Nov 28, 2022
0 parents commit d4470a1
Show file tree
Hide file tree
Showing 12 changed files with 553 additions and 0 deletions.
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# go-cyclic

<h4> Go 循环依赖检测工具 </h4>

快速开始
===============
```bash
go install github.com/elza2/go-cyclic@latest
# path 路径要设置为 go.mod 文件所在的全路径.
go-cyclic gocyclic --dir .path
```

运行测试
===============
```bash
git
go run ./cmd/main.go gocyclic --dir .path
```

```bash
# success output.
Success. Not circular dependence.

# failed output.
┌---→ daji.go
┆ ↓
┆ routes.go
┆ ↓
└--- handler.go
```


25 changes: 25 additions & 0 deletions cmd/actions/cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package actions

import (
"github.com/spf13/cobra"
"go-cyclic/tool"
"log"
)

func RunCyclic(cmd *cobra.Command, args []string) {
dir, err := cmd.Flags().GetString("dir")
if err != nil {
log.Fatalf("get dir params failed: %v\n", err)
}
if err = tool.CheckCycleDepend(dir); err != nil {
log.Fatalf("run failed: %v\n", err)
}
}

func init() {
cmd := &cobra.Command{
Use: "gocyclic",
Run: RunCyclic,
}
rootCmd.AddCommand(cmd)
}
24 changes: 24 additions & 0 deletions cmd/actions/root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package actions

import (
"github.com/spf13/cobra"
"log"
"os"
)

const Service = "go-cyclic"

var (
rootCmd = &cobra.Command{Use: Service}
)

func Execute() {
if err := rootCmd.Execute(); err != nil {
log.Println(err)
os.Exit(1)
}
}

func init() {
rootCmd.PersistentFlags().String("dir", os.Getenv("dir"), "dir. eg: full path of the go.mod file")
}
9 changes: 9 additions & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package main

import (
"go-cyclic/cmd/actions"
)

func main() {
actions.Execute()
}
8 changes: 8 additions & 0 deletions errors/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package errors

import "errors"

var (
GoModNotExist = errors.New("not find go.mod file")
GoModParseFailed = errors.New("go.mod file parse failed")
)
10 changes: 10 additions & 0 deletions example_cyclic_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package main

import (
"go-cyclic/tool"
"testing"
)

func TestCyclic(t *testing.T) {
tool.CheckCycleDepend("/Users/yuanyou/go/src/daji")
}
14 changes: 14 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
module go-cyclic

go 1.18

require (
github.com/fatih/color v1.13.0 // indirect
github.com/inconshreveable/mousetrap v1.0.1 // indirect
github.com/mattn/go-colorable v0.1.9 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/spf13/cobra v1.6.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/mod v0.7.0 // indirect
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect
)
23 changes: 23 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA=
github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA=
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
87 changes: 87 additions & 0 deletions resolver/resolver.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package resolver

import (
"fmt"
"go-cyclic/errors"
"go-cyclic/sprite"
"go/parser"
"go/token"
"golang.org/x/mod/modfile"
"io/ioutil"
"strings"
)

var (
GoMod = "go.mod"

fset = token.NewFileSet()
)

// ParseDir parse path.
func ParseDir(dir string) string {
idx := strings.LastIndex(dir, "/")
if idx == -1 {
return dir
}
return dir[idx+1:]
}

func ParseGoModule(dir string) (moduleName string, err error) {
readFile, err := ioutil.ReadFile(dir + "/" + GoMod)
if err != nil {
return "", errors.GoModNotExist
}
modFile, err := modfile.Parse(GoMod, readFile, nil)
if err != nil {
return "", errors.GoModParseFailed
}
return modFile.Module.Mod.Path, nil
}

func ParseNodeSprite(root string, module string, dir string) (nodes []*sprite.NodeSprite, err error) {
nodeSprites := make([]*sprite.NodeSprite, 0)
files, err := ioutil.ReadDir(dir)
if err != nil {
return nil, err
}
parseDir, err := parser.ParseDir(fset, dir, nil, 0)
if err != nil {
return nodeSprites, err
}
for k, v := range parseDir {
nodeImports := map[string][]string{}
for key, value := range v.Files {
for _, port := range value.Imports {
nodeImports[key] = append(nodeImports[key], port.Path.Value[1:len(port.Path.Value)-1])
}
filePath, nodeName := GetNodeSpritePathName(key)
nodeSprites = append(nodeSprites, &sprite.NodeSprite{
FilePath: filePath,
RootName: root,
ModuleName: module,
PackageName: k,
NodeName: nodeName,
ImportNames: nodeImports[key],
})
}
}
for _, file := range files {
if file.IsDir() {
sprites, err := ParseNodeSprite(root, module, fmt.Sprintf("%s/%s", dir, file.Name()))
if err != nil {
return sprites, err
}
nodeSprites = append(nodeSprites, sprites...)
}
}
return nodeSprites, nil
}

func GetNodeSpritePathName(content string) (filePath, nodeName string) {
contains := strings.Contains(content, "/")
if !contains {
return content, content
}
index := strings.LastIndex(content, "/")
return content[:index], content[index+1:]
}
78 changes: 78 additions & 0 deletions sprite/sprite.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package sprite

import (
"fmt"
"strings"
)

type NodeSprites struct {
nodeSprites []*NodeSprite
}

func (nodes *NodeSprites) SetNodeSprites(sprites []*NodeSprite) {
nodes.nodeSprites = sprites
}

func (nodes *NodeSprites) GetNodeSprites() []*NodeSprite {
return nodes.nodeSprites
}

type NodeSprite struct {
// full file path.
FilePath string
// root name.
RootName string
// module name.
ModuleName string
// package name.
PackageName string
// node name.
NodeName string
// import names.
ImportNames []string
}

// MatchImportNodeSprite matches the specified sprite.
func (nodes *NodeSprites) MatchImportNodeSprite(match string) []*NodeSprite {
nodeSprites := make([]*NodeSprite, 0)
for _, sprite := range nodes.nodeSprites {
path := sprite.GetRootPath()
if path == match {
nodeSprites = append(nodeSprites, sprite)
}
}
return nodeSprites
}

func (nodes *NodeSprites) GetNodeSprite(allPath string) *NodeSprite {
for _, sprite := range nodes.nodeSprites {
if sprite.GetAllPath() == allPath {
return sprite
}
}
return nil
}

func (node *NodeSprite) GetFilePath() string {
return node.FilePath
}

func (node *NodeSprite) GetAllPath() string {
return fmt.Sprintf("%s/%s", node.FilePath, node.NodeName)
}

func (node *NodeSprite) GetRootName() string {
return node.RootName
}

func (node *NodeSprite) GetRootPath() string {
idx := strings.LastIndex(node.FilePath, "/"+node.RootName)
if idx == -1 {
return node.FilePath
}
return node.ModuleName + node.FilePath[idx+len(node.RootName)+1:]
}

func (node *NodeSprite) GetImportNames() []string {
return node.ImportNames
}
31 changes: 31 additions & 0 deletions tool/tool.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package tool

import (
"go-cyclic/resolver"
"go-cyclic/sprite"
"go-cyclic/topology"
)

func CheckCycleDepend(dir string) error {
// parse root path.
root := resolver.ParseDir(dir)
// parse module name.
module, err := resolver.ParseGoModule(dir)
if err != nil {
return err
}
// parse sprite nodes by path.
sprites, err := resolver.ParseNodeSprite(root, module, dir)
if err != nil {
return err
}
nodeSprites := new(sprite.NodeSprites)
nodeSprites.SetNodeSprites(sprites)
// constructor topology struct.
topologies := topology.ConstructorTopology(nodeSprites)
// check cycle depend.
hasCycle := topologies.CycleDepend()
// print cycle depend.
topologies.PrintCycleDepend(hasCycle)
return nil
}
Loading

0 comments on commit d4470a1

Please sign in to comment.