| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407 |
- // 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 (
- "context"
- "fmt"
- "github.com/gogf/gf/v2/container/garray"
- "github.com/gogf/gf/v2/container/gmap"
- "github.com/gogf/gf/v2/container/gset"
- "github.com/gogf/gf/v2/frame/g"
- "github.com/gogf/gf/v2/os/gfile"
- "github.com/gogf/gf/v2/os/gtime"
- "github.com/gogf/gf/v2/text/gregex"
- "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 (
- CGenServiceConfig = `gfcli.gen.service`
- CGenServiceUsage = `gf gen service [OPTION]`
- CGenServiceBrief = `parse struct and associated functions from packages to generate service go file`
- CGenServiceEg = `
- gf gen service
- gf gen service -f Snake
- `
- CGenServiceBriefSrcFolder = `source folder path to be parsed. default: internal/logic`
- CGenServiceBriefDstFolder = `destination folder path storing automatically generated go files. default: internal/service`
- CGenServiceBriefFileNameCase = `
- destination file name storing automatically generated go files, cases are as follows:
- | Case | Example |
- |---------------- |--------------------|
- | Lower | anykindofstring |
- | Camel | AnyKindOfString |
- | CamelLower | anyKindOfString |
- | Snake | any_kind_of_string | default
- | SnakeScreaming | ANY_KIND_OF_STRING |
- | SnakeFirstUpper | rgb_code_md5 |
- | Kebab | any-kind-of-string |
- | KebabScreaming | ANY-KIND-OF-STRING |
- `
- CGenServiceBriefWatchFile = `used in file watcher, it re-generates all service go files only if given file is under srcFolder`
- CGenServiceBriefStPattern = `regular expression matching struct name for generating service. default: ^s([A-Z]\\\\w+)$`
- CGenServiceBriefPackages = `produce go files only for given source packages(source folders)`
- CGenServiceBriefImportPrefix = `custom import prefix to calculate import path for generated importing go file of logic`
- CGenServiceBriefClear = `delete all generated go files that are not used any further`
- )
- func init() {
- gtag.Sets(g.MapStrStr{
- `CGenServiceConfig`: CGenServiceConfig,
- `CGenServiceUsage`: CGenServiceUsage,
- `CGenServiceBrief`: CGenServiceBrief,
- `CGenServiceEg`: CGenServiceEg,
- `CGenServiceBriefSrcFolder`: CGenServiceBriefSrcFolder,
- `CGenServiceBriefDstFolder`: CGenServiceBriefDstFolder,
- `CGenServiceBriefFileNameCase`: CGenServiceBriefFileNameCase,
- `CGenServiceBriefWatchFile`: CGenServiceBriefWatchFile,
- `CGenServiceBriefStPattern`: CGenServiceBriefStPattern,
- `CGenServiceBriefPackages`: CGenServiceBriefPackages,
- `CGenServiceBriefImportPrefix`: CGenServiceBriefImportPrefix,
- `CGenServiceBriefClear`: CGenServiceBriefClear,
- })
- }
- type (
- CGenService struct{}
- CGenServiceInput struct {
- g.Meta `name:"service" config:"{CGenServiceConfig}" usage:"{CGenServiceUsage}" brief:"{CGenServiceBrief}" eg:"{CGenServiceEg}"`
- SrcFolder string `short:"s" name:"srcFolder" brief:"{CGenServiceBriefSrcFolder}" d:"internal/logic"`
- DstFolder string `short:"d" name:"dstFolder" brief:"{CGenServiceBriefDstFolder}" d:"internal/service"`
- DstFileNameCase string `short:"f" name:"dstFileNameCase" brief:"{CGenServiceBriefFileNameCase}" d:"Snake"`
- WatchFile string `short:"w" name:"watchFile" brief:"{CGenServiceBriefWatchFile}"`
- StPattern string `short:"a" name:"stPattern" brief:"{CGenServiceBriefStPattern}" d:"^s([A-Z]\\w+)$"`
- Packages []string `short:"p" name:"packages" brief:"{CGenServiceBriefPackages}"`
- ImportPrefix string `short:"i" name:"importPrefix" brief:"{CGenServiceBriefImportPrefix}"`
- Clear bool `short:"l" name:"clear" brief:"{CGenServiceBriefClear}" orphan:"true"`
- }
- CGenServiceOutput struct{}
- )
- const (
- genServiceFileLockSeconds = 10
- )
- func (c CGenService) Service(ctx context.Context, in CGenServiceInput) (out *CGenServiceOutput, err error) {
- in.SrcFolder = gstr.TrimRight(in.SrcFolder, `\/`)
- in.SrcFolder = gstr.Replace(in.SrcFolder, "\\", "/")
- in.WatchFile = gstr.TrimRight(in.WatchFile, `\/`)
- in.WatchFile = gstr.Replace(in.WatchFile, "\\", "/")
- // Watch file handling.
- if in.WatchFile != "" {
- // File lock to avoid multiple processes.
- var (
- flockFilePath = gfile.Temp("gf.cli.gen.service.lock")
- flockContent = gfile.GetContents(flockFilePath)
- )
- if flockContent != "" {
- if gtime.Timestamp()-gconv.Int64(flockContent) < genServiceFileLockSeconds {
- // If another "gen service" process is running, it just exits.
- mlog.Debug(`another "gen service" process is running, exit`)
- return
- }
- }
- defer gfile.Remove(flockFilePath)
- _ = gfile.PutContents(flockFilePath, gtime.TimestampStr())
- // It works only if given WatchFile is in SrcFolder.
- var (
- watchFileDir = gfile.Dir(in.WatchFile)
- srcFolderDir = gfile.Dir(watchFileDir)
- )
- mlog.Debug("watchFileDir:", watchFileDir)
- mlog.Debug("logicFolderDir:", srcFolderDir)
- if !gstr.HasSuffix(gstr.Replace(srcFolderDir, `\`, `/`), in.SrcFolder) {
- mlog.Printf(`ignore watch file "%s", not in source path "%s"`, in.WatchFile, in.SrcFolder)
- return
- }
- var newWorkingDir = gfile.Dir(gfile.Dir(srcFolderDir))
- if err = gfile.Chdir(newWorkingDir); err != nil {
- mlog.Fatalf(`%+v`, err)
- }
- mlog.Debug("Chdir:", newWorkingDir)
- in.WatchFile = ""
- in.Packages = []string{gfile.Basename(watchFileDir)}
- return c.Service(ctx, in)
- }
- if !gfile.Exists(in.SrcFolder) {
- mlog.Fatalf(`source folder path "%s" does not exist`, in.SrcFolder)
- }
- if in.ImportPrefix == "" {
- in.ImportPrefix = utils.GetImportPath(in.SrcFolder)
- }
- var (
- isDirty bool // 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.
- )
- // The first level folders.
- srcFolderPaths, err := gfile.ScanDir(in.SrcFolder, "*", false)
- if err != nil {
- return nil, err
- }
- for _, srcFolderPath := range srcFolderPaths {
- if !gfile.IsDir(srcFolderPath) {
- continue
- }
- // Only retrieve sub files, no recursively.
- if files, err = gfile.ScanDir(srcFolderPath, "*.go", false); err != nil {
- return nil, err
- }
- if len(files) == 0 {
- continue
- }
- // Parse single logic package folder.
- var (
- // StructName => FunctionDefinitions
- srcPkgInterfaceMap = make(map[string]*garray.StrArray)
- 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,
- c.getDstFileNameCase(srcPackageName, in.DstFileNameCase)+".go",
- )
- srcCodeCommentedMap = make(map[string]string)
- )
- generatedDstFilePathSet.Add(dstFilePath)
- 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(`/[/|\*](.*)`, "", fileContent)
- if err != nil {
- return nil, err
- }
- // Calculate imported packages of source go files.
- packageItems, err = c.calculateImportedPackages(fileContent)
- 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)
- if err != nil {
- return nil, err
- }
- }
- initImportSrcPackages = append(
- initImportSrcPackages,
- fmt.Sprintf(`%s/%s`, in.ImportPrefix, srcPackageName),
- )
- // Ignore source packages if input packages given.
- if len(inputPackages) > 0 && !gstr.InArray(inputPackages, srcPackageName) {
- mlog.Debugf(
- `ignore source package "%s" as it is not in desired packages: %+v`,
- srcPackageName, inputPackages,
- )
- continue
- }
- // Generating service go file for single logic package.
- if ok, err = c.generateServiceFile(generateServiceFilesInput{
- CGenServiceInput: in,
- SrcStructFunctions: srcPkgInterfaceMap,
- SrcImportedPackages: srcImportedPackages.Slice(),
- SrcPackageName: srcPackageName,
- DstPackageName: dstPackageName,
- DstFilePath: dstFilePath,
- SrcCodeCommentedMap: srcCodeCommentedMap,
- }); err != nil {
- return
- }
- if ok {
- isDirty = true
- }
- }
- if in.Clear {
- files, err = gfile.ScanDirFile(in.DstFolder, "*.go", false)
- if err != nil {
- return nil, err
- }
- var relativeFilePath string
- for _, file := range files {
- relativeFilePath = gstr.SubStrFromR(file, in.DstFolder)
- 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
- }
- }
- }
- }
- if isDirty {
- // Generate initialization go file.
- if len(initImportSrcPackages) > 0 {
- if err = c.generateInitializationFile(in, initImportSrcPackages); err != nil {
- return
- }
- }
- // Replace v1 to v2 for GoFrame.
- if err = utils.ReplaceGeneratedContentGFV2(in.DstFolder); err != nil {
- return nil, err
- }
- mlog.Printf(`gofmt go files in "%s"`, in.DstFolder)
- utils.GoFmt(in.DstFolder)
- }
- // auto update main.go.
- if err = c.checkAndUpdateMain(in.SrcFolder); err != nil {
- return nil, err
- }
- mlog.Print(`done!`)
- return
- }
- func (c CGenService) checkAndUpdateMain(srcFolder string) (err error) {
- var (
- logicPackageName = gstr.ToLower(gfile.Basename(srcFolder))
- logicFilePath = gfile.Join(srcFolder, logicPackageName+".go")
- importPath = utils.GetImportPath(logicFilePath)
- importStr = fmt.Sprintf(`_ "%s"`, importPath)
- mainFilePath = gfile.Join(gfile.Dir(gfile.Dir(gfile.Dir(logicFilePath))), "main.go")
- mainFileContent = gfile.GetContents(mainFilePath)
- )
- // No main content found.
- if mainFileContent == "" {
- return nil
- }
- if gstr.Contains(mainFileContent, importStr) {
- return nil
- }
- match, err := gregex.MatchString(`import \(([\s\S]+?)\)`, mainFileContent)
- if err != nil {
- return err
- }
- // No match.
- if len(match) < 2 {
- return nil
- }
- lines := garray.NewStrArrayFrom(gstr.Split(match[1], "\n"))
- for i, line := range lines.Slice() {
- line = gstr.Trim(line)
- if len(line) == 0 {
- continue
- }
- if line[0] == '_' {
- continue
- }
- // Insert the logic import into imports.
- if err = lines.InsertBefore(i, fmt.Sprintf("\t%s\n\n", importStr)); err != nil {
- return err
- }
- break
- }
- mainFileContent, err = gregex.ReplaceString(
- `import \(([\s\S]+?)\)`,
- fmt.Sprintf(`import (%s)`, lines.Join("\n")),
- mainFileContent,
- )
- if err != nil {
- return err
- }
- mlog.Print(`update main.go`)
- err = gfile.PutContents(mainFilePath, mainFileContent)
- utils.GoFmt(mainFilePath)
- return
- }
|