feat(cmd/gf): add interface functions generating for embedded struct of logic struct in command gen service (#3802)

This commit is contained in:
Hunk Zhu
2024-12-05 14:44:21 +08:00
committed by GitHub
parent e2cafa35e0
commit 7cd672d22c
13 changed files with 436 additions and 9 deletions

View File

@ -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"),
})
@ -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"),
}

View File

@ -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, `/`)
@ -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
@ -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)
@ -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)

View File

@ -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"
)
@ -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()
@ -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.

View File

@ -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]
}

View File

@ -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() {
}

View File

@ -0,0 +1,13 @@
package base
type baseDestory struct{}
// baseDestory Destory
func (baseDestory) Destory() {
}
// baseDestory BeforeDestory
func (baseDestory) BeforeDestory() {
}

View File

@ -0,0 +1,14 @@
package sub
type SubBase struct {
}
// subbase init
func (*SubBase) Init() {
}
// subbase GetSubBase
func (*SubBase) GetSubBase() {
}

View File

@ -6,6 +6,7 @@ package logic
import (
_ "github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genservice/logic/article"
_ "github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genservice/logic/base"
_ "github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genservice/logic/delivery"
_ "github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genservice/logic/user"
)

View File

@ -0,0 +1,27 @@
package user
import (
"context"
"github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genservice/service"
)
func init() {
service.RegisterSuperVipUser(&sSuperVipUser{
sVipUser: &sVipUser{},
})
}
type sSuperVipUser struct {
*sVipUser `gen:"extend"`
}
// Get supper vip user level
func (s sSuperVipUser) GetVipLevel(ctx context.Context) (vipLevel int, err error) {
return 1, nil
}
// Set supper vip user level
func (s *sSuperVipUser) SetVipLevel(ctx context.Context, id int, vipLevel int) (err error) {
return nil
}

View File

@ -9,6 +9,7 @@ package user
import (
"context"
"github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genservice/logic/base"
"github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genservice/service"
)
@ -17,6 +18,7 @@ func init() {
}
type sUser struct {
base.Base
}
func New() *sUser {

View File

@ -0,0 +1,29 @@
package user
import (
"context"
bbb "github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genservice/logic/base"
"github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genservice/service"
)
func init() {
service.RegisterVipUser(&sVipUser{})
}
type mybase = bbb.Base
type sVipUser struct {
sUser `gen:"extend"`
mybase `gen:"extend"`
}
// Create creates a new vip user.
func (s *sVipUser) Create(ctx context.Context, name string, vipLevel int) (id int, err error) {
return 0, nil
}
// Get vip user level
func (s *sVipUser) GetVipLevel() (vipLevel int, err error) {
return 1, nil
}

View File

@ -0,0 +1,32 @@
// ================================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// You can delete these comments if you wish manually maintain this interface file.
// ================================================================================
package service
type (
IBase interface {
// sBase Init
Init()
// sBase Destory
Destory()
// baseDestory BeforeDestory
BeforeDestory()
}
)
var (
localBase IBase
)
func Base() IBase {
if localBase == nil {
panic("implement not found for interface IBase, forgot register?")
}
return localBase
}
func RegisterBase(i IBase) {
localBase = i
}

View File

@ -10,6 +10,28 @@ import (
)
type (
ISuperVipUser interface {
// Get supper vip user level
GetVipLevel(ctx context.Context) (vipLevel int, err error)
// Set supper vip user level
SetVipLevel(ctx context.Context, id int, vipLevel int) (err error)
// Create creates a new vip user.
Create(ctx context.Context, name string, vipLevel int) (id int, err error)
// GetOne retrieves user by id.
GetOne(ctx context.Context, id int) (name string, err error)
// GetList retrieves user list.
GetList(ctx context.Context) (names []string, err error)
// Update updates user by id.
Update(ctx context.Context, id int) (name string, err error)
// Delete deletes user by id.
Delete(ctx context.Context, id int) (err error)
// sBase Init
Init()
// sBase Destory
Destory()
// baseDestory BeforeDestory
BeforeDestory()
}
IUser interface {
// Create creates a new user.
Create(ctx context.Context, name string) (id int, err error)
@ -22,12 +44,45 @@ type (
// Delete deletes user by id.
Delete(ctx context.Context, id int) (err error)
}
IVipUser interface {
// Create creates a new vip user.
Create(ctx context.Context, name string, vipLevel int) (id int, err error)
// Get vip user level
GetVipLevel() (vipLevel int, err error)
// GetOne retrieves user by id.
GetOne(ctx context.Context, id int) (name string, err error)
// GetList retrieves user list.
GetList(ctx context.Context) (names []string, err error)
// Update updates user by id.
Update(ctx context.Context, id int) (name string, err error)
// Delete deletes user by id.
Delete(ctx context.Context, id int) (err error)
// sBase Init
Init()
// sBase Destory
Destory()
// baseDestory BeforeDestory
BeforeDestory()
}
)
var (
localUser IUser
localSuperVipUser ISuperVipUser
localUser IUser
localVipUser IVipUser
)
func SuperVipUser() ISuperVipUser {
if localSuperVipUser == nil {
panic("implement not found for interface ISuperVipUser, forgot register?")
}
return localSuperVipUser
}
func RegisterSuperVipUser(i ISuperVipUser) {
localSuperVipUser = i
}
func User() IUser {
if localUser == nil {
panic("implement not found for interface IUser, forgot register?")
@ -38,3 +93,14 @@ func User() IUser {
func RegisterUser(i IUser) {
localUser = i
}
func VipUser() IVipUser {
if localVipUser == nil {
panic("implement not found for interface IVipUser, forgot register?")
}
return localVipUser
}
func RegisterVipUser(i IVipUser) {
localVipUser = i
}