genctrl.go 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. // Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
  2. //
  3. // This Source Code Form is subject to the terms of the MIT License.
  4. // If a copy of the MIT was not distributed with this file,
  5. // You can obtain one at https://github.com/gogf/gf.
  6. package genctrl
  7. import (
  8. "context"
  9. "github.com/gogf/gf/v2/container/gset"
  10. "github.com/gogf/gf/v2/frame/g"
  11. "github.com/gogf/gf/v2/os/gfile"
  12. "github.com/gogf/gf/v2/os/gtime"
  13. "github.com/gogf/gf/v2/text/gregex"
  14. "github.com/gogf/gf/v2/util/gconv"
  15. "github.com/gogf/gf/v2/util/gtag"
  16. "github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
  17. )
  18. const (
  19. CGenCtrlConfig = `gfcli.gen.ctrl`
  20. CGenCtrlUsage = `gf gen ctrl [OPTION]`
  21. CGenCtrlBrief = `parse api definitions to generate controller/sdk go files`
  22. CGenCtrlEg = `
  23. gf gen ctrl
  24. `
  25. CGenCtrlBriefSrcFolder = `source folder path to be parsed. default: api`
  26. CGenCtrlBriefDstFolder = `destination folder path storing automatically generated go files. default: internal/controller`
  27. CGenCtrlBriefWatchFile = `used in file watcher, it re-generates go files only if given file is under srcFolder`
  28. CGenCtrlBriefSdkPath = `also generate SDK go files for api definitions to specified directory`
  29. CGenCtrlBriefSdkStdVersion = `use standard version prefix for generated sdk request path`
  30. CGenCtrlBriefSdkNoV1 = `do not add version suffix for interface module name if version is v1`
  31. CGenCtrlBriefClear = `auto delete generated and unimplemented controller go files if api definitions are missing`
  32. )
  33. const (
  34. PatternApiDefinition = `type\s+(\w+)Req\s+struct\s+{`
  35. PatternCtrlDefinition = `func\s+\(.+?\)\s+\w+\(.+?\*(\w+)\.(\w+)Req\)\s+\(.+?\*(\w+)\.(\w+)Res,\s+\w+\s+error\)\s+{`
  36. )
  37. const (
  38. genCtrlFileLockSeconds = 10
  39. )
  40. func init() {
  41. gtag.Sets(g.MapStrStr{
  42. `CGenCtrlConfig`: CGenCtrlConfig,
  43. `CGenCtrlUsage`: CGenCtrlUsage,
  44. `CGenCtrlBrief`: CGenCtrlBrief,
  45. `CGenCtrlEg`: CGenCtrlEg,
  46. `CGenCtrlBriefSrcFolder`: CGenCtrlBriefSrcFolder,
  47. `CGenCtrlBriefDstFolder`: CGenCtrlBriefDstFolder,
  48. `CGenCtrlBriefWatchFile`: CGenCtrlBriefWatchFile,
  49. `CGenCtrlBriefSdkPath`: CGenCtrlBriefSdkPath,
  50. `CGenCtrlBriefSdkStdVersion`: CGenCtrlBriefSdkStdVersion,
  51. `CGenCtrlBriefSdkNoV1`: CGenCtrlBriefSdkNoV1,
  52. `CGenCtrlBriefClear`: CGenCtrlBriefClear,
  53. })
  54. }
  55. type (
  56. CGenCtrl struct{}
  57. CGenCtrlInput struct {
  58. g.Meta `name:"ctrl" config:"{CGenCtrlConfig}" usage:"{CGenCtrlUsage}" brief:"{CGenCtrlBrief}" eg:"{CGenCtrlEg}"`
  59. SrcFolder string `short:"s" name:"srcFolder" brief:"{CGenCtrlBriefSrcFolder}" d:"api"`
  60. DstFolder string `short:"d" name:"dstFolder" brief:"{CGenCtrlBriefDstFolder}" d:"internal/controller"`
  61. WatchFile string `short:"w" name:"watchFile" brief:"{CGenCtrlBriefWatchFile}"`
  62. SdkPath string `short:"k" name:"sdkPath" brief:"{CGenCtrlBriefSdkPath}"`
  63. SdkStdVersion bool `short:"v" name:"sdkStdVersion" brief:"{CGenCtrlBriefSdkStdVersion}" orphan:"true"`
  64. SdkNoV1 bool `short:"n" name:"sdkNoV1" brief:"{CGenCtrlBriefSdkNoV1}" orphan:"true"`
  65. Clear bool `short:"c" name:"clear" brief:"{CGenCtrlBriefClear}" orphan:"true"`
  66. }
  67. CGenCtrlOutput struct{}
  68. )
  69. func (c CGenCtrl) Ctrl(ctx context.Context, in CGenCtrlInput) (out *CGenCtrlOutput, err error) {
  70. if in.WatchFile != "" {
  71. err = c.generateByWatchFile(
  72. in.WatchFile, in.SdkPath, in.SdkStdVersion, in.SdkNoV1, in.Clear,
  73. )
  74. mlog.Print(`done!`)
  75. return
  76. }
  77. if !gfile.Exists(in.SrcFolder) {
  78. mlog.Fatalf(`source folder path "%s" does not exist`, in.SrcFolder)
  79. }
  80. // retrieve all api modules.
  81. apiModuleFolderPaths, err := gfile.ScanDir(in.SrcFolder, "*", false)
  82. if err != nil {
  83. return nil, err
  84. }
  85. for _, apiModuleFolderPath := range apiModuleFolderPaths {
  86. if !gfile.IsDir(apiModuleFolderPath) {
  87. continue
  88. }
  89. // generate go files by api module.
  90. var (
  91. module = gfile.Basename(apiModuleFolderPath)
  92. dstModuleFolderPath = gfile.Join(in.DstFolder, module)
  93. )
  94. err = c.generateByModule(
  95. apiModuleFolderPath, dstModuleFolderPath, in.SdkPath,
  96. in.SdkStdVersion, in.SdkNoV1, in.Clear,
  97. )
  98. if err != nil {
  99. return nil, err
  100. }
  101. }
  102. mlog.Print(`done!`)
  103. return
  104. }
  105. func (c CGenCtrl) generateByWatchFile(watchFile, sdkPath string, sdkStdVersion, sdkNoV1, clear bool) (err error) {
  106. // File lock to avoid multiple processes.
  107. var (
  108. flockFilePath = gfile.Temp("gf.cli.gen.service.lock")
  109. flockContent = gfile.GetContents(flockFilePath)
  110. )
  111. if flockContent != "" {
  112. if gtime.Timestamp()-gconv.Int64(flockContent) < genCtrlFileLockSeconds {
  113. // If another generating process is running, it just exits.
  114. mlog.Debug(`another "gen service" process is running, exit`)
  115. return
  116. }
  117. }
  118. defer gfile.Remove(flockFilePath)
  119. _ = gfile.PutContents(flockFilePath, gtime.TimestampStr())
  120. // check this updated file is an api file.
  121. // watch file should be in standard goframe project structure.
  122. var (
  123. apiVersionPath = gfile.Dir(watchFile)
  124. apiModuleFolderPath = gfile.Dir(apiVersionPath)
  125. shouldBeNameOfAPi = gfile.Basename(gfile.Dir(apiModuleFolderPath))
  126. )
  127. if shouldBeNameOfAPi != "api" {
  128. return nil
  129. }
  130. // watch file should have api definitions.
  131. if gfile.Exists(watchFile) {
  132. if !gregex.IsMatchString(PatternApiDefinition, gfile.GetContents(watchFile)) {
  133. return nil
  134. }
  135. }
  136. var (
  137. projectRootPath = gfile.Dir(gfile.Dir(apiModuleFolderPath))
  138. module = gfile.Basename(apiModuleFolderPath)
  139. dstModuleFolderPath = gfile.Join(projectRootPath, "internal", "controller", module)
  140. )
  141. return c.generateByModule(
  142. apiModuleFolderPath, dstModuleFolderPath, sdkPath, sdkStdVersion, sdkNoV1, clear,
  143. )
  144. }
  145. // parseApiModule parses certain api and generate associated go files by certain module, not all api modules.
  146. func (c CGenCtrl) generateByModule(
  147. apiModuleFolderPath, dstModuleFolderPath, sdkPath string,
  148. sdkStdVersion, sdkNoV1, clear bool,
  149. ) (err error) {
  150. // parse src and dst folder go files.
  151. apiItemsInSrc, err := c.getApiItemsInSrc(apiModuleFolderPath)
  152. if err != nil {
  153. return err
  154. }
  155. apiItemsInDst, err := c.getApiItemsInDst(dstModuleFolderPath)
  156. if err != nil {
  157. return err
  158. }
  159. // generate api interface go files.
  160. if err = newApiInterfaceGenerator().Generate(apiModuleFolderPath, apiItemsInSrc); err != nil {
  161. return
  162. }
  163. // generate controller go files.
  164. // api filtering for already implemented api controllers.
  165. var (
  166. alreadyImplementedCtrlSet = gset.NewStrSet()
  167. toBeImplementedApiItems = make([]apiItem, 0)
  168. )
  169. for _, item := range apiItemsInDst {
  170. alreadyImplementedCtrlSet.Add(item.String())
  171. }
  172. for _, item := range apiItemsInSrc {
  173. if alreadyImplementedCtrlSet.Contains(item.String()) {
  174. continue
  175. }
  176. toBeImplementedApiItems = append(toBeImplementedApiItems, item)
  177. }
  178. if len(toBeImplementedApiItems) > 0 {
  179. err = newControllerGenerator().Generate(dstModuleFolderPath, toBeImplementedApiItems)
  180. if err != nil {
  181. return
  182. }
  183. }
  184. // delete unimplemented controllers if api definitions are missing.
  185. if clear {
  186. var (
  187. apiDefinitionSet = gset.NewStrSet()
  188. extraApiItemsInCtrl = make([]apiItem, 0)
  189. )
  190. for _, item := range apiItemsInSrc {
  191. apiDefinitionSet.Add(item.String())
  192. }
  193. for _, item := range apiItemsInDst {
  194. if apiDefinitionSet.Contains(item.String()) {
  195. continue
  196. }
  197. extraApiItemsInCtrl = append(extraApiItemsInCtrl, item)
  198. }
  199. if len(extraApiItemsInCtrl) > 0 {
  200. err = newControllerClearer().Clear(dstModuleFolderPath, extraApiItemsInCtrl)
  201. if err != nil {
  202. return
  203. }
  204. }
  205. }
  206. // generate sdk go files.
  207. if sdkPath != "" {
  208. if err = newApiSdkGenerator().Generate(apiItemsInSrc, sdkPath, sdkStdVersion, sdkNoV1); err != nil {
  209. return
  210. }
  211. }
  212. return
  213. }