This commit is contained in:
孟帅
2023-07-20 18:01:10 +08:00
parent 9113fc5297
commit 373d9627fb
492 changed files with 12170 additions and 6982 deletions

View File

@@ -6,6 +6,9 @@
package hggen
import (
_ "hotgo/internal/library/hggen/internal/cmd/gendao"
_ "unsafe"
"context"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
@@ -23,6 +26,9 @@ import (
"sort"
)
//go:linkname doGenDaoForArray hotgo/internal/library/hggen/internal/cmd/gendao.doGenDaoForArray
func doGenDaoForArray(ctx context.Context, index int, in gendao.CGenDaoInput)
// Dao 生成数据库实体
func Dao(ctx context.Context) (err error) {
for _, v := range daoConfig {
@@ -31,7 +37,7 @@ func Dao(ctx context.Context) (err error) {
if err != nil {
return
}
gendao.DoGenDaoForArray(ctx, inp)
doGenDaoForArray(ctx, -1, inp)
}
return
}
@@ -52,11 +58,11 @@ func ServiceWithCfg(ctx context.Context, cfg ...genservice.CGenServiceInput) (er
}
// TableColumns 获取指定表生成字段列表
func TableColumns(ctx context.Context, in sysin.GenCodesColumnListInp) (fields []*sysin.GenCodesColumnListModel, err error) {
func TableColumns(ctx context.Context, in *sysin.GenCodesColumnListInp) (fields []*sysin.GenCodesColumnListModel, err error) {
return views.DoTableColumns(ctx, in, GetDaoConfig(in.Name))
}
func TableSelects(ctx context.Context, in sysin.GenCodesSelectsInp) (res *sysin.GenCodesSelectsModel, err error) {
func TableSelects(ctx context.Context, in *sysin.GenCodesSelectsInp) (res *sysin.GenCodesSelectsModel, err error) {
res = new(sysin.GenCodesSelectsModel)
res.GenType, err = GenTypeSelect(ctx)
if err != nil {
@@ -110,7 +116,7 @@ func TableSelects(ctx context.Context, in sysin.GenCodesSelectsInp) (res *sysin.
}
sort.Sort(res.FormRole)
dictMode, err := service.SysDictType().TreeSelect(ctx, sysin.DictTreeSelectInp{})
dictMode, err := service.SysDictType().TreeSelect(ctx, &sysin.DictTreeSelectInp{})
if err != nil {
return
}
@@ -183,7 +189,7 @@ func DbSelect(ctx context.Context) (res form.Selects) {
}
// Preview 生成预览
func Preview(ctx context.Context, in sysin.GenCodesPreviewInp) (res *sysin.GenCodesPreviewModel, err error) {
func Preview(ctx context.Context, in *sysin.GenCodesPreviewInp) (res *sysin.GenCodesPreviewModel, err error) {
genConfig, err := service.SysConfig().GetLoadGenerate(ctx)
if err != nil {
return nil, err
@@ -209,7 +215,7 @@ func Preview(ctx context.Context, in sysin.GenCodesPreviewInp) (res *sysin.GenCo
}
// Build 提交生成
func Build(ctx context.Context, in sysin.GenCodesBuildInp) (err error) {
func Build(ctx context.Context, in *sysin.GenCodesBuildInp) (err error) {
genConfig, err := service.SysConfig().GetLoadGenerate(ctx)
if err != nil {
return err
@@ -217,7 +223,7 @@ func Build(ctx context.Context, in sysin.GenCodesBuildInp) (err error) {
switch in.GenType {
case consts.GenCodesTypeCurd:
pin := sysin.GenCodesPreviewInp(in)
pin := &sysin.GenCodesPreviewInp{SysGenCodes: in.SysGenCodes}
return views.Curd.DoBuild(ctx, &views.CurdBuildInput{
PreviewIn: &views.CurdPreviewInput{
In: pin,

View File

@@ -1,9 +1,16 @@
// 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 cmd
import (
"context"
"strings"
"github.com/gogf/gf/v2"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gcmd"
"github.com/gogf/gf/v2/util/gtag"
@@ -48,17 +55,21 @@ func (c cGF) Index(ctx context.Context, in cGFInput) (out *cGFOutput, err error)
_, err = Version.Index(ctx, cVersionInput{})
return
}
answer := "n"
// No argument or option, do installation checks.
if !service.Install.IsInstalled() {
if data, isInstalled := service.Install.IsInstalled(); !isInstalled {
mlog.Print("hi, it seams it's the first time you installing gf cli.")
s := gcmd.Scanf("do you want to install gf binary to your system? [y/n]: ")
if strings.EqualFold(s, "y") {
if err = service.Install.Run(ctx); err != nil {
return
}
gcmd.Scan("press `Enter` to exit...")
answer = gcmd.Scanf("do you want to install gf(%s) binary to your system? [y/n]: ", gf.VERSION)
} else if !data.IsSelf {
mlog.Print("hi, you have installed gf cli.")
answer = gcmd.Scanf("do you want to install gf(%s) binary to your system? [y/n]: ", gf.VERSION)
}
if strings.EqualFold(answer, "y") {
if err = service.Install.Run(ctx); err != nil {
return
}
gcmd.Scan("press `Enter` to exit...")
return
}
// Print help content.
gcmd.CommandFromCtx(ctx).Print()

View File

@@ -1,3 +1,9 @@
// 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 cmd
import (
@@ -123,15 +129,21 @@ type cBuildInput struct {
VarMap g.Map `short:"r" name:"varMap" brief:"custom built embedded variable into binary"`
PackSrc string `short:"ps" name:"packSrc" brief:"pack one or more folders into one go file before building"`
PackDst string `short:"pd" name:"packDst" brief:"temporary go file path for pack, this go file will be automatically removed after built" d:"internal/packed/build_pack_data.go"`
ExitWhenError bool `short:"ew" name:"exitWhenError" brief:"exit building when any error occurs, default is false" orphan:"true"`
ExitWhenError bool `short:"ew" name:"exitWhenError" brief:"exit building when any error occurs, specially for multiple arch and system buildings. default is false" orphan:"true"`
DumpENV bool `short:"de" name:"dumpEnv" brief:"dump current go build environment before building binary" orphan:"true"`
}
type cBuildOutput struct{}
func (c cBuild) Index(ctx context.Context, in cBuildInput) (out *cBuildOutput, err error) {
// print used go env
if in.DumpENV {
_, _ = Env.Index(ctx, cEnvInput{})
}
mlog.SetHeaderPrint(true)
mlog.Debugf(`build input: %+v`, in)
mlog.Debugf(`build command input: %+v`, in)
// Necessary check.
if gproc.SearchBinary("go") == "" {
mlog.Fatalf(`command "go" not found in your environment, please install golang first to proceed this command`)
@@ -236,7 +248,7 @@ func (c cBuild) Index(ctx context.Context, in cBuildInput) (out *cBuildOutput, e
if len(customSystems) > 0 && customSystems[0] != "all" && !gstr.InArray(customSystems, system) {
continue
}
for arch, _ := range item {
for arch := range item {
if len(customArches) > 0 && customArches[0] != "all" && !gstr.InArray(customArches, arch) {
continue
}
@@ -299,8 +311,9 @@ func (c cBuild) getBuildInVarStr(ctx context.Context, in cBuildInput) string {
if buildInVarMap == nil {
buildInVarMap = make(g.Map)
}
buildInVarMap["builtGit"] = c.getGitCommit(ctx)
buildInVarMap["builtTime"] = gtime.Now().String()
buildInVarMap[`builtGit`] = c.getGitCommit(ctx)
buildInVarMap[`builtTime`] = gtime.Now().String()
buildInVarMap[`builtVersion`] = in.Version
b, err := json.Marshal(buildInVarMap)
if err != nil {
mlog.Fatal(err)

View File

@@ -1,3 +1,9 @@
// 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 cmd
import (
@@ -33,6 +39,7 @@ gf docker main.go
gf docker main.go -t hub.docker.com/john/image:tag
gf docker main.go -t hub.docker.com/john/image:tag
gf docker main.go -p -t hub.docker.com/john/image:tag
gf docker main.go -p -tp ["hub.docker.com/john","hub.docker.com/smith"] -tn image:tag
`
cDockerDc = `
The "docker" command builds the GF project to a docker images.
@@ -45,6 +52,7 @@ You should have docker installed, and there must be a Dockerfile in the root of
cDockerFileBrief = `file path of the Dockerfile. it's "manifest/docker/Dockerfile" in default`
cDockerShellBrief = `path of the shell file which is executed before docker build`
cDockerPushBrief = `auto push the docker image to docker registry if "-t" option passed`
cDockerTagBrief = `full tag for this docker, pattern like "xxx.xxx.xxx/image:tag"`
cDockerTagNameBrief = `tag name for this docker, pattern like "image:tag". this option is required with TagPrefixes`
cDockerTagPrefixesBrief = `tag prefixes for this docker, which are used for docker push. this option is required with TagName`
cDockerExtraBrief = `extra build options passed to "docker image"`
@@ -61,6 +69,7 @@ func init() {
`cDockerShellBrief`: cDockerShellBrief,
`cDockerBuildBrief`: cDockerBuildBrief,
`cDockerPushBrief`: cDockerPushBrief,
`cDockerTagBrief`: cDockerTagBrief,
`cDockerTagNameBrief`: cDockerTagNameBrief,
`cDockerTagPrefixesBrief`: cDockerTagPrefixesBrief,
`cDockerExtraBrief`: cDockerExtraBrief,
@@ -69,10 +78,11 @@ func init() {
type cDockerInput struct {
g.Meta `name:"docker" config:"gfcli.docker"`
Main string `name:"MAIN" arg:"true" brief:"{cDockerMainBrief}" d:"main.go"`
Main string `name:"MAIN" arg:"true" brief:"{cDockerMainBrief}" d:"main.go"`
File string `name:"file" short:"f" brief:"{cDockerFileBrief}" d:"manifest/docker/Dockerfile"`
Shell string `name:"shell" short:"s" brief:"{cDockerShellBrief}" d:"manifest/docker/docker.sh"`
Build string `name:"build" short:"b" brief:"{cDockerBuildBrief}" d:"-a amd64 -s linux"`
Build string `name:"build" short:"b" brief:"{cDockerBuildBrief}"`
Tag string `name:"tag" short:"t" brief:"{cDockerTagBrief}"`
TagName string `name:"tagName" short:"tn" brief:"{cDockerTagNameBrief}" v:"required-with:TagPrefixes"`
TagPrefixes []string `name:"tagPrefixes" short:"tp" brief:"{cDockerTagPrefixesBrief}" v:"required-with:TagName"`
Push bool `name:"push" short:"p" brief:"{cDockerPushBrief}" orphan:"true"`
@@ -87,17 +97,23 @@ func (c cDocker) Index(ctx context.Context, in cDockerInput) (out *cDockerOutput
mlog.Fatalf(`command "docker" not found in your environment, please install docker first to proceed this command`)
}
mlog.Debugf(`docker command input: %+v`, in)
// Binary build.
in.Build += " --exit"
if in.Main != "" {
if err = gproc.ShellRun(ctx, fmt.Sprintf(`gf build %s %s`, in.Main, in.Build)); err != nil {
return
if in.Main != "" && in.Build != "" {
in.Build += " --exitWhenError"
if in.Main != "" {
if err = gproc.ShellRun(ctx, fmt.Sprintf(`gf build %s %s`, in.Main, in.Build)); err != nil {
mlog.Debugf(`build binary failed with error: %+v`, err)
return
}
}
}
// Shell executing.
if in.Shell != "" && gfile.Exists(in.Shell) {
if err = c.exeDockerShell(ctx, in.Shell); err != nil {
mlog.Debugf(`build docker failed with error: %+v`, err)
return
}
}
@@ -114,7 +130,7 @@ func (c cDocker) Index(ctx context.Context, in cDockerInput) (out *cDockerOutput
}
}
if len(dockerTags) == 0 {
dockerTags = []string{""}
dockerTags = []string{in.Tag}
}
for i, dockerTag := range dockerTags {
if i > 0 {

View File

@@ -1,14 +1,21 @@
// 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 cmd
import (
"bytes"
"context"
"github.com/olekukonko/tablewriter"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gproc"
"github.com/gogf/gf/v2/text/gregex"
"github.com/gogf/gf/v2/text/gstr"
"github.com/olekukonko/tablewriter"
"hotgo/internal/library/hggen/internal/utility/mlog"
)

View File

@@ -1,8 +1,17 @@
// 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 cmd
import (
"context"
"github.com/gogf/gf/v2/os/gproc"
"github.com/gogf/gf/v2/text/gregex"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gfile"
@@ -19,7 +28,9 @@ type cFix struct {
}
type cFixInput struct {
g.Meta `name:"fix"`
g.Meta `name:"fix"`
Path string `name:"path" short:"p" brief:"directory path, it uses current working directory in default"`
Version string `name:"version" short:"v" brief:"custom specified version to fix, leave it empty to auto detect"`
}
type cFixOutput struct{}
@@ -30,35 +41,46 @@ type cFixItem struct {
}
func (c cFix) Index(ctx context.Context, in cFixInput) (out *cFixOutput, err error) {
mlog.Print(`start auto fixing...`)
defer mlog.Print(`done!`)
err = c.doFix()
if in.Path == "" {
in.Path = gfile.Pwd()
}
if in.Version == "" {
in.Version, err = c.autoDetectVersion(in)
if err != nil {
mlog.Fatal(err)
}
if in.Version == "" {
mlog.Print(`no GoFrame usage found, exit fixing`)
return
}
mlog.Debugf(`current GoFrame version auto detect "%s"`, in.Version)
}
if !gproc.IsChild() {
mlog.Printf(`start auto fixing directory path "%s"...`, in.Path)
defer mlog.Print(`done!`)
}
err = c.doFix(in)
return
}
func (c cFix) doFix() (err error) {
version, err := c.getVersion()
if err != nil {
mlog.Fatal(err)
}
if version == "" {
mlog.Print(`no GoFrame usage found, exit fixing`)
return
}
mlog.Debugf(`current GoFrame version found "%s"`, version)
func (c cFix) doFix(in cFixInput) (err error) {
var items = []cFixItem{
{Version: "v2.3", Func: c.doFixV23},
{Version: "v2.5", Func: c.doFixV25},
}
for _, item := range items {
if gstr.CompareVersionGo(version, item.Version) < 0 {
if gstr.CompareVersionGo(in.Version, item.Version) < 0 {
mlog.Debugf(
`current GoFrame version "%s" is lesser than "%s", nothing to do`,
version, item.Version,
`current GoFrame or contrib package version "%s" is lesser than "%s", nothing to do`,
in.Version, item.Version,
)
continue
}
if err = item.Func(version); err != nil {
if err = item.Func(in.Version); err != nil {
return
}
}
@@ -68,16 +90,47 @@ func (c cFix) doFix() (err error) {
// doFixV23 fixes code when upgrading to GoFrame v2.3.
func (c cFix) doFixV23(version string) error {
replaceFunc := func(path, content string) string {
// gdb.TX from struct to interface.
content = gstr.Replace(content, "*gdb.TX", "gdb.TX")
// function name changes for package gtcp/gudp.
if gstr.Contains(content, "/gf/v2/net/gtcp") || gstr.Contains(content, "/gf/v2/net/gudp") {
content = gstr.ReplaceByMap(content, g.MapStrStr{
".SetSendDeadline": ".SetDeadlineSend",
".SetReceiveDeadline": ".SetDeadlineRecv",
".SetReceiveBufferWait": ".SetBufferWaitRecv",
})
}
return content
}
return gfile.ReplaceDirFunc(replaceFunc, ".", "*.go", true)
}
func (c cFix) getVersion() (string, error) {
// doFixV25 fixes code when upgrading to GoFrame v2.5.
func (c cFix) doFixV25(version string) (err error) {
replaceFunc := func(path, content string) string {
content, err = c.doFixV25Content(content)
return content
}
return gfile.ReplaceDirFunc(replaceFunc, ".", "*.go", true)
}
func (c cFix) doFixV25Content(content string) (newContent string, err error) {
newContent = content
if gstr.Contains(content, `.BindHookHandlerByMap(`) {
var pattern = `\.BindHookHandlerByMap\((.+?), map\[string\]ghttp\.HandlerFunc`
newContent, err = gregex.ReplaceString(
pattern,
`.BindHookHandlerByMap($1, map[ghttp.HookName]ghttp.HandlerFunc`,
content,
)
}
return
}
func (c cFix) autoDetectVersion(in cFixInput) (string, error) {
var (
err error
path = "go.mod"
path = gfile.Join(in.Path, "go.mod")
version string
)
if !gfile.Exists(path) {
@@ -86,7 +139,7 @@ func (c cFix) getVersion() (string, error) {
err = gfile.ReadLines(path, func(line string) error {
array := gstr.SplitAndTrim(line, " ")
if len(array) > 0 {
if array[0] == gfPackage {
if gstr.HasPrefix(array[0], gfPackage) {
version = array[1]
}
}

View File

@@ -0,0 +1,20 @@
package cmd
import (
"fmt"
"testing"
"github.com/gogf/gf/v2/test/gtest"
)
func Test_Fix_doFixV25Content(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var (
content = gtest.DataContent(`fix25_content.go.txt`)
f = cFix{}
)
newContent, err := f.doFixV25Content(content)
t.AssertNil(err)
fmt.Println(newContent)
})
}

View File

@@ -1,3 +1,9 @@
// 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 cmd
import (
@@ -13,6 +19,7 @@ type cGen struct {
g.Meta `name:"gen" brief:"{cGenBrief}" dc:"{cGenDc}"`
cGenDao
cGenEnums
cGenCtrl
cGenPb
cGenPbEntity
cGenService

View File

@@ -0,0 +1,15 @@
// 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 cmd
import (
"hotgo/internal/library/hggen/internal/cmd/genctrl"
)
type (
cGenCtrl = genctrl.CGenCtrl
)

View File

@@ -1,8 +1,15 @@
// 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 cmd
import (
//_ "github.com/gogf/gf/contrib/drivers/clickhouse/v2"
//_ "github.com/gogf/gf/contrib/drivers/mssql/v2"
//_ "github.com/gogf/gf/contrib/drivers/mysql/v2"
_ "github.com/gogf/gf/contrib/drivers/mysql/v2"
//_ "github.com/gogf/gf/contrib/drivers/oracle/v2"
//_ "github.com/gogf/gf/contrib/drivers/pgsql/v2"
//_ "github.com/gogf/gf/contrib/drivers/sqlite/v2"

View File

@@ -1,79 +1,13 @@
// 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 cmd
import (
"context"
"fmt"
"github.com/gogf/gf/v2/container/gset"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/genv"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/os/gproc"
"hotgo/internal/library/hggen/internal/utility/mlog"
)
import "hotgo/internal/library/hggen/internal/cmd/genpb"
type (
cGenPb struct{}
cGenPbInput struct {
g.Meta `name:"pb" brief:"parse proto files and generate protobuf go files"`
}
cGenPbOutput struct{}
cGenPb = genpb.CGenPb
)
func (c cGenPb) Pb(ctx context.Context, in cGenPbInput) (out *cGenPbOutput, err error) {
// Necessary check.
if gproc.SearchBinary("protoc") == "" {
mlog.Fatalf(`command "protoc" not found in your environment, please install protoc first to proceed this command`)
}
// protocol fold checks.
protoFolder := "protocol"
if !gfile.Exists(protoFolder) {
mlog.Fatalf(`proto files folder "%s" does not exist`, protoFolder)
}
// folder scanning.
files, err := gfile.ScanDirFile(protoFolder, "*.proto", true)
if err != nil {
mlog.Fatal(err)
}
if len(files) == 0 {
mlog.Fatalf(`no proto files found in folder "%s"`, protoFolder)
}
dirSet := gset.NewStrSet()
for _, file := range files {
dirSet.Add(gfile.Dir(file))
}
var (
servicePath = gfile.RealPath(".")
goPathSrc = gfile.RealPath(gfile.Join(genv.Get("GOPATH").String(), "src"))
)
dirSet.Iterator(func(protoDirPath string) bool {
parsingCommand := fmt.Sprintf(
"protoc --gofast_out=plugins=grpc:. %s/*.proto -I%s",
protoDirPath,
servicePath,
)
if goPathSrc != "" {
parsingCommand += " -I" + goPathSrc
}
mlog.Print(parsingCommand)
if output, err := gproc.ShellExec(ctx, parsingCommand); err != nil {
mlog.Print(output)
mlog.Fatal(err)
}
return true
})
// Custom replacement.
//pbFolder := "protobuf"
//_, _ = gfile.ScanDirFileFunc(pbFolder, "*.go", true, func(path string) string {
// content := gfile.GetContents(path)
// content = gstr.ReplaceByArray(content, g.SliceStr{
// `gtime "gtime"`, `gtime "github.com/gogf/gf/v2/os/gtime"`,
// })
// _ = gfile.PutContents(path, content)
// utils.GoFmt(path)
// return path
//})
mlog.Print("done!")
return
}

View File

@@ -1,411 +1,13 @@
// 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 cmd
import (
"bytes"
"context"
"fmt"
"strings"
"github.com/gogf/gf/v2/database/gdb"
"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/olekukonko/tablewriter"
"hotgo/internal/library/hggen/internal/consts"
"hotgo/internal/library/hggen/internal/utility/mlog"
)
import "hotgo/internal/library/hggen/internal/cmd/genpbentity"
type (
cGenPbEntity struct{}
cGenPbEntityInput struct {
g.Meta `name:"pbentity" config:"{cGenPbEntityConfig}" brief:"{cGenPbEntityBrief}" eg:"{cGenPbEntityEg}" ad:"{cGenPbEntityAd}"`
Path string `name:"path" short:"p" brief:"{cGenPbEntityBriefPath}"`
Package string `name:"package" short:"k" brief:"{cGenPbEntityBriefPackage}"`
Link string `name:"link" short:"l" brief:"{cGenPbEntityBriefLink}"`
Tables string `name:"tables" short:"t" brief:"{cGenPbEntityBriefTables}"`
Prefix string `name:"prefix" short:"f" brief:"{cGenPbEntityBriefPrefix}"`
RemovePrefix string `name:"removePrefix" short:"r" brief:"{cGenPbEntityBriefRemovePrefix}"`
NameCase string `name:"nameCase" short:"n" brief:"{cGenPbEntityBriefNameCase}" d:"Camel"`
JsonCase string `name:"jsonCase" short:"j" brief:"{cGenPbEntityBriefJsonCase}" d:"CamelLower"`
Option string `name:"option" short:"o" brief:"{cGenPbEntityBriefOption}"`
}
cGenPbEntityOutput struct{}
cGenPbEntityInternalInput struct {
cGenPbEntityInput
TableName string // TableName specifies the table name of the table.
NewTableName string // NewTableName specifies the prefix-stripped name of the table.
}
cGenPbEntity = genpbentity.CGenPbEntity
)
const (
cGenPbEntityConfig = `gfcli.gen.pbentity`
cGenPbEntityBrief = `generate entity message files in protobuf3 format`
cGenPbEntityEg = `
gf gen pbentity
gf gen pbentity -l "mysql:root:12345678@tcp(127.0.0.1:3306)/test"
gf gen pbentity -p ./protocol/demos/entity -t user,user_detail,user_login
gf gen pbentity -r user_
`
cGenPbEntityAd = `
CONFIGURATION SUPPORT
Options are also supported by configuration file.
It's suggested using configuration file instead of command line arguments making producing.
The configuration node name is "gf.gen.pbentity", which also supports multiple databases, for example(config.yaml):
gfcli:
gen:
- pbentity:
link: "mysql:root:12345678@tcp(127.0.0.1:3306)/test"
path: "protocol/demos/entity"
tables: "order,products"
package: "demos"
- pbentity:
link: "mysql:root:12345678@tcp(127.0.0.1:3306)/primary"
path: "protocol/demos/entity"
prefix: "primary_"
tables: "user, userDetail"
package: "demos"
option: |
option go_package = "protobuf/demos";
option java_package = "protobuf/demos";
option php_namespace = "protobuf/demos";
`
cGenPbEntityBriefPath = `directory path for generated files`
cGenPbEntityBriefPackage = `package name for all entity proto files`
cGenPbEntityBriefLink = `database configuration, the same as the ORM configuration of GoFrame`
cGenPbEntityBriefTables = `generate models only for given tables, multiple table names separated with ','`
cGenPbEntityBriefPrefix = `add specified prefix for all entity names and entity proto files`
cGenPbEntityBriefRemovePrefix = `remove specified prefix of the table, multiple prefix separated with ','`
cGenPbEntityBriefOption = `extra protobuf options`
cGenPbEntityBriefGroup = `
specifying the configuration group name of database for generated ORM instance,
it's not necessary and the default value is "default"
`
cGenPbEntityBriefNameCase = `
case for message attribute names, default is "Camel":
| Case | Example |
|---------------- |--------------------|
| Camel | AnyKindOfString |
| CamelLower | anyKindOfString | default
| Snake | any_kind_of_string |
| SnakeScreaming | ANY_KIND_OF_STRING |
| SnakeFirstUpper | rgb_code_md5 |
| Kebab | any-kind-of-string |
| KebabScreaming | ANY-KIND-OF-STRING |
`
cGenPbEntityBriefJsonCase = `
case for message json tag, cases are the same as "nameCase", default "CamelLower".
set it to "none" to ignore json tag generating.
`
)
func init() {
gtag.Sets(g.MapStrStr{
`cGenPbEntityConfig`: cGenPbEntityConfig,
`cGenPbEntityBrief`: cGenPbEntityBrief,
`cGenPbEntityEg`: cGenPbEntityEg,
`cGenPbEntityAd`: cGenPbEntityAd,
`cGenPbEntityBriefPath`: cGenPbEntityBriefPath,
`cGenPbEntityBriefPackage`: cGenPbEntityBriefPackage,
`cGenPbEntityBriefLink`: cGenPbEntityBriefLink,
`cGenPbEntityBriefTables`: cGenPbEntityBriefTables,
`cGenPbEntityBriefPrefix`: cGenPbEntityBriefPrefix,
`cGenPbEntityBriefRemovePrefix`: cGenPbEntityBriefRemovePrefix,
`cGenPbEntityBriefGroup`: cGenPbEntityBriefGroup,
`cGenPbEntityBriefNameCase`: cGenPbEntityBriefNameCase,
`cGenPbEntityBriefJsonCase`: cGenPbEntityBriefJsonCase,
`cGenPbEntityBriefOption`: cGenPbEntityBriefOption,
})
}
func (c cGenPbEntity) PbEntity(ctx context.Context, in cGenPbEntityInput) (out *cGenPbEntityOutput, err error) {
var (
config = g.Cfg()
)
if config.Available(ctx) {
v := config.MustGet(ctx, cGenPbEntityConfig)
if v.IsSlice() {
for i := 0; i < len(v.Interfaces()); i++ {
doGenPbEntityForArray(ctx, i, in)
}
} else {
doGenPbEntityForArray(ctx, -1, in)
}
} else {
doGenPbEntityForArray(ctx, -1, in)
}
mlog.Print("done!")
return
}
func doGenPbEntityForArray(ctx context.Context, index int, in cGenPbEntityInput) {
var (
err error
db gdb.DB
)
if index >= 0 {
err = g.Cfg().MustGet(
ctx,
fmt.Sprintf(`%s.%d`, cGenPbEntityConfig, index),
).Scan(&in)
if err != nil {
mlog.Fatalf(`invalid configuration of "%s": %+v`, cGenPbEntityConfig, err)
}
}
if in.Package == "" {
mlog.Fatal("package name should not be empty")
}
removePrefixArray := gstr.SplitAndTrim(in.RemovePrefix, ",")
// It uses user passed database configuration.
if in.Link != "" {
var (
tempGroup = gtime.TimestampNanoStr()
match, _ = gregex.MatchString(`([a-z]+):(.+)`, in.Link)
)
if len(match) == 3 {
gdb.AddConfigNode(tempGroup, gdb.ConfigNode{
Type: gstr.Trim(match[1]),
Link: gstr.Trim(match[2]),
})
db, _ = gdb.Instance(tempGroup)
}
} else {
db = g.DB()
}
if db == nil {
mlog.Fatal("database initialization failed")
}
tableNames := ([]string)(nil)
if in.Tables != "" {
tableNames = gstr.SplitAndTrim(in.Tables, ",")
} else {
tableNames, err = db.Tables(context.TODO())
if err != nil {
mlog.Fatalf("fetching tables failed: \n %v", err)
}
}
for _, tableName := range tableNames {
newTableName := tableName
for _, v := range removePrefixArray {
newTableName = gstr.TrimLeftStr(newTableName, v, 1)
}
generatePbEntityContentFile(ctx, db, cGenPbEntityInternalInput{
cGenPbEntityInput: in,
TableName: tableName,
NewTableName: newTableName,
})
}
}
// generatePbEntityContentFile generates the protobuf files for given table.
func generatePbEntityContentFile(ctx context.Context, db gdb.DB, in cGenPbEntityInternalInput) {
fieldMap, err := db.TableFields(ctx, in.TableName)
if err != nil {
mlog.Fatalf("fetching tables fields failed for table '%s':\n%v", in.TableName, err)
}
// Change the `newTableName` if `Prefix` is given.
newTableName := "Entity_" + in.Prefix + in.NewTableName
var (
tableNameCamelCase = gstr.CaseCamel(newTableName)
tableNameSnakeCase = gstr.CaseSnake(newTableName)
entityMessageDefine = generateEntityMessageDefinition(tableNameCamelCase, fieldMap, in)
fileName = gstr.Trim(tableNameSnakeCase, "-_.")
path = gfile.Join(in.Path, fileName+".proto")
)
entityContent := gstr.ReplaceByMap(getTplPbEntityContent(""), g.MapStrStr{
"{PackageName}": in.Package,
"{OptionContent}": in.Option,
"{EntityMessage}": entityMessageDefine,
})
if err := gfile.PutContents(path, strings.TrimSpace(entityContent)); err != nil {
mlog.Fatalf("writing content to '%s' failed: %v", path, err)
} else {
mlog.Print("generated:", path)
}
}
// generateEntityMessageDefinition generates and returns the message definition for specified table.
func generateEntityMessageDefinition(entityName string, fieldMap map[string]*gdb.TableField, in cGenPbEntityInternalInput) string {
var (
buffer = bytes.NewBuffer(nil)
array = make([][]string, len(fieldMap))
names = sortFieldKeyForPbEntity(fieldMap)
)
for index, name := range names {
array[index] = generateMessageFieldForPbEntity(index+1, fieldMap[name], in)
}
tw := tablewriter.NewWriter(buffer)
tw.SetBorder(false)
tw.SetRowLine(false)
tw.SetAutoWrapText(false)
tw.SetColumnSeparator("")
tw.AppendBulk(array)
tw.Render()
stContent := buffer.String()
// Let's do this hack of table writer for indent!
stContent = gstr.Replace(stContent, " #", "")
buffer.Reset()
buffer.WriteString(fmt.Sprintf("message %s {\n", entityName))
buffer.WriteString(stContent)
buffer.WriteString("}")
return buffer.String()
}
// generateMessageFieldForPbEntity generates and returns the message definition for specified field.
func generateMessageFieldForPbEntity(index int, field *gdb.TableField, in cGenPbEntityInternalInput) []string {
var (
typeName string
comment string
jsonTagStr string
)
t, _ := gregex.ReplaceString(`\(.+\)`, "", field.Type)
t = gstr.Split(gstr.Trim(t), " ")[0]
t = gstr.ToLower(t)
switch t {
case "binary", "varbinary", "blob", "tinyblob", "mediumblob", "longblob":
typeName = "bytes"
case "bit", "int", "tinyint", "small_int", "smallint", "medium_int", "mediumint", "serial":
if gstr.ContainsI(field.Type, "unsigned") {
typeName = "uint32"
} else {
typeName = "int32"
}
case "int8", "big_int", "bigint", "bigserial":
if gstr.ContainsI(field.Type, "unsigned") {
typeName = "uint64"
} else {
typeName = "int64"
}
case "real":
typeName = "float"
case "float", "double", "decimal", "smallmoney":
typeName = "double"
case "bool":
typeName = "bool"
case "datetime", "timestamp", "date", "time":
typeName = "int64"
default:
// Auto detecting type.
switch {
case strings.Contains(t, "int"):
typeName = "int"
case strings.Contains(t, "text") || strings.Contains(t, "char"):
typeName = "string"
case strings.Contains(t, "float") || strings.Contains(t, "double"):
typeName = "double"
case strings.Contains(t, "bool"):
typeName = "bool"
case strings.Contains(t, "binary") || strings.Contains(t, "blob"):
typeName = "bytes"
case strings.Contains(t, "date") || strings.Contains(t, "time"):
typeName = "int64"
default:
typeName = "string"
}
}
comment = gstr.ReplaceByArray(field.Comment, g.SliceStr{
"\n", " ",
"\r", " ",
})
comment = gstr.Trim(comment)
comment = gstr.Replace(comment, `\n`, " ")
comment, _ = gregex.ReplaceString(`\s{2,}`, ` `, comment)
if jsonTagName := formatCase(field.Name, in.JsonCase); jsonTagName != "" {
jsonTagStr = fmt.Sprintf(`[(gogoproto.jsontag) = "%s"]`, jsonTagName)
// beautiful indent.
if index < 10 {
// 3 spaces
jsonTagStr = " " + jsonTagStr
} else if index < 100 {
// 2 spaces
jsonTagStr = " " + jsonTagStr
} else {
// 1 spaces
jsonTagStr = " " + jsonTagStr
}
}
return []string{
" #" + typeName,
" #" + formatCase(field.Name, in.NameCase),
" #= " + gconv.String(index) + jsonTagStr + ";",
" #" + fmt.Sprintf(`// %s`, comment),
}
}
func getTplPbEntityContent(tplEntityPath string) string {
if tplEntityPath != "" {
return gfile.GetContents(tplEntityPath)
}
return consts.TemplatePbEntityMessageContent
}
// formatCase call gstr.Case* function to convert the s to specified case.
func formatCase(str, caseStr string) string {
switch gstr.ToLower(caseStr) {
case gstr.ToLower("Camel"):
return gstr.CaseCamel(str)
case gstr.ToLower("CamelLower"):
return gstr.CaseCamelLower(str)
case gstr.ToLower("Kebab"):
return gstr.CaseKebab(str)
case gstr.ToLower("KebabScreaming"):
return gstr.CaseKebabScreaming(str)
case gstr.ToLower("Snake"):
return gstr.CaseSnake(str)
case gstr.ToLower("SnakeFirstUpper"):
return gstr.CaseSnakeFirstUpper(str)
case gstr.ToLower("SnakeScreaming"):
return gstr.CaseSnakeScreaming(str)
case "none":
return ""
}
return str
}
func sortFieldKeyForPbEntity(fieldMap map[string]*gdb.TableField) []string {
names := make(map[int]string)
for _, field := range fieldMap {
names[field.Index] = field.Name
}
var (
result = make([]string, len(names))
i = 0
j = 0
)
for {
if len(names) == 0 {
break
}
if val, ok := names[i]; ok {
result[j] = val
j++
delete(names, i)
}
i++
}
return result
}

View File

@@ -1,3 +1,9 @@
// 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 cmd
import (

View File

@@ -1,9 +1,14 @@
// 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 cmd
import (
"context"
"fmt"
"github.com/gogf/gf/v2/text/gstr"
"os"
"strings"
@@ -12,6 +17,7 @@ import (
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/os/gproc"
"github.com/gogf/gf/v2/os/gres"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gtag"
"hotgo/internal/library/hggen/internal/utility/allyes"

View File

@@ -1,3 +1,9 @@
// 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 cmd
import (

View File

@@ -1,3 +1,9 @@
// 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 cmd
import (

View File

@@ -1,9 +1,16 @@
// 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 cmd
import (
"context"
"fmt"
"runtime"
"strings"
"github.com/gogf/gf/v2/container/gtype"
"github.com/gogf/gf/v2/frame/g"
@@ -154,7 +161,7 @@ func (app *cRunApp) Run(ctx context.Context) {
if runtime.GOOS == "windows" {
// Special handling for windows platform.
// DO NOT USE "cmd /c" command.
process = gproc.NewProcess(runCommand, nil)
process = gproc.NewProcess(outputPath, strings.Fields(app.Args))
} else {
process = gproc.NewProcessCmd(runCommand, nil)
}

View File

@@ -1,3 +1,9 @@
// 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 cmd
import (

View File

@@ -1,15 +1,26 @@
// 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 cmd
import (
"context"
"fmt"
"runtime"
"github.com/minio/selfupdate"
"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/gproc"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gtag"
"hotgo/internal/library/hggen/internal/utility/mlog"
"hotgo/internal/library/hggen/internal/utility/utils"
)
var (
@@ -21,12 +32,12 @@ type cUp struct {
}
const (
gfPackage = `github.com/gogf/gf/v2`
gfPackage = `github.com/gogf/gf/`
cUpEg = `
gf up
gf up -a
gf up -c
gf up -f -c
gf up -cf
`
)
@@ -39,51 +50,90 @@ func init() {
type cUpInput struct {
g.Meta `name:"up" config:"gfcli.up"`
All bool `name:"all" short:"a" brief:"upgrade both version and cli, auto fix codes" orphan:"true"`
Fix bool `name:"fix" short:"f" brief:"auto fix codes" orphan:"true"`
Cli bool `name:"cli" short:"c" brief:"also upgrade CLI tool (not supported yet)" orphan:"true"`
Cli bool `name:"cli" short:"c" brief:"also upgrade CLI tool" orphan:"true"`
Fix bool `name:"fix" short:"f" brief:"auto fix codes(it only make sense if cli is to be upgraded)" orphan:"true"`
}
type cUpOutput struct{}
func (c cUp) Index(ctx context.Context, in cUpInput) (out *cUpOutput, err error) {
defer mlog.Print(`done!`)
defer func() {
if err == nil {
mlog.Print()
mlog.Print(`👏congratulations! you've upgraded to the latest version of GoFrame! enjoy it!👏`)
mlog.Print()
}
}()
var doUpgradeVersionOut *doUpgradeVersionOutput
if in.All {
in.Cli = true
in.Fix = true
}
if err = c.doUpgradeVersion(ctx); err != nil {
if doUpgradeVersionOut, err = c.doUpgradeVersion(ctx, in); err != nil {
return nil, err
}
if in.Fix {
if err = c.doAutoFixing(ctx); err != nil {
if in.Cli {
if err = c.doUpgradeCLI(ctx); err != nil {
return nil, err
}
}
//if in.Cli {
// if err = c.doUpgradeCLI(ctx); err != nil {
// return nil, err
// }
//}
if in.Cli && in.Fix {
if doUpgradeVersionOut != nil && len(doUpgradeVersionOut.Items) > 0 {
upgradedPathSet := gset.NewStrSet()
for _, item := range doUpgradeVersionOut.Items {
if !upgradedPathSet.AddIfNotExist(item.DirPath) {
continue
}
if err = c.doAutoFixing(ctx, item.DirPath, item.Version); err != nil {
return nil, err
}
}
}
}
return
}
func (c cUp) doUpgradeVersion(ctx context.Context) (err error) {
type doUpgradeVersionOutput struct {
Items []doUpgradeVersionOutputItem
}
type doUpgradeVersionOutputItem struct {
DirPath string
Version string
}
func (c cUp) doUpgradeVersion(ctx context.Context, in cUpInput) (out *doUpgradeVersionOutput, err error) {
mlog.Print(`start upgrading version...`)
out = &doUpgradeVersionOutput{
Items: make([]doUpgradeVersionOutputItem, 0),
}
type Package struct {
Name string
Version string
}
var (
dir = gfile.Pwd()
temp string
path = gfile.Join(dir, "go.mod")
temp string
dirPath = gfile.Pwd()
goModPath = gfile.Join(dirPath, "go.mod")
)
// It recursively upgrades the go.mod from sub folder to its parent folders.
for {
if gfile.Exists(path) {
var packages []string
err = gfile.ReadLines(path, func(line string) error {
if gfile.Exists(goModPath) {
var packages []Package
err = gfile.ReadLines(goModPath, func(line string) error {
line = gstr.Trim(line)
line = gstr.TrimLeftStr(line, "require ")
line = gstr.Trim(line)
if gstr.HasPrefix(line, gfPackage) {
pkg := gstr.Explode(" ", line)[0]
packages = append(packages, pkg)
array := gstr.SplitAndTrim(line, " ")
packages = append(packages, Package{
Name: array[0],
Version: array[1],
})
}
return nil
})
@@ -91,31 +141,79 @@ func (c cUp) doUpgradeVersion(ctx context.Context) (err error) {
return
}
for _, pkg := range packages {
mlog.Printf(`upgrading %s`, pkg)
command := fmt.Sprintf(`go get -u %s@latest`, pkg)
mlog.Printf(`upgrading "%s" from "%s" to "latest"`, pkg.Name, pkg.Version)
// go get -u
command := fmt.Sprintf(`cd %s && go get -u %s@latest`, dirPath, pkg.Name)
if err = gproc.ShellRun(ctx, command); err != nil {
return
}
// go mod tidy
if err = utils.GoModTidy(ctx, dirPath); err != nil {
return nil, err
}
out.Items = append(out.Items, doUpgradeVersionOutputItem{
DirPath: dirPath,
Version: pkg.Version,
})
}
return
}
temp = gfile.Dir(dir)
if temp == "" || temp == dir {
temp = gfile.Dir(dirPath)
if temp == "" || temp == dirPath {
return
}
dir = temp
path = gfile.Join(dir, "go.mod")
dirPath = temp
goModPath = gfile.Join(dirPath, "go.mod")
}
}
// doUpgradeCLI downloads the new version binary with process.
func (c cUp) doUpgradeCLI(ctx context.Context) (err error) {
mlog.Print(`start upgrading cli...`)
var (
downloadUrl = fmt.Sprintf(
`https://github.com/gogf/gf/releases/latest/download/gf_%s_%s`,
runtime.GOOS, runtime.GOARCH,
)
localSaveFilePath = gfile.SelfPath() + "~"
)
if runtime.GOOS == "windows" {
downloadUrl += ".exe"
}
mlog.Printf(`start downloading "%s" to "%s", it may take some time`, downloadUrl, localSaveFilePath)
err = utils.HTTPDownloadFileWithPercent(downloadUrl, localSaveFilePath)
if err != nil {
return err
}
defer func() {
mlog.Printf(`new version cli binary is successfully installed to "%s"`, gfile.SelfPath())
mlog.Printf(`remove temporary buffer file "%s"`, localSaveFilePath)
_ = gfile.Remove(localSaveFilePath)
}()
// It fails if file not exist or its size is less than 1MB.
if !gfile.Exists(localSaveFilePath) || gfile.Size(localSaveFilePath) < 1024*1024 {
mlog.Fatalf(`download "%s" to "%s" failed`, downloadUrl, localSaveFilePath)
}
newFile, err := gfile.Open(localSaveFilePath)
if err != nil {
return err
}
// selfupdate
err = selfupdate.Apply(newFile, selfupdate.Options{})
if err != nil {
return err
}
return
}
func (c cUp) doAutoFixing(ctx context.Context) (err error) {
mlog.Print(`start auto fixing...`)
err = cFix{}.doFix()
func (c cUp) doAutoFixing(ctx context.Context, dirPath string, version string) (err error) {
mlog.Printf(`auto fixing directory path "%s" from version "%s" ...`, dirPath, version)
command := fmt.Sprintf(`gf fix -p %s`, dirPath)
_ = gproc.ShellRun(ctx, command)
return
}

View File

@@ -1,3 +1,9 @@
// 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 cmd
import (
@@ -64,6 +70,8 @@ func (c cVersion) getGFVersionOfCurrentProject() (string, error) {
if gfile.Exists(goModPath) {
lines := gstr.SplitAndTrim(gfile.GetContents(goModPath), "\n")
for _, line := range lines {
line = gstr.Trim(line)
line = gstr.TrimLeftStr(line, "require ")
line = gstr.Trim(line)
// Version 1.
match, err := gregex.MatchString(`^github\.com/gogf/gf\s+(.+)$`, line)

View File

@@ -0,0 +1,233 @@
// 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 genctrl
import (
"context"
"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/util/gconv"
"github.com/gogf/gf/v2/util/gtag"
"hotgo/internal/library/hggen/internal/utility/mlog"
)
const (
CGenCtrlConfig = `gfcli.gen.ctrl`
CGenCtrlUsage = `gf gen ctrl [OPTION]`
CGenCtrlBrief = `parse api definitions to generate controller/sdk go files`
CGenCtrlEg = `
gf gen ctrl
`
CGenCtrlBriefSrcFolder = `source folder path to be parsed. default: api`
CGenCtrlBriefDstFolder = `destination folder path storing automatically generated go files. default: internal/controller`
CGenCtrlBriefWatchFile = `used in file watcher, it re-generates go files only if given file is under srcFolder`
CGenCtrlBriefSdkPath = `also generate SDK go files for api definitions to specified directory`
CGenCtrlBriefSdkStdVersion = `use standard version prefix for generated sdk request path`
CGenCtrlBriefSdkNoV1 = `do not add version suffix for interface module name if version is v1`
CGenCtrlBriefClear = `auto delete generated and unimplemented controller go files if api definitions are missing`
)
const (
PatternApiDefinition = `type\s+(\w+)Req\s+struct\s+{`
PatternCtrlDefinition = `func\s+\(.+?\)\s+\w+\(.+?\*(\w+)\.(\w+)Req\)\s+\(.+?\*(\w+)\.(\w+)Res,\s+\w+\s+error\)\s+{`
)
const (
genCtrlFileLockSeconds = 10
)
func init() {
gtag.Sets(g.MapStrStr{
`CGenCtrlConfig`: CGenCtrlConfig,
`CGenCtrlUsage`: CGenCtrlUsage,
`CGenCtrlBrief`: CGenCtrlBrief,
`CGenCtrlEg`: CGenCtrlEg,
`CGenCtrlBriefSrcFolder`: CGenCtrlBriefSrcFolder,
`CGenCtrlBriefDstFolder`: CGenCtrlBriefDstFolder,
`CGenCtrlBriefWatchFile`: CGenCtrlBriefWatchFile,
`CGenCtrlBriefSdkPath`: CGenCtrlBriefSdkPath,
`CGenCtrlBriefSdkStdVersion`: CGenCtrlBriefSdkStdVersion,
`CGenCtrlBriefSdkNoV1`: CGenCtrlBriefSdkNoV1,
`CGenCtrlBriefClear`: CGenCtrlBriefClear,
})
}
type (
CGenCtrl struct{}
CGenCtrlInput struct {
g.Meta `name:"ctrl" config:"{CGenCtrlConfig}" usage:"{CGenCtrlUsage}" brief:"{CGenCtrlBrief}" eg:"{CGenCtrlEg}"`
SrcFolder string `short:"s" name:"srcFolder" brief:"{CGenCtrlBriefSrcFolder}" d:"api"`
DstFolder string `short:"d" name:"dstFolder" brief:"{CGenCtrlBriefDstFolder}" d:"internal/controller"`
WatchFile string `short:"w" name:"watchFile" brief:"{CGenCtrlBriefWatchFile}"`
SdkPath string `short:"k" name:"sdkPath" brief:"{CGenCtrlBriefSdkPath}"`
SdkStdVersion bool `short:"v" name:"sdkStdVersion" brief:"{CGenCtrlBriefSdkStdVersion}" orphan:"true"`
SdkNoV1 bool `short:"n" name:"sdkNoV1" brief:"{CGenCtrlBriefSdkNoV1}" orphan:"true"`
Clear bool `short:"c" name:"clear" brief:"{CGenCtrlBriefClear}" orphan:"true"`
}
CGenCtrlOutput struct{}
)
func (c CGenCtrl) Ctrl(ctx context.Context, in CGenCtrlInput) (out *CGenCtrlOutput, err error) {
if in.WatchFile != "" {
err = c.generateByWatchFile(
in.WatchFile, in.SdkPath, in.SdkStdVersion, in.SdkNoV1, in.Clear,
)
mlog.Print(`done!`)
return
}
if !gfile.Exists(in.SrcFolder) {
mlog.Fatalf(`source folder path "%s" does not exist`, in.SrcFolder)
}
// retrieve all api modules.
apiModuleFolderPaths, err := gfile.ScanDir(in.SrcFolder, "*", false)
if err != nil {
return nil, err
}
for _, apiModuleFolderPath := range apiModuleFolderPaths {
if !gfile.IsDir(apiModuleFolderPath) {
continue
}
// generate go files by api module.
var (
module = gfile.Basename(apiModuleFolderPath)
dstModuleFolderPath = gfile.Join(in.DstFolder, module)
)
err = c.generateByModule(
apiModuleFolderPath, dstModuleFolderPath, in.SdkPath,
in.SdkStdVersion, in.SdkNoV1, in.Clear,
)
if err != nil {
return nil, err
}
}
mlog.Print(`done!`)
return
}
func (c CGenCtrl) generateByWatchFile(watchFile, sdkPath string, sdkStdVersion, sdkNoV1, clear bool) (err error) {
// 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) < genCtrlFileLockSeconds {
// If another generating 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())
// check this updated file is an api file.
// watch file should be in standard goframe project structure.
var (
apiVersionPath = gfile.Dir(watchFile)
apiModuleFolderPath = gfile.Dir(apiVersionPath)
shouldBeNameOfAPi = gfile.Basename(gfile.Dir(apiModuleFolderPath))
)
if shouldBeNameOfAPi != "api" {
return nil
}
// watch file should have api definitions.
if gfile.Exists(watchFile) {
if !gregex.IsMatchString(PatternApiDefinition, gfile.GetContents(watchFile)) {
return nil
}
}
var (
projectRootPath = gfile.Dir(gfile.Dir(apiModuleFolderPath))
module = gfile.Basename(apiModuleFolderPath)
dstModuleFolderPath = gfile.Join(projectRootPath, "internal", "controller", module)
)
return c.generateByModule(
apiModuleFolderPath, dstModuleFolderPath, sdkPath, sdkStdVersion, sdkNoV1, clear,
)
}
// parseApiModule parses certain api and generate associated go files by certain module, not all api modules.
func (c CGenCtrl) generateByModule(
apiModuleFolderPath, dstModuleFolderPath, sdkPath string,
sdkStdVersion, sdkNoV1, clear bool,
) (err error) {
// parse src and dst folder go files.
apiItemsInSrc, err := c.getApiItemsInSrc(apiModuleFolderPath)
if err != nil {
return err
}
apiItemsInDst, err := c.getApiItemsInDst(dstModuleFolderPath)
if err != nil {
return err
}
// generate api interface go files.
if err = newApiInterfaceGenerator().Generate(apiModuleFolderPath, apiItemsInSrc); err != nil {
return
}
// generate controller go files.
// api filtering for already implemented api controllers.
var (
alreadyImplementedCtrlSet = gset.NewStrSet()
toBeImplementedApiItems = make([]apiItem, 0)
)
for _, item := range apiItemsInDst {
alreadyImplementedCtrlSet.Add(item.String())
}
for _, item := range apiItemsInSrc {
if alreadyImplementedCtrlSet.Contains(item.String()) {
continue
}
toBeImplementedApiItems = append(toBeImplementedApiItems, item)
}
if len(toBeImplementedApiItems) > 0 {
err = newControllerGenerator().Generate(dstModuleFolderPath, toBeImplementedApiItems)
if err != nil {
return
}
}
// delete unimplemented controllers if api definitions are missing.
if clear {
var (
apiDefinitionSet = gset.NewStrSet()
extraApiItemsInCtrl = make([]apiItem, 0)
)
for _, item := range apiItemsInSrc {
apiDefinitionSet.Add(item.String())
}
for _, item := range apiItemsInDst {
if apiDefinitionSet.Contains(item.String()) {
continue
}
extraApiItemsInCtrl = append(extraApiItemsInCtrl, item)
}
if len(extraApiItemsInCtrl) > 0 {
err = newControllerClearer().Clear(dstModuleFolderPath, extraApiItemsInCtrl)
if err != nil {
return
}
}
}
// generate sdk go files.
if sdkPath != "" {
if err = newApiSdkGenerator().Generate(apiItemsInSrc, sdkPath, sdkStdVersion, sdkNoV1); err != nil {
return
}
}
return
}

View File

@@ -0,0 +1,22 @@
// 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 genctrl
import "github.com/gogf/gf/v2/text/gstr"
type apiItem struct {
Import string `eg:"demo.com/api/user/v1"`
Module string `eg:"user"`
Version string `eg:"v1"`
MethodName string `eg:"GetList"`
}
func (a apiItem) String() string {
return gstr.Join([]string{
a.Import, a.Module, a.Version, a.MethodName,
}, ",")
}

View File

@@ -0,0 +1,136 @@
// 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 genctrl
import (
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/text/gregex"
"github.com/gogf/gf/v2/text/gstr"
"hotgo/internal/library/hggen/internal/utility/utils"
)
func (c CGenCtrl) getApiItemsInSrc(apiModuleFolderPath string) (items []apiItem, err error) {
var (
fileContent string
importPath string
)
// The second level folders: versions.
apiVersionFolderPaths, err := gfile.ScanDir(apiModuleFolderPath, "*", false)
if err != nil {
return nil, err
}
for _, apiVersionFolderPath := range apiVersionFolderPaths {
if !gfile.IsDir(apiVersionFolderPath) {
continue
}
// The second level folders: versions.
apiFileFolderPaths, err := gfile.ScanDir(apiVersionFolderPath, "*.go", false)
if err != nil {
return nil, err
}
importPath = utils.GetImportPath(apiVersionFolderPath)
for _, apiFileFolderPath := range apiFileFolderPaths {
if gfile.IsDir(apiFileFolderPath) {
continue
}
fileContent = gfile.GetContents(apiFileFolderPath)
matches, err := gregex.MatchAllString(PatternApiDefinition, fileContent)
if err != nil {
return nil, err
}
for _, match := range matches {
item := apiItem{
Import: gstr.Trim(importPath, `"`),
Module: gfile.Basename(apiModuleFolderPath),
Version: gfile.Basename(apiVersionFolderPath),
MethodName: match[1],
}
items = append(items, item)
}
}
}
return
}
func (c CGenCtrl) getApiItemsInDst(dstFolder string) (items []apiItem, err error) {
if !gfile.Exists(dstFolder) {
return nil, nil
}
type importItem struct {
Path string
Alias string
}
var fileContent string
filePaths, err := gfile.ScanDir(dstFolder, "*.go", true)
if err != nil {
return nil, err
}
for _, filePath := range filePaths {
fileContent = gfile.GetContents(filePath)
match, err := gregex.MatchString(`import\s+\(([\s\S]+?)\)`, fileContent)
if err != nil {
return nil, err
}
if len(match) < 2 {
continue
}
var (
array []string
importItems []importItem
importLines = gstr.SplitAndTrim(match[1], "\n")
module = gfile.Basename(gfile.Dir(filePath))
)
// retrieve all imports.
for _, importLine := range importLines {
array = gstr.SplitAndTrim(importLine, " ")
if len(array) == 2 {
importItems = append(importItems, importItem{
Path: gstr.Trim(array[1], `"`),
Alias: array[0],
})
} else {
importItems = append(importItems, importItem{
Path: gstr.Trim(array[0], `"`),
})
}
}
// retrieve all api usages.
matches, err := gregex.MatchAllString(PatternCtrlDefinition, fileContent)
if err != nil {
return nil, err
}
for _, match = range matches {
// try to find the import path of the api.
var (
importPath string
version = match[1]
methodName = match[2] // not the function name, but the method name in api definition.
)
for _, item := range importItems {
if item.Alias != "" {
if item.Alias == version {
importPath = item.Path
break
}
continue
}
if gfile.Basename(item.Path) == version {
importPath = item.Path
break
}
}
item := apiItem{
Import: gstr.Trim(importPath, `"`),
Module: module,
Version: gfile.Basename(importPath),
MethodName: methodName,
}
items = append(items, item)
}
}
return
}

View File

@@ -0,0 +1,137 @@
// 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 genctrl
import (
"fmt"
"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/text/gstr"
"hotgo/internal/library/hggen/internal/consts"
"hotgo/internal/library/hggen/internal/utility/mlog"
)
type controllerGenerator struct{}
func newControllerGenerator() *controllerGenerator {
return &controllerGenerator{}
}
func (c *controllerGenerator) Generate(dstModuleFolderPath string, apiModuleApiItems []apiItem) (err error) {
var (
doneApiItemSet = gset.NewStrSet()
)
for _, item := range apiModuleApiItems {
if doneApiItemSet.Contains(item.String()) {
continue
}
// retrieve all api items of the same module.
subItems := c.getSubItemsByModuleAndVersion(apiModuleApiItems, item.Module, item.Version)
if err = c.doGenerateCtrlNewByModuleAndVersion(
dstModuleFolderPath, item.Module, item.Version, gfile.Dir(item.Import),
); err != nil {
return
}
for _, subItem := range subItems {
if err = c.doGenerateCtrlItem(dstModuleFolderPath, subItem); err != nil {
return
}
doneApiItemSet.Add(subItem.String())
}
}
return
}
func (c *controllerGenerator) getSubItemsByModuleAndVersion(items []apiItem, module, version string) (subItems []apiItem) {
for _, item := range items {
if item.Module == module && item.Version == version {
subItems = append(subItems, item)
}
}
return
}
func (c *controllerGenerator) doGenerateCtrlNewByModuleAndVersion(
dstModuleFolderPath, module, version, importPath string,
) (err error) {
var (
moduleFilePath = gfile.Join(dstModuleFolderPath, module+".go")
moduleFilePathNew = gfile.Join(dstModuleFolderPath, module+"_new.go")
ctrlName = fmt.Sprintf(`Controller%s`, gstr.UcFirst(version))
interfaceName = fmt.Sprintf(`%s.I%s%s`, module, gstr.CaseCamel(module), gstr.UcFirst(version))
newFuncName = fmt.Sprintf(`New%s`, gstr.UcFirst(version))
newFuncNameDefinition = fmt.Sprintf(`func %s()`, newFuncName)
alreadyCreated bool
)
// replace "\" to "/", fix import error
importPath = gstr.Replace(importPath, "\\", "/", -1)
if !gfile.Exists(moduleFilePath) {
content := gstr.ReplaceByMap(consts.TemplateGenCtrlControllerEmpty, g.MapStrStr{
"{Module}": module,
})
if err = gfile.PutContents(moduleFilePath, gstr.TrimLeft(content)); err != nil {
return err
}
mlog.Printf(`generated: %s`, moduleFilePath)
}
if !gfile.Exists(moduleFilePathNew) {
content := gstr.ReplaceByMap(consts.TemplateGenCtrlControllerNewEmpty, g.MapStrStr{
"{Module}": module,
"{ImportPath}": fmt.Sprintf(`"%s"`, importPath),
})
if err = gfile.PutContents(moduleFilePathNew, gstr.TrimLeft(content)); err != nil {
return err
}
mlog.Printf(`generated: %s`, moduleFilePathNew)
}
filePaths, err := gfile.ScanDir(dstModuleFolderPath, "*.go", false)
if err != nil {
return err
}
for _, filePath := range filePaths {
if gstr.Contains(gfile.GetContents(filePath), newFuncNameDefinition) {
alreadyCreated = true
break
}
}
if !alreadyCreated {
content := gstr.ReplaceByMap(consts.TemplateGenCtrlControllerNewFunc, g.MapStrStr{
"{CtrlName}": ctrlName,
"{NewFuncName}": newFuncName,
"{InterfaceName}": interfaceName,
})
err = gfile.PutContentsAppend(moduleFilePathNew, gstr.TrimLeft(content))
if err != nil {
return err
}
}
return
}
func (c *controllerGenerator) doGenerateCtrlItem(dstModuleFolderPath string, item apiItem) (err error) {
var (
methodNameSnake = gstr.CaseSnake(item.MethodName)
ctrlName = fmt.Sprintf(`Controller%s`, gstr.UcFirst(item.Version))
methodFilePath = gfile.Join(dstModuleFolderPath, fmt.Sprintf(
`%s_%s_%s.go`, item.Module, item.Version, methodNameSnake,
))
)
content := gstr.ReplaceByMap(consts.TemplateGenCtrlControllerMethodFunc, g.MapStrStr{
"{Module}": item.Module,
"{ImportPath}": item.Import,
"{CtrlName}": ctrlName,
"{Version}": item.Version,
"{MethodName}": item.MethodName,
})
if err = gfile.PutContents(methodFilePath, gstr.TrimLeft(content)); err != nil {
return err
}
mlog.Printf(`generated: %s`, methodFilePath)
return
}

View File

@@ -0,0 +1,57 @@
// 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 genctrl
import (
"fmt"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/text/gregex"
"github.com/gogf/gf/v2/text/gstr"
"hotgo/internal/library/hggen/internal/utility/mlog"
)
type controllerClearer struct{}
func newControllerClearer() *controllerClearer {
return &controllerClearer{}
}
func (c *controllerClearer) Clear(dstModuleFolderPath string, extraApiItemsInCtrl []apiItem) (err error) {
for _, item := range extraApiItemsInCtrl {
if err = c.doClear(dstModuleFolderPath, item); err != nil {
return err
}
}
return
}
func (c *controllerClearer) doClear(dstModuleFolderPath string, item apiItem) (err error) {
var (
methodNameSnake = gstr.CaseSnake(item.MethodName)
methodFilePath = gfile.Join(dstModuleFolderPath, fmt.Sprintf(
`%s_%s_%s.go`, item.Module, item.Version, methodNameSnake,
))
fileContent = gstr.Trim(gfile.GetContents(methodFilePath))
)
match, err := gregex.MatchString(`.+?Req.+?Res.+?{([\s\S]+?)}`, fileContent)
if err != nil {
return err
}
if len(match) > 1 {
implements := gstr.Trim(match[1])
// One line.
if !gstr.Contains(implements, "\n") && gstr.Contains(implements, `CodeNotImplemented`) {
mlog.Printf(
`remove unimplemented and of no api definitions controller file: %s`,
methodFilePath,
)
err = gfile.Remove(methodFilePath)
}
}
return
}

View File

@@ -0,0 +1,111 @@
// 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 genctrl
import (
"fmt"
"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/text/gstr"
"github.com/gogf/gf/v2/util/gconv"
"hotgo/internal/library/hggen/internal/consts"
"hotgo/internal/library/hggen/internal/utility/mlog"
)
type apiInterfaceGenerator struct{}
func newApiInterfaceGenerator() *apiInterfaceGenerator {
return &apiInterfaceGenerator{}
}
func (c *apiInterfaceGenerator) Generate(apiModuleFolderPath string, apiModuleApiItems []apiItem) (err error) {
if len(apiModuleApiItems) == 0 {
return nil
}
var firstApiItem = apiModuleApiItems[0]
if err = c.doGenerate(apiModuleFolderPath, firstApiItem.Module, apiModuleApiItems); err != nil {
return
}
return
}
func (c *apiInterfaceGenerator) doGenerate(apiModuleFolderPath string, module string, items []apiItem) (err error) {
var (
moduleFilePath = gfile.Join(apiModuleFolderPath, fmt.Sprintf(`%s.go`, module))
importPathMap = gmap.NewListMap()
importPaths []string
)
// all import paths.
importPathMap.Set("\t"+`"context"`, 1)
importPathMap.Set("\t"+``, 1)
for _, item := range items {
importPathMap.Set(fmt.Sprintf("\t"+`"%s"`, item.Import), 1)
}
importPaths = gconv.Strings(importPathMap.Keys())
// interface definitions.
var (
doneApiItemSet = gset.NewStrSet()
interfaceDefinition string
interfaceContent = gstr.TrimLeft(gstr.ReplaceByMap(consts.TemplateGenCtrlApiInterface, g.MapStrStr{
"{Module}": module,
"{ImportPaths}": gstr.Join(importPaths, "\n"),
}))
)
for _, item := range items {
if doneApiItemSet.Contains(item.String()) {
continue
}
// retrieve all api items of the same module.
subItems := c.getSubItemsByModuleAndVersion(items, item.Module, item.Version)
var (
method string
methods = make([]string, 0)
interfaceName = fmt.Sprintf(`I%s%s`, gstr.CaseCamel(item.Module), gstr.UcFirst(item.Version))
)
for _, subItem := range subItems {
method = fmt.Sprintf(
"\t%s(ctx context.Context, req *%s.%sReq) (res *%s.%sRes, err error)",
subItem.MethodName, subItem.Version, subItem.MethodName, subItem.Version, subItem.MethodName,
)
methods = append(methods, method)
doneApiItemSet.Add(subItem.String())
}
interfaceDefinition += fmt.Sprintf("type %s interface {", interfaceName)
interfaceDefinition += "\n"
interfaceDefinition += gstr.Join(methods, "\n")
interfaceDefinition += "\n"
interfaceDefinition += fmt.Sprintf("}")
interfaceDefinition += "\n\n"
}
interfaceContent = gstr.TrimLeft(gstr.ReplaceByMap(interfaceContent, g.MapStrStr{
"{Interfaces}": interfaceDefinition,
}))
err = gfile.PutContents(moduleFilePath, interfaceContent)
mlog.Printf(`generated: %s`, moduleFilePath)
return
}
func (c *apiInterfaceGenerator) getSubItemsByModule(items []apiItem, module string) (subItems []apiItem) {
for _, item := range items {
if item.Module == module {
subItems = append(subItems, item)
}
}
return
}
func (c *apiInterfaceGenerator) getSubItemsByModuleAndVersion(items []apiItem, module, version string) (subItems []apiItem) {
for _, item := range items {
if item.Module == module && item.Version == version {
subItems = append(subItems, item)
}
}
return
}

View File

@@ -0,0 +1,194 @@
// 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 genctrl
import (
"fmt"
"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/text/gregex"
"github.com/gogf/gf/v2/text/gstr"
"hotgo/internal/library/hggen/internal/consts"
"hotgo/internal/library/hggen/internal/utility/mlog"
)
type apiSdkGenerator struct{}
func newApiSdkGenerator() *apiSdkGenerator {
return &apiSdkGenerator{}
}
func (c *apiSdkGenerator) Generate(apiModuleApiItems []apiItem, sdkFolderPath string, sdkStdVersion, sdkNoV1 bool) (err error) {
if err = c.doGenerateSdkPkgFile(sdkFolderPath); err != nil {
return
}
var doneApiItemSet = gset.NewStrSet()
for _, item := range apiModuleApiItems {
if doneApiItemSet.Contains(item.String()) {
continue
}
// retrieve all api items of the same module.
subItems := c.getSubItemsByModuleAndVersion(apiModuleApiItems, item.Module, item.Version)
if err = c.doGenerateSdkIClient(sdkFolderPath, item.Import, item.Module, item.Version, sdkNoV1); err != nil {
return
}
if err = c.doGenerateSdkImplementer(
subItems, sdkFolderPath, item.Import, item.Module, item.Version, sdkStdVersion, sdkNoV1,
); err != nil {
return
}
for _, subItem := range subItems {
doneApiItemSet.Add(subItem.String())
}
}
return
}
func (c *apiSdkGenerator) doGenerateSdkPkgFile(sdkFolderPath string) (err error) {
var (
pkgName = gfile.Basename(sdkFolderPath)
pkgFilePath = gfile.Join(sdkFolderPath, fmt.Sprintf(`%s.go`, pkgName))
fileContent string
)
if gfile.Exists(pkgFilePath) {
return nil
}
fileContent = gstr.TrimLeft(gstr.ReplaceByMap(consts.TemplateGenCtrlSdkPkgNew, g.MapStrStr{
"{PkgName}": pkgName,
}))
err = gfile.PutContents(pkgFilePath, fileContent)
mlog.Printf(`generated: %s`, pkgFilePath)
return
}
func (c *apiSdkGenerator) doGenerateSdkIClient(
sdkFolderPath, versionImportPath, module, version string, sdkNoV1 bool,
) (err error) {
var (
fileContent string
isDirty bool
isExist bool
pkgName = gfile.Basename(sdkFolderPath)
funcName = gstr.CaseCamel(module) + gstr.UcFirst(version)
interfaceName = fmt.Sprintf(`I%s`, funcName)
moduleImportPath = fmt.Sprintf(`"%s"`, gfile.Dir(versionImportPath))
iClientFilePath = gfile.Join(sdkFolderPath, fmt.Sprintf(`%s.iclient.go`, pkgName))
interfaceFuncDefinition = fmt.Sprintf(
`%s() %s.%s`,
gstr.CaseCamel(module)+gstr.UcFirst(version), module, interfaceName,
)
)
if sdkNoV1 && version == "v1" {
interfaceFuncDefinition = fmt.Sprintf(
`%s() %s.%s`,
gstr.CaseCamel(module), module, interfaceName,
)
}
if isExist = gfile.Exists(iClientFilePath); isExist {
fileContent = gfile.GetContents(iClientFilePath)
} else {
fileContent = gstr.TrimLeft(gstr.ReplaceByMap(consts.TemplateGenCtrlSdkIClient, g.MapStrStr{
"{PkgName}": pkgName,
}))
}
// append the import path to current import paths.
if !gstr.Contains(fileContent, moduleImportPath) {
isDirty = true
fileContent, err = gregex.ReplaceString(
`(import \([\s\S]*?)\)`,
fmt.Sprintf("$1\t%s\n)", moduleImportPath),
fileContent,
)
if err != nil {
return
}
}
// append the function definition to interface definition.
if !gstr.Contains(fileContent, interfaceFuncDefinition) {
isDirty = true
fileContent, err = gregex.ReplaceString(
`(type iClient interface {[\s\S]*?)}`,
fmt.Sprintf("$1\t%s\n}", interfaceFuncDefinition),
fileContent,
)
if err != nil {
return
}
}
if isDirty {
err = gfile.PutContents(iClientFilePath, fileContent)
if isExist {
mlog.Printf(`updated: %s`, iClientFilePath)
} else {
mlog.Printf(`generated: %s`, iClientFilePath)
}
}
return
}
func (c *apiSdkGenerator) doGenerateSdkImplementer(
items []apiItem, sdkFolderPath, versionImportPath, module, version string, sdkStdVersion, sdkNoV1 bool,
) (err error) {
var (
pkgName = gfile.Basename(sdkFolderPath)
moduleNameCamel = gstr.CaseCamel(module)
moduleNameSnake = gstr.CaseSnake(module)
moduleImportPath = gfile.Dir(versionImportPath)
versionPrefix = ""
implementerName = moduleNameCamel + gstr.UcFirst(version)
implementerFilePath = gfile.Join(sdkFolderPath, fmt.Sprintf(
`%s_%s_%s.go`, pkgName, moduleNameSnake, version,
))
)
if sdkNoV1 && version == "v1" {
implementerName = moduleNameCamel
}
// implementer file template.
var importPaths = make([]string, 0)
importPaths = append(importPaths, fmt.Sprintf("\t\"%s\"", moduleImportPath))
importPaths = append(importPaths, fmt.Sprintf("\t\"%s\"", versionImportPath))
implementerFileContent := gstr.TrimLeft(gstr.ReplaceByMap(consts.TemplateGenCtrlSdkImplementer, g.MapStrStr{
"{PkgName}": pkgName,
"{ImportPaths}": gstr.Join(importPaths, "\n"),
"{ImplementerName}": implementerName,
}))
// implementer new function definition.
if sdkStdVersion {
versionPrefix = fmt.Sprintf(`/api/%s`, version)
}
implementerFileContent += gstr.TrimLeft(gstr.ReplaceByMap(consts.TemplateGenCtrlSdkImplementerNew, g.MapStrStr{
"{Module}": module,
"{VersionPrefix}": versionPrefix,
"{ImplementerName}": implementerName,
}))
// implementer functions definitions.
for _, item := range items {
implementerFileContent += gstr.TrimLeft(gstr.ReplaceByMap(consts.TemplateGenCtrlSdkImplementerFunc, g.MapStrStr{
"{Version}": item.Version,
"{MethodName}": item.MethodName,
"{ImplementerName}": implementerName,
}))
implementerFileContent += "\n"
}
err = gfile.PutContents(implementerFilePath, implementerFileContent)
mlog.Printf(`generated: %s`, implementerFilePath)
return
}
func (c *apiSdkGenerator) getSubItemsByModuleAndVersion(items []apiItem, module, version string) (subItems []apiItem) {
for _, item := range items {
if item.Module == module && item.Version == version {
subItems = append(subItems, item)
}
}
return
}

View File

@@ -1,18 +1,26 @@
// 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 gendao
import (
"context"
"fmt"
"golang.org/x/mod/modfile"
"strings"
"github.com/gogf/gf/v2/container/garray"
"github.com/gogf/gf/v2/database/gdb"
"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/gtag"
"hotgo/internal/library/hggen/internal/utility/utils"
"hotgo/internal/library/hggen/internal/utility/mlog"
)
@@ -43,6 +51,12 @@ CONFIGURATION SUPPORT
path: "./my-app"
prefix: "primary_"
tables: "user, userDetail"
typeMapping:
decimal:
type: decimal.Decimal
import: github.com/shopspring/decimal
numeric:
type: string
`
CGenDaoBriefPath = `directory path for generated files`
CGenDaoBriefLink = `database configuration, the same as the ORM configuration of GoFrame`
@@ -64,6 +78,7 @@ CONFIGURATION SUPPORT
CGenDaoBriefNoJsonTag = `no json tag will be added for each field`
CGenDaoBriefNoModelComment = `no model comment will be added for each field`
CGenDaoBriefClear = `delete all generated go files that do not exist in database`
CGenDaoBriefTypeMapping = `custom local type mapping for generated struct attributes relevant to fields of table`
CGenDaoBriefGroup = `
specifying the configuration group name of database for generated ORM instance,
it's not necessary and the default value is "default"
@@ -99,7 +114,21 @@ generated json tag case for model struct, cases are as follows:
)
var (
createdAt = gtime.Now()
createdAt = gtime.Now()
defaultTypeMapping = map[string]TypeMapping{
"decimal": {
Type: "float64",
},
"money": {
Type: "float64",
},
"numeric": {
Type: "float64",
},
"smallmoney": {
Type: "float64",
},
}
)
func init() {
@@ -129,6 +158,7 @@ func init() {
`CGenDaoBriefNoJsonTag`: CGenDaoBriefNoJsonTag,
`CGenDaoBriefNoModelComment`: CGenDaoBriefNoModelComment,
`CGenDaoBriefClear`: CGenDaoBriefClear,
`CGenDaoBriefTypeMapping`: CGenDaoBriefTypeMapping,
`CGenDaoBriefGroup`: CGenDaoBriefGroup,
`CGenDaoBriefJsonCase`: CGenDaoBriefJsonCase,
`CGenDaoBriefTplDaoIndexPath`: CGenDaoBriefTplDaoIndexPath,
@@ -142,30 +172,31 @@ type (
CGenDao struct{}
CGenDaoInput struct {
g.Meta `name:"dao" config:"{CGenDaoConfig}" usage:"{CGenDaoUsage}" brief:"{CGenDaoBrief}" eg:"{CGenDaoEg}" ad:"{CGenDaoAd}"`
Path string `name:"path" short:"p" brief:"{CGenDaoBriefPath}" d:"internal"`
Link string `name:"link" short:"l" brief:"{CGenDaoBriefLink}"`
Tables string `name:"tables" short:"t" brief:"{CGenDaoBriefTables}"`
TablesEx string `name:"tablesEx" short:"x" brief:"{CGenDaoBriefTablesEx}"`
Group string `name:"group" short:"g" brief:"{CGenDaoBriefGroup}" d:"default"`
Prefix string `name:"prefix" short:"f" brief:"{CGenDaoBriefPrefix}"`
RemovePrefix string `name:"removePrefix" short:"r" brief:"{CGenDaoBriefRemovePrefix}"`
JsonCase string `name:"jsonCase" short:"j" brief:"{CGenDaoBriefJsonCase}" d:"CamelLower"`
ImportPrefix string `name:"importPrefix" short:"i" brief:"{CGenDaoBriefImportPrefix}"`
DaoPath string `name:"daoPath" short:"d" brief:"{CGenDaoBriefDaoPath}" d:"dao"`
DoPath string `name:"doPath" short:"o" brief:"{CGenDaoBriefDoPath}" d:"model/do"`
EntityPath string `name:"entityPath" short:"e" brief:"{CGenDaoBriefEntityPath}" d:"model/entity"`
TplDaoIndexPath string `name:"tplDaoIndexPath" short:"t1" brief:"{CGenDaoBriefTplDaoIndexPath}"`
TplDaoInternalPath string `name:"tplDaoInternalPath" short:"t2" brief:"{CGenDaoBriefTplDaoInternalPath}"`
TplDaoDoPath string `name:"tplDaoDoPath" short:"t3" brief:"{CGenDaoBriefTplDaoDoPathPath}"`
TplDaoEntityPath string `name:"tplDaoEntityPath" short:"t4" brief:"{CGenDaoBriefTplDaoEntityPath}"`
StdTime bool `name:"stdTime" short:"s" brief:"{CGenDaoBriefStdTime}" orphan:"true"`
WithTime bool `name:"withTime" short:"w" brief:"{CGenDaoBriefWithTime}" orphan:"true"`
GJsonSupport bool `name:"gJsonSupport" short:"n" brief:"{CGenDaoBriefGJsonSupport}" orphan:"true"`
OverwriteDao bool `name:"overwriteDao" short:"v" brief:"{CGenDaoBriefOverwriteDao}" orphan:"true"`
DescriptionTag bool `name:"descriptionTag" short:"c" brief:"{CGenDaoBriefDescriptionTag}" orphan:"true"`
NoJsonTag bool `name:"noJsonTag" short:"k" brief:"{CGenDaoBriefNoJsonTag}" orphan:"true"`
NoModelComment bool `name:"noModelComment" short:"m" brief:"{CGenDaoBriefNoModelComment}" orphan:"true"`
Clear bool `name:"clear" short:"a" brief:"{CGenDaoBriefClear}" orphan:"true"`
Path string `name:"path" short:"p" brief:"{CGenDaoBriefPath}" d:"internal"`
Link string `name:"link" short:"l" brief:"{CGenDaoBriefLink}"`
Tables string `name:"tables" short:"t" brief:"{CGenDaoBriefTables}"`
TablesEx string `name:"tablesEx" short:"x" brief:"{CGenDaoBriefTablesEx}"`
Group string `name:"group" short:"g" brief:"{CGenDaoBriefGroup}" d:"default"`
Prefix string `name:"prefix" short:"f" brief:"{CGenDaoBriefPrefix}"`
RemovePrefix string `name:"removePrefix" short:"r" brief:"{CGenDaoBriefRemovePrefix}"`
JsonCase string `name:"jsonCase" short:"j" brief:"{CGenDaoBriefJsonCase}" d:"CamelLower"`
ImportPrefix string `name:"importPrefix" short:"i" brief:"{CGenDaoBriefImportPrefix}"`
DaoPath string `name:"daoPath" short:"d" brief:"{CGenDaoBriefDaoPath}" d:"dao"`
DoPath string `name:"doPath" short:"o" brief:"{CGenDaoBriefDoPath}" d:"model/do"`
EntityPath string `name:"entityPath" short:"e" brief:"{CGenDaoBriefEntityPath}" d:"model/entity"`
TplDaoIndexPath string `name:"tplDaoIndexPath" short:"t1" brief:"{CGenDaoBriefTplDaoIndexPath}"`
TplDaoInternalPath string `name:"tplDaoInternalPath" short:"t2" brief:"{CGenDaoBriefTplDaoInternalPath}"`
TplDaoDoPath string `name:"tplDaoDoPath" short:"t3" brief:"{CGenDaoBriefTplDaoDoPathPath}"`
TplDaoEntityPath string `name:"tplDaoEntityPath" short:"t4" brief:"{CGenDaoBriefTplDaoEntityPath}"`
StdTime bool `name:"stdTime" short:"s" brief:"{CGenDaoBriefStdTime}" orphan:"true"`
WithTime bool `name:"withTime" short:"w" brief:"{CGenDaoBriefWithTime}" orphan:"true"`
GJsonSupport bool `name:"gJsonSupport" short:"n" brief:"{CGenDaoBriefGJsonSupport}" orphan:"true"`
OverwriteDao bool `name:"overwriteDao" short:"v" brief:"{CGenDaoBriefOverwriteDao}" orphan:"true"`
DescriptionTag bool `name:"descriptionTag" short:"c" brief:"{CGenDaoBriefDescriptionTag}" orphan:"true"`
NoJsonTag bool `name:"noJsonTag" short:"k" brief:"{CGenDaoBriefNoJsonTag}" orphan:"true"`
NoModelComment bool `name:"noModelComment" short:"m" brief:"{CGenDaoBriefNoModelComment}" orphan:"true"`
Clear bool `name:"clear" short:"a" brief:"{CGenDaoBriefClear}" orphan:"true"`
TypeMapping map[string]TypeMapping `name:"typeMapping" short:"y" brief:"{CGenDaoBriefTypeMapping}" orphan:"true"`
}
CGenDaoOutput struct{}
@@ -174,7 +205,11 @@ type (
DB gdb.DB
TableNames []string
NewTableNames []string
ModName string // Module name of current golang project, which is used for import purpose.
}
TypeMapping struct {
Type string `brief:"custom attribute type name"`
Import string `brief:"custom import for this type"`
}
)
@@ -195,16 +230,11 @@ func (c CGenDao) Dao(ctx context.Context, in CGenDaoInput) (out *CGenDaoOutput,
return
}
func DoGenDaoForArray(ctx context.Context, in CGenDaoInput) {
doGenDaoForArray(ctx, -1, in)
}
// doGenDaoForArray implements the "gen dao" command for configuration array.
func doGenDaoForArray(ctx context.Context, index int, in CGenDaoInput) {
var (
err error
db gdb.DB
modName string // Go module name, eg: github.com/gogf/gf.
err error
db gdb.DB
)
if index >= 0 {
err = g.Cfg().MustGet(
@@ -219,20 +249,6 @@ func doGenDaoForArray(ctx context.Context, index int, in CGenDaoInput) {
mlog.Fatalf(`path "%s" does not exist`, in.Path)
}
removePrefixArray := gstr.SplitAndTrim(in.RemovePrefix, ",")
if in.ImportPrefix == "" {
if !gfile.Exists("go.mod") {
mlog.Fatal("go.mod does not exist in current working directory")
}
var (
goModContent = gfile.GetContents("go.mod")
match, _ = gregex.MatchString(`^module\s+(.+)\s*`, goModContent)
)
if len(match) > 1 {
modName = gstr.Trim(match[1])
} else {
mlog.Fatal("module name does not found in go.mod")
}
}
// It uses user passed database configuration.
if in.Link != "" {
@@ -268,6 +284,17 @@ func doGenDaoForArray(ctx context.Context, index int, in CGenDaoInput) {
tableNames = array.Slice()
}
// merge default typeMapping to input typeMapping.
if in.TypeMapping == nil {
in.TypeMapping = defaultTypeMapping
} else {
for key, typeMapping := range defaultTypeMapping {
if _, ok := in.TypeMapping[key]; !ok {
in.TypeMapping[key] = typeMapping
}
}
}
// Generating dao & model go files one by one according to given table name.
newTableNames := make([]string, len(tableNames))
for i, tableName := range tableNames {
@@ -278,13 +305,13 @@ func doGenDaoForArray(ctx context.Context, index int, in CGenDaoInput) {
newTableName = in.Prefix + newTableName
newTableNames[i] = newTableName
}
// Dao: index and internal.
generateDao(ctx, CGenDaoInternalInput{
CGenDaoInput: in,
DB: db,
TableNames: tableNames,
NewTableNames: newTableNames,
ModName: modName,
})
// Do.
generateDo(ctx, CGenDaoInternalInput{
@@ -292,7 +319,6 @@ func doGenDaoForArray(ctx context.Context, index int, in CGenDaoInput) {
DB: db,
TableNames: tableNames,
NewTableNames: newTableNames,
ModName: modName,
})
// Entity.
generateEntity(ctx, CGenDaoInternalInput{
@@ -300,15 +326,11 @@ func doGenDaoForArray(ctx context.Context, index int, in CGenDaoInput) {
DB: db,
TableNames: tableNames,
NewTableNames: newTableNames,
ModName: modName,
})
}
func getImportPartContent(source string, isDo bool) string {
var (
packageImportsArray = garray.NewStrArray()
)
func getImportPartContent(ctx context.Context, source string, isDo bool, appendImports []string) string {
var packageImportsArray = garray.NewStrArray()
if isDo {
packageImportsArray.Append(`"github.com/gogf/gf/v2/frame/g"`)
}
@@ -325,6 +347,32 @@ func getImportPartContent(source string, isDo bool) string {
packageImportsArray.Append(`"github.com/gogf/gf/v2/encoding/gjson"`)
}
// Check and update imports in go.mod
if appendImports != nil && len(appendImports) > 0 {
goModPath := utils.GetModPath()
if goModPath == "" {
mlog.Fatal("go.mod not found in current project")
}
mod, err := modfile.Parse(goModPath, gfile.GetBytes(goModPath), nil)
if err != nil {
mlog.Fatalf("parse go.mod failed: %+v", err)
}
for _, appendImport := range appendImports {
found := false
for _, require := range mod.Require {
if gstr.Contains(appendImport, require.Mod.Path) {
found = true
break
}
}
if !found {
err = gproc.ShellRun(ctx, `go get `+appendImport)
mlog.Fatalf(`%+v`, err)
}
packageImportsArray.Append(fmt.Sprintf(`"%s"`, appendImport))
}
}
// Generate and write content to golang file.
packageImportsStr := ""
if packageImportsArray.Len() > 0 {

View File

@@ -1,3 +1,9 @@
// 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 gendao
import (

View File

@@ -1,3 +1,9 @@
// 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 gendao
import (
@@ -6,12 +12,12 @@ import (
"fmt"
"strings"
"github.com/olekukonko/tablewriter"
"github.com/gogf/gf/v2/database/gdb"
"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/olekukonko/tablewriter"
"hotgo/internal/library/hggen/internal/consts"
"hotgo/internal/library/hggen/internal/utility/mlog"
@@ -53,23 +59,13 @@ func generateDaoSingle(ctx context.Context, in generateDaoSingleInput) {
mlog.Fatalf(`fetching tables fields failed for table "%s": %+v`, in.TableName, err)
}
var (
dirRealPath = gfile.RealPath(in.Path)
tableNameCamelCase = gstr.CaseCamel(in.NewTableName)
tableNameCamelLowerCase = gstr.CaseCamelLower(in.NewTableName)
tableNameSnakeCase = gstr.CaseSnake(in.NewTableName)
importPrefix = in.ImportPrefix
)
if importPrefix == "" {
if dirRealPath == "" {
dirRealPath = in.Path
importPrefix = dirRealPath
importPrefix = gstr.Trim(dirRealPath, "./")
} else {
importPrefix = gstr.Replace(dirRealPath, gfile.Pwd(), "")
}
importPrefix = gstr.Replace(importPrefix, gfile.Separator, "/")
importPrefix = gstr.Join(g.SliceStr{in.ModName, importPrefix, in.DaoPath}, "/")
importPrefix, _ = gregex.ReplaceString(`\/{2,}`, `/`, gstr.Trim(importPrefix, "/"))
importPrefix = utils.GetImportPath(gfile.Join(in.Path, in.DaoPath))
} else {
importPrefix = gstr.Join(g.SliceStr{importPrefix, in.DaoPath}, "/")
}

View File

@@ -1,3 +1,9 @@
// 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 gendao
import (
@@ -30,9 +36,9 @@ func generateDo(ctx context.Context, in CGenDaoInternalInput) {
mlog.Fatalf("fetching tables fields failed for table '%s':\n%v", tableName, err)
}
var (
newTableName = in.NewTableNames[i]
doFilePath = gfile.Join(dirPathDo, gstr.CaseSnake(newTableName)+".go")
structDefinition = generateStructDefinition(ctx, generateStructDefinitionInput{
newTableName = in.NewTableNames[i]
doFilePath = gfile.Join(dirPathDo, gstr.CaseSnake(newTableName)+".go")
structDefinition, _ = generateStructDefinition(ctx, generateStructDefinitionInput{
CGenDaoInternalInput: in,
TableName: tableName,
StructName: gstr.CaseCamel(newTableName),
@@ -53,6 +59,7 @@ func generateDo(ctx context.Context, in CGenDaoInternalInput) {
},
)
modelContent := generateDoContent(
ctx,
in,
tableName,
gstr.CaseCamel(newTableName),
@@ -68,15 +75,18 @@ func generateDo(ctx context.Context, in CGenDaoInternalInput) {
}
}
func generateDoContent(in CGenDaoInternalInput, tableName, tableNameCamelCase, structDefine string) string {
func generateDoContent(
ctx context.Context, in CGenDaoInternalInput, tableName, tableNameCamelCase, structDefine string,
) string {
doContent := gstr.ReplaceByMap(
getTemplateFromPathOrDefault(in.TplDaoDoPath, consts.TemplateGenDaoDoContent),
g.MapStrStr{
tplVarTableName: tableName,
tplVarPackageImports: getImportPartContent(structDefine, true),
tplVarPackageImports: getImportPartContent(ctx, structDefine, true, nil),
tplVarTableNameCamelCase: tableNameCamelCase,
tplVarStructDefine: structDefine,
})
},
)
doContent = replaceDefaultVar(in, doContent)
return doContent
}

View File

@@ -1,3 +1,9 @@
// 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 gendao
import (
@@ -24,22 +30,27 @@ func generateEntity(ctx context.Context, in CGenDaoInternalInput) {
if err != nil {
mlog.Fatalf("fetching tables fields failed for table '%s':\n%v", tableName, err)
}
var (
newTableName = in.NewTableNames[i]
entityFilePath = gfile.Join(dirPathEntity, gstr.CaseSnake(newTableName)+".go")
entityContent = generateEntityContent(
newTableName = in.NewTableNames[i]
entityFilePath = gfile.Join(dirPathEntity, gstr.CaseSnake(newTableName)+".go")
structDefinition, appendImports = generateStructDefinition(ctx, generateStructDefinitionInput{
CGenDaoInternalInput: in,
TableName: tableName,
StructName: gstr.CaseCamel(newTableName),
FieldMap: fieldMap,
IsDo: false,
})
entityContent = generateEntityContent(
ctx,
in,
newTableName,
gstr.CaseCamel(newTableName),
generateStructDefinition(ctx, generateStructDefinitionInput{
CGenDaoInternalInput: in,
TableName: tableName,
StructName: gstr.CaseCamel(newTableName),
FieldMap: fieldMap,
IsDo: false,
}),
structDefinition,
appendImports,
)
)
err = gfile.PutContents(entityFilePath, strings.TrimSpace(entityContent))
if err != nil {
mlog.Fatalf("writing content to '%s' failed: %v", entityFilePath, err)
@@ -50,15 +61,18 @@ func generateEntity(ctx context.Context, in CGenDaoInternalInput) {
}
}
func generateEntityContent(in CGenDaoInternalInput, tableName, tableNameCamelCase, structDefine string) string {
func generateEntityContent(
ctx context.Context, in CGenDaoInternalInput, tableName, tableNameCamelCase, structDefine string, appendImports []string,
) string {
entityContent := gstr.ReplaceByMap(
getTemplateFromPathOrDefault(in.TplDaoEntityPath, consts.TemplateGenDaoEntityContent),
g.MapStrStr{
tplVarTableName: tableName,
tplVarPackageImports: getImportPartContent(structDefine, false),
tplVarPackageImports: getImportPartContent(ctx, structDefine, false, appendImports),
tplVarTableNameCamelCase: tableNameCamelCase,
tplVarStructDefine: structDefine,
})
},
)
entityContent = replaceDefaultVar(in, entityContent)
return entityContent
}

View File

@@ -1,15 +1,22 @@
// 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 gendao
import (
"bytes"
"context"
"fmt"
"github.com/olekukonko/tablewriter"
"strings"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/text/gregex"
"github.com/gogf/gf/v2/text/gstr"
"github.com/olekukonko/tablewriter"
)
type generateStructDefinitionInput struct {
@@ -20,13 +27,18 @@ type generateStructDefinitionInput struct {
IsDo bool // Is generating DTO struct.
}
func generateStructDefinition(ctx context.Context, in generateStructDefinitionInput) string {
func generateStructDefinition(ctx context.Context, in generateStructDefinitionInput) (string, []string) {
var appendImports []string
buffer := bytes.NewBuffer(nil)
array := make([][]string, len(in.FieldMap))
names := sortFieldKeyForDao(in.FieldMap)
for index, name := range names {
var imports string
field := in.FieldMap[name]
array[index] = generateStructFieldDefinition(ctx, field, in)
array[index], imports = generateStructFieldDefinition(ctx, field, in)
if imports != "" {
appendImports = append(appendImports, imports)
}
}
tw := tablewriter.NewWriter(buffer)
tw.SetBorder(false)
@@ -47,21 +59,42 @@ func generateStructDefinition(ctx context.Context, in generateStructDefinitionIn
}
buffer.WriteString(stContent)
buffer.WriteString("}")
return buffer.String()
return buffer.String(), appendImports
}
// generateStructFieldDefinition generates and returns the attribute definition for specified field.
func generateStructFieldDefinition(
ctx context.Context, field *gdb.TableField, in generateStructDefinitionInput,
) []string {
) (attrLines []string, appendImport string) {
var (
err error
typeName string
jsonTag = getJsonTagFromCase(field.Name, in.JsonCase)
)
typeName, err = in.DB.CheckLocalTypeForField(ctx, field.Type, nil)
if err != nil {
panic(err)
if in.TypeMapping != nil && len(in.TypeMapping) > 0 {
var (
tryTypeName string
)
tryTypeMatch, _ := gregex.MatchString(`(.+?)\((.+)\)`, field.Type)
if len(tryTypeMatch) == 3 {
tryTypeName = gstr.Trim(tryTypeMatch[1])
} else {
tryTypeName = gstr.Split(field.Type, " ")[0]
}
if tryTypeName != "" {
if typeMapping, ok := in.TypeMapping[strings.ToLower(tryTypeName)]; ok {
typeName = typeMapping.Type
appendImport = typeMapping.Import
}
}
}
if typeName == "" {
typeName, err = in.DB.CheckLocalTypeForField(ctx, field.Type, nil)
if err != nil {
panic(err)
}
}
switch typeName {
case gdb.LocalTypeDate, gdb.LocalTypeDatetime:
@@ -87,19 +120,18 @@ func generateStructFieldDefinition(
}
var (
tagKey = "`"
result = []string{
" #" + gstr.CaseCamel(field.Name),
" #" + typeName,
}
tagKey = "`"
descriptionTag = gstr.Replace(formatComment(field.Comment), `"`, `\"`)
)
attrLines = []string{
" #" + gstr.CaseCamel(field.Name),
" #" + typeName,
}
attrLines = append(attrLines, " #"+fmt.Sprintf(tagKey+`json:"%s"`, jsonTag))
attrLines = append(attrLines, " #"+fmt.Sprintf(`description:"%s"`+tagKey, descriptionTag))
attrLines = append(attrLines, " #"+fmt.Sprintf(`// %s`, formatComment(field.Comment)))
result = append(result, " #"+fmt.Sprintf(tagKey+`json:"%s"`, jsonTag))
result = append(result, " #"+fmt.Sprintf(`description:"%s"`+tagKey, descriptionTag))
result = append(result, " #"+fmt.Sprintf(`// %s`, formatComment(field.Comment)))
for k, v := range result {
for k, v := range attrLines {
if in.NoJsonTag {
v, _ = gregex.ReplaceString(`json:".+"`, ``, v)
}
@@ -109,9 +141,9 @@ func generateStructFieldDefinition(
if in.NoModelComment {
v, _ = gregex.ReplaceString(`//.+`, ``, v)
}
result[k] = v
attrLines[k] = v
}
return result
return attrLines, appendImport
}
// formatComment formats the comment string to fit the golang code without any lines.

View File

@@ -21,10 +21,10 @@ import (
type (
CGenEnums struct{}
CGenEnumsInput struct {
g.Meta `name:"enums" config:"{CGenEnumsConfig}" brief:"{CGenEnumsBrief}" eg:"{CGenEnumsEg}"`
Src string `name:"src" short:"s" dc:"source folder path to be parsed" d:"."`
Path string `name:"path" short:"p" dc:"output go file path storing enums content" d:"internal/boot/boot_enums.go"`
Prefix string `name:"prefix" short:"x" dc:"only exports packages that starts with specified prefix"`
g.Meta `name:"enums" config:"{CGenEnumsConfig}" brief:"{CGenEnumsBrief}" eg:"{CGenEnumsEg}"`
Src string `name:"src" short:"s" dc:"source folder path to be parsed" d:"."`
Path string `name:"path" short:"p" dc:"output go file path storing enums content" d:"internal/boot/boot_enums.go"`
Prefixes []string `name:"prefixes" short:"x" dc:"only exports packages that starts with specified prefixes"`
}
CGenEnumsOutput struct{}
)
@@ -57,7 +57,7 @@ func (c CGenEnums) Enums(ctx context.Context, in CGenEnumsInput) (out *CGenEnums
if err != nil {
mlog.Fatal(err)
}
mlog.Printf(`scanning: %s`, realPath)
mlog.Printf(`scanning for enums: %s`, realPath)
cfg := &packages.Config{
Dir: realPath,
Mode: pkgLoadMode,
@@ -67,7 +67,7 @@ func (c CGenEnums) Enums(ctx context.Context, in CGenEnumsInput) (out *CGenEnums
if err != nil {
mlog.Fatal(err)
}
p := NewEnumsParser(in.Prefix)
p := NewEnumsParser(in.Prefixes)
p.ParsePackages(pkgs)
var enumsContent = gstr.ReplaceByMap(consts.TemplateGenEnums, g.MapStrStr{
"{PackageName}": gfile.Basename(gfile.Dir(in.Path)),
@@ -77,7 +77,7 @@ func (c CGenEnums) Enums(ctx context.Context, in CGenEnumsInput) (out *CGenEnums
if err = gfile.PutContents(in.Path, enumsContent); err != nil {
return
}
mlog.Printf(`generated: %s`, in.Path)
mlog.Printf(`generated enums go file: %s`, in.Path)
mlog.Print("done!")
return
}

View File

@@ -21,7 +21,7 @@ const pkgLoadMode = 0xffffff
type EnumsParser struct {
enums []EnumItem
parsedPkg map[string]struct{}
prefix string
prefixes []string
}
type EnumItem struct {
@@ -33,21 +33,21 @@ type EnumItem struct {
var standardPackages = make(map[string]struct{})
//func init() {
// stdPackages, err := packages.Load(nil, "std")
// if err != nil {
// panic(err)
// }
// for _, p := range stdPackages {
// standardPackages[p.ID] = struct{}{}
// }
//}
func init() {
stdPackages, err := packages.Load(nil, "std")
if err != nil {
panic(err)
}
for _, p := range stdPackages {
standardPackages[p.ID] = struct{}{}
}
}
func NewEnumsParser(prefix string) *EnumsParser {
func NewEnumsParser(prefixes []string) *EnumsParser {
return &EnumsParser{
enums: make([]EnumItem, 0),
parsedPkg: make(map[string]struct{}),
prefix: prefix,
prefixes: prefixes,
}
}
@@ -67,9 +67,16 @@ func (p *EnumsParser) ParsePackage(pkg *packages.Package) {
return
}
p.parsedPkg[pkg.ID] = struct{}{}
// Only parse specified prefix.
if p.prefix != "" {
if !gstr.HasPrefix(pkg.ID, p.prefix) {
// Only parse specified prefixes.
if len(p.prefixes) > 0 {
var hasPrefix bool
for _, prefix := range p.prefixes {
if hasPrefix = gstr.HasPrefix(pkg.ID, prefix); hasPrefix {
break
}
}
if !hasPrefix {
return
}
}
@@ -108,7 +115,6 @@ func (p *EnumsParser) ParsePackage(pkg *packages.Package) {
Type: enumType,
Kind: enumKind,
})
}
for _, im := range pkg.Imports {
p.ParsePackage(im)

View File

@@ -0,0 +1,128 @@
// 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 genpb
import (
"context"
"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/util/gtag"
"hotgo/internal/library/hggen/internal/utility/mlog"
)
type (
CGenPb struct{}
CGenPbInput struct {
g.Meta `name:"pb" config:"{CGenPbConfig}" brief:"{CGenPbBrief}" eg:"{CGenPbEg}"`
Path string `name:"path" short:"p" dc:"protobuf file folder path" d:"manifest/protobuf"`
OutputApi string `name:"api" short:"a" dc:"output folder path storing generated go files of api" d:"api"`
OutputCtrl string `name:"ctrl" short:"c" dc:"output folder path storing generated go files of controller" d:"internal/controller"`
}
CGenPbOutput struct{}
)
const (
CGenPbConfig = `gfcli.gen.pb`
CGenPbBrief = `parse proto files and generate protobuf go files`
CGenPbEg = `
gf gen pb
gf gen pb -p . -a . -p .
`
)
func init() {
gtag.Sets(g.MapStrStr{
`CGenPbEg`: CGenPbEg,
`CGenPbBrief`: CGenPbBrief,
`CGenPbConfig`: CGenPbConfig,
})
}
func (c CGenPb) Pb(ctx context.Context, in CGenPbInput) (out *CGenPbOutput, err error) {
// Necessary check.
protoc := gproc.SearchBinary("protoc")
if protoc == "" {
mlog.Fatalf(`command "protoc" not found in your environment, please install protoc first: https://grpc.io/docs/languages/go/quickstart/`)
}
// protocol fold checks.
var (
protoPath = gfile.RealPath(in.Path)
isParsingPWD bool
)
if protoPath == "" {
// Use current working directory as protoPath if there are proto files under.
currentPath := gfile.Pwd()
currentFiles, _ := gfile.ScanDirFile(currentPath, "*.proto")
if len(currentFiles) > 0 {
protoPath = currentPath
isParsingPWD = true
} else {
mlog.Fatalf(`proto files folder "%s" does not exist`, in.Path)
}
}
// output path checks.
outputApiPath := gfile.RealPath(in.OutputApi)
if outputApiPath == "" {
if isParsingPWD {
outputApiPath = protoPath
} else {
mlog.Fatalf(`output api folder "%s" does not exist`, in.OutputApi)
}
}
outputCtrlPath := gfile.RealPath(in.OutputCtrl)
if outputCtrlPath == "" {
if isParsingPWD {
outputCtrlPath = ""
} else {
mlog.Fatalf(`output controller folder "%s" does not exist`, in.OutputCtrl)
}
}
// folder scanning.
files, err := gfile.ScanDirFile(protoPath, "*.proto", true)
if err != nil {
mlog.Fatal(err)
}
if len(files) == 0 {
mlog.Fatalf(`no proto files found in folder "%s"`, in.Path)
}
if err = gfile.Chdir(protoPath); err != nil {
mlog.Fatal(err)
}
for _, file := range files {
var command = gproc.NewProcess(protoc, nil)
command.Args = append(command.Args, "--proto_path="+gfile.Pwd())
command.Args = append(command.Args, "--go_out=paths=source_relative:"+outputApiPath)
command.Args = append(command.Args, "--go-grpc_out=paths=source_relative:"+outputApiPath)
command.Args = append(command.Args, file)
mlog.Print(command.String())
if err = command.Run(ctx); err != nil {
mlog.Fatal(err)
}
}
// Generate struct tag according comment rules.
err = c.generateStructTag(ctx, generateStructTagInput{OutputApiPath: outputApiPath})
if err != nil {
return
}
// Generate controllers according comment rules.
if outputCtrlPath != "" {
err = c.generateController(ctx, generateControllerInput{
OutputApiPath: outputApiPath,
OutputCtrlPath: outputCtrlPath,
})
if err != nil {
return
}
}
mlog.Print("done!")
return
}

View File

@@ -0,0 +1,196 @@
// 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 genpb
import (
"context"
"fmt"
"strings"
"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"
"hotgo/internal/library/hggen/internal/utility/utils"
)
type generateControllerInput struct {
OutputApiPath string
OutputCtrlPath string
}
type generateCtrl struct {
Name string
Package string
Version string
Methods []generateCtrlMethod
}
type generateCtrlMethod struct {
Name string
Definition string
}
const (
controllerTemplate = `
package {Package}
type Controller struct {
{Version}.Unimplemented{Name}Server
}
func Register(s *grpcx.GrpcServer) {
{Version}.Register{Name}Server(s.Server, &Controller{})
}
`
controllerMethodTemplate = `
func (*Controller) {Definition} {
return nil, gerror.NewCode(gcode.CodeNotImplemented)
}
`
)
func (c CGenPb) generateController(ctx context.Context, in generateControllerInput) (err error) {
files, err := gfile.ScanDirFile(in.OutputApiPath, "*_grpc.pb.go", true)
if err != nil {
return err
}
var controllers []generateCtrl
for _, file := range files {
fileControllers, err := c.parseControllers(file)
if err != nil {
return err
}
controllers = append(controllers, fileControllers...)
}
if len(controllers) == 0 {
return nil
}
// Generate controller files.
err = c.doGenerateControllers(in, controllers)
return
}
func (c CGenPb) parseControllers(filePath string) ([]generateCtrl, error) {
var (
controllers []generateCtrl
content = gfile.GetContents(filePath)
)
_, err := gregex.ReplaceStringFuncMatch(
`type (\w+)Server interface {([\s\S]+?)}`,
content,
func(match []string) string {
ctrl := generateCtrl{
Name: match[1],
Package: strings.ReplaceAll(gfile.Basename(gfile.Dir(gfile.Dir(filePath))), "-", "_"),
Version: gfile.Basename(gfile.Dir(filePath)),
Methods: make([]generateCtrlMethod, 0),
}
lines := gstr.Split(match[2], "\n")
for _, line := range lines {
line = gstr.Trim(line)
if line == "" || !gstr.IsLetterUpper(line[0]) {
continue
}
// Comment.
if gregex.IsMatchString(`^//.+`, line) {
continue
}
line, _ = gregex.ReplaceStringFuncMatch(
`^(\w+)\(context\.Context, \*(\w+)\) \(\*(\w+), error\)$`,
line,
func(match []string) string {
return fmt.Sprintf(
`%s(ctx context.Context, req *%s.%s) (res *%s.%s, err error)`,
match[1], ctrl.Version, match[2], ctrl.Version, match[3],
)
},
)
ctrl.Methods = append(ctrl.Methods, generateCtrlMethod{
Name: gstr.Split(line, "(")[0],
Definition: line,
})
}
if len(ctrl.Methods) > 0 {
controllers = append(controllers, ctrl)
}
return match[0]
},
)
return controllers, err
}
func (c CGenPb) doGenerateControllers(in generateControllerInput, controllers []generateCtrl) (err error) {
for _, controller := range controllers {
err = c.doGenerateController(in, controller)
if err != nil {
return err
}
}
err = utils.ReplaceGeneratedContentGFV2(in.OutputCtrlPath)
return nil
}
func (c CGenPb) doGenerateController(in generateControllerInput, controller generateCtrl) (err error) {
var (
folderPath = gfile.Join(in.OutputCtrlPath, controller.Package)
filePath = gfile.Join(folderPath, controller.Package+".go")
isDirty bool
)
if !gfile.Exists(folderPath) {
if err = gfile.Mkdir(folderPath); err != nil {
return err
}
}
if !gfile.Exists(filePath) {
templateContent := gstr.ReplaceByMap(controllerTemplate, g.MapStrStr{
"{Name}": controller.Name,
"{Version}": controller.Version,
"{Package}": controller.Package,
})
if err = gfile.PutContents(filePath, templateContent); err != nil {
return err
}
isDirty = true
}
// Exist controller content.
var ctrlContent string
files, err := gfile.ScanDirFile(folderPath, "*.go", false)
if err != nil {
return err
}
for _, file := range files {
if ctrlContent != "" {
ctrlContent += "\n"
}
ctrlContent += gfile.GetContents(file)
}
// Generate method content.
var generatedContent string
for _, method := range controller.Methods {
if gstr.Contains(ctrlContent, fmt.Sprintf(`%s(`, method.Name)) {
continue
}
if generatedContent != "" {
generatedContent += "\n"
}
generatedContent += gstr.ReplaceByMap(controllerMethodTemplate, g.MapStrStr{
"{Definition}": method.Definition,
})
}
if generatedContent != "" {
err = gfile.PutContentsAppend(filePath, generatedContent)
if err != nil {
return err
}
isDirty = true
}
if isDirty {
utils.GoFmt(filePath)
}
return nil
}

View File

@@ -0,0 +1,113 @@
// 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 genpb
import (
"context"
"fmt"
"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"
"github.com/gogf/gf/v2/util/gconv"
"hotgo/internal/library/hggen/internal/utility/utils"
)
type generateStructTagInput struct {
OutputApiPath string
}
func (c CGenPb) generateStructTag(ctx context.Context, in generateStructTagInput) (err error) {
files, err := gfile.ScanDirFile(in.OutputApiPath, "*.pb.go", true)
if err != nil {
return err
}
var content string
for _, file := range files {
content = gfile.GetContents(file)
content, err = c.doTagReplacement(ctx, content)
if err != nil {
return err
}
if err = gfile.PutContents(file, content); err != nil {
return err
}
utils.GoFmt(file)
}
return
}
func (c CGenPb) doTagReplacement(ctx context.Context, content string) (string, error) {
content, err := gregex.ReplaceStringFuncMatch(`type (\w+) struct {([\s\S]+?)}`, content, func(match []string) string {
var (
topCommentMatch []string
tailCommentMatch []string
lines = gstr.Split(match[2], "\n")
lineTagMap = gmap.NewListMap()
)
for index, line := range lines {
line = gstr.Trim(line)
if line == "" {
continue
}
// Top comment.
topCommentMatch, _ = gregex.MatchString(`^/[/|\*](.+)`, line)
if len(topCommentMatch) > 1 {
c.tagCommentIntoListMap(gstr.Trim(topCommentMatch[1]), lineTagMap)
continue
}
// Tail comment.
tailCommentMatch, _ = gregex.MatchString(".+?`.+?`.+?//(.+)", line)
if len(tailCommentMatch) > 1 {
c.tagCommentIntoListMap(gstr.Trim(tailCommentMatch[1]), lineTagMap)
}
// Tag injection.
if !lineTagMap.IsEmpty() {
tagContent := c.listMapToStructTag(lineTagMap)
lineTagMap.Clear()
line, _ = gregex.ReplaceString("`(.+)`", fmt.Sprintf("`$1 %s`", tagContent), line)
}
lines[index] = line
}
match[2] = gstr.Join(lines, "\n")
return fmt.Sprintf("type %s struct {%s}", match[1], match[2])
})
return content, err
}
func (c CGenPb) tagCommentIntoListMap(comment string, lineTagMap *gmap.ListMap) {
tagCommentMatch, _ := gregex.MatchString(`^(\w+):(.+)`, comment)
if len(tagCommentMatch) > 1 {
var (
tagName = gstr.Trim(tagCommentMatch[1])
tagContent = gstr.Trim(tagCommentMatch[2])
)
lineTagMap.Set(tagName, lineTagMap.GetVar(tagName).String()+tagContent)
} else {
var (
tagName = "dc"
tagContent = comment
)
lineTagMap.Set(tagName, lineTagMap.GetVar(tagName).String()+tagContent)
}
}
func (c CGenPb) listMapToStructTag(lineTagMap *gmap.ListMap) string {
var tag string
lineTagMap.Iterator(func(key, value interface{}) bool {
if tag != "" {
tag += " "
}
tag += fmt.Sprintf(
`%s:"%s"`,
key, gstr.Replace(gconv.String(value), `"`, `\"`),
)
return true
})
return tag
}

View File

@@ -0,0 +1,419 @@
// 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 genpbentity
import (
"bytes"
"context"
"fmt"
"strings"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gctx"
"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/olekukonko/tablewriter"
"hotgo/internal/library/hggen/internal/consts"
"hotgo/internal/library/hggen/internal/utility/mlog"
)
type (
CGenPbEntity struct{}
CGenPbEntityInput struct {
g.Meta `name:"pbentity" config:"{CGenPbEntityConfig}" brief:"{CGenPbEntityBrief}" eg:"{CGenPbEntityEg}" ad:"{CGenPbEntityAd}"`
Path string `name:"path" short:"p" brief:"{CGenPbEntityBriefPath}" d:"manifest/protobuf/pbentity"`
Package string `name:"package" short:"k" brief:"{CGenPbEntityBriefPackage}"`
Link string `name:"link" short:"l" brief:"{CGenPbEntityBriefLink}"`
Tables string `name:"tables" short:"t" brief:"{CGenPbEntityBriefTables}"`
Prefix string `name:"prefix" short:"f" brief:"{CGenPbEntityBriefPrefix}"`
RemovePrefix string `name:"removePrefix" short:"r" brief:"{CGenPbEntityBriefRemovePrefix}"`
NameCase string `name:"nameCase" short:"n" brief:"{CGenPbEntityBriefNameCase}" d:"Camel"`
JsonCase string `name:"jsonCase" short:"j" brief:"{CGenPbEntityBriefJsonCase}" d:"CamelLower"`
Option string `name:"option" short:"o" brief:"{CGenPbEntityBriefOption}"`
}
CGenPbEntityOutput struct{}
CGenPbEntityInternalInput struct {
CGenPbEntityInput
DB gdb.DB
TableName string // TableName specifies the table name of the table.
NewTableName string // NewTableName specifies the prefix-stripped name of the table.
}
)
const (
defaultPackageSuffix = `api/pbentity`
CGenPbEntityConfig = `gfcli.gen.pbentity`
CGenPbEntityBrief = `generate entity message files in protobuf3 format`
CGenPbEntityEg = `
gf gen pbentity
gf gen pbentity -l "mysql:root:12345678@tcp(127.0.0.1:3306)/test"
gf gen pbentity -p ./protocol/demos/entity -t user,user_detail,user_login
gf gen pbentity -r user_ -k github.com/gogf/gf/example/protobuf
gf gen pbentity -r user_
`
CGenPbEntityAd = `
CONFIGURATION SUPPORT
Options are also supported by configuration file.
It's suggested using configuration file instead of command line arguments making producing.
The configuration node name is "gf.gen.pbentity", which also supports multiple databases, for example(config.yaml):
gfcli:
gen:
- pbentity:
link: "mysql:root:12345678@tcp(127.0.0.1:3306)/test"
path: "protocol/demos/entity"
tables: "order,products"
package: "demos"
- pbentity:
link: "mysql:root:12345678@tcp(127.0.0.1:3306)/primary"
path: "protocol/demos/entity"
prefix: "primary_"
tables: "user, userDetail"
package: "demos"
option: |
option go_package = "protobuf/demos";
option java_package = "protobuf/demos";
option php_namespace = "protobuf/demos";
`
CGenPbEntityBriefPath = `directory path for generated files storing`
CGenPbEntityBriefPackage = `package path for all entity proto files`
CGenPbEntityBriefLink = `database configuration, the same as the ORM configuration of GoFrame`
CGenPbEntityBriefTables = `generate models only for given tables, multiple table names separated with ','`
CGenPbEntityBriefPrefix = `add specified prefix for all entity names and entity proto files`
CGenPbEntityBriefRemovePrefix = `remove specified prefix of the table, multiple prefix separated with ','`
CGenPbEntityBriefOption = `extra protobuf options`
CGenPbEntityBriefGroup = `
specifying the configuration group name of database for generated ORM instance,
it's not necessary and the default value is "default"
`
CGenPbEntityBriefNameCase = `
case for message attribute names, default is "Camel":
| Case | Example |
|---------------- |--------------------|
| Camel | AnyKindOfString |
| CamelLower | anyKindOfString | default
| Snake | any_kind_of_string |
| SnakeScreaming | ANY_KIND_OF_STRING |
| SnakeFirstUpper | rgb_code_md5 |
| Kebab | any-kind-of-string |
| KebabScreaming | ANY-KIND-OF-STRING |
`
CGenPbEntityBriefJsonCase = `
case for message json tag, cases are the same as "nameCase", default "CamelLower".
set it to "none" to ignore json tag generating.
`
)
func init() {
gtag.Sets(g.MapStrStr{
`CGenPbEntityConfig`: CGenPbEntityConfig,
`CGenPbEntityBrief`: CGenPbEntityBrief,
`CGenPbEntityEg`: CGenPbEntityEg,
`CGenPbEntityAd`: CGenPbEntityAd,
`CGenPbEntityBriefPath`: CGenPbEntityBriefPath,
`CGenPbEntityBriefPackage`: CGenPbEntityBriefPackage,
`CGenPbEntityBriefLink`: CGenPbEntityBriefLink,
`CGenPbEntityBriefTables`: CGenPbEntityBriefTables,
`CGenPbEntityBriefPrefix`: CGenPbEntityBriefPrefix,
`CGenPbEntityBriefRemovePrefix`: CGenPbEntityBriefRemovePrefix,
`CGenPbEntityBriefGroup`: CGenPbEntityBriefGroup,
`CGenPbEntityBriefNameCase`: CGenPbEntityBriefNameCase,
`CGenPbEntityBriefJsonCase`: CGenPbEntityBriefJsonCase,
`CGenPbEntityBriefOption`: CGenPbEntityBriefOption,
})
}
func (c CGenPbEntity) PbEntity(ctx context.Context, in CGenPbEntityInput) (out *CGenPbEntityOutput, err error) {
var (
config = g.Cfg()
)
if config.Available(ctx) {
v := config.MustGet(ctx, CGenPbEntityConfig)
if v.IsSlice() {
for i := 0; i < len(v.Interfaces()); i++ {
doGenPbEntityForArray(ctx, i, in)
}
} else {
doGenPbEntityForArray(ctx, -1, in)
}
} else {
doGenPbEntityForArray(ctx, -1, in)
}
mlog.Print("done!")
return
}
func doGenPbEntityForArray(ctx context.Context, index int, in CGenPbEntityInput) {
var (
err error
db gdb.DB
)
if index >= 0 {
err = g.Cfg().MustGet(
ctx,
fmt.Sprintf(`%s.%d`, CGenPbEntityConfig, index),
).Scan(&in)
if err != nil {
mlog.Fatalf(`invalid configuration of "%s": %+v`, CGenPbEntityConfig, err)
}
}
if in.Package == "" {
mlog.Debug(`package parameter is empty, trying calculating the package path using go.mod`)
if !gfile.Exists("go.mod") {
mlog.Fatal("go.mod does not exist in current working directory")
}
var (
modName string
goModContent = gfile.GetContents("go.mod")
match, _ = gregex.MatchString(`^module\s+(.+)\s*`, goModContent)
)
if len(match) > 1 {
modName = gstr.Trim(match[1])
in.Package = modName + "/" + defaultPackageSuffix
} else {
mlog.Fatal("module name does not found in go.mod")
}
}
removePrefixArray := gstr.SplitAndTrim(in.RemovePrefix, ",")
// It uses user passed database configuration.
if in.Link != "" {
var (
tempGroup = gtime.TimestampNanoStr()
match, _ = gregex.MatchString(`([a-z]+):(.+)`, in.Link)
)
if len(match) == 3 {
gdb.AddConfigNode(tempGroup, gdb.ConfigNode{
Type: gstr.Trim(match[1]),
Link: gstr.Trim(match[2]),
})
db, _ = gdb.Instance(tempGroup)
}
} else {
db = g.DB()
}
if db == nil {
mlog.Fatal("database initialization failed")
}
tableNames := ([]string)(nil)
if in.Tables != "" {
tableNames = gstr.SplitAndTrim(in.Tables, ",")
} else {
tableNames, err = db.Tables(context.TODO())
if err != nil {
mlog.Fatalf("fetching tables failed: \n %v", err)
}
}
for _, tableName := range tableNames {
newTableName := tableName
for _, v := range removePrefixArray {
newTableName = gstr.TrimLeftStr(newTableName, v, 1)
}
generatePbEntityContentFile(ctx, CGenPbEntityInternalInput{
CGenPbEntityInput: in,
DB: db,
TableName: tableName,
NewTableName: newTableName,
})
}
}
// generatePbEntityContentFile generates the protobuf files for given table.
func generatePbEntityContentFile(ctx context.Context, in CGenPbEntityInternalInput) {
fieldMap, err := in.DB.TableFields(ctx, in.TableName)
if err != nil {
mlog.Fatalf("fetching tables fields failed for table '%s':\n%v", in.TableName, err)
}
// Change the `newTableName` if `Prefix` is given.
newTableName := in.Prefix + in.NewTableName
var (
imports string
tableNameCamelCase = gstr.CaseCamel(newTableName)
tableNameSnakeCase = gstr.CaseSnake(newTableName)
entityMessageDefine = generateEntityMessageDefinition(tableNameCamelCase, fieldMap, in)
fileName = gstr.Trim(tableNameSnakeCase, "-_.")
path = gfile.Join(in.Path, fileName+".proto")
)
if gstr.Contains(entityMessageDefine, "google.protobuf.Timestamp") {
imports = `import "google/protobuf/timestamp.proto";`
}
entityContent := gstr.ReplaceByMap(getTplPbEntityContent(""), g.MapStrStr{
"{Imports}": imports,
"{PackageName}": gfile.Basename(in.Package),
"{GoPackage}": in.Package,
"{OptionContent}": in.Option,
"{EntityMessage}": entityMessageDefine,
})
if err := gfile.PutContents(path, strings.TrimSpace(entityContent)); err != nil {
mlog.Fatalf("writing content to '%s' failed: %v", path, err)
} else {
mlog.Print("generated:", path)
}
}
// generateEntityMessageDefinition generates and returns the message definition for specified table.
func generateEntityMessageDefinition(entityName string, fieldMap map[string]*gdb.TableField, in CGenPbEntityInternalInput) string {
var (
buffer = bytes.NewBuffer(nil)
array = make([][]string, len(fieldMap))
names = sortFieldKeyForPbEntity(fieldMap)
)
for index, name := range names {
array[index] = generateMessageFieldForPbEntity(index+1, fieldMap[name], in)
}
tw := tablewriter.NewWriter(buffer)
tw.SetBorder(false)
tw.SetRowLine(false)
tw.SetAutoWrapText(false)
tw.SetColumnSeparator("")
tw.AppendBulk(array)
tw.Render()
stContent := buffer.String()
// Let's do this hack of table writer for indent!
stContent = gstr.Replace(stContent, " #", "")
buffer.Reset()
buffer.WriteString(fmt.Sprintf("message %s {\n", entityName))
buffer.WriteString(stContent)
buffer.WriteString("}")
return buffer.String()
}
// generateMessageFieldForPbEntity generates and returns the message definition for specified field.
func generateMessageFieldForPbEntity(index int, field *gdb.TableField, in CGenPbEntityInternalInput) []string {
var (
typeName string
comment string
jsonTagStr string
err error
ctx = gctx.GetInitCtx()
)
typeName, err = in.DB.CheckLocalTypeForField(ctx, field.Type, nil)
if err != nil {
panic(err)
}
var typeMapping = map[string]string{
gdb.LocalTypeString: "string",
gdb.LocalTypeDate: "google.protobuf.Timestamp",
gdb.LocalTypeDatetime: "google.protobuf.Timestamp",
gdb.LocalTypeInt: "int32",
gdb.LocalTypeUint: "uint32",
gdb.LocalTypeInt64: "int64",
gdb.LocalTypeUint64: "uint64",
gdb.LocalTypeIntSlice: "repeated int32",
gdb.LocalTypeInt64Slice: "repeated int64",
gdb.LocalTypeUint64Slice: "repeated uint64",
gdb.LocalTypeInt64Bytes: "repeated int64",
gdb.LocalTypeUint64Bytes: "repeated uint64",
gdb.LocalTypeFloat32: "float",
gdb.LocalTypeFloat64: "double",
gdb.LocalTypeBytes: "bytes",
gdb.LocalTypeBool: "bool",
gdb.LocalTypeJson: "string",
gdb.LocalTypeJsonb: "string",
}
typeName = typeMapping[typeName]
if typeName == "" {
typeName = "string"
}
comment = gstr.ReplaceByArray(field.Comment, g.SliceStr{
"\n", " ",
"\r", " ",
})
comment = gstr.Trim(comment)
comment = gstr.Replace(comment, `\n`, " ")
comment, _ = gregex.ReplaceString(`\s{2,}`, ` `, comment)
if jsonTagName := formatCase(field.Name, in.JsonCase); jsonTagName != "" {
// beautiful indent.
if index < 10 {
// 3 spaces
jsonTagStr = " " + jsonTagStr
} else if index < 100 {
// 2 spaces
jsonTagStr = " " + jsonTagStr
} else {
// 1 spaces
jsonTagStr = " " + jsonTagStr
}
}
return []string{
" #" + typeName,
" #" + formatCase(field.Name, in.NameCase),
" #= " + gconv.String(index) + jsonTagStr + ";",
" #" + fmt.Sprintf(`// %s`, comment),
}
}
func getTplPbEntityContent(tplEntityPath string) string {
if tplEntityPath != "" {
return gfile.GetContents(tplEntityPath)
}
return consts.TemplatePbEntityMessageContent
}
// formatCase call gstr.Case* function to convert the s to specified case.
func formatCase(str, caseStr string) string {
switch gstr.ToLower(caseStr) {
case gstr.ToLower("Camel"):
return gstr.CaseCamel(str)
case gstr.ToLower("CamelLower"):
return gstr.CaseCamelLower(str)
case gstr.ToLower("Kebab"):
return gstr.CaseKebab(str)
case gstr.ToLower("KebabScreaming"):
return gstr.CaseKebabScreaming(str)
case gstr.ToLower("Snake"):
return gstr.CaseSnake(str)
case gstr.ToLower("SnakeFirstUpper"):
return gstr.CaseSnakeFirstUpper(str)
case gstr.ToLower("SnakeScreaming"):
return gstr.CaseSnakeScreaming(str)
case "none":
return ""
}
return str
}
func sortFieldKeyForPbEntity(fieldMap map[string]*gdb.TableField) []string {
names := make(map[int]string)
for _, field := range fieldMap {
names[field.Index] = field.Name
}
var (
result = make([]string, len(names))
i = 0
j = 0
)
for {
if len(names) == 0 {
break
}
if val, ok := names[i]; ok {
result[j] = val
j++
delete(names, i)
}
i++
}
return result
}

View File

@@ -1,3 +1,9 @@
// 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 (
@@ -5,10 +11,10 @@ import (
"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/gproc"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/text/gregex"
"github.com/gogf/gf/v2/text/gstr"
@@ -44,7 +50,7 @@ destination file name storing automatically generated go files, cases are as fol
`
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`
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`
)
@@ -87,21 +93,6 @@ const (
)
func (c CGenService) Service(ctx context.Context, in CGenServiceInput) (out *CGenServiceOutput, err error) {
// 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, `\/`)
@@ -109,6 +100,21 @@ func (c CGenService) Service(ctx context.Context, in CGenServiceInput) (out *CGe
// 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)
@@ -125,13 +131,10 @@ func (c CGenService) Service(ctx context.Context, in CGenServiceInput) (out *CGe
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(ctx, command)
return
in.WatchFile = ""
in.Packages = []string{gfile.Basename(watchFileDir)}
return c.Service(ctx, in)
}
if !gfile.Exists(in.SrcFolder) {
@@ -139,16 +142,7 @@ func (c CGenService) Service(ctx context.Context, in CGenServiceInput) (out *CGe
}
if in.ImportPrefix == "" {
if !gfile.Exists("go.mod") {
mlog.Fatal("ImportPrefix is empty and go.mod does not exist in current working directory")
}
var (
goModContent = gfile.GetContents("go.mod")
match, _ = gregex.MatchString(`^module\s+(.+)\s*`, goModContent)
)
if len(match) > 1 {
in.ImportPrefix = fmt.Sprintf(`%s/%s`, gstr.Trim(match[1]), gstr.Replace(in.SrcFolder, `\`, `/`))
}
in.ImportPrefix = utils.GetImportPath(in.SrcFolder)
}
var (
@@ -176,30 +170,118 @@ func (c CGenService) Service(ctx context.Context, in CGenServiceInput) (out *CGe
if len(files) == 0 {
continue
}
// Parse single logic package folder.
var (
// StructName => FunctionDefinitions
srcPkgInterfaceMap = make(map[string]*garray.StrArray)
srcImportedPackages = garray.NewSortedStrArray().SetUnique(true)
srcPackageName = gfile.Basename(srcFolderPath)
ok bool
dstFilePath = gfile.Join(in.DstFolder,
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 imported packages of source go files.
err = c.calculateImportedPackages(fileContent, srcImportedPackages)
// 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, dstPackageName)
err = c.calculateInterfaceFunctions(in, fileContent, srcPkgInterfaceMap)
if err != nil {
return nil, err
}
}
initImportSrcPackages = append(
initImportSrcPackages,
fmt.Sprintf(`%s/%s`, in.ImportPrefix, srcPackageName),
@@ -212,7 +294,7 @@ func (c CGenService) Service(ctx context.Context, in CGenServiceInput) (out *CGe
)
continue
}
// Generating service go file for logic.
// Generating service go file for single logic package.
if ok, err = c.generateServiceFile(generateServiceFilesInput{
CGenServiceInput: in,
SrcStructFunctions: srcPkgInterfaceMap,
@@ -220,6 +302,7 @@ func (c CGenService) Service(ctx context.Context, in CGenServiceInput) (out *CGe
SrcPackageName: srcPackageName,
DstPackageName: dstPackageName,
DstFilePath: dstFilePath,
SrcCodeCommentedMap: srcCodeCommentedMap,
}); err != nil {
return
}
@@ -254,24 +337,71 @@ func (c CGenService) Service(ctx context.Context, in CGenServiceInput) (out *CGe
}
// Replace v1 to v2 for GoFrame.
if err = c.replaceGeneratedServiceContentGFV2(in); err != nil {
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) replaceGeneratedServiceContentGFV2(in CGenServiceInput) (err error) {
return gfile.ReplaceDirFunc(func(path, content string) string {
if gstr.Contains(content, `"github.com/gogf/gf`) && !gstr.Contains(content, `"github.com/gogf/gf/v2`) {
content = gstr.Replace(content, `"github.com/gogf/gf"`, `"github.com/gogf/gf/v2"`)
content = gstr.Replace(content, `"github.com/gogf/gf/`, `"github.com/gogf/gf/v2/`)
return content
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
}
return content
}, in.DstFolder, "*.go", false)
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
}

View File

@@ -1,6 +1,13 @@
// 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 (
"fmt"
"go/parser"
"go/token"
@@ -9,29 +16,90 @@ import (
"github.com/gogf/gf/v2/text/gstr"
)
func (c CGenService) calculateImportedPackages(fileContent string, srcImportedPackages *garray.SortedStrArray) (err error) {
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 err
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 != "_" {
srcImportedPackages.Add(pkgAlias + " " + s.Path.Value)
packages = append(packages, packageItem{
Alias: pkgAlias,
Path: s.Path.Value,
RawImport: pkgAlias + " " + s.Path.Value,
})
}
} else {
// no alias
srcImportedPackages.Add(s.Path.Value)
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 map[string]*garray.StrArray, dstPackageName string,
in CGenServiceInput, fileContent string, srcPkgInterfaceMap map[string]*garray.StrArray,
) (err error) {
var (
ok bool
@@ -53,7 +121,7 @@ func (c CGenService) calculateInterfaceFunctions(
)
if len(receiverArray) > 1 {
structName = receiverArray[1]
} else {
} else if len(receiverArray) == 1 {
structName = receiverArray[0]
}
structName = gstr.Trim(structName, "*")

View File

@@ -1,3 +1,9 @@
// 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 (
@@ -21,6 +27,7 @@ type generateServiceFilesInput struct {
SrcImportedPackages []string
SrcPackageName string
DstPackageName string
SrcCodeCommentedMap map[string]string
}
func (c CGenService) generateServiceFile(in generateServiceFilesInput) (ok bool, err error) {
@@ -41,6 +48,13 @@ func (c CGenService) generateServiceFile(in generateServiceFilesInput) (ok bool,
generatedContent += "\n"
for structName, funcArray := range in.SrcStructFunctions {
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"),
@@ -56,7 +70,7 @@ func (c CGenService) generateServiceFile(in generateServiceFilesInput) (ok bool,
generatingInterfaceCheck string
)
// Variable definitions.
for structName, _ := range in.SrcStructFunctions {
for structName := range in.SrcStructFunctions {
generatingInterfaceCheck = fmt.Sprintf(`[^\w\d]+%s.I%s[^\w\d]`, in.DstPackageName, structName)
if gregex.IsMatchString(generatingInterfaceCheck, generatedContent) {
continue
@@ -75,7 +89,7 @@ func (c CGenService) generateServiceFile(in generateServiceFilesInput) (ok bool,
generatedContent += "\n"
}
// Variable register function definitions.
for structName, _ := range in.SrcStructFunctions {
for structName := range in.SrcStructFunctions {
generatingInterfaceCheck = fmt.Sprintf(`[^\w\d]+%s.I%s[^\w\d]`, in.DstPackageName, structName)
if gregex.IsMatchString(generatingInterfaceCheck, generatedContent) {
continue
@@ -114,6 +128,7 @@ func (c CGenService) generateServiceFile(in generateServiceFilesInput) (ok bool,
// 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()
@@ -121,6 +136,12 @@ func (c CGenService) isToGenerateServiceGoFile(dstPackageName, filePath string,
if fileContent == "" {
return true
}
// remove all comments.
fileContent, err = gregex.ReplaceString(`/[/|\*](.*)`, "", 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")...)
@@ -145,29 +166,30 @@ func (c CGenService) isToGenerateServiceGoFile(dstPackageName, filePath string,
return false
}
// generateInitializationFile generates `logic.go`.
func (c CGenService) generateInitializationFile(in CGenServiceInput, importSrcPackages []string) (err error) {
var (
srcPackageName = gstr.ToLower(gfile.Basename(in.SrcFolder))
srcFilePath = gfile.Join(in.SrcFolder, srcPackageName+".go")
srcImports string
logicPackageName = gstr.ToLower(gfile.Basename(in.SrcFolder))
logicFilePath = gfile.Join(in.SrcFolder, logicPackageName+".go")
logicImports string
generatedContent string
)
if !utils.IsFileDoNotEdit(srcFilePath) {
mlog.Debugf(`ignore file as it is manually maintained: %s`, srcFilePath)
if !utils.IsFileDoNotEdit(logicFilePath) {
mlog.Debugf(`ignore file as it is manually maintained: %s`, logicFilePath)
return nil
}
for _, importSrcPackage := range importSrcPackages {
srcImports += fmt.Sprintf(`%s_ "%s"%s`, "\t", importSrcPackage, "\n")
logicImports += fmt.Sprintf(`%s_ "%s"%s`, "\t", importSrcPackage, "\n")
}
generatedContent = gstr.ReplaceByMap(consts.TemplateGenServiceLogicContent, g.MapStrStr{
"{PackageName}": srcPackageName,
"{Imports}": srcImports,
"{PackageName}": logicPackageName,
"{Imports}": logicImports,
})
mlog.Printf(`generating init go file: %s`, srcFilePath)
if err = gfile.PutContents(srcFilePath, generatedContent); err != nil {
mlog.Printf(`generating init go file: %s`, logicFilePath)
if err = gfile.PutContents(logicFilePath, generatedContent); err != nil {
return err
}
utils.GoFmt(srcFilePath)
utils.GoFmt(logicFilePath)
return nil
}

View File

@@ -0,0 +1,30 @@
package testdata
import (
"fmt"
"testing"
"time"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/test/gtest"
"github.com/gogf/gf/v2/util/guid"
)
func Test_Router_Hook_Multi(t *testing.T) {
s := g.Server(guid.S())
s.BindHandler("/multi-hook", func(r *ghttp.Request) {
r.Response.Write("show")
})
s.BindHookHandlerByMap("/multi-hook", map[string]ghttp.HandlerFunc{
ghttp.HookBeforeServe: func(r *ghttp.Request) {
r.Response.Write("1")
},
})
s.BindHookHandlerByMap("/multi-hook/{id}", map[string]ghttp.HandlerFunc{
ghttp.HookBeforeServe: func(r *ghttp.Request) {
r.Response.Write("2")
},
})
}

View File

@@ -1,3 +1,9 @@
// 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 consts
const (

View File

@@ -0,0 +1,65 @@
// 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 consts
const TemplateGenCtrlControllerEmpty = `
package {Module}
`
const TemplateGenCtrlControllerNewEmpty = `
// =================================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// =================================================================================
package {Module}
import (
{ImportPath}
)
`
const TemplateGenCtrlControllerNewFunc = `
type {CtrlName} struct{}
func {NewFuncName}() {InterfaceName} {
return &{CtrlName}{}
}
`
const TemplateGenCtrlControllerMethodFunc = `
package {Module}
import (
"context"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"{ImportPath}"
)
func (c *{CtrlName}) {MethodName}(ctx context.Context, req *{Version}.{MethodName}Req) (res *{Version}.{MethodName}Res, err error) {
return nil, gerror.NewCode(gcode.CodeNotImplemented)
}
`
const TemplateGenCtrlApiInterface = `
// =================================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// =================================================================================
package {Module}
import (
{ImportPaths}
)
{Interfaces}
`

View File

@@ -0,0 +1,94 @@
// 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 consts
const TemplateGenCtrlSdkPkgNew = `
// =================================================================================
// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish.
// =================================================================================
package {PkgName}
import (
"fmt"
"github.com/gogf/gf/contrib/sdk/httpclient/v2"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/text/gstr"
)
type implementer struct {
config httpclient.Config
}
func New(config httpclient.Config) iClient {
if !gstr.HasPrefix(config.URL, "http") {
config.URL = fmt.Sprintf("http://%s", config.URL)
}
if config.Logger == nil {
config.Logger = g.Log()
}
return &implementer{
config: config,
}
}
`
const TemplateGenCtrlSdkIClient = `
// =================================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// =================================================================================
package {PkgName}
import (
)
type iClient interface {
}
`
const TemplateGenCtrlSdkImplementer = `
// =================================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// =================================================================================
package {PkgName}
import (
"context"
"github.com/gogf/gf/contrib/sdk/httpclient/v2"
"github.com/gogf/gf/v2/text/gstr"
{ImportPaths}
)
type implementer{ImplementerName} struct {
*httpclient.Client
}
`
const TemplateGenCtrlSdkImplementerNew = `
func (i *implementer) {ImplementerName}() {Module}.I{ImplementerName} {
var (
client = httpclient.New(i.config)
prefix = gstr.TrimRight(i.config.URL, "/") + "{VersionPrefix}"
)
client.Client = client.Prefix(prefix)
return &implementer{ImplementerName}{client}
}
`
const TemplateGenCtrlSdkImplementerFunc = `
func (i *implementer{ImplementerName}) {MethodName}(ctx context.Context, req *{Version}.{MethodName}Req) (res *{Version}.{MethodName}Res, err error) {
err = i.Request(ctx, req, &res)
return
}
`

View File

@@ -1,3 +1,9 @@
// 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 consts
const TemplateGenDaoIndexContent = `
@@ -33,7 +39,7 @@ var (
const TemplateGenDaoInternalContent = `
// ==========================================================================
// Code generated by GoFrame CLI tool. DO NOT EDIT. {TplCreatedAtDatetimeStr}
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. {TplCreatedAtDatetimeStr}
// ==========================================================================
package internal

View File

@@ -1,8 +1,14 @@
// 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 consts
const TemplateGenDaoDoContent = `
// =================================================================================
// Code generated by GoFrame CLI tool. DO NOT EDIT. {TplCreatedAtDatetimeStr}
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. {TplCreatedAtDatetimeStr}
// =================================================================================
package do

View File

@@ -1,8 +1,14 @@
// 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 consts
const TemplateGenDaoEntityContent = `
// =================================================================================
// Code generated by GoFrame CLI tool. DO NOT EDIT. {TplCreatedAtDatetimeStr}
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. {TplCreatedAtDatetimeStr}
// =================================================================================
package entity

View File

@@ -8,7 +8,7 @@ package consts
const TemplateGenEnums = `
// ================================================================================
// Code generated by GoFrame CLI tool. DO NOT EDIT.
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// ================================================================================
package {PackageName}

View File

@@ -1,17 +1,23 @@
// 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 consts
const TemplatePbEntityMessageContent = `
// ==========================================================================
// Code generated by GoFrame CLI tool. DO NOT EDIT.
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// ==========================================================================
syntax = "proto3";
package {PackageName};
import "github.com/gogo/protobuf/gogoproto/gogo.proto";
option go_package = "{GoPackage}";
{OptionContent}
{Imports}
{EntityMessage}
`

View File

@@ -1,8 +1,14 @@
// 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 consts
const TemplateGenServiceContentHead = `
// ================================================================================
// Code generated by GoFrame CLI tool. DO NOT EDIT.
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// You can delete these comments if you wish manually maintain this interface file.
// ================================================================================

View File

@@ -1,8 +1,14 @@
// 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 consts
const TemplateGenServiceLogicContent = `
// ==========================================================================
// Code generated by GoFrame CLI tool. DO NOT EDIT.
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// ==========================================================================
package {PackageName}

View File

@@ -1 +1,7 @@
// 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 packed

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,3 +1,9 @@
// 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 service
import (
@@ -29,6 +35,7 @@ type serviceInstallAvailablePath struct {
filePath string
writable bool
installed bool
IsSelf bool
}
func (s serviceInstall) Run(ctx context.Context) (err error) {
@@ -104,6 +111,8 @@ func (s serviceInstall) Run(ctx context.Context) (err error) {
)
if input != "" {
inputID = gconv.Int(input)
} else {
break
}
// Check if out of range.
if inputID >= len(paths) || inputID < 0 {
@@ -131,17 +140,17 @@ func (s serviceInstall) Run(ctx context.Context) (err error) {
}
// IsInstalled checks and returns whether the binary is installed.
func (s serviceInstall) IsInstalled() bool {
func (s serviceInstall) IsInstalled() (*serviceInstallAvailablePath, bool) {
paths := s.getAvailablePaths()
for _, aPath := range paths {
if aPath.installed {
return true
return &aPath, true
}
}
return false
return nil, false
}
// getGoPathBinFilePath retrieves ad returns the GOPATH/bin path for binary.
// getGoPathBin retrieves ad returns the GOPATH/bin path for binary.
func (s serviceInstall) getGoPathBin() string {
if goPath := genv.Get(`GOPATH`).String(); goPath != "" {
return gfile.Join(goPath, "bin")
@@ -214,6 +223,7 @@ func (s serviceInstall) checkAndAppendToAvailablePath(folderPaths []serviceInsta
filePath = gfile.Join(dirPath, binaryFileName)
writable = gfile.IsWritable(dirPath)
installed = gfile.Exists(filePath)
self = gfile.SelfPath() == filePath
)
if !writable && !installed {
return folderPaths
@@ -225,5 +235,6 @@ func (s serviceInstall) checkAndAppendToAvailablePath(folderPaths []serviceInsta
writable: writable,
filePath: filePath,
installed: installed,
IsSelf: self,
})
}

View File

@@ -1,3 +1,9 @@
// 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 allyes
import (

View File

@@ -1,3 +1,9 @@
// 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 mlog
import (

View File

@@ -1,10 +1,20 @@
// 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 utils
import (
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/text/gstr"
"context"
"fmt"
"golang.org/x/tools/imports"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/os/gproc"
"github.com/gogf/gf/v2/text/gregex"
"github.com/gogf/gf/v2/text/gstr"
"hotgo/internal/library/hggen/internal/consts"
"hotgo/internal/library/hggen/internal/utility/mlog"
)
@@ -36,6 +46,13 @@ func GoFmt(path string) {
}
}
// GoModTidy executes `go mod tidy` at specified directory `dirPath`.
func GoModTidy(ctx context.Context, dirPath string) error {
command := fmt.Sprintf(`cd %s && go mod tidy`, dirPath)
err := gproc.ShellRun(ctx, command)
return err
}
// IsFileDoNotEdit checks and returns whether file contains `do not edit` key.
func IsFileDoNotEdit(filePath string) bool {
if !gfile.Exists(filePath) {
@@ -43,3 +60,78 @@ func IsFileDoNotEdit(filePath string) bool {
}
return gstr.Contains(gfile.GetContents(filePath), consts.DoNotEditKey)
}
// ReplaceGeneratedContentGFV2 replaces generated go content from goframe v1 to v2.
func ReplaceGeneratedContentGFV2(folderPath string) (err error) {
return gfile.ReplaceDirFunc(func(path, content string) string {
if gstr.Contains(content, `"github.com/gogf/gf`) && !gstr.Contains(content, `"github.com/gogf/gf/v2`) {
content = gstr.Replace(content, `"github.com/gogf/gf"`, `"github.com/gogf/gf/v2"`)
content = gstr.Replace(content, `"github.com/gogf/gf/`, `"github.com/gogf/gf/v2/`)
content = gstr.Replace(content, `"github.com/gogf/gf/v2/contrib/`, `"github.com/gogf/gf/contrib/`)
return content
}
return content
}, folderPath, "*.go", true)
}
// GetImportPath calculates and returns the golang import path for given `filePath`.
// Note that it needs a `go.mod` in current working directory or parent directories to detect the path.
func GetImportPath(filePath string) string {
// If `filePath` does not exist, create it firstly to find the import path.
var realPath = gfile.RealPath(filePath)
if realPath == "" {
_ = gfile.Mkdir(filePath)
realPath = gfile.RealPath(filePath)
}
var (
newDir = gfile.Dir(realPath)
oldDir string
suffix string
goModName = "go.mod"
goModPath string
importPath string
)
if gfile.IsDir(filePath) {
suffix = gfile.Basename(filePath)
}
for {
goModPath = gfile.Join(newDir, goModName)
if gfile.Exists(goModPath) {
match, _ := gregex.MatchString(`^module\s+(.+)\s*`, gfile.GetContents(goModPath))
importPath = gstr.Trim(match[1]) + "/" + suffix
importPath = gstr.Replace(importPath, `\`, `/`)
importPath = gstr.TrimRight(importPath, `/`)
return importPath
}
oldDir = newDir
newDir = gfile.Dir(oldDir)
if newDir == oldDir {
return ""
}
suffix = gfile.Basename(oldDir) + "/" + suffix
}
}
// GetModPath retrieves and returns the file path of go.mod for current project.
func GetModPath() string {
var (
oldDir = gfile.Pwd()
newDir = gfile.Dir(oldDir)
goModName = "go.mod"
goModPath string
)
for {
goModPath = gfile.Join(newDir, goModName)
if gfile.Exists(goModPath) {
return goModPath
}
oldDir = newDir
newDir = gfile.Dir(oldDir)
if newDir == oldDir {
break
}
}
return ""
}

View File

@@ -0,0 +1,105 @@
// 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 utils
import (
"fmt"
"io"
"net/http"
"os"
"strconv"
"time"
"github.com/gogf/gf/v2/errors/gerror"
"hotgo/internal/library/hggen/internal/utility/mlog"
)
// HTTPDownloadFileWithPercent downloads target url file to local path with percent process printing.
func HTTPDownloadFileWithPercent(url string, localSaveFilePath string) error {
start := time.Now()
out, err := os.Create(localSaveFilePath)
if err != nil {
return gerror.Wrapf(err, `download "%s" to "%s" failed`, url, localSaveFilePath)
}
defer out.Close()
headResp, err := http.Head(url)
if err != nil {
return gerror.Wrapf(err, `download "%s" to "%s" failed`, url, localSaveFilePath)
}
defer headResp.Body.Close()
size, err := strconv.Atoi(headResp.Header.Get("Content-Length"))
if err != nil {
return gerror.Wrap(err, "retrieve Content-Length failed")
}
doneCh := make(chan int64)
go doPrintDownloadPercent(doneCh, localSaveFilePath, int64(size))
resp, err := http.Get(url)
if err != nil {
return gerror.Wrapf(err, `download "%s" to "%s" failed`, url, localSaveFilePath)
}
defer resp.Body.Close()
wroteBytesCount, err := io.Copy(out, resp.Body)
if err != nil {
return gerror.Wrapf(err, `download "%s" to "%s" failed`, url, localSaveFilePath)
}
doneCh <- wroteBytesCount
elapsed := time.Since(start)
if elapsed > time.Minute {
mlog.Printf(`download completed in %.0fm`, float64(elapsed)/float64(time.Minute))
} else {
mlog.Printf(`download completed in %.0fs`, elapsed.Seconds())
}
return nil
}
func doPrintDownloadPercent(doneCh chan int64, localSaveFilePath string, total int64) {
var (
stop = false
lastPercentFmt string
)
for {
select {
case <-doneCh:
stop = true
default:
file, err := os.Open(localSaveFilePath)
if err != nil {
mlog.Fatal(err)
}
fi, err := file.Stat()
if err != nil {
mlog.Fatal(err)
}
size := fi.Size()
if size == 0 {
size = 1
}
var (
percent = float64(size) / float64(total) * 100
percentFmt = fmt.Sprintf(`%.0f`, percent) + "%"
)
if lastPercentFmt != percentFmt {
lastPercentFmt = percentFmt
mlog.Print(percentFmt)
}
}
if stop {
break
}
time.Sleep(time.Second)
}
}

View File

@@ -0,0 +1,22 @@
// 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 utils_test
import (
"fmt"
"testing"
"github.com/gogf/gf/v2/test/gtest"
"hotgo/internal/library/hggen/internal/utility/utils"
)
func Test_GetModPath(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
goModPath := utils.GetModPath()
fmt.Println(goModPath)
})
}

View File

@@ -3,7 +3,6 @@
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
//
package views
import (
@@ -20,7 +19,7 @@ import (
)
// DoTableColumns 获取指定表生成字段列表
func DoTableColumns(ctx context.Context, in sysin.GenCodesColumnListInp, config gendao.CGenDaoInput) (fields []*sysin.GenCodesColumnListModel, err error) {
func DoTableColumns(ctx context.Context, in *sysin.GenCodesColumnListInp, config gendao.CGenDaoInput) (fields []*sysin.GenCodesColumnListModel, err error) {
var (
sql = "select ORDINAL_POSITION as `id`, COLUMN_NAME as `name`, COLUMN_COMMENT as `dc`, DATA_TYPE as `dataType`, COLUMN_TYPE as `sqlType`, CHARACTER_MAXIMUM_LENGTH as `length`, IS_NULLABLE as `isAllowNull`, COLUMN_DEFAULT as `defaultValue`, COLUMN_KEY as `index`, EXTRA as `extra` from information_schema.COLUMNS where TABLE_SCHEMA = '%s' and TABLE_NAME = '%s' ORDER BY `id` ASC"
conf = g.DB(in.Name).GetConfig()
@@ -42,7 +41,6 @@ func DoTableColumns(ctx context.Context, in sysin.GenCodesColumnListInp, config
CustomAttributes(ctx, field, config)
}
}
return
}
@@ -65,10 +63,7 @@ func CustomAttributes(ctx context.Context, field *sysin.GenCodesColumnListModel,
// GenGotype 生成字段的go类型
func GenGotype(ctx context.Context, field *sysin.GenCodesColumnListModel, in gendao.CGenDaoInput) (goName, typeName, tsName, tsType string) {
var (
err error
)
var err error
tsName = getJsonTagFromCase(field.Name, in.JsonCase)
goName = gstr.CaseCamel(field.Name)
@@ -100,7 +95,6 @@ func GenGotype(ctx context.Context, field *sysin.GenCodesColumnListModel, in gen
}
tsType = ShiftMap[typeName]
return
}

View File

@@ -317,5 +317,10 @@ func setDefaultValue(field *sysin.GenCodesColumnListModel) {
value = consts.ConvType(field.DefaultValue, field.GoType)
}
// 时间类型不做默认值处理
if field.GoType == GoTypeGTime {
value = ""
}
field.DefaultValue = value
}

View File

@@ -7,7 +7,6 @@ package views
import (
"context"
"fmt"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gfile"
@@ -67,14 +66,14 @@ type CurdOptions struct {
HeadOps []string `json:"headOps"`
Join []*CurdOptionsJoin `json:"join"`
Menu *CurdOptionsMenu `json:"menu"`
Step *CurdStep // 转换后的流程控制条件
dictMap g.Map // 字典选项 -> 字段映射关系
TemplateGroup string `json:"templateGroup"`
ApiPrefix string `json:"apiPrefix"`
Step *CurdStep // 转换后的流程控制条件
dictMap g.Map // 字典选项 -> 字段映射关系
}
type CurdPreviewInput struct {
In sysin.GenCodesPreviewInp // 提交参数
In *sysin.GenCodesPreviewInp // 提交参数
DaoConfig gendao.CGenDaoInput // 生成dao配置
Config *model.GenerateConfig // 生成配置
view *gview.View // 视图模板
@@ -102,7 +101,7 @@ func (l *gCurd) initInput(ctx context.Context, in *CurdPreviewInput) (err error)
}
if len(in.masterFields) == 0 {
if in.masterFields, err = DoTableColumns(ctx, sysin.GenCodesColumnListInp{Name: in.In.DbName, Table: in.In.TableName}, in.DaoConfig); err != nil {
if in.masterFields, err = DoTableColumns(ctx, &sysin.GenCodesColumnListInp{Name: in.In.DbName, Table: in.In.TableName}, in.DaoConfig); err != nil {
return
}
}
@@ -236,7 +235,7 @@ func (l *gCurd) DoBuild(ctx context.Context, in *CurdBuildInput) (err error) {
for name, f := range in.BeforeEvent {
if gstr.InArray(in.PreviewIn.options.AutoOps, name) {
if err = f(ctx); err != nil {
return fmt.Errorf("in doBuild operation beforeEvent to '%s' failed::%v", name, err)
return gerror.Newf("in doBuild operation beforeEvent to '%s' failed:%v", name, err)
}
}
}
@@ -254,7 +253,7 @@ func (l *gCurd) DoBuild(ctx context.Context, in *CurdBuildInput) (err error) {
}
if err = gfile.PutContents(vi.Path, strings.TrimSpace(vi.Content)); err != nil {
return fmt.Errorf("writing content to '%s' failed: %v", vi.Path, err)
return gerror.Newf("writing content to '%s' failed: %v", vi.Path, err)
}
if gstr.Str(vi.Path, `.`) == ".sql" && needExecSql {
@@ -266,7 +265,6 @@ func (l *gCurd) DoBuild(ctx context.Context, in *CurdBuildInput) (err error) {
if gstr.Str(vi.Path, `.`) == ".go" {
utils.GoFmt(vi.Path)
}
}
// 后置操作
@@ -274,7 +272,7 @@ func (l *gCurd) DoBuild(ctx context.Context, in *CurdBuildInput) (err error) {
for name, f := range in.AfterEvent {
if gstr.InArray(in.PreviewIn.options.AutoOps, name) {
if err = f(ctx); err != nil {
return fmt.Errorf("in doBuild operation afterEvent to '%s' failed::%v", name, err)
return gerror.Newf("in doBuild operation afterEvent to '%s' failed:%v", name, err)
}
}
}
@@ -515,12 +513,12 @@ func (l *gCurd) generateWebModelContent(ctx context.Context, in *CurdPreviewInpu
tplData, err := l.webModelTplData(ctx, in)
if err != nil {
return err
return
}
genFile.Content, err = in.view.Parse(ctx, name+".template", tplData)
if err != nil {
return err
return
}
genFile.Path = file.MergeAbs(in.Config.Application.Crud.Templates[in.In.GenTemplate].WebViewsPath, gstr.LcFirst(in.In.VarName), "model.ts")
@@ -564,7 +562,6 @@ func (l *gCurd) generateWebIndexContent(ctx context.Context, in *CurdPreviewInpu
genFile.Meth = consts.GenCodesBuildMethCover
}
in.content.Views[name] = genFile
return
}

View File

@@ -164,7 +164,6 @@ func (l *gCurd) generateStructFieldDefinition(field *sysin.GenCodesColumnListMod
default:
panic("inputType is invalid")
}
return result
}

View File

@@ -48,7 +48,7 @@ func (l *gCurd) generateWebEditFormItem(ctx context.Context, in *CurdPreviewInpu
component = fmt.Sprintf("<n-form-item label=\"%s\" path=\"%s\">\n <n-input type=\"textarea\" placeholder=\"%s\" v-model:value=\"params.%s\" />\n </n-form-item>", field.Dc, field.TsName, field.Dc, field.TsName)
case FormModeInputEditor:
component = fmt.Sprintf("<n-form-item label=\"%s\" path=\"%s\">\n <Editor style=\"height: 450px\" v-model:value=\"params.%s\" />\n </n-form-item>", field.Dc, field.TsName, field.TsName)
component = fmt.Sprintf("<n-form-item label=\"%s\" path=\"%s\">\n <Editor style=\"height: 450px\" id=\"%s\" v-model:value=\"params.%s\" />\n </n-form-item>", field.Dc, field.TsName, field.TsName, field.TsName)
case FormModeInputDynamic:
component = fmt.Sprintf("<n-form-item label=\"%s\" path=\"%s\">\n <n-dynamic-input\n v-model:value=\"params.%s\"\n preset=\"pair\"\n key-placeholder=\"键名\"\n value-placeholder=\"键值\"\n />\n </n-form-item>", field.Dc, field.TsName, field.TsName)

View File

@@ -83,6 +83,5 @@ func (l *gCurd) webIndexTplData(ctx context.Context, in *CurdPreviewInput) (g.Ma
}
}
data["isSearchForm"] = isSearchForm
return data, nil
}

View File

@@ -25,7 +25,6 @@ func (l *gCurd) parseServFunName(templateGroup, varName string) string {
if gstr.HasPrefix(varName, templateGroup) && varName != templateGroup {
return varName
}
return templateGroup + varName
}
@@ -94,7 +93,6 @@ func ImportSql(ctx context.Context, path string) error {
return err
}
}
return nil
}
@@ -144,7 +142,6 @@ func checkCurdPath(temp *model.GenerateAppCrudTemplate, addonName string) (err e
if !gfile.Exists(temp.WebViewsPath) {
return gerror.Newf(tip, "WebViewsPath", temp.WebViewsPath)
}
return
}