cmd/gf: refactor gf gen service with AST (#3488)

This commit is contained in:
oldme
2024-05-27 20:41:05 +08:00
committed by GitHub
parent b675d01418
commit 8f4d5f7d74
16 changed files with 843 additions and 392 deletions

View File

@ -57,12 +57,16 @@ func Test_Gen_Service_Default(t *testing.T) {
t.AssertNil(err)
t.Assert(files, []string{
dstFolder + filepath.FromSlash("/article.go"),
dstFolder + filepath.FromSlash("/delivery.go"),
dstFolder + filepath.FromSlash("/user.go"),
})
// contents
testPath := gtest.DataPath("genservice", "service")
expectFiles := []string{
testPath + filepath.FromSlash("/article.go"),
testPath + filepath.FromSlash("/delivery.go"),
testPath + filepath.FromSlash("/user.go"),
}
for i := range files {
t.Assert(gfile.GetContents(files[i]), gfile.GetContents(expectFiles[i]))

View File

@ -10,7 +10,11 @@ import (
"context"
"fmt"
"path/filepath"
"sync"
"sync/atomic"
"github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
"github.com/gogf/gf/cmd/gf/v2/internal/utility/utils"
"github.com/gogf/gf/v2/container/garray"
"github.com/gogf/gf/v2/container/gmap"
"github.com/gogf/gf/v2/container/gset"
@ -21,9 +25,6 @@ import (
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gconv"
"github.com/gogf/gf/v2/util/gtag"
"github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
"github.com/gogf/gf/cmd/gf/v2/internal/utility/utils"
)
const (
@ -147,19 +148,22 @@ func (c CGenService) Service(ctx context.Context, in CGenServiceInput) (out *CGe
}
var (
isDirty bool // Temp boolean.
isDirty atomic.Value // Temp boolean.
files []string // Temp file array.
fileContent string // Temp file content for handling go file.
initImportSrcPackages []string // Used for generating logic.go.
inputPackages = in.Packages // Custom packages.
dstPackageName = gstr.ToLower(gfile.Basename(in.DstFolder)) // Package name for generated go files.
generatedDstFilePathSet = gset.NewStrSet() // All generated file path set.
)
isDirty.Store(false)
// The first level folders.
srcFolderPaths, err := gfile.ScanDir(in.SrcFolder, "*", false)
if err != nil {
return nil, err
}
// it will use goroutine to generate service files for each package.
var wg = sync.WaitGroup{}
for _, srcFolderPath := range srcFolderPaths {
if !gfile.IsDir(srcFolderPath) {
continue
@ -173,111 +177,30 @@ func (c CGenService) Service(ctx context.Context, in CGenServiceInput) (out *CGe
}
// Parse single logic package folder.
var (
// StructName => FunctionDefinitions
srcPkgInterfaceMap = gmap.NewListMap()
srcImportedPackages = garray.NewSortedStrArray().SetUnique(true)
importAliasToPathMap = gmap.NewStrStrMap() // for conflict imports check. alias => import path(with `"`)
importPathToAliasMap = gmap.NewStrStrMap() // for conflict imports check. import path(with `"`) => alias
srcPackageName = gfile.Basename(srcFolderPath)
ok bool
dstFilePath = gfile.Join(in.DstFolder,
srcPackageName = gfile.Basename(srcFolderPath)
srcImportedPackages = garray.NewSortedStrArray().SetUnique(true)
srcStructFunctions = gmap.NewListMap()
dstFilePath = gfile.Join(in.DstFolder,
c.getDstFileNameCase(srcPackageName, in.DstFileNameCase)+".go",
)
srcCodeCommentedMap = make(map[string]string)
)
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 files {
var packageItems []packageItem
fileContent = gfile.GetContents(file)
// Calculate code comments in source Go files.
err = c.calculateCodeCommented(in, fileContent, srcCodeCommentedMap)
if err != nil {
return nil, err
}
// remove all comments.
fileContent, err = gregex.ReplaceString(`(//.*)|((?s)/\*.*?\*/)`, "", fileContent)
pkgItems, funcItems, err := c.parseItemsInSrc(file)
if err != nil {
return nil, err
}
// Calculate imported packages of source go files.
packageItems, err = c.calculateImportedPackages(fileContent)
// Calculate imported packages for service generating.
err = c.calculateImportedItems(in, pkgItems, funcItems, srcImportedPackages)
if err != nil {
return nil, err
}
// try finding the conflicts imports between files.
for _, item := range packageItems {
var alias = item.Alias
if alias == "" {
alias = gfile.Basename(gstr.Trim(item.Path, `"`))
}
// ignore unused import paths, which do not exist in function definitions.
if !gregex.IsMatchString(fmt.Sprintf(`func .+?([^\w])%s(\.\w+).+?{`, alias), fileContent) {
mlog.Debugf(`ignore unused package: %s`, item.RawImport)
continue
}
// find the exist alias with the same import path.
var existAlias = importPathToAliasMap.Get(item.Path)
if existAlias != "" {
fileContent, err = gregex.ReplaceStringFuncMatch(
fmt.Sprintf(`([^\w])%s(\.\w+)`, alias), fileContent,
func(match []string) string {
return match[1] + existAlias + match[2]
},
)
if err != nil {
return nil, err
}
continue
}
// resolve alias conflicts.
var importPath = importAliasToPathMap.Get(alias)
if importPath == "" {
importAliasToPathMap.Set(alias, item.Path)
importPathToAliasMap.Set(item.Path, alias)
srcImportedPackages.Add(item.RawImport)
continue
}
if importPath != item.Path {
// update the conflicted alias for import path with suffix.
// eg:
// v1 -> v10
// v11 -> v110
for aliasIndex := 0; ; aliasIndex++ {
item.Alias = fmt.Sprintf(`%s%d`, alias, aliasIndex)
var existPathForAlias = importAliasToPathMap.Get(item.Alias)
if existPathForAlias != "" {
if existPathForAlias == item.Path {
break
}
continue
}
break
}
importPathToAliasMap.Set(item.Path, item.Alias)
importAliasToPathMap.Set(item.Alias, item.Path)
// reformat the import path with alias.
item.RawImport = fmt.Sprintf(`%s %s`, item.Alias, item.Path)
// update the file content with new alias import.
fileContent, err = gregex.ReplaceStringFuncMatch(
fmt.Sprintf(`([^\w])%s(\.\w+)`, alias), fileContent,
func(match []string) string {
return match[1] + item.Alias + match[2]
},
)
if err != nil {
return nil, err
}
srcImportedPackages.Add(item.RawImport)
}
}
// Calculate functions and interfaces for service generating.
err = c.calculateInterfaceFunctions(in, fileContent, srcPkgInterfaceMap)
err = c.calculateFuncItems(in, funcItems, srcStructFunctions)
if err != nil {
return nil, err
}
@ -295,22 +218,28 @@ func (c CGenService) Service(ctx context.Context, in CGenServiceInput) (out *CGe
)
continue
}
// Generating service go file for single logic package.
if ok, err = c.generateServiceFile(generateServiceFilesInput{
wg.Add(1)
go func(generateServiceFilesInput generateServiceFilesInput) {
defer wg.Done()
ok, err := c.generateServiceFile(generateServiceFilesInput)
if err != nil {
mlog.Printf(`error generating service file "%s": %v`, generateServiceFilesInput.DstFilePath, err)
}
if !isDirty.Load().(bool) && ok {
isDirty.Store(true)
}
}(generateServiceFilesInput{
CGenServiceInput: in,
SrcStructFunctions: srcPkgInterfaceMap,
SrcImportedPackages: srcImportedPackages.Slice(),
SrcPackageName: srcPackageName,
SrcImportedPackages: srcImportedPackages.Slice(),
SrcStructFunctions: srcStructFunctions,
DstPackageName: dstPackageName,
DstFilePath: dstFilePath,
SrcCodeCommentedMap: srcCodeCommentedMap,
}); err != nil {
return
}
if ok {
isDirty = true
}
})
}
wg.Wait()
if in.Clear {
files, err = gfile.ScanDirFile(in.DstFolder, "*.go", false)
@ -320,7 +249,9 @@ func (c CGenService) Service(ctx context.Context, in CGenServiceInput) (out *CGe
var relativeFilePath string
for _, file := range files {
relativeFilePath = gstr.SubStrFromR(file, in.DstFolder)
if !generatedDstFilePathSet.Contains(relativeFilePath) && utils.IsFileDoNotEdit(relativeFilePath) {
if !generatedDstFilePathSet.Contains(relativeFilePath) &&
utils.IsFileDoNotEdit(relativeFilePath) {
mlog.Printf(`remove no longer used service file: %s`, relativeFilePath)
if err = gfile.Remove(file); err != nil {
return nil, err
@ -329,7 +260,7 @@ func (c CGenService) Service(ctx context.Context, in CGenServiceInput) (out *CGe
}
}
if isDirty {
if isDirty.Load().(bool) {
// Generate initialization go file.
if len(initImportSrcPackages) > 0 {
if err = c.generateInitializationFile(in, initImportSrcPackages); err != nil {

View File

@ -0,0 +1,186 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package genservice
import (
"go/ast"
"go/parser"
"go/token"
"github.com/gogf/gf/v2/os/gfile"
)
type pkgItem struct {
Alias string `eg:"gdbas"`
Path string `eg:"github.com/gogf/gf/v2/database/gdb"`
RawImport string `eg:"gdbas github.com/gogf/gf/v2/database/gdb"`
}
type funcItem struct {
Receiver string `eg:"sUser"`
MethodName string `eg:"GetList"`
Params []map[string]string `eg:"ctx: context.Context, cond: *SearchInput"`
Results []map[string]string `eg:"list: []*User, err: error"`
Comment string `eg:"Get user list"`
}
// 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) {
var (
fileContent = gfile.GetContents(filePath)
fileSet = token.NewFileSet()
)
node, err := parser.ParseFile(fileSet, "", fileContent, parser.ParseComments)
if err != nil {
return
}
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))
case *ast.FuncDecl:
// parse the function items.
if x.Recv == nil {
return true
}
var funcName = x.Name.Name
funcItems = append(funcItems, funcItem{
Receiver: c.parseFuncReceiverTypeName(x),
MethodName: funcName,
Params: c.parseFuncParams(x),
Results: c.parseFuncResults(x),
Comment: c.parseFuncComment(x),
})
}
return true
})
return
}
// parseImportPackages retrieves the imported packages from the specified ast.ImportSpec.
func (c CGenService) parseImportPackages(node *ast.ImportSpec) (packages pkgItem) {
if node.Path == nil {
return
}
var (
alias string
path = node.Path.Value
rawImport string
)
if node.Name != nil {
alias = node.Name.Name
rawImport = alias + " " + path
} else {
rawImport = path
}
return pkgItem{
Alias: alias,
Path: path,
RawImport: rawImport,
}
}
// parseFuncReceiverTypeName retrieves the receiver type of the function.
// For example:
//
// func(s *sArticle) -> *sArticle
// func(s sArticle) -> sArticle
func (c CGenService) parseFuncReceiverTypeName(node *ast.FuncDecl) (receiverType string) {
if node.Recv == nil {
return ""
}
receiverType, err := c.astExprToString(node.Recv.List[0].Type)
if err != nil {
return ""
}
return
}
// parseFuncParams retrieves the input parameters of the function.
// It returns the name and type of the input parameters.
// For example:
//
// []map[string]string{paramName:ctx paramType:context.Context, paramName:info paramType:struct{}}
func (c CGenService) parseFuncParams(node *ast.FuncDecl) (params []map[string]string) {
if node.Type.Params == nil {
return
}
for _, param := range node.Type.Params.List {
if param.Names == nil {
// No name for the return value.
resultType, err := c.astExprToString(param.Type)
if err != nil {
continue
}
params = append(params, map[string]string{
"paramName": "",
"paramType": resultType,
})
continue
}
for _, name := range param.Names {
paramType, err := c.astExprToString(param.Type)
if err != nil {
continue
}
params = append(params, map[string]string{
"paramName": name.Name,
"paramType": paramType,
})
}
}
return
}
// parseFuncResults retrieves the output parameters of the function.
// It returns the name and type of the output parameters.
// For example:
//
// []map[string]string{resultName:list resultType:[]*User, resultName:err resultType:error}
// []map[string]string{resultName: "", resultType: error}
func (c CGenService) parseFuncResults(node *ast.FuncDecl) (results []map[string]string) {
if node.Type.Results == nil {
return
}
for _, result := range node.Type.Results.List {
if result.Names == nil {
// No name for the return value.
resultType, err := c.astExprToString(result.Type)
if err != nil {
continue
}
results = append(results, map[string]string{
"resultName": "",
"resultType": resultType,
})
continue
}
for _, name := range result.Names {
resultType, err := c.astExprToString(result.Type)
if err != nil {
continue
}
results = append(results, map[string]string{
"resultName": name.Name,
"resultType": resultType,
})
}
}
return
}
// parseFuncComment retrieves the comment of the function.
func (c CGenService) parseFuncComment(node *ast.FuncDecl) string {
return c.astCommentToString(node.Doc)
}

View File

@ -0,0 +1,48 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package genservice
import (
"bytes"
"go/ast"
"go/format"
"go/token"
"strings"
)
// exprToString converts ast.Expr to string.
// For example:
//
// ast.Expr -> "context.Context"
// ast.Expr -> "*v1.XxxReq"
// ast.Expr -> "error"
// ast.Expr -> "int"
func (c CGenService) astExprToString(expr ast.Expr) (string, error) {
var (
buf bytes.Buffer
err error
)
err = format.Node(&buf, token.NewFileSet(), expr)
if err != nil {
return "", err
}
return buf.String(), nil
}
// astCommentToString returns the raw (original) text of the comment.
// It includes the comment markers (//, /*, and */).
// It adds a newline at the end of the comment.
func (c CGenService) astCommentToString(node *ast.CommentGroup) string {
if node == nil {
return ""
}
var b strings.Builder
for _, c := range node.List {
b.WriteString(c.Text + "\n")
}
return b.String()
}

View File

@ -8,168 +8,145 @@ package genservice
import (
"fmt"
"go/parser"
"go/token"
"strings"
"github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
"github.com/gogf/gf/v2/container/garray"
"github.com/gogf/gf/v2/container/gmap"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/text/gregex"
"github.com/gogf/gf/v2/text/gstr"
)
type packageItem struct {
Alias string
Path string
RawImport string
}
func (c CGenService) calculateImportedPackages(fileContent string) (packages []packageItem, err error) {
f, err := parser.ParseFile(token.NewFileSet(), "", fileContent, parser.ImportsOnly)
if err != nil {
return nil, err
}
packages = make([]packageItem, 0)
for _, s := range f.Imports {
if s.Path != nil {
if s.Name != nil {
// If it has alias, and it is not `_`.
if pkgAlias := s.Name.String(); pkgAlias != "_" {
packages = append(packages, packageItem{
Alias: pkgAlias,
Path: s.Path.Value,
RawImport: pkgAlias + " " + s.Path.Value,
})
}
} else {
// no alias
packages = append(packages, packageItem{
Alias: "",
Path: s.Path.Value,
RawImport: s.Path.Value,
})
}
}
}
return packages, nil
}
func (c CGenService) calculateCodeCommented(in CGenServiceInput, fileContent string, srcCodeCommentedMap map[string]string) error {
matches, err := gregex.MatchAllString(`((((//.*)|(/\*[\s\S]*?\*/))\s)+)func \((.+?)\) ([\s\S]+?) {`, fileContent)
if err != nil {
return err
}
for _, match := range matches {
var (
structName string
structMatch []string
funcReceiver = gstr.Trim(match[1+5])
receiverArray = gstr.SplitAndTrim(funcReceiver, " ")
functionHead = gstr.Trim(gstr.Replace(match[2+5], "\n", ""))
commentedInfo = ""
)
if len(receiverArray) > 1 {
structName = receiverArray[1]
} else if len(receiverArray) == 1 {
structName = receiverArray[0]
}
structName = gstr.Trim(structName, "*")
// Case of:
// Xxx(\n ctx context.Context, req *v1.XxxReq,\n) -> Xxx(ctx context.Context, req *v1.XxxReq)
functionHead = gstr.Replace(functionHead, `,)`, `)`)
functionHead, _ = gregex.ReplaceString(`\(\s+`, `(`, functionHead)
functionHead, _ = gregex.ReplaceString(`\s{2,}`, ` `, functionHead)
if !gstr.IsLetterUpper(functionHead[0]) {
continue
}
// Match and pick the struct name from receiver.
if structMatch, err = gregex.MatchString(in.StPattern, structName); err != nil {
return err
}
if len(structMatch) < 1 {
continue
}
structName = gstr.CaseCamel(structMatch[1])
commentedInfo = match[1]
if len(commentedInfo) > 0 {
srcCodeCommentedMap[fmt.Sprintf("%s-%s", structName, functionHead)] = commentedInfo
}
}
return nil
}
func (c CGenService) calculateInterfaceFunctions(
in CGenServiceInput, fileContent string, srcPkgInterfaceMap *gmap.ListMap,
func (c CGenService) calculateImportedItems(
in CGenServiceInput,
pkgItems []pkgItem, funcItems []funcItem,
srcImportedPackages *garray.SortedStrArray,
) (err error) {
var (
matches [][]string
srcPkgInterfaceFuncArray *garray.StrArray
)
// calculate struct name and its functions according function definitions.
matches, err = gregex.MatchAllString(`func \((.+?)\) ([\s\S]+?) {`, fileContent)
if err != nil {
return err
}
for _, match := range matches {
var (
structName string
structMatch []string
funcReceiver = gstr.Trim(match[1])
receiverArray = gstr.SplitAndTrim(funcReceiver, " ")
functionHead = gstr.Trim(gstr.Replace(match[2], "\n", ""))
)
if len(receiverArray) > 1 {
structName = receiverArray[1]
} else if len(receiverArray) == 1 {
structName = receiverArray[0]
}
structName = gstr.Trim(structName, "*")
// allFuncParamType saves all the param and result types of the functions.
var allFuncParamType strings.Builder
// Case of:
// Xxx(\n ctx context.Context, req *v1.XxxReq,\n) -> Xxx(ctx context.Context, req *v1.XxxReq)
functionHead = gstr.Replace(functionHead, `,)`, `)`)
functionHead, _ = gregex.ReplaceString(`\(\s+`, `(`, functionHead)
functionHead, _ = gregex.ReplaceString(`\s{2,}`, ` `, functionHead)
if !gstr.IsLetterUpper(functionHead[0]) {
continue
for _, item := range funcItems {
for _, param := range item.Params {
allFuncParamType.WriteString(param["paramType"] + ",")
}
// Match and pick the struct name from receiver.
if structMatch, err = gregex.MatchString(in.StPattern, structName); err != nil {
return err
for _, result := range item.Results {
allFuncParamType.WriteString(result["resultType"] + ",")
}
if len(structMatch) < 1 {
continue
}
structName = gstr.CaseCamel(structMatch[1])
if !srcPkgInterfaceMap.Contains(structName) {
srcPkgInterfaceFuncArray = garray.NewStrArray()
srcPkgInterfaceMap.Set(structName, srcPkgInterfaceFuncArray)
} else {
srcPkgInterfaceFuncArray = srcPkgInterfaceMap.Get(structName).(*garray.StrArray)
}
srcPkgInterfaceFuncArray.Append(functionHead)
}
// calculate struct name according type definitions.
matches, err = gregex.MatchAllString(`type (.+) struct\s*{`, fileContent)
if err != nil {
return err
}
for _, match := range matches {
var (
structName string
structMatch []string
)
if structMatch, err = gregex.MatchString(in.StPattern, match[1]); err != nil {
return err
}
if len(structMatch) < 1 {
for _, item := range pkgItems {
alias := item.Alias
// If the alias is _, it means that the package is not generated.
if alias == "_" {
mlog.Debugf(`ignore anonymous package: %s`, item.RawImport)
continue
}
structName = gstr.CaseCamel(structMatch[1])
if !srcPkgInterfaceMap.Contains(structName) {
srcPkgInterfaceMap.Set(structName, garray.NewStrArray())
// If the alias is empty, it will use the package name as the alias.
if alias == "" {
alias = gfile.Basename(gstr.Trim(item.Path, `"`))
}
if !gstr.Contains(allFuncParamType.String(), alias) {
mlog.Debugf(`ignore unused package: %s`, item.RawImport)
continue
}
srcImportedPackages.Add(item.RawImport)
}
return nil
}
func (c CGenService) calculateFuncItems(
in CGenServiceInput,
funcItems []funcItem,
srcPkgInterfaceMap *gmap.ListMap,
) (err error) {
var srcPkgInterfaceFunc []map[string]string
for _, item := range funcItems {
var (
// eg: "sArticle"
receiverName string
receiverMatch []string
// eg: "GetList(ctx context.Context, req *v1.ArticleListReq) (list []*v1.Article, err error)"
funcHead string
)
// handle the receiver name.
if item.Receiver == "" {
continue
}
receiverName = item.Receiver
receiverName = gstr.Trim(receiverName, "*")
// Match and pick the struct name from receiver.
if receiverMatch, err = gregex.MatchString(in.StPattern, receiverName); err != nil {
return err
}
if len(receiverMatch) < 1 {
continue
}
receiverName = gstr.CaseCamel(receiverMatch[1])
// check if the func name is public.
if !gstr.IsLetterUpper(item.MethodName[0]) {
continue
}
if !srcPkgInterfaceMap.Contains(receiverName) {
srcPkgInterfaceFunc = make([]map[string]string, 0)
srcPkgInterfaceMap.Set(receiverName, srcPkgInterfaceFunc)
} else {
srcPkgInterfaceFunc = srcPkgInterfaceMap.Get(receiverName).([]map[string]string)
}
// make the func head.
paramsStr := c.tidyParam(item.Params)
resultsStr := c.tidyResult(item.Results)
funcHead = fmt.Sprintf("%s(%s) (%s)", item.MethodName, paramsStr, resultsStr)
srcPkgInterfaceFunc = append(srcPkgInterfaceFunc, map[string]string{
"funcHead": funcHead,
"funcComment": item.Comment,
})
srcPkgInterfaceMap.Set(receiverName, srcPkgInterfaceFunc)
}
return nil
}
// tidyParam tidies the input parameters.
// For example:
//
// []map[string]string{paramName:ctx paramType:context.Context, paramName:info paramType:struct{}}
// -> ctx context.Context, info struct{}
func (c CGenService) tidyParam(paramSlice []map[string]string) (paramStr string) {
for i, param := range paramSlice {
if i > 0 {
paramStr += ", "
}
paramStr += fmt.Sprintf("%s %s", param["paramName"], param["paramType"])
}
return
}
// tidyResult tidies the output parameters.
// For example:
//
// []map[string]string{resultName:list resultType:[]*User, resultName:err resultType:error}
// -> list []*User, err error
//
// []map[string]string{resultName: "", resultType: error}
// -> error
func (c CGenService) tidyResult(resultSlice []map[string]string) (resultStr string) {
for i, result := range resultSlice {
if i > 0 {
resultStr += ", "
}
if result["resultName"] != "" {
resultStr += fmt.Sprintf("%s %s", result["resultName"], result["resultType"])
} else {
resultStr += result["resultType"]
}
}
return
}

View File

@ -7,112 +7,34 @@
package genservice
import (
"bytes"
"fmt"
"github.com/gogf/gf/v2/container/garray"
"github.com/gogf/gf/v2/container/gmap"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/text/gregex"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/cmd/gf/v2/internal/consts"
"github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
"github.com/gogf/gf/cmd/gf/v2/internal/utility/utils"
"github.com/gogf/gf/v2/container/gmap"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/text/gstr"
)
type generateServiceFilesInput struct {
CGenServiceInput
DstFilePath string // Absolute file path for generated service go file.
SrcStructFunctions *gmap.ListMap
SrcImportedPackages []string
SrcPackageName string
SrcImportedPackages []string
SrcStructFunctions *gmap.ListMap
DstPackageName string
SrcCodeCommentedMap map[string]string
DstFilePath string // Absolute file path for generated service go file.
}
func (c CGenService) generateServiceFile(in generateServiceFilesInput) (ok bool, err error) {
var (
generatedContent string
allFuncArray = garray.NewStrArray() // Used for check whether interface dirty, going to change file content.
importedPackagesContent = fmt.Sprintf(
"import (\n%s\n)", gstr.Join(in.SrcImportedPackages, "\n"),
)
)
generatedContent += gstr.ReplaceByMap(consts.TemplateGenServiceContentHead, g.MapStrStr{
"{Imports}": importedPackagesContent,
"{PackageName}": in.DstPackageName,
})
var generatedContent bytes.Buffer
// Type definitions.
generatedContent += "type("
generatedContent += "\n"
in.SrcStructFunctions.Iterator(func(key, value interface{}) bool {
structName, funcArray := key.(string), value.(*garray.StrArray)
allFuncArray.Append(funcArray.Slice()...)
// Add comments to a method.
for index, funcName := range funcArray.Slice() {
if commentedInfo, exist := in.SrcCodeCommentedMap[fmt.Sprintf("%s-%s", structName, funcName)]; exist {
funcName = commentedInfo + funcName
_ = funcArray.Set(index, funcName)
}
}
generatedContent += gstr.Trim(gstr.ReplaceByMap(consts.TemplateGenServiceContentInterface, g.MapStrStr{
"{InterfaceName}": "I" + structName,
"{FuncDefinition}": funcArray.Join("\n\t"),
}))
generatedContent += "\n"
return true
})
generatedContent += ")"
generatedContent += "\n"
// Generating variable and register definitions.
var (
variableContent string
generatingInterfaceCheck string
)
// Variable definitions.
in.SrcStructFunctions.Iterator(func(key, value interface{}) bool {
structName := key.(string)
generatingInterfaceCheck = fmt.Sprintf(`[^\w\d]+%s.I%s[^\w\d]`, in.DstPackageName, structName)
if gregex.IsMatchString(generatingInterfaceCheck, generatedContent) {
return true
}
variableContent += gstr.Trim(gstr.ReplaceByMap(consts.TemplateGenServiceContentVariable, g.MapStrStr{
"{StructName}": structName,
"{InterfaceName}": "I" + structName,
}))
variableContent += "\n"
return true
})
if variableContent != "" {
generatedContent += "var("
generatedContent += "\n"
generatedContent += variableContent
generatedContent += ")"
generatedContent += "\n"
}
// Variable register function definitions.
in.SrcStructFunctions.Iterator(func(key, value interface{}) bool {
structName := key.(string)
generatingInterfaceCheck = fmt.Sprintf(`[^\w\d]+%s.I%s[^\w\d]`, in.DstPackageName, structName)
if gregex.IsMatchString(generatingInterfaceCheck, generatedContent) {
return true
}
generatedContent += gstr.Trim(gstr.ReplaceByMap(consts.TemplateGenServiceContentRegister, g.MapStrStr{
"{StructName}": structName,
"{InterfaceName}": "I" + structName,
}))
generatedContent += "\n\n"
return true
})
// Replace empty braces that have new line.
generatedContent, _ = gregex.ReplaceString(`{[\s\t]+}`, `{}`, generatedContent)
// Remove package name calls of `dstPackageName` in produced codes.
generatedContent, _ = gregex.ReplaceString(fmt.Sprintf(`\*{0,1}%s\.`, in.DstPackageName), ``, generatedContent)
c.generatePackageImports(&generatedContent, in.DstPackageName, in.SrcImportedPackages)
c.generateType(&generatedContent, in.SrcStructFunctions, in.DstPackageName)
c.generateVar(&generatedContent, in.SrcStructFunctions)
c.generateFunc(&generatedContent, in.SrcStructFunctions)
// Write file content to disk.
if gfile.Exists(in.DstFilePath) {
@ -120,59 +42,14 @@ func (c CGenService) generateServiceFile(in generateServiceFilesInput) (ok bool,
mlog.Printf(`ignore file as it is manually maintained: %s`, in.DstFilePath)
return false, nil
}
if !c.isToGenerateServiceGoFile(in.DstPackageName, in.DstFilePath, allFuncArray) {
mlog.Printf(`not dirty, ignore generating service go file: %s`, in.DstFilePath)
return false, nil
}
}
mlog.Printf(`generating service go file: %s`, in.DstFilePath)
if err = gfile.PutContents(in.DstFilePath, generatedContent); err != nil {
if err = gfile.PutBytes(in.DstFilePath, generatedContent.Bytes()); err != nil {
return true, err
}
return true, nil
}
// isToGenerateServiceGoFile checks and returns whether the service content dirty.
func (c CGenService) isToGenerateServiceGoFile(dstPackageName, filePath string, funcArray *garray.StrArray) bool {
var (
err error
fileContent = gfile.GetContents(filePath)
generatedFuncArray = garray.NewSortedStrArrayFrom(funcArray.Slice())
contentFuncArray = garray.NewSortedStrArray()
)
if fileContent == "" {
return true
}
// remove all comments.
fileContent, err = gregex.ReplaceString(`(//.*)|((?s)/\*.*?\*/)`, "", fileContent)
if err != nil {
panic(err)
return false
}
matches, _ := gregex.MatchAllString(`\s+interface\s+{([\s\S]+?)}`, fileContent)
for _, match := range matches {
contentFuncArray.Append(gstr.SplitAndTrim(match[1], "\n")...)
}
if generatedFuncArray.Len() != contentFuncArray.Len() {
mlog.Debugf(
`dirty, generatedFuncArray.Len()[%d] != contentFuncArray.Len()[%d]`,
generatedFuncArray.Len(), contentFuncArray.Len(),
)
return true
}
var funcDefinition string
for i := 0; i < generatedFuncArray.Len(); i++ {
funcDefinition, _ = gregex.ReplaceString(
fmt.Sprintf(`\*{0,1}%s\.`, dstPackageName), ``, generatedFuncArray.At(i),
)
if funcDefinition != contentFuncArray.At(i) {
mlog.Debugf(`dirty, %s != %s`, funcDefinition, contentFuncArray.At(i))
return true
}
}
return false
}
// generateInitializationFile generates `logic.go`.
func (c CGenService) generateInitializationFile(in CGenServiceInput, importSrcPackages []string) (err error) {
var (

View File

@ -0,0 +1,104 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package genservice
import (
"bytes"
"fmt"
"github.com/gogf/gf/cmd/gf/v2/internal/consts"
"github.com/gogf/gf/v2/container/gmap"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/text/gregex"
"github.com/gogf/gf/v2/text/gstr"
)
func (c CGenService) generatePackageImports(generatedContent *bytes.Buffer, packageName string, imports []string) {
generatedContent.WriteString(gstr.ReplaceByMap(consts.TemplateGenServiceContentHead, g.MapStrStr{
"{PackageName}": packageName,
"{Imports}": fmt.Sprintf(
"import (\n%s\n)", gstr.Join(imports, "\n"),
),
}))
}
// generateType type definitions.
// See: const.TemplateGenServiceContentInterface
func (c CGenService) generateType(generatedContent *bytes.Buffer, srcStructFunctions *gmap.ListMap, dstPackageName string) {
generatedContent.WriteString("type(")
generatedContent.WriteString("\n")
srcStructFunctions.Iterator(func(key, value interface{}) bool {
var (
funcContents = make([]string, 0)
funcContent string
)
structName, funcSlice := key.(string), value.([]map[string]string)
// Generating interface content.
for _, funcInfo := range funcSlice {
// Remove package name calls of `dstPackageName` in produced codes.
funcHead, _ := gregex.ReplaceString(
fmt.Sprintf(`\*{0,1}%s\.`, dstPackageName),
``, funcInfo["funcHead"],
)
funcContent = funcInfo["funcComment"] + funcHead
funcContents = append(funcContents, funcContent)
}
// funcContents to string.
generatedContent.WriteString(
gstr.Trim(gstr.ReplaceByMap(consts.TemplateGenServiceContentInterface, g.MapStrStr{
"{InterfaceName}": "I" + structName,
"{FuncDefinition}": gstr.Join(funcContents, "\n\t"),
})),
)
generatedContent.WriteString("\n")
return true
})
generatedContent.WriteString(")")
generatedContent.WriteString("\n")
}
// generateVar variable definitions.
// See: const.TemplateGenServiceContentVariable
func (c CGenService) generateVar(generatedContent *bytes.Buffer, srcStructFunctions *gmap.ListMap) {
// Generating variable and register definitions.
var variableContent string
srcStructFunctions.Iterator(func(key, value interface{}) bool {
structName := key.(string)
variableContent += gstr.Trim(gstr.ReplaceByMap(consts.TemplateGenServiceContentVariable, g.MapStrStr{
"{StructName}": structName,
"{InterfaceName}": "I" + structName,
}))
variableContent += "\n"
return true
})
if variableContent != "" {
generatedContent.WriteString("var(")
generatedContent.WriteString("\n")
generatedContent.WriteString(variableContent)
generatedContent.WriteString(")")
generatedContent.WriteString("\n")
}
}
// generateFunc function definitions.
// See: const.TemplateGenServiceContentRegister
func (c CGenService) generateFunc(generatedContent *bytes.Buffer, srcStructFunctions *gmap.ListMap) {
// Variable register function definitions.
srcStructFunctions.Iterator(func(key, value interface{}) bool {
structName := key.(string)
generatedContent.WriteString(gstr.Trim(gstr.ReplaceByMap(consts.TemplateGenServiceContentRegister, g.MapStrStr{
"{StructName}": structName,
"{InterfaceName}": "I" + structName,
})))
generatedContent.WriteString("\n\n")
return true
})
}

View File

@ -8,8 +8,11 @@ package article
import (
"context"
"go/ast"
t "time"
"github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genservice/service"
gdbalias "github.com/gogf/gf/v2/database/gdb"
)
type sArticle struct {
@ -30,5 +33,15 @@ func (s *sArticle) Get(ctx context.Context, id uint) (info struct{}, err error)
* @author oldme
*/
func (s *sArticle) Create(ctx context.Context, info struct{}) (id uint, err error) {
// Use time package to test alias import.
t.Now()
return id, err
}
func (s *sArticle) A1o2(ctx context.Context, str string, a, b *ast.GoStmt) error {
return nil
}
func (s *sArticle) B_2(ctx context.Context, db gdbalias.Raw) (err error) {
return nil
}

View File

@ -0,0 +1,75 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package article
// import (
// "context"
//
// "github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genservice/service"
// )
import (
"context"
// This is a random comment
gdbas "github.com/gogf/gf/v2/database/gdb"
/**
*
*/
_ "github.com/gogf/gf/v2/os/gfile"
)
// T1 random comment
func (s sArticle) T1(ctx context.Context, id, id2 uint) (gdb gdbas.Model, err error) {
g := gdbas.Model{}
return g, err
}
// I'm a random comment
// t2 random comment
func (s *sArticle) t2(ctx context.Context) (err error) {
/**
* random comment
* i (1). func (s *sArticle) t2(ctx context.Context) (err error) { /** 1883
*
*/
_ = func(ctx2 context.Context) {}
return nil
}
// T3
/**
* random comment @*4213hHY1&%##%><<Y
* @param b
* @return c, d
* @return err
* @author oldme
*/
func (s *sArticle) T3(ctx context.Context, b *gdbas.Model) (c, d *gdbas.Model, err error) {
/* import (
* "context"
*
* "github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genservice/service"
*/
return nil, nil, nil
}
/**
* random comment
*/
// func (s *sArticle) T4(i interface{}) interface{}
// # $ % ^ & * ( ) _ + - = { } | [ ] \ : " ; ' < > ? , . /
func (s *sArticle) T4(i interface{}) interface{} {
return nil
}
/**
* func (s *sArticle) T4(i interface{}) interface{} {
* return nil
* }
*/

View File

@ -0,0 +1,38 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package delivery
import (
"context"
"github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genservice/service"
)
type sDeliveryApp struct{}
func NewDeliveryApp() *sDeliveryApp {
return &sDeliveryApp{}
}
func (s *sDeliveryApp) Create(ctx context.Context) (i service.IDeliveryCluster, err error) {
return
}
func (s *sDeliveryApp) GetList(ctx context.Context, i service.IDeliveryCluster) (err error) {
service.Article().Get(ctx, 1)
return
}
func (s *sDeliveryApp) GetOne(ctx context.Context) (err error) {
return
}
func (s *sDeliveryApp) Delete(ctx context.Context) (err error) {
return
}
func (s *sDeliveryApp) AA(ctx context.Context) (err error) { return }

View File

@ -0,0 +1,32 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package delivery
import (
"context"
gdbas "github.com/gogf/gf/v2/database/gdb"
)
type sDeliveryCluster struct{}
func NewDeliveryCluster() *sDeliveryCluster {
return &sDeliveryCluster{}
}
// Create 自动创建Cluster及Project.
func (s *sDeliveryCluster) Create(ctx context.Context) (err error, gdb gdbas.Model) {
return
}
func (s *sDeliveryCluster) Delete(ctx context.Context) (err error) {
return
}
func (s *sDeliveryCluster) GetList(ctx context.Context) (err error) {
return
}

View File

@ -6,4 +6,6 @@ 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/delivery"
_ "github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genservice/logic/user"
)

View File

@ -0,0 +1,49 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package user
import (
"context"
"github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genservice/service"
)
func init() {
service.RegisterUser(New())
}
type sUser struct {
}
func New() *sUser {
return &sUser{}
}
// Create creates a new user.
func (s *sUser) Create(ctx context.Context, name string) (id int, err error) {
return 0, nil
}
// GetOne retrieves user by id.
func (s *sUser) GetOne(ctx context.Context, id int) (name string, err error) {
return "", nil
}
// GetList retrieves user list.
func (s *sUser) GetList(ctx context.Context) (names []string, err error) {
return nil, nil
}
// Update updates user by id.
func (s *sUser) Update(ctx context.Context, id int) (name string, err error) {
return "", nil
}
// Delete deletes user by id.
func (s *sUser) Delete(ctx context.Context, id int) (err error) {
return nil
}

View File

@ -7,6 +7,10 @@ package service
import (
"context"
"go/ast"
gdbalias "github.com/gogf/gf/v2/database/gdb"
gdbas "github.com/gogf/gf/v2/database/gdb"
)
type (
@ -19,6 +23,22 @@ type (
* @author oldme
*/
Create(ctx context.Context, info struct{}) (id uint, err error)
A1o2(ctx context.Context, str string, a *ast.GoStmt, b *ast.GoStmt) error
B_2(ctx context.Context, db gdbalias.Raw) (err error)
// T1 random comment
T1(ctx context.Context, id uint, id2 uint) (gdb gdbas.Model, err error)
// T3
/**
* random comment @*4213hHY1&%##%><<Y
* @param b
* @return c, d
* @return err
* @author oldme
*/
T3(ctx context.Context, b *gdbas.Model) (c *gdbas.Model, d *gdbas.Model, err error)
// func (s *sArticle) T4(i interface{}) interface{}
// # $ % ^ & * ( ) _ + - = { } | [ ] \ : " ; ' < > ? , . /
T4(i interface{}) interface{}
}
)

View File

@ -0,0 +1,55 @@
// ================================================================================
// 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
import (
"context"
gdbas "github.com/gogf/gf/v2/database/gdb"
)
type (
IDeliveryApp interface {
Create(ctx context.Context) (i IDeliveryCluster, err error)
GetList(ctx context.Context, i IDeliveryCluster) (err error)
GetOne(ctx context.Context) (err error)
Delete(ctx context.Context) (err error)
AA(ctx context.Context) (err error)
}
IDeliveryCluster interface {
// Create 自动创建Cluster及Project.
Create(ctx context.Context) (err error, gdb gdbas.Model)
Delete(ctx context.Context) (err error)
GetList(ctx context.Context) (err error)
}
)
var (
localDeliveryApp IDeliveryApp
localDeliveryCluster IDeliveryCluster
)
func DeliveryApp() IDeliveryApp {
if localDeliveryApp == nil {
panic("implement not found for interface IDeliveryApp, forgot register?")
}
return localDeliveryApp
}
func RegisterDeliveryApp(i IDeliveryApp) {
localDeliveryApp = i
}
func DeliveryCluster() IDeliveryCluster {
if localDeliveryCluster == nil {
panic("implement not found for interface IDeliveryCluster, forgot register?")
}
return localDeliveryCluster
}
func RegisterDeliveryCluster(i IDeliveryCluster) {
localDeliveryCluster = i
}

View File

@ -0,0 +1,40 @@
// ================================================================================
// 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
import (
"context"
)
type (
IUser interface {
// Create creates a new user.
Create(ctx context.Context, name string) (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)
}
)
var (
localUser IUser
)
func User() IUser {
if localUser == nil {
panic("implement not found for interface IUser, forgot register?")
}
return localUser
}
func RegisterUser(i IUser) {
localUser = i
}