Skip to content

Commit bf7f1d8

Browse files
committed
adding go-code-tree-fn + rework of gct for imports
1 parent 8410838 commit bf7f1d8

File tree

9 files changed

+350
-91
lines changed

9 files changed

+350
-91
lines changed

.github/workflows/build.yml

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,43 +42,47 @@ jobs:
4242

4343
- name: Build for Linux
4444
run: |
45-
GOOS=linux GOARCH=amd64 go build -ldflags "-s -w" -o bin/go-code-tree-linux main.go
45+
GOOS=linux GOARCH=amd64 go build -ldflags "-s -w" -o bin/go-code-tree-linux cmd/go-tree-code-imp/main.go
46+
GOOS=linux GOARCH=amd64 go build -ldflags "-s -w" -o bin/go-code-tree-fn-linux cmd/go-tree-code-fn/main.go
4647
4748
- name: Build for macOS (amd64)
4849
run: |
49-
GOOS=darwin GOARCH=amd64 go build -ldflags "-s -w" -o bin/go-code-tree-macos-intel main.go
50+
GOOS=darwin GOARCH=amd64 go build -ldflags "-s -w" -o bin/go-code-tree-macos-intel cmd/go-tree-code-imp/main.go
51+
GOOS=darwin GOARCH=amd64 go build -ldflags "-s -w" -o bin/go-code-tree-fn-macos-intel cmd/go-tree-code-fn/main.go
5052
5153
- name: Build for macOS (arm64)
5254
run: |
53-
GOOS=darwin GOARCH=arm64 go build -ldflags "-s -w" -o bin/go-code-tree-macos-arm main.go
55+
GOOS=darwin GOARCH=arm64 go build -ldflags "-s -w" -o bin/go-code-tree-macos-arm cmd/go-tree-code-imp/main.go
56+
GOOS=darwin GOARCH=arm64 go build -ldflags "-s -w" -o bin/go-code-tree-fn-macos-arm cmd/go-tree-code-fn/main.go
5457
5558
- name: Build for Windows
5659
run: |
57-
GOOS=windows GOARCH=amd64 go build -ldflags "-s -w" -o bin/go-code-tree-windows.exe main.go
60+
GOOS=windows GOARCH=amd64 go build -ldflags "-s -w" -o bin/go-code-tree-windows.exe cmd/go-tree-code-imp/main.go
61+
GOOS=windows GOARCH=amd64 go build -ldflags "-s -w" -o bin/go-code-tree-fn-windows.exe cmd/go-tree-code-fn/main.go
5862
5963
- name: Upload build artifact for Linux
6064
uses: actions/upload-artifact@v4
6165
with:
6266
name: go-code-tree-linux
63-
path: bin/go-code-tree-linux
67+
path: bin/go-code-tree*-linux
6468

6569
- name: Upload build artifact for macOS (intel)
6670
uses: actions/upload-artifact@v4
6771
with:
6872
name: go-code-tree-macos-intel
69-
path: bin/go-code-tree-macos-intel
73+
path: bin/go-code-tree*-macos-intel
7074

7175
- name: Upload build artifact for macOS (arm64)
7276
uses: actions/upload-artifact@v4
7377
with:
7478
name: go-code-tree-macos-arm
75-
path: bin/go-code-tree-macos-arm
79+
path: bin/go-code-tree*-macos-arm
7680

7781
- name: Upload build artifact for Windows
7882
uses: actions/upload-artifact@v4
7983
with:
8084
name: go-code-tree-windows
81-
path: bin/go-code-tree-windows.exe
85+
path: bin/go-code-tree*-windows.exe
8286

8387
- name: Create Release
8488
uses: ncipollo/release-action@v1

cmd/go-code-tree-fn/main.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package main
2+
3+
import (
4+
"flag"
5+
"fmt"
6+
"go-code-tree/pkg/codetree"
7+
"log"
8+
)
9+
10+
func main() {
11+
dir := flag.String("dir", "./", "Directory of the Go project to scan code")
12+
scanMocks := flag.Bool("mocks", false, "Scan mock files")
13+
scanTests := flag.Bool("tests", false, "Scan test files")
14+
flag.Parse()
15+
16+
mod, err := codetree.GetModule(*dir)
17+
if err != nil {
18+
log.Print("Error reading module name:", err)
19+
return
20+
}
21+
22+
ft := codetree.NewFuncTree(*dir, *mod)
23+
24+
funcs, err := ft.GetFuncs(*scanMocks, *scanTests)
25+
if err != nil {
26+
log.Print("Error getting funcs:", err)
27+
return
28+
}
29+
ft.Funcs = funcs
30+
31+
graph, err := ft.GenerateGraph()
32+
if err != nil {
33+
log.Print("Failed to generate graph:", err)
34+
return
35+
}
36+
37+
fmt.Println(graph)
38+
}

cmd/go-code-tree-imp/main.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package main
2+
3+
import (
4+
"flag"
5+
"fmt"
6+
"go-code-tree/pkg/codetree"
7+
"log"
8+
)
9+
10+
func main() {
11+
dir := flag.String("dir", "./", "Directory of the Go project to scan for imports")
12+
showThirdParty := flag.Bool("third", false, "Show third-party imports")
13+
scanMocks := flag.Bool("mocks", false, "Scan mock files")
14+
scanTests := flag.Bool("tests", false, "Scan test files")
15+
flag.Parse()
16+
17+
mod, err := codetree.GetModule(*dir)
18+
if err != nil {
19+
log.Print("Error reading module name:", err)
20+
return
21+
}
22+
23+
ct := codetree.NewImpTree(*dir, *mod)
24+
25+
imports, err := ct.GetImports(*scanMocks, *scanTests)
26+
if err != nil {
27+
log.Print("Error getting imports:", err)
28+
return
29+
}
30+
ct.Imports = imports
31+
32+
graph, err := ct.GenerateGraph(*showThirdParty)
33+
if err != nil {
34+
log.Print("Failed to generate graph:", err)
35+
return
36+
}
37+
38+
fmt.Println(graph)
39+
}

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
module go-code-tree
22

33
go 1.23.1
4+
5+
require golang.org/x/mod v0.21.0

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
2+
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=

pkg/codetree/functree.go

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
package codetree
2+
3+
import (
4+
"fmt"
5+
"go/ast"
6+
"go/parser"
7+
"go/token"
8+
"os"
9+
"path/filepath"
10+
"strings"
11+
)
12+
13+
type (
14+
FuncTree struct {
15+
Root string
16+
Module Module
17+
Funcs Relation
18+
}
19+
)
20+
21+
func NewFuncTree(root string, module Module) *FuncTree {
22+
return &FuncTree{
23+
Root: root,
24+
Module: module,
25+
Funcs: nil,
26+
}
27+
}
28+
29+
func parseExpr(e ast.Expr) string {
30+
switch xpr := e.(type) {
31+
case *ast.ArrayType:
32+
return fmt.Sprintf("[]%v", parseExpr(xpr.Elt)) // TODO: complete if slice has len -> [N]arr
33+
case *ast.ChanType:
34+
var arr string
35+
switch xpr.Dir {
36+
case ast.SEND:
37+
arr = "chan<-"
38+
case ast.RECV:
39+
arr = "<-chan"
40+
default:
41+
arr = "chan"
42+
}
43+
return fmt.Sprintf("%v%v", arr, xpr.Value)
44+
case *ast.Ident:
45+
return fmt.Sprintf("%v", xpr.Name)
46+
case *ast.MapType:
47+
return fmt.Sprintf("map[%s]%s", parseExpr(xpr.Key), parseExpr(xpr.Value))
48+
case *ast.SelectorExpr:
49+
return fmt.Sprintf("%s.%s", parseExpr(xpr.X), parseExpr(xpr.Sel))
50+
case *ast.StarExpr:
51+
return fmt.Sprintf("*%v", xpr.X)
52+
default:
53+
return ""
54+
}
55+
56+
}
57+
58+
func parseFieldList(fl ast.FieldList) string {
59+
var sb strings.Builder
60+
var comma string = ""
61+
for i, field := range fl.List {
62+
var nsb strings.Builder
63+
var ncomma string = ""
64+
for j, name := range field.Names {
65+
if j > 0 {
66+
ncomma = ","
67+
}
68+
nsb.WriteString(fmt.Sprintf("%s%s ", ncomma, name.Name))
69+
}
70+
71+
if i > 0 {
72+
comma = ", "
73+
}
74+
sb.WriteString(fmt.Sprintf("%s%s%s", comma, nsb.String(), parseExpr(field.Type)))
75+
}
76+
77+
return sb.String()
78+
}
79+
80+
func parseFuncs(filePath string) ([]string, error) {
81+
var (
82+
sb strings.Builder
83+
funcs []string
84+
)
85+
86+
inspector := func(n ast.Node) bool {
87+
if fn, ok := n.(*ast.FuncDecl); ok {
88+
sb.WriteString("func ")
89+
90+
if fn.Recv != nil {
91+
sb.WriteString(fmt.Sprintf("(%s) ", parseFieldList(*fn.Recv)))
92+
}
93+
94+
sb.WriteString(fn.Name.Name)
95+
96+
if fn.Type.Params != nil {
97+
sb.WriteString(fmt.Sprintf("(%s)", parseFieldList(*fn.Type.Params)))
98+
}
99+
100+
if fn.Type.Results != nil {
101+
sb.WriteString(fmt.Sprintf(" (%s)", parseFieldList(*fn.Type.Results)))
102+
}
103+
104+
sb.WriteString("\n")
105+
}
106+
return true
107+
}
108+
109+
fset := token.NewFileSet()
110+
node, err := parser.ParseFile(fset, filePath, nil, parser.AllErrors)
111+
if err != nil {
112+
return nil, err
113+
}
114+
115+
ast.Inspect(node, inspector)
116+
117+
for _, fn := range strings.Split(sb.String(), "\n") {
118+
if len(fn) > 0 {
119+
funcs = append(funcs, fn)
120+
}
121+
}
122+
123+
return funcs, nil
124+
}
125+
126+
func (ft *FuncTree) GetFuncs(scanMocks, scanTests bool) (Relation, error) {
127+
funcs := make(Relation)
128+
walker := func(path string, info os.FileInfo, err error) error {
129+
if err != nil {
130+
return err
131+
}
132+
133+
if strings.HasSuffix(path, "_test.go") && !scanTests || strings.HasSuffix(path, "_mock.go") && !scanMocks {
134+
return nil
135+
}
136+
137+
if !info.IsDir() && filepath.Ext(path) == ".go" {
138+
fns, err := parseFuncs(path)
139+
if err != nil {
140+
return err
141+
}
142+
funcs[path] = fns
143+
}
144+
145+
return nil
146+
}
147+
148+
if err := filepath.Walk(ft.Root, walker); err != nil {
149+
return nil, err
150+
}
151+
152+
return funcs, nil
153+
}
154+
155+
func (ft *FuncTree) GenerateGraph() (string, error) {
156+
var (
157+
sb strings.Builder
158+
fileFuncs Relation = ft.Funcs
159+
rels map[string]bool = make(map[string]bool)
160+
)
161+
162+
for _, line := range []string{
163+
fmt.Sprintf("digraph \"%s\" {\n", ft.Module.Basename()),
164+
" rankdir=LR;\n",
165+
" node [shape=box, color=\"burlywood\", style=\"filled\", fillcolor=\"seashell\"];\n",
166+
" edge [color=\"burlywood\"];\n",
167+
} {
168+
sb.WriteString(line)
169+
}
170+
171+
for file, funcs := range fileFuncs {
172+
path := strings.Split(file, string(filepath.Separator))
173+
basename := filepath.Base(file)
174+
for i := 0; i < len(path)-1; i++ {
175+
left := path[i]
176+
right := path[i+1]
177+
rels[fmt.Sprintf(" \"%s\" -> \"%s\" [color=\"orange\", style=\"filled\", fillcolor=\"lightyellow\"];\n", left, right)] = true
178+
}
179+
180+
rels[fmt.Sprintf(" \"%s\" [color=\"seagreen\", style=\"filled\", fillcolor=\"mintcream\"];\n", basename)] = true
181+
for _, fn := range funcs {
182+
rels[fmt.Sprintf(" \"%s\" [color=\"dodgerblue4\", style=\"filled\", fillcolor=\"aliceblue\"];\n", fn)] = true
183+
rels[fmt.Sprintf(" \"%s\" -> \"%s\" [color=\"seagreen\", style=\"filled\", fillcolor=\"mintcream\"];\n", basename, fn)] = true
184+
}
185+
}
186+
187+
for rel := range rels {
188+
sb.WriteString(rel)
189+
}
190+
191+
sb.WriteString("}")
192+
193+
return sb.String(), nil
194+
}

0 commit comments

Comments
 (0)