improve command gen service for cli

This commit is contained in:
John Guo
2022-04-27 22:10:49 +08:00
parent ae5891068e
commit c82e612258

View File

@ -10,39 +10,86 @@ import (
"github.com/gogf/gf/v2/container/garray"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/os/gproc"
"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"
)
type (
cGenServiceInput struct {
g.Meta `name:"service" brief:"parse struct and associated functions from packages to generate service go file"`
SrcFolder string `short:"s" name:"srcFolder" brief:"source folder path to be parsed" d:"internal/logic"`
DstFolder string `short:"d" name:"dstFolder" brief:"destination folder path storing automatically generated go files" d:"internal/service"`
StPattern string `short:"a" name:"stPattern" brief:"regular expression matching struct name for generating service" d:"s(\\w+)"`
ImportPrefix string `short:"p" name:"importPrefix" brief:"custom import prefix to calculate import path for generated go files"`
WatchFile string `short:"w" name:"watchFile" brief:"used in file watcher, it generates service go files only if given file is under Logic folder"`
OverWrite bool `short:"o" name:"overwrite" brief:"overwrite files that already exist in generating folder" d:"true" orphan:"true"`
g.Meta `name:"service" config:"gfcli.gen.service" brief:"parse struct and associated functions from packages to generate service go file"`
SrcFolder string `short:"s" name:"srcFolder" brief:"source folder path to be parsed. default: internal/logic" d:"internal/logic"`
DstFolder string `short:"d" name:"dstFolder" brief:"destination folder path storing automatically generated go files. default: internal/service" d:"internal/service"`
WatchFile string `short:"w" name:"watchFile" brief:"used in file watcher, it generates service go files only if given file is under srcFolder"`
StPattern string `short:"a" name:"stPattern" brief:"regular expression matching struct name for generating service. default: s(\\w+)" d:"s(\\w+)"`
Packages string `short:"p" name:"packages" brief:"produce go files only for given source packages, multiple packages joined with char ','"`
ImportPrefix string `short:"i" name:"importPrefix" brief:"custom import prefix to calculate import path for generated go files"`
OverWrite bool `short:"o" name:"overwrite" brief:"overwrite files that already exist in generating folder. default: true" d:"true" orphan:"true"`
}
cGenServiceOutput struct{}
)
const (
genServiceFileLockSeconds = 10
)
func (c cGen) Service(ctx context.Context, in cGenServiceInput) (out *cGenServiceOutput, err error) {
in.SrcFolder = gstr.Trim(in.SrcFolder, `\/`)
in.WatchFile = gstr.Trim(in.WatchFile, `\/`)
if !gfile.Exists(in.SrcFolder) {
mlog.Fatalf(`logic folder path "%s" does not exist`, in.SrcFolder)
}
if in.WatchFile != "" {
// It works only if given WatchFile is in Logic folder.
if !gstr.Contains(gstr.Replace(in.WatchFile, "\\", "/"), gstr.Replace(in.SrcFolder, "\\", "/")) {
mlog.Printf(`ignore watch file "%s", not in source path "%s"`, in.WatchFile, in.SrcFolder)
// 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())
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 != "" {
// 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(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)
_ = gfile.Remove(flockFilePath)
var command = fmt.Sprintf(
`%s gen service -packages=%s`,
gfile.SelfName(), gfile.Basename(watchFileDir),
)
err = gproc.ShellRun(command)
return
}
if !gfile.Exists(in.SrcFolder) {
mlog.Fatalf(`source folder path "%s" does not exist`, in.SrcFolder)
}
if in.ImportPrefix == "" {
if !gfile.Exists("go.mod") {
mlog.Fatal("go.mod does not exist in current working directory")
mlog.Fatal("ImportPrefix is empty and go.mod does not exist in current working directory")
}
var (
goModContent = gfile.GetContents("go.mod")
@ -54,21 +101,22 @@ func (c cGen) Service(ctx context.Context, in cGenServiceInput) (out *cGenServic
}
var (
files []string
fileContent string
matches [][]string
srcPackages []string
dstPackageName = gstr.ToLower(gfile.Basename(in.DstFolder))
files []string
fileContent string
matches [][]string
importSrcPackages []string
inputPackages = gstr.SplitAndTrim(in.Packages, ",")
dstPackageName = gstr.ToLower(gfile.Basename(in.DstFolder))
)
logicFolders, err := gfile.ScanDir(in.SrcFolder, "*", false)
srcFolders, err := gfile.ScanDir(in.SrcFolder, "*", false)
if err != nil {
return nil, err
}
for _, logicFolder := range logicFolders {
if !gfile.IsDir(logicFolder) {
for _, srcFolder := range srcFolders {
if !gfile.IsDir(srcFolder) {
continue
}
if files, err = gfile.ScanDir(logicFolder, "*.go", false); err != nil {
if files, err = gfile.ScanDir(srcFolder, "*.go", false); err != nil {
return nil, err
}
if len(files) == 0 {
@ -82,21 +130,20 @@ func (c cGen) Service(ctx context.Context, in cGenServiceInput) (out *cGenServic
)
for _, file := range files {
fileContent = gfile.GetContents(file)
matches, err = gregex.MatchAllString(`func \(\w+ (.+?)\) (.+?) {`, fileContent)
matches, err = gregex.MatchAllString(`func \(\w+ (.+?)\) ([\s\S]+?) {`, fileContent)
if err != nil {
return nil, err
}
for _, match := range matches {
var (
structMatch []string
structName = gstr.Trim(match[1], "*")
funcTitle = gstr.Trim(gstr.Replace(match[2], "\n", ""))
structMatch []string
structName = gstr.Trim(match[1], "*")
functionHead = gstr.Trim(gstr.Replace(match[2], "\n", ""))
)
if !gstr.IsLetterUpper(funcTitle[0]) {
if !gstr.IsLetterUpper(functionHead[0]) {
continue
}
structMatch, err = gregex.MatchString(in.StPattern, structName)
if err != nil {
if structMatch, err = gregex.MatchString(in.StPattern, structName); err != nil {
return nil, err
}
if len(structMatch) < 1 {
@ -107,50 +154,33 @@ func (c cGen) Service(ctx context.Context, in cGenServiceInput) (out *cGenServic
interfaceMap[structName] = garray.NewStrArray()
interfaceFuncArray = interfaceMap[structName]
}
interfaceFuncArray.Append(funcTitle)
// Remove package name calls of `dstPackageName` in produced codes.
functionHead, _ = gregex.ReplaceString(fmt.Sprintf(`\*{0,1}%s\.`, dstPackageName), ``, functionHead)
interfaceFuncArray.Append(functionHead)
}
}
srcPackages = append(srcPackages, fmt.Sprintf(`%s/%s`, in.ImportPrefix, gfile.Basename(logicFolder)))
// Generating go files for service.
for structName, funcArray := range interfaceMap {
var (
filePath = gfile.Join(in.DstFolder, gstr.ToLower(structName)+".go")
generatedContent = gstr.ReplaceByMap(consts.TemplateGenServiceContent, g.MapStrStr{
"{StructName}": structName,
"{PackageName}": dstPackageName,
"{FuncDefinition}": funcArray.Join("\n\t"),
})
importSrcPackages = append(
importSrcPackages,
fmt.Sprintf(`%s/%s`, in.ImportPrefix, gfile.Basename(srcFolder)),
)
// Ignore source packages if input packages given.
if len(inputPackages) > 0 && !gstr.InArray(inputPackages, gfile.Basename(srcFolder)) {
mlog.Debugf(
`ignore source package "%s" as it is not in desired packages: %+v`,
gfile.Basename(srcFolder), inputPackages,
)
if !in.OverWrite && gfile.Exists(filePath) {
mlog.Printf(`ignore generating service go file: %s`, filePath)
continue
}
mlog.Printf(`generating service go file: %s`, filePath)
if err = gfile.PutContents(filePath, generatedContent); err != nil {
return nil, err
}
continue
}
// Generating go files for service.
if err = c.generateServiceFiles(in, interfaceMap, dstPackageName); err != nil {
return
}
}
// Generate initialization go file.
if len(srcPackages) > 0 {
var (
srcPackageName = gstr.ToLower(gfile.Basename(in.SrcFolder))
srcFilePath = gfile.Join(in.SrcFolder, srcPackageName+".go")
srcImports string
generatedContent string
)
for _, srcPackage := range srcPackages {
srcImports += fmt.Sprintf(`%s_ "%s"%s`, "\t", srcPackage, "\n")
if len(importSrcPackages) > 0 {
if err = c.generateInitializationFile(in, importSrcPackages); err != nil {
return
}
generatedContent = gstr.ReplaceByMap(consts.TemplateGenServiceLogicContent, g.MapStrStr{
"{PackageName}": srcPackageName,
"{Imports}": srcImports,
})
mlog.Printf(`generating init go file: %s`, srcFilePath)
if err = gfile.PutContents(srcFilePath, generatedContent); err != nil {
return nil, err
}
utils.GoFmt(srcFilePath)
}
// Go imports updating.
@ -174,3 +204,52 @@ func (c cGen) Service(ctx context.Context, in cGenServiceInput) (out *cGenServic
mlog.Print(`done!`)
return
}
func (c cGen) generateServiceFiles(
in cGenServiceInput, interfaceMap map[string]*garray.StrArray, dstPackageName string,
) (err error) {
for structName, funcArray := range interfaceMap {
var (
filePath = gfile.Join(in.DstFolder, gstr.ToLower(structName)+".go")
generatedContent = gstr.ReplaceByMap(consts.TemplateGenServiceContent, g.MapStrStr{
"{StructName}": structName,
"{PackageName}": dstPackageName,
"{FuncDefinition}": funcArray.Join("\n\t"),
})
)
if gfile.Exists(filePath) {
if !in.OverWrite {
mlog.Printf(`ignore generating service go file: %s`, filePath)
continue
}
}
mlog.Printf(`generating service go file: %s`, filePath)
if err = gfile.PutContents(filePath, generatedContent); err != nil {
return err
}
}
return nil
}
func (c cGen) generateInitializationFile(in cGenServiceInput, importSrcPackages []string) (err error) {
var (
srcPackageName = gstr.ToLower(gfile.Basename(in.SrcFolder))
srcFilePath = gfile.Join(in.SrcFolder, srcPackageName+".go")
srcImports string
generatedContent string
)
for _, importSrcPackage := range importSrcPackages {
srcImports += fmt.Sprintf(`%s_ "%s"%s`, "\t", importSrcPackage, "\n")
}
generatedContent = gstr.ReplaceByMap(consts.TemplateGenServiceLogicContent, g.MapStrStr{
"{PackageName}": srcPackageName,
"{Imports}": srcImports,
})
mlog.Printf(`generating init go file: %s`, srcFilePath)
if err = gfile.PutContents(srcFilePath, generatedContent); err != nil {
return err
}
utils.GoFmt(srcFilePath)
return nil
}