Skip to content

Commit

Permalink
feat(cmd/gf): add interface functions generating for embedded struct …
Browse files Browse the repository at this point in the history
…of logic struct in command `gen service` (#3802)
  • Loading branch information
joy999 authored Dec 5, 2024
1 parent e2cafa3 commit 7cd672d
Show file tree
Hide file tree
Showing 13 changed files with 436 additions and 9 deletions.
2 changes: 2 additions & 0 deletions cmd/gf/internal/cmd/cmd_z_unit_gen_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ func Test_Gen_Service_Default(t *testing.T) {
t.AssertNil(err)
t.Assert(files, []string{
dstFolder + filepath.FromSlash("/article.go"),
dstFolder + filepath.FromSlash("/base.go"),
dstFolder + filepath.FromSlash("/delivery.go"),
dstFolder + filepath.FromSlash("/user.go"),
})
Expand All @@ -65,6 +66,7 @@ func Test_Gen_Service_Default(t *testing.T) {
testPath := gtest.DataPath("genservice", "service")
expectFiles := []string{
testPath + filepath.FromSlash("/article.go"),
testPath + filepath.FromSlash("/base.go"),
testPath + filepath.FromSlash("/delivery.go"),
testPath + filepath.FromSlash("/user.go"),
}
Expand Down
63 changes: 57 additions & 6 deletions cmd/gf/internal/cmd/genservice/genservice.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,20 @@ const (
genServiceFileLockSeconds = 10
)

type fileInfo struct {
PkgItems []pkgItem
FuncItems []funcItem
}

type folderInfo struct {
SrcPackageName string
SrcImportedPackages *garray.SortedStrArray
SrcStructFunctions *gmap.ListMap
DstFilePath string

FileInfos []*fileInfo
}

func (c CGenService) Service(ctx context.Context, in CGenServiceInput) (out *CGenServiceOutput, err error) {
in.SrcFolder = filepath.ToSlash(in.SrcFolder)
in.SrcFolder = gstr.TrimRight(in.SrcFolder, `/`)
Expand Down Expand Up @@ -163,7 +177,12 @@ func (c CGenService) Service(ctx context.Context, in CGenServiceInput) (out *CGe
return nil, err
}
// it will use goroutine to generate service files for each package.
var wg = sync.WaitGroup{}
var (
folderInfos []folderInfo
wg = sync.WaitGroup{}
allStructItems = make(map[string][]string)
)

for _, srcFolderPath := range srcFolderPaths {
if !gfile.IsDir(srcFolderPath) {
continue
Expand All @@ -175,7 +194,7 @@ func (c CGenService) Service(ctx context.Context, in CGenServiceInput) (out *CGe
if len(files) == 0 {
continue
}
// Parse single logic package folder.

var (
srcPackageName = gfile.Basename(srcFolderPath)
srcImportedPackages = garray.NewSortedStrArray().SetUnique(true)
Expand All @@ -184,14 +203,46 @@ func (c CGenService) Service(ctx context.Context, in CGenServiceInput) (out *CGe
c.getDstFileNameCase(srcPackageName, in.DstFileNameCase)+".go",
)
)
generatedDstFilePathSet.Add(dstFilePath)
// if it were to use goroutine,
// it would cause the order of the generated functions in the file to be disordered.

folder := folderInfo{
SrcPackageName: srcPackageName,
SrcImportedPackages: srcImportedPackages,
SrcStructFunctions: srcStructFunctions,
DstFilePath: dstFilePath,
}

for _, file := range files {
pkgItems, funcItems, err := c.parseItemsInSrc(file)
pkgItems, structItems, funcItems, err := c.parseItemsInSrc(file)
if err != nil {
return nil, err
}
for k, v := range structItems {
allStructItems[k] = v
}
folder.FileInfos = append(folder.FileInfos, &fileInfo{
PkgItems: pkgItems,
FuncItems: funcItems,
})
}

folderInfos = append(folderInfos, folder)
}

folderInfos = c.calculateStructEmbeddedFuncInfos(folderInfos, allStructItems)

for _, folder := range folderInfos {
// Parse single logic package folder.
var (
srcPackageName = folder.SrcPackageName
srcImportedPackages = folder.SrcImportedPackages
srcStructFunctions = folder.SrcStructFunctions
dstFilePath = folder.DstFilePath
)
generatedDstFilePathSet.Add(dstFilePath)
// if it were to use goroutine,
// it would cause the order of the generated functions in the file to be disordered.
for _, file := range folder.FileInfos {
pkgItems, funcItems := file.PkgItems, file.FuncItems

// Calculate imported packages for service generating.
err = c.calculateImportedItems(in, pkgItems, funcItems, srcImportedPackages)
Expand Down
102 changes: 100 additions & 2 deletions cmd/gf/internal/cmd/genservice/genservice_ast_parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ import (
"go/ast"
"go/parser"
"go/token"
"strings"

"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/os/gstructs"
"github.com/gogf/gf/v2/text/gstr"
)

Expand All @@ -32,7 +34,7 @@ type funcItem struct {
// parseItemsInSrc parses the pkgItem and funcItem from the specified file.
// It can't skip the private methods.
// It can't skip the imported packages of import alias equal to `_`.
func (c CGenService) parseItemsInSrc(filePath string) (pkgItems []pkgItem, funcItems []funcItem, err error) {
func (c CGenService) parseItemsInSrc(filePath string) (pkgItems []pkgItem, structItems map[string][]string, funcItems []funcItem, err error) {
var (
fileContent = gfile.GetContents(filePath)
fileSet = token.NewFileSet()
Expand All @@ -43,11 +45,107 @@ func (c CGenService) parseItemsInSrc(filePath string) (pkgItems []pkgItem, funcI
return
}

structItems = make(map[string][]string)
pkg := node.Name.Name
pkgAliasMap := make(map[string]string)
ast.Inspect(node, func(n ast.Node) bool {
switch x := n.(type) {
case *ast.ImportSpec:
// parse the imported packages.
pkgItems = append(pkgItems, c.parseImportPackages(x))
pkgItem := c.parseImportPackages(x)
pkgItems = append(pkgItems, pkgItem)
pkgPath := strings.Trim(pkgItem.Path, "\"")
pkgPath = strings.ReplaceAll(pkgPath, "\\", "/")
tmp := strings.Split(pkgPath, "/")
srcPkg := tmp[len(tmp)-1]
if srcPkg != pkgItem.Alias {
pkgAliasMap[pkgItem.Alias] = srcPkg
}
case *ast.TypeSpec: // type define
switch xType := x.Type.(type) {
case *ast.StructType: // define struct
// parse the struct declaration.
var structName = pkg + "." + x.Name.Name
var structEmbeddedStruct []string
for _, field := range xType.Fields.List {
if len(field.Names) > 0 || field.Tag == nil { // not anonymous field
continue
}

tagValue := strings.Trim(field.Tag.Value, "`")
tagValue = strings.TrimSpace(tagValue)
if len(tagValue) == 0 { // not set tag
continue
}
tags := gstructs.ParseTag(tagValue)

if v, ok := tags["gen"]; !ok || v != "extend" {
continue
}

var embeddedStruct string
switch v := field.Type.(type) {
case *ast.Ident:
if embeddedStruct, err = c.astExprToString(v); err != nil {
embeddedStruct = ""
break
}
embeddedStruct = pkg + "." + embeddedStruct
case *ast.StarExpr:
if embeddedStruct, err = c.astExprToString(v.X); err != nil {
embeddedStruct = ""
break
}
embeddedStruct = pkg + "." + embeddedStruct
case *ast.SelectorExpr:
var pkg string
if pkg, err = c.astExprToString(v.X); err != nil {
embeddedStruct = ""
break
}
if v, ok := pkgAliasMap[pkg]; ok {
pkg = v
}
if embeddedStruct, err = c.astExprToString(v.Sel); err != nil {
embeddedStruct = ""
break
}
embeddedStruct = pkg + "." + embeddedStruct
}

if embeddedStruct == "" {
continue
}
structEmbeddedStruct = append(structEmbeddedStruct, embeddedStruct)

}
if len(structEmbeddedStruct) > 0 {
structItems[structName] = structEmbeddedStruct
}
case *ast.Ident: // define ident
var (
structName = pkg + "." + x.Name.Name
typeName = pkg + "." + xType.Name
)
structItems[structName] = []string{typeName}
case *ast.SelectorExpr: // define selector
var (
structName = pkg + "." + x.Name.Name
selecotrPkg string
typeName string
)
if selecotrPkg, err = c.astExprToString(xType.X); err != nil {
break
}
if v, ok := pkgAliasMap[selecotrPkg]; ok {
selecotrPkg = v
}
if typeName, err = c.astExprToString(xType.Sel); err != nil {
break
}
typeName = selecotrPkg + "." + typeName
structItems[structName] = []string{typeName}
}

case *ast.FuncDecl:
// parse the function items.
Expand Down
75 changes: 75 additions & 0 deletions cmd/gf/internal/cmd/genservice/genservice_calculate.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,3 +150,78 @@ func (c CGenService) tidyResult(resultSlice []map[string]string) (resultStr stri
}
return
}

func (c CGenService) getStructFuncItems(structName string, allStructItems map[string][]string, funcItemsWithoutEmbed map[string][]*funcItem) (funcItems []*funcItem) {
funcItemNameSet := map[string]struct{}{}

if items, ok := funcItemsWithoutEmbed[structName]; ok {
funcItems = append(funcItems, items...)
for _, item := range items {
funcItemNameSet[item.MethodName] = struct{}{}
}
}

embeddedStructNames, ok := allStructItems[structName]
if !ok {
return
}

for _, embeddedStructName := range embeddedStructNames {
items := c.getStructFuncItems(embeddedStructName, allStructItems, funcItemsWithoutEmbed)

for _, item := range items {
if _, ok := funcItemNameSet[item.MethodName]; ok {
continue
}
funcItemNameSet[item.MethodName] = struct{}{}
funcItems = append(funcItems, item)
}
}

return
}

func (c CGenService) calculateStructEmbeddedFuncInfos(folderInfos []folderInfo, allStructItems map[string][]string) (newFolerInfos []folderInfo) {
funcItemsWithoutEmbed := make(map[string][]*funcItem)
funcItemMap := make(map[string]*([]funcItem))
funcItemsWithoutEmbedMap := make(map[string]*funcItem)

newFolerInfos = append(newFolerInfos, folderInfos...)

for _, folder := range newFolerInfos {
for k := range folder.FileInfos {
fi := folder.FileInfos[k]
for k := range fi.FuncItems {
item := &fi.FuncItems[k]
receiver := folder.SrcPackageName + "." + strings.ReplaceAll(item.Receiver, "*", "")
funcItemMap[receiver] = &fi.FuncItems
funcItemsWithoutEmbed[receiver] = append(funcItemsWithoutEmbed[receiver], item)
funcItemsWithoutEmbedMap[fmt.Sprintf("%s:%s", receiver, item.MethodName)] = item
}
}
}

for receiver, structItems := range allStructItems {
receiverName := strings.ReplaceAll(receiver, "*", "")
for _, structName := range structItems {
// Get the list of methods for the corresponding structName.
for _, funcItem := range c.getStructFuncItems(structName, allStructItems, funcItemsWithoutEmbed) {
if _, ok := funcItemsWithoutEmbedMap[fmt.Sprintf("%s:%s", receiverName, funcItem.MethodName)]; ok {
continue
}
if funcItemsPtr, ok := funcItemMap[receiverName]; ok {
newFuncItem := *funcItem
newFuncItem.Receiver = getReceiverName(receiver)
(*funcItemsPtr) = append((*funcItemsPtr), newFuncItem)
}
}
}
}

return
}

func getReceiverName(receiver string) string {
ss := strings.Split(receiver, ".")
return ss[len(ss)-1]
}
17 changes: 17 additions & 0 deletions cmd/gf/internal/cmd/testdata/genservice/logic/base/base.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package base

type Base = sBase

type sBase struct {
baseDestory `gen:"extend"`
}

// sBase Init
func (*sBase) Init() {

}

// sBase Destory
func (*sBase) Destory() {

}
13 changes: 13 additions & 0 deletions cmd/gf/internal/cmd/testdata/genservice/logic/base/base_destory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package base

type baseDestory struct{}

// baseDestory Destory
func (baseDestory) Destory() {

}

// baseDestory BeforeDestory
func (baseDestory) BeforeDestory() {

}
14 changes: 14 additions & 0 deletions cmd/gf/internal/cmd/testdata/genservice/logic/base/sub/sub.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package sub

type SubBase struct {
}

// subbase init
func (*SubBase) Init() {

}

// subbase GetSubBase
func (*SubBase) GetSubBase() {

}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 7cd672d

Please sign in to comment.