goctl added
This commit is contained in:
134
tools/goctl/api/gogen/gen.go
Normal file
134
tools/goctl/api/gogen/gen.go
Normal file
@@ -0,0 +1,134 @@
|
||||
package gogen
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"zero/core/lang"
|
||||
apiformat "zero/tools/goctl/api/format"
|
||||
"zero/tools/goctl/api/parser"
|
||||
apiutil "zero/tools/goctl/api/util"
|
||||
"zero/tools/goctl/util"
|
||||
|
||||
"github.com/logrusorgru/aurora"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
const tmpFile = "%s-%d"
|
||||
|
||||
var tmpDir = path.Join(os.TempDir(), "goctl")
|
||||
|
||||
func GoCommand(c *cli.Context) error {
|
||||
apiFile := c.String("api")
|
||||
dir := c.String("dir")
|
||||
if len(apiFile) == 0 {
|
||||
return errors.New("missing -api")
|
||||
}
|
||||
if len(dir) == 0 {
|
||||
return errors.New("missing -dir")
|
||||
}
|
||||
|
||||
p, err := parser.NewParser(apiFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
api, err := p.Parse()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
lang.Must(util.MkdirIfNotExist(dir))
|
||||
lang.Must(genEtc(dir, api))
|
||||
lang.Must(genConfig(dir, api))
|
||||
lang.Must(genMain(dir, api))
|
||||
lang.Must(genServiceContext(dir, api))
|
||||
lang.Must(genTypes(dir, api))
|
||||
lang.Must(genHandlers(dir, api))
|
||||
lang.Must(genRoutes(dir, api))
|
||||
lang.Must(genLogic(dir, api))
|
||||
// it does not work
|
||||
format(dir)
|
||||
|
||||
if err := backupAndSweep(apiFile); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = apiformat.ApiFormat(apiFile, false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println(aurora.Green("Done."))
|
||||
return nil
|
||||
}
|
||||
|
||||
func backupAndSweep(apiFile string) error {
|
||||
var err error
|
||||
var wg sync.WaitGroup
|
||||
|
||||
wg.Add(2)
|
||||
_ = os.MkdirAll(tmpDir, os.ModePerm)
|
||||
|
||||
go func() {
|
||||
_, fileName := filepath.Split(apiFile)
|
||||
_, e := apiutil.Copy(apiFile, fmt.Sprintf(path.Join(tmpDir, tmpFile), fileName, time.Now().Unix()))
|
||||
if e != nil {
|
||||
err = e
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
go func() {
|
||||
if e := sweep(); e != nil {
|
||||
err = e
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
wg.Wait()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func format(dir string) {
|
||||
cmd := exec.Command("go", "fmt", "./"+dir+"...")
|
||||
_, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
print(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func sweep() error {
|
||||
keepTime := time.Now().AddDate(0, 0, -7)
|
||||
return filepath.Walk(tmpDir, func(fpath string, info os.FileInfo, err error) error {
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
pos := strings.LastIndexByte(info.Name(), '-')
|
||||
if pos > 0 {
|
||||
timestamp := info.Name()[pos+1:]
|
||||
seconds, err := strconv.ParseInt(timestamp, 10, 64)
|
||||
if err != nil {
|
||||
// print error and ignore
|
||||
fmt.Println(aurora.Red(fmt.Sprintf("sweep ignored file: %s", fpath)))
|
||||
return nil
|
||||
}
|
||||
|
||||
tm := time.Unix(seconds, 0)
|
||||
if tm.Before(keepTime) {
|
||||
if err := os.Remove(fpath); err != nil {
|
||||
fmt.Println(aurora.Red(fmt.Sprintf("failed to remove file: %s", fpath)))
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
48
tools/goctl/api/gogen/genconfig.go
Normal file
48
tools/goctl/api/gogen/genconfig.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package gogen
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"text/template"
|
||||
|
||||
"zero/tools/goctl/api/spec"
|
||||
"zero/tools/goctl/api/util"
|
||||
)
|
||||
|
||||
const (
|
||||
configFile = "config.go"
|
||||
configTemplate = `package config
|
||||
|
||||
import (
|
||||
"zero/ngin"
|
||||
{{.authImport}}
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
ngin.NgConf
|
||||
}
|
||||
`
|
||||
)
|
||||
|
||||
func genConfig(dir string, api *spec.ApiSpec) error {
|
||||
fp, created, err := util.MaybeCreateFile(dir, configDir, configFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !created {
|
||||
return nil
|
||||
}
|
||||
defer fp.Close()
|
||||
|
||||
var authImportStr = ""
|
||||
t := template.Must(template.New("configTemplate").Parse(configTemplate))
|
||||
buffer := new(bytes.Buffer)
|
||||
err = t.Execute(buffer, map[string]string{
|
||||
"authImport": authImportStr,
|
||||
})
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
formatCode := formatCode(buffer.String())
|
||||
_, err = fp.WriteString(formatCode)
|
||||
return err
|
||||
}
|
||||
56
tools/goctl/api/gogen/genetc.go
Normal file
56
tools/goctl/api/gogen/genetc.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package gogen
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"text/template"
|
||||
|
||||
"zero/tools/goctl/api/spec"
|
||||
"zero/tools/goctl/api/util"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultPort = 8888
|
||||
etcDir = "etc"
|
||||
etcTemplate = `{
|
||||
"Name": "{{.serviceName}}",
|
||||
"Host": "{{.host}}",
|
||||
"Port": {{.port}}
|
||||
}`
|
||||
)
|
||||
|
||||
func genEtc(dir string, api *spec.ApiSpec) error {
|
||||
fp, created, err := util.MaybeCreateFile(dir, etcDir, fmt.Sprintf("%s.json", api.Service.Name))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !created {
|
||||
return nil
|
||||
}
|
||||
defer fp.Close()
|
||||
|
||||
service := api.Service
|
||||
host, ok := util.GetAnnotationValue(service.Annotations, "server", "host")
|
||||
if !ok {
|
||||
host = "0.0.0.0"
|
||||
}
|
||||
port, ok := util.GetAnnotationValue(service.Annotations, "server", "port")
|
||||
if !ok {
|
||||
port = strconv.Itoa(defaultPort)
|
||||
}
|
||||
|
||||
t := template.Must(template.New("etcTemplate").Parse(etcTemplate))
|
||||
buffer := new(bytes.Buffer)
|
||||
err = t.Execute(buffer, map[string]string{
|
||||
"serviceName": service.Name,
|
||||
"host": host,
|
||||
"port": port,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
formatCode := formatCode(buffer.String())
|
||||
_, err = fp.WriteString(formatCode)
|
||||
return err
|
||||
}
|
||||
199
tools/goctl/api/gogen/genhandlers.go
Normal file
199
tools/goctl/api/gogen/genhandlers.go
Normal file
@@ -0,0 +1,199 @@
|
||||
package gogen
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"path"
|
||||
"sort"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"zero/tools/goctl/api/spec"
|
||||
apiutil "zero/tools/goctl/api/util"
|
||||
"zero/tools/goctl/util"
|
||||
)
|
||||
|
||||
const (
|
||||
handlerTemplate = `package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
{{.importPackages}}
|
||||
)
|
||||
|
||||
func {{.handlerName}}(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
l := logic.{{.logic}}(r.Context(), ctx)
|
||||
{{.handlerBody}}
|
||||
}
|
||||
}
|
||||
`
|
||||
handlerBodyTemplate = `{{.parseRequest}}
|
||||
{{.processBody}}
|
||||
`
|
||||
parseRequestTemplate = `var req {{.requestType}}
|
||||
if err := httpx.Parse(r, &req); err != nil {
|
||||
logx.Error(err)
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
`
|
||||
hasRespTemplate = `
|
||||
{{.logicResponse}} l.{{.callee}}({{.req}})
|
||||
// TODO write data to response
|
||||
`
|
||||
)
|
||||
|
||||
func genHandler(dir string, group spec.Group, route spec.Route) error {
|
||||
handler, ok := apiutil.GetAnnotationValue(route.Annotations, "server", "handler")
|
||||
if !ok {
|
||||
return fmt.Errorf("missing handler annotation for %q", route.Path)
|
||||
}
|
||||
handler = getHandlerName(handler)
|
||||
var reqBody string
|
||||
if len(route.RequestType.Name) > 0 {
|
||||
var bodyBuilder strings.Builder
|
||||
t := template.Must(template.New("parseRequest").Parse(parseRequestTemplate))
|
||||
if err := t.Execute(&bodyBuilder, map[string]string{
|
||||
"requestType": typesPacket + "." + util.Title(route.RequestType.Name),
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
reqBody = bodyBuilder.String()
|
||||
}
|
||||
|
||||
var req = "req"
|
||||
if len(route.RequestType.Name) == 0 {
|
||||
req = ""
|
||||
}
|
||||
var logicResponse = ""
|
||||
var writeResponse = "nil, nil"
|
||||
if len(route.ResponseType.Name) > 0 {
|
||||
logicResponse = "resp, err :="
|
||||
writeResponse = "resp, err"
|
||||
} else {
|
||||
logicResponse = "err :="
|
||||
writeResponse = "nil, err"
|
||||
}
|
||||
var logicBodyBuilder strings.Builder
|
||||
t := template.Must(template.New("hasRespTemplate").Parse(hasRespTemplate))
|
||||
if err := t.Execute(&logicBodyBuilder, map[string]string{
|
||||
"callee": strings.Title(strings.TrimSuffix(handler, "Handler")),
|
||||
"req": req,
|
||||
"logicResponse": logicResponse,
|
||||
"writeResponse": writeResponse,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
respBody := logicBodyBuilder.String()
|
||||
|
||||
if !strings.HasSuffix(handler, "Handler") {
|
||||
handler = handler + "Handler"
|
||||
}
|
||||
|
||||
var bodyBuilder strings.Builder
|
||||
bodyTemplate := template.Must(template.New("handlerBodyTemplate").Parse(handlerBodyTemplate))
|
||||
if err := bodyTemplate.Execute(&bodyBuilder, map[string]string{
|
||||
"parseRequest": reqBody,
|
||||
"processBody": respBody,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
return doGenToFile(dir, handler, group, route, bodyBuilder)
|
||||
}
|
||||
|
||||
func doGenToFile(dir, handler string, group spec.Group, route spec.Route, bodyBuilder strings.Builder) error {
|
||||
if getHandlerFolderPath(group, route) != handlerDir {
|
||||
handler = strings.Title(handler)
|
||||
}
|
||||
parentPkg, err := getParentPackage(dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
filename := strings.ToLower(handler)
|
||||
if strings.HasSuffix(filename, "handler") {
|
||||
filename = filename + ".go"
|
||||
} else {
|
||||
filename = filename + "handler.go"
|
||||
}
|
||||
fp, created, err := apiutil.MaybeCreateFile(dir, getHandlerFolderPath(group, route), filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !created {
|
||||
return nil
|
||||
}
|
||||
defer fp.Close()
|
||||
t := template.Must(template.New("handlerTemplate").Parse(handlerTemplate))
|
||||
buffer := new(bytes.Buffer)
|
||||
err = t.Execute(buffer, map[string]string{
|
||||
"logic": "New" + strings.TrimSuffix(strings.Title(handler), "Handler") + "Logic",
|
||||
"importPackages": genHandlerImports(group, route, parentPkg),
|
||||
"handlerName": handler,
|
||||
"handlerBody": strings.TrimSpace(bodyBuilder.String()),
|
||||
})
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
formatCode := formatCode(buffer.String())
|
||||
_, err = fp.WriteString(formatCode)
|
||||
return err
|
||||
}
|
||||
|
||||
func genHandlers(dir string, api *spec.ApiSpec) error {
|
||||
for _, group := range api.Service.Groups {
|
||||
for _, route := range group.Routes {
|
||||
if err := genHandler(dir, group, route); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func genHandlerImports(group spec.Group, route spec.Route, parentPkg string) string {
|
||||
var imports []string
|
||||
if len(route.RequestType.Name) > 0 || len(route.ResponseType.Name) > 0 {
|
||||
imports = append(imports, "\"zero/core/httpx\"")
|
||||
}
|
||||
if len(route.RequestType.Name) > 0 {
|
||||
imports = append(imports, "\"zero/core/logx\"")
|
||||
}
|
||||
imports = append(imports, fmt.Sprintf("\"%s\"", path.Join(parentPkg, contextDir)))
|
||||
if len(route.RequestType.Name) > 0 || len(route.ResponseType.Name) > 0 {
|
||||
imports = append(imports, fmt.Sprintf("\"%s\"", path.Join(parentPkg, typesDir)))
|
||||
}
|
||||
imports = append(imports, fmt.Sprintf("\"%s\"", path.Join(parentPkg, getLogicFolderPath(group, route))))
|
||||
sort.Strings(imports)
|
||||
|
||||
return strings.Join(imports, "\n\t")
|
||||
}
|
||||
|
||||
func getHandlerBaseName(handler string) string {
|
||||
handlerName := util.Untitle(handler)
|
||||
if strings.HasSuffix(handlerName, "handler") {
|
||||
handlerName = strings.ReplaceAll(handlerName, "handler", "")
|
||||
} else if strings.HasSuffix(handlerName, "Handler") {
|
||||
handlerName = strings.ReplaceAll(handlerName, "Handler", "")
|
||||
}
|
||||
return handlerName
|
||||
}
|
||||
|
||||
func getHandlerFolderPath(group spec.Group, route spec.Route) string {
|
||||
folder, ok := apiutil.GetAnnotationValue(route.Annotations, "server", folderProperty)
|
||||
if !ok {
|
||||
folder, ok = apiutil.GetAnnotationValue(group.Annotations, "server", folderProperty)
|
||||
if !ok {
|
||||
return handlerDir
|
||||
}
|
||||
}
|
||||
folder = strings.TrimPrefix(folder, "/")
|
||||
folder = strings.TrimSuffix(folder, "/")
|
||||
return path.Join(handlerDir, folder)
|
||||
}
|
||||
|
||||
func getHandlerName(handler string) string {
|
||||
return getHandlerBaseName(handler) + "Handler"
|
||||
}
|
||||
130
tools/goctl/api/gogen/genlogic.go
Normal file
130
tools/goctl/api/gogen/genlogic.go
Normal file
@@ -0,0 +1,130 @@
|
||||
package gogen
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"path"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"zero/tools/goctl/api/spec"
|
||||
"zero/tools/goctl/api/util"
|
||||
)
|
||||
|
||||
const logicTemplate = `package logic
|
||||
|
||||
import (
|
||||
{{.imports}}
|
||||
)
|
||||
|
||||
type {{.logic}} struct {
|
||||
ctx context.Context
|
||||
logx.Logger
|
||||
}
|
||||
|
||||
func New{{.logic}}(ctx context.Context, svcCtx *svc.ServiceContext) {{.logic}} {
|
||||
return {{.logic}}{
|
||||
ctx: ctx,
|
||||
Logger: logx.WithContext(ctx),
|
||||
}
|
||||
// TODO need set model here from svc
|
||||
}
|
||||
|
||||
func (l *{{.logic}}) {{.function}}({{.request}}) {{.responseType}} {
|
||||
{{.returnString}}
|
||||
}
|
||||
|
||||
`
|
||||
|
||||
func genLogic(dir string, api *spec.ApiSpec) error {
|
||||
for _, g := range api.Service.Groups {
|
||||
for _, r := range g.Routes {
|
||||
err := genLogicByRoute(dir, g, r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func genLogicByRoute(dir string, group spec.Group, route spec.Route) error {
|
||||
handler, ok := util.GetAnnotationValue(route.Annotations, "server", "handler")
|
||||
if !ok {
|
||||
return fmt.Errorf("missing handler annotation for %q", route.Path)
|
||||
}
|
||||
handler = strings.TrimSuffix(handler, "handler")
|
||||
handler = strings.TrimSuffix(handler, "Handler")
|
||||
filename := strings.ToLower(handler)
|
||||
goFile := filename + "logic.go"
|
||||
fp, created, err := util.MaybeCreateFile(dir, getLogicFolderPath(group, route), goFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !created {
|
||||
return nil
|
||||
}
|
||||
defer fp.Close()
|
||||
|
||||
parentPkg, err := getParentPackage(dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
imports := genLogicImports(route, parentPkg)
|
||||
|
||||
responseString := ""
|
||||
returnString := ""
|
||||
requestString := ""
|
||||
if len(route.ResponseType.Name) > 0 {
|
||||
responseString = "(*types." + strings.Title(route.ResponseType.Name) + ", error)"
|
||||
returnString = "return nil, nil"
|
||||
} else {
|
||||
responseString = "error"
|
||||
returnString = "return nil"
|
||||
}
|
||||
if len(route.RequestType.Name) > 0 {
|
||||
requestString = "req " + "types." + strings.Title(route.RequestType.Name)
|
||||
}
|
||||
|
||||
t := template.Must(template.New("logicTemplate").Parse(logicTemplate))
|
||||
buffer := new(bytes.Buffer)
|
||||
err = t.Execute(fp, map[string]string{
|
||||
"imports": imports,
|
||||
"logic": strings.Title(handler) + "Logic",
|
||||
"function": strings.Title(strings.TrimSuffix(handler, "Handler")),
|
||||
"responseType": responseString,
|
||||
"returnString": returnString,
|
||||
"request": requestString,
|
||||
})
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
formatCode := formatCode(buffer.String())
|
||||
_, err = fp.WriteString(formatCode)
|
||||
return err
|
||||
}
|
||||
|
||||
func getLogicFolderPath(group spec.Group, route spec.Route) string {
|
||||
folder, ok := util.GetAnnotationValue(route.Annotations, "server", folderProperty)
|
||||
if !ok {
|
||||
folder, ok = util.GetAnnotationValue(group.Annotations, "server", folderProperty)
|
||||
if !ok {
|
||||
return logicDir
|
||||
}
|
||||
}
|
||||
folder = strings.TrimPrefix(folder, "/")
|
||||
folder = strings.TrimSuffix(folder, "/")
|
||||
return path.Join(logicDir, folder)
|
||||
}
|
||||
|
||||
func genLogicImports(route spec.Route, parentPkg string) string {
|
||||
var imports []string
|
||||
imports = append(imports, `"context"`)
|
||||
imports = append(imports, "\n")
|
||||
imports = append(imports, `"zero/core/logx"`)
|
||||
if len(route.ResponseType.Name) > 0 || len(route.RequestType.Name) > 0 {
|
||||
imports = append(imports, fmt.Sprintf("\"%s\"", path.Join(parentPkg, typesDir)))
|
||||
}
|
||||
imports = append(imports, fmt.Sprintf("\"%s\"", path.Join(parentPkg, contextDir)))
|
||||
return strings.Join(imports, "\n\t")
|
||||
}
|
||||
85
tools/goctl/api/gogen/genmain.go
Normal file
85
tools/goctl/api/gogen/genmain.go
Normal file
@@ -0,0 +1,85 @@
|
||||
package gogen
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"path"
|
||||
"sort"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"zero/tools/goctl/api/spec"
|
||||
"zero/tools/goctl/api/util"
|
||||
)
|
||||
|
||||
const mainTemplate = `package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
|
||||
{{.importPackages}}
|
||||
)
|
||||
|
||||
var configFile = flag.String("f", "etc/{{.serviceName}}.json", "the config file")
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
var c config.Config
|
||||
conf.MustLoad(*configFile, &c)
|
||||
|
||||
ctx := svc.NewServiceContext(c)
|
||||
|
||||
engine := ngin.MustNewEngine(c.NgConf)
|
||||
defer engine.Stop()
|
||||
|
||||
handler.RegisterHandlers(engine, ctx)
|
||||
engine.Start()
|
||||
}
|
||||
`
|
||||
|
||||
func genMain(dir string, api *spec.ApiSpec) error {
|
||||
name := strings.ToLower(api.Service.Name)
|
||||
if strings.HasSuffix(name, "-api") {
|
||||
name = strings.ReplaceAll(name, "-api", "")
|
||||
}
|
||||
goFile := name + ".go"
|
||||
fp, created, err := util.MaybeCreateFile(dir, "", goFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !created {
|
||||
return nil
|
||||
}
|
||||
defer fp.Close()
|
||||
|
||||
parentPkg, err := getParentPackage(dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
t := template.Must(template.New("mainTemplate").Parse(mainTemplate))
|
||||
buffer := new(bytes.Buffer)
|
||||
err = t.Execute(buffer, map[string]string{
|
||||
"importPackages": genMainImports(parentPkg),
|
||||
"serviceName": api.Service.Name,
|
||||
})
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
formatCode := formatCode(buffer.String())
|
||||
_, err = fp.WriteString(formatCode)
|
||||
return err
|
||||
}
|
||||
|
||||
func genMainImports(parentPkg string) string {
|
||||
imports := []string{
|
||||
`"zero/core/conf"`,
|
||||
`"zero/ngin"`,
|
||||
}
|
||||
imports = append(imports, fmt.Sprintf("\"%s\"", path.Join(parentPkg, configDir)))
|
||||
imports = append(imports, fmt.Sprintf("\"%s\"", path.Join(parentPkg, handlerDir)))
|
||||
imports = append(imports, fmt.Sprintf("\"%s\"", path.Join(parentPkg, contextDir)))
|
||||
sort.Strings(imports)
|
||||
return strings.Join(imports, "\n\t")
|
||||
}
|
||||
193
tools/goctl/api/gogen/genroutes.go
Normal file
193
tools/goctl/api/gogen/genroutes.go
Normal file
@@ -0,0 +1,193 @@
|
||||
package gogen
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"path"
|
||||
"sort"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"zero/core/collection"
|
||||
"zero/tools/goctl/api/spec"
|
||||
apiutil "zero/tools/goctl/api/util"
|
||||
"zero/tools/goctl/util"
|
||||
)
|
||||
|
||||
const (
|
||||
routesFilename = "routes.go"
|
||||
routesTemplate = `// DO NOT EDIT, generated by goctl
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
{{.importPackages}}
|
||||
)
|
||||
|
||||
func RegisterHandlers(engine *ngin.Engine, serverCtx *svc.ServiceContext) {
|
||||
{{.routesAdditions}}
|
||||
}
|
||||
`
|
||||
routesAdditionTemplate = `
|
||||
engine.AddRoutes([]ngin.Route{
|
||||
{{.routes}}
|
||||
}{{.jwt}}{{.signature}})
|
||||
`
|
||||
)
|
||||
|
||||
var mapping = map[string]string{
|
||||
"delete": "http.MethodDelete",
|
||||
"get": "http.MethodGet",
|
||||
"head": "http.MethodHead",
|
||||
"post": "http.MethodPost",
|
||||
"put": "http.MethodPut",
|
||||
}
|
||||
|
||||
type (
|
||||
group struct {
|
||||
routes []route
|
||||
jwtEnabled bool
|
||||
signatureEnabled bool
|
||||
authName string
|
||||
}
|
||||
route struct {
|
||||
method string
|
||||
path string
|
||||
handler string
|
||||
}
|
||||
)
|
||||
|
||||
func genRoutes(dir string, api *spec.ApiSpec) error {
|
||||
var builder strings.Builder
|
||||
groups, err := getRoutes(api)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gt := template.Must(template.New("groupTemplate").Parse(routesAdditionTemplate))
|
||||
for _, g := range groups {
|
||||
var gbuilder strings.Builder
|
||||
for _, r := range g.routes {
|
||||
fmt.Fprintf(&gbuilder, `
|
||||
{
|
||||
Method: %s,
|
||||
Path: "%s",
|
||||
Handler: %s,
|
||||
},`,
|
||||
r.method, r.path, r.handler)
|
||||
}
|
||||
jwt := ""
|
||||
if g.jwtEnabled {
|
||||
jwt = fmt.Sprintf(", ngin.WithJwt(serverCtx.Config.%s.AccessSecret)", g.authName)
|
||||
}
|
||||
signature := ""
|
||||
if g.signatureEnabled {
|
||||
signature = fmt.Sprintf(", ngin.WithSignature(serverCtx.Config.%s.Signature)", g.authName)
|
||||
}
|
||||
if err := gt.Execute(&builder, map[string]string{
|
||||
"routes": strings.TrimSpace(gbuilder.String()),
|
||||
"jwt": jwt,
|
||||
"signature": signature,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
parentPkg, err := getParentPackage(dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
filename := path.Join(dir, handlerDir, routesFilename)
|
||||
if err := util.RemoveOrQuit(filename); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fp, created, err := apiutil.MaybeCreateFile(dir, handlerDir, routesFilename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !created {
|
||||
return nil
|
||||
}
|
||||
defer fp.Close()
|
||||
|
||||
t := template.Must(template.New("routesTemplate").Parse(routesTemplate))
|
||||
buffer := new(bytes.Buffer)
|
||||
err = t.Execute(buffer, map[string]string{
|
||||
"importPackages": genRouteImports(parentPkg, api),
|
||||
"routesAdditions": strings.TrimSpace(builder.String()),
|
||||
})
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
formatCode := formatCode(buffer.String())
|
||||
_, err = fp.WriteString(formatCode)
|
||||
return err
|
||||
}
|
||||
|
||||
func genRouteImports(parentPkg string, api *spec.ApiSpec) string {
|
||||
var importSet = collection.NewSet()
|
||||
importSet.AddStr(`"zero/ngin"`)
|
||||
importSet.AddStr(fmt.Sprintf("\"%s\"", path.Join(parentPkg, contextDir)))
|
||||
for _, group := range api.Service.Groups {
|
||||
for _, route := range group.Routes {
|
||||
folder, ok := apiutil.GetAnnotationValue(route.Annotations, "server", folderProperty)
|
||||
if !ok {
|
||||
folder, ok = apiutil.GetAnnotationValue(group.Annotations, "server", folderProperty)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
}
|
||||
importSet.AddStr(fmt.Sprintf("%s \"%s\"", folder, path.Join(parentPkg, handlerDir, folder)))
|
||||
}
|
||||
}
|
||||
imports := importSet.KeysStr()
|
||||
sort.Strings(imports)
|
||||
return strings.Join(imports, "\n\t")
|
||||
}
|
||||
|
||||
func getRoutes(api *spec.ApiSpec) ([]group, error) {
|
||||
var routes []group
|
||||
|
||||
for _, g := range api.Service.Groups {
|
||||
var groupedRoutes group
|
||||
for _, r := range g.Routes {
|
||||
handler, ok := apiutil.GetAnnotationValue(r.Annotations, "server", "handler")
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("missing handler annotation for route %q", r.Path)
|
||||
}
|
||||
handler = getHandlerBaseName(handler) + "Handler(serverCtx)"
|
||||
folder, ok := apiutil.GetAnnotationValue(r.Annotations, "server", folderProperty)
|
||||
if ok {
|
||||
handler = folder + "." + strings.ToUpper(handler[:1]) + handler[1:]
|
||||
} else {
|
||||
folder, ok = apiutil.GetAnnotationValue(g.Annotations, "server", folderProperty)
|
||||
if ok {
|
||||
handler = folder + "." + strings.ToUpper(handler[:1]) + handler[1:]
|
||||
}
|
||||
}
|
||||
groupedRoutes.routes = append(groupedRoutes.routes, route{
|
||||
method: mapping[r.Method],
|
||||
path: r.Path,
|
||||
handler: handler,
|
||||
})
|
||||
}
|
||||
|
||||
if value, ok := apiutil.GetAnnotationValue(g.Annotations, "server", "jwt"); ok {
|
||||
groupedRoutes.authName = value
|
||||
groupedRoutes.jwtEnabled = true
|
||||
}
|
||||
if value, ok := apiutil.GetAnnotationValue(g.Annotations, "server", "signature"); ok {
|
||||
if groupedRoutes.authName != "" && groupedRoutes.authName != value {
|
||||
return nil, errors.New("auth signature should config same")
|
||||
}
|
||||
groupedRoutes.signatureEnabled = true
|
||||
}
|
||||
routes = append(routes, groupedRoutes)
|
||||
}
|
||||
|
||||
return routes, nil
|
||||
}
|
||||
63
tools/goctl/api/gogen/genservicecontext.go
Normal file
63
tools/goctl/api/gogen/genservicecontext.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package gogen
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"path"
|
||||
"text/template"
|
||||
|
||||
"zero/tools/goctl/api/spec"
|
||||
"zero/tools/goctl/api/util"
|
||||
)
|
||||
|
||||
const (
|
||||
contextFilename = "servicecontext.go"
|
||||
contextTemplate = `package svc
|
||||
|
||||
import {{.configImport}}
|
||||
|
||||
type ServiceContext struct {
|
||||
Config {{.config}}
|
||||
}
|
||||
|
||||
func NewServiceContext(config {{.config}}) *ServiceContext {
|
||||
return &ServiceContext{Config: config}
|
||||
}
|
||||
|
||||
`
|
||||
)
|
||||
|
||||
func genServiceContext(dir string, api *spec.ApiSpec) error {
|
||||
fp, created, err := util.MaybeCreateFile(dir, contextDir, contextFilename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !created {
|
||||
return nil
|
||||
}
|
||||
defer fp.Close()
|
||||
|
||||
var authNames = getAuths(api)
|
||||
var auths []string
|
||||
for _, item := range authNames {
|
||||
auths = append(auths, fmt.Sprintf("%s config.AuthConfig", item))
|
||||
}
|
||||
|
||||
parentPkg, err := getParentPackage(dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var configImport = "\"" + path.Join(parentPkg, configDir) + "\""
|
||||
t := template.Must(template.New("contextTemplate").Parse(contextTemplate))
|
||||
buffer := new(bytes.Buffer)
|
||||
err = t.Execute(buffer, map[string]string{
|
||||
"configImport": configImport,
|
||||
"config": "config.Config",
|
||||
})
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
formatCode := formatCode(buffer.String())
|
||||
_, err = fp.WriteString(formatCode)
|
||||
return err
|
||||
}
|
||||
146
tools/goctl/api/gogen/gentypes.go
Normal file
146
tools/goctl/api/gogen/gentypes.go
Normal file
@@ -0,0 +1,146 @@
|
||||
package gogen
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"path"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"zero/tools/goctl/api/spec"
|
||||
apiutil "zero/tools/goctl/api/util"
|
||||
"zero/tools/goctl/util"
|
||||
)
|
||||
|
||||
const (
|
||||
typesFile = "types.go"
|
||||
typesTemplate = `// DO NOT EDIT, generated by goctl
|
||||
package types{{if .containsTime}}
|
||||
import (
|
||||
"time"
|
||||
){{end}}
|
||||
{{.types}}
|
||||
`
|
||||
)
|
||||
|
||||
func BuildTypes(types []spec.Type) (string, error) {
|
||||
var builder strings.Builder
|
||||
first := true
|
||||
for _, tp := range types {
|
||||
if first {
|
||||
first = false
|
||||
} else {
|
||||
builder.WriteString("\n\n")
|
||||
}
|
||||
if err := writeType(&builder, tp, types); err != nil {
|
||||
return "", apiutil.WrapErr(err, "Type "+tp.Name+" generate error")
|
||||
}
|
||||
}
|
||||
|
||||
return builder.String(), nil
|
||||
}
|
||||
|
||||
func genTypes(dir string, api *spec.ApiSpec) error {
|
||||
val, err := BuildTypes(api.Types)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
filename := path.Join(dir, typesDir, typesFile)
|
||||
if err := util.RemoveOrQuit(filename); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fp, created, err := apiutil.MaybeCreateFile(dir, typesDir, typesFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !created {
|
||||
return nil
|
||||
}
|
||||
defer fp.Close()
|
||||
|
||||
t := template.Must(template.New("typesTemplate").Parse(typesTemplate))
|
||||
buffer := new(bytes.Buffer)
|
||||
err = t.Execute(buffer, map[string]interface{}{
|
||||
"types": val,
|
||||
"containsTime": api.ContainsTime(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
formatCode := formatCode(buffer.String())
|
||||
_, err = fp.WriteString(formatCode)
|
||||
return err
|
||||
}
|
||||
|
||||
func convertTypeCase(types []spec.Type, t string) (string, error) {
|
||||
ts, err := apiutil.DecomposeType(t)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var defTypes []string
|
||||
for _, tp := range ts {
|
||||
for _, typ := range types {
|
||||
if typ.Name == tp {
|
||||
defTypes = append(defTypes, tp)
|
||||
}
|
||||
|
||||
if len(typ.Annotations) > 0 {
|
||||
if value, ok := apiutil.GetAnnotationValue(typ.Annotations, "serverReplacer", tp); ok {
|
||||
t = strings.ReplaceAll(t, tp, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, tp := range defTypes {
|
||||
t = strings.ReplaceAll(t, tp, util.Title(tp))
|
||||
}
|
||||
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func writeType(writer io.Writer, tp spec.Type, types []spec.Type) error {
|
||||
fmt.Fprintf(writer, "type %s struct {\n", util.Title(tp.Name))
|
||||
for _, member := range tp.Members {
|
||||
if member.IsInline {
|
||||
var found = false
|
||||
for _, ty := range types {
|
||||
if strings.ToLower(ty.Name) == strings.ToLower(member.Name) {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return errors.New("inline type " + member.Name + " not exist, please correct api file")
|
||||
}
|
||||
if _, err := fmt.Fprintf(writer, "%s\n", strings.Title(member.Type)); err != nil {
|
||||
return err
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
tpString, err := convertTypeCase(types, member.Type)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pm, err := member.GetPropertyName()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !strings.Contains(pm, "_") {
|
||||
if strings.Title(member.Name) != strings.Title(pm) {
|
||||
fmt.Printf("type: %s, property name %s json tag illegal, "+
|
||||
"should set json tag as `json:\"%s\"` \n", tp.Name, member.Name, util.Untitle(member.Name))
|
||||
}
|
||||
}
|
||||
if err := writeProperty(writer, member.Name, tpString, member.Tag, member.GetComment(), 1); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(writer, "}")
|
||||
return nil
|
||||
}
|
||||
67
tools/goctl/api/gogen/util.go
Normal file
67
tools/goctl/api/gogen/util.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package gogen
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
goformat "go/format"
|
||||
"io"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"zero/core/collection"
|
||||
"zero/tools/goctl/api/spec"
|
||||
"zero/tools/goctl/api/util"
|
||||
"zero/tools/goctl/vars"
|
||||
)
|
||||
|
||||
func getParentPackage(dir string) (string, error) {
|
||||
absDir, err := filepath.Abs(dir)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
pos := strings.Index(absDir, vars.ProjectName)
|
||||
if pos < 0 {
|
||||
return "", fmt.Errorf("%s not in project directory", dir)
|
||||
}
|
||||
|
||||
return absDir[pos:], nil
|
||||
}
|
||||
|
||||
func writeIndent(writer io.Writer, indent int) {
|
||||
for i := 0; i < indent; i++ {
|
||||
fmt.Fprint(writer, "\t")
|
||||
}
|
||||
}
|
||||
|
||||
func writeProperty(writer io.Writer, name, tp, tag, comment string, indent int) error {
|
||||
writeIndent(writer, indent)
|
||||
var err error
|
||||
if len(comment) > 0 {
|
||||
comment = strings.TrimPrefix(comment, "//")
|
||||
comment = "//" + comment
|
||||
_, err = fmt.Fprintf(writer, "%s %s %s %s\n", strings.Title(name), tp, tag, comment)
|
||||
} else {
|
||||
_, err = fmt.Fprintf(writer, "%s %s %s\n", strings.Title(name), tp, tag)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func getAuths(api *spec.ApiSpec) []string {
|
||||
var authNames = collection.NewSet()
|
||||
for _, g := range api.Service.Groups {
|
||||
if value, ok := util.GetAnnotationValue(g.Annotations, "server", "jwt"); ok {
|
||||
authNames.Add(value)
|
||||
}
|
||||
if value, ok := util.GetAnnotationValue(g.Annotations, "server", "signature"); ok {
|
||||
authNames.Add(value)
|
||||
}
|
||||
}
|
||||
return authNames.KeysStr()
|
||||
}
|
||||
|
||||
func formatCode(code string) string {
|
||||
ret, err := goformat.Source([]byte(code))
|
||||
if err != nil {
|
||||
return code
|
||||
}
|
||||
return string(ret)
|
||||
}
|
||||
12
tools/goctl/api/gogen/vars.go
Normal file
12
tools/goctl/api/gogen/vars.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package gogen
|
||||
|
||||
const (
|
||||
interval = "internal/"
|
||||
typesPacket = "types"
|
||||
configDir = interval + "config"
|
||||
contextDir = interval + "svc"
|
||||
handlerDir = interval + "handler"
|
||||
logicDir = interval + "logic"
|
||||
typesDir = interval + typesPacket
|
||||
folderProperty = "folder"
|
||||
)
|
||||
Reference in New Issue
Block a user